Author: troioiconngu

  • [Mobile – Flutter] Slider trong Flutter

    [Mobile – Flutter] Slider trong Flutter

    Bản thân Flutter là một framework khá hoàn thiện đến mức việc tạo giao diện với Flutter trở nên dễ dàng và nhanh chóng đến mức bất ngờ! Điều này khiến lập trình viên Flutter cảm thấy việc lập trình mobile quá dễ (trừ khi họ là người đã từng nếm trái đắng từ khi code native). Mình cũng thấy dễ cho đến khi nhìn thấy giao diện ứa nước mắt mà designer gửi (đôi khi là kèm theo video animation siêu xịn…).

    Thế bạn đã trang bị cho mình gì để ứng phó trước những design đẹp (hoặc k) nào? Bạn đã biết custom Slider trong Flutter chưa? Hay chỉ là những bé slider cũ mèn như này này này:

    mặc định
    range slider
    cupertino *hình ảnh chụp từ video youtube nên hơi mờ

    Nhưng khi nhận được design thì trông như này

    như này nè
    rồi như này
    trông cũng dễ?

    Chắc ai nhận design cũng sẽ nghĩ: trông dễ phết, khó thì dùng thư viện (nhiều thư viện Slider quá mà). Có thể kể ra một số thư viện cho ai muốn dùng nè:

    Đọc đến đây thì ai thích dùng thư viện có thể dừng lại và tham khảo 3 thư viện này. Đôi khi một số lại thấy thư viện thì quá là nặng thêm nhiều thứ lắt nhắt, rồi cho customize đủ thứ mà cái mình cần thì không có? Hoặc bạn muốn tự mình thử sức làm những slider này, thì tiếp tục đọc cách custom slider nào :3

    Đầu tiên, chúng ta sẽ tìm hiểu về các thành phần của 1 slider và tên gọi của nó trong Flutter với thiết kế theo Material: (đoạn này cop trên document lười dịch nên nhờ bạn đọc tạm nhé)

    • The “thumb”, which is a shape that slides horizontally when the user drags it.
    • The “track”, which is the line that the slider thumb slides along.
    • The “value indicator”, which is a shape that pops up when the user is dragging the thumb to indicate the value being selected.
    • The “active” side of the slider is the side between the thumb and the minimum value.
    • The “inactive” side of the slider is the side between the thumb and the maximum value.
    • The “tick marks”, which are regularly spaced marks that are drawn when using discrete divisions.
    • The “value indicator”, which appears when the user is dragging the thumb to indicate the value being selected.
    • The “overlay”, which appears around the thumb, and is shown when the thumb is pressed, focused, or hovered. It is painted underneath the thumb, so it must extend beyond the bounds of the thumb itself to actually be visible.

    Để thiết kế ra một Slider thì đầu tiên chúng ta sẽ xem đến các widget trực tiếp để tạo ra slider: Slider, RangeSlider, and CupertinoSlider(Flutter Widget of the Week) – YouTube. Tuy nhiên khi xem document (ờ lại là document, document của Flutter là tài liệu hữu ích nhất mà dev Flutter có thể tìm được bên cạnh source code open của nó) thì bạn phát hiện ra nó không có nhiều thứ liên quan đến giao diện cho lắm, ngoài việc cho phép đổi tí màu. Bạn bắt đầu tuyệt vọng.

    một constructor của slider
    có gì đó

    và rồi cuối document, họ bảo, vẻ bề ngoài của nó thì dùng SliderThemeData từ widget SliderTheme hoặc một cái theme nào đó cha ông của nó mà có định nghĩa sliderTheme. Yeah XD

    Bạn tiếp tục đi đến wiki của SliderThemeData xem tùy chỉnh được gì, ở đây nhé SliderThemeData class – material library – Dart API (flutter.dev). Có thể chỉnh được kha khá, hình tròn hình chữ nhật, bo góc (hoặc k), … bạn có thể dừng lại, đọc, và thử xem bạn có thể custom như nào (ảnh phía trên là 1 slider đã được 1 tác giả khác tạo ra nhờ tùy chỉnh các thuộc tính này).

    Để rồi nhận ra rằng: nó không giống mấy cái design nhận được xíu nào 🙁

    hình ảnh slider trong blog cuối bài

    Thế làm sao để custom chúng nó như này? Hãy xem Flutter Team đã tạo ra Slider như nào đã nhé :3

    ồ là một file hơn 3000 dòng code và rất nhiều tác giả

    Họ đã tạo ra RoundedRectSliderTrackShape, RoundSliderThumbShape, PaddleSliderValueIndicatorShape, PaddleSliderValueIndicatorShape, RoundSliderTickMarkShape,… và document cũng nói là:

    The thumb, track, tick marks, value indicator, and overlay can be customized by creating subclasses of SliderTrackShapeSliderComponentShape, and/or SliderTickMarkShape. See RoundSliderThumbShapeRectangularSliderTrackShapeRoundSliderTickMarkShapeRectangularSliderValueIndicatorShape, and RoundSliderOverlayShape for examples.

    Document

    Nên cứ nghe theo thôi ha, họ bảo code mẫu rồi đấy, muốn custom thì xem mà học :3 họ bảo kế thừa mấy cái có sẵn này đi mà custom, nhưng custom sao thì họ không nói 🙁 nên t ở đây để giúp bạn đây hehe. Không dài dòng nữa, bắt đầu thôi :3

    Khi bạn extend các lớp kể ở trên, bạn sẽ được trình gợi ý yêu cầu override hàm paint, đây là hàm để vẽ nên chúng nó. Trong hàm paint có một số thứ bạn cần chú ý:
    – Đầu tiên là PaintingContext context, ở đây bạn sẽ lấy được canvas ra và vẽ những gì bạn cần (context.canvas)
    – Tiếp theo là center với Thumb và parentBox với Track, bạn sẽ tìm được những điểm hữu dụng để vẽ
    – Tiếp theo là sliderTheme, từ đó bạn có thể lấy ra các thuộc tính khác mà bạn có thể truyền vào theme của slider (màu active, inactive…)
    – Cuối cùng, mn luôn luôn có thể tham khảo code open của Flutter xem họ làm như nào với các shape mặc định, hoặc tham khảo code của mình sau đây :3

    Đây là code để tạo ra chiếc slider này

    Chụp từ màn hình điện thoại
    import 'package:flutter/material.dart';
    
    class CustomSlider extends StatelessWidget {
      final double max, min, value;
      final void Function(double) onValueChange;
    
      const CustomSlider({
        required this.min,
        required this.max,
        required this.value,
        required this.onValueChange,
        Key? key,
      }) : super(key: key);
    
      @override
      Widget build(BuildContext context) {
        return Container(
          decoration: BoxDecoration(
            border: Border.all(color: Colors.grey),
            borderRadius: BorderRadius.circular(10),
          ),
          padding: const EdgeInsets.fromLTRB(4, 16, 4, 16),
          child: Row(
            children: [
              Text(min.toInt().toString()),
              const SizedBox(width: 6),
              Expanded(
                child: SliderTheme(
                  data: SliderTheme.of(context).copyWith(
                    trackHeight: 14.0,
                    trackShape: CustomTrackShape(),
                    activeTrackColor: const Color(0xFF219653),
                    inactiveTrackColor: const Color(0xFFF1E1E1),
                    thumbShape: CustomSliderMarkShape(sliderValue: value, tickMarkRadius: 16),
                    thumbColor: const Color(0xFF219653),
                    // overlayColor: Color(0xFF219653).withOpacity(0.5),
                    // overlayShape: RoundSliderOverlayShape(overlayRadius: 24),
                    overlayShape: SliderComponentShape.noOverlay,
                    showValueIndicator: ShowValueIndicator.always,
                    valueIndicatorShape: const RectangularSliderValueIndicatorShape(),
                    valueIndicatorColor: Colors.black,
                    valueIndicatorTextStyle: const TextStyle(
                      color: Colors.white,
                      fontSize: 16.0,
                    ),
                  ),
                  child: Slider(
                    min: min,
                    max: max,
                    value: value,
                    label: '${value.round()}',
                    onChanged: onValueChange,
                  ),
                ),
              ),
              const SizedBox(width: 6),
              Text(max.toInt().toString()),
            ],
          ),
        );
      }
    }
    
    class CustomSliderMarkShape extends SliderComponentShape {
      final double tickMarkRadius;
      final double sliderValue;
    
      CustomSliderMarkShape({
        required this.tickMarkRadius,
        required this.sliderValue,
      });
    
      @override
      Size getPreferredSize(bool isEnabled, bool isDiscrete) {
        return Size(tickMarkRadius, tickMarkRadius);
      }
    
      @override
      void paint(
        PaintingContext context,
        Offset center, {
        required Animation<double> activationAnimation,
        required Animation<double> enableAnimation,
        required bool isDiscrete,
        required TextPainter labelPainter,
        required RenderBox parentBox,
        required SliderThemeData sliderTheme,
        required TextDirection textDirection,
        required double value,
        required double textScaleFactor,
        required Size sizeWithOverflow,
      }) {
        final Canvas canvas = context.canvas;
    
        canvas.drawRRect(
          RRect.fromRectAndRadius(
            Rect.fromCenter(center: center, width: 36, height: 24), const Radius.circular(16)),
          Paint()..color = Colors.white,
        );
    
        canvas.drawRRect(
          RRect.fromRectAndRadius(
            Rect.fromCenter(center: center, width: 36, height: 24), const Radius.circular(16)),
          Paint()..color = Colors.black
            ..style = PaintingStyle.stroke
            ..strokeWidth = 2,
        );
    
        TextSpan span = TextSpan(
          style: TextStyle(
            fontSize: tickMarkRadius * 0.9,
            fontWeight: FontWeight.w700,
            color: Colors.black
          ),
          text: sliderValue.round().toString(),
        );
    
        TextPainter tp = TextPainter(
          text: span,
          textAlign: TextAlign.center,
          textDirection: TextDirection.ltr,
        );
    
        tp.layout();
    
        Offset textCenter = Offset(
          center.dx - (tp.width / 2),
          center.dy - (tp.height / 2),
        );
        tp.paint(canvas, textCenter);
      }
    }
    
    class CustomTrackShape extends RoundedRectSliderTrackShape {
      @override
      Rect getPreferredRect({
        required RenderBox parentBox,
        Offset offset = Offset.zero,
        required SliderThemeData sliderTheme,
        bool isEnabled = false,
        bool isDiscrete = false,
      }) {
        final double trackHeight = sliderTheme.trackHeight ?? 8;
        final double trackLeft = offset.dx;
        final double trackTop = offset.dy + (parentBox.size.height - trackHeight) / 2;
        final double trackWidth = parentBox.size.width;
        return Rect.fromLTWH(trackLeft, trackTop, trackWidth, trackHeight);
      }
    }
    // sử dụng
    CustomSlider(
      max: 100,
      min: 10,
      onValueChange: (_) {  },
      value: 30,
    ),


    Mình đã custom cái slider này từ rất lâu rồi (nên code của nó mình không chắc là ổn lắm).

    Và lí do mình viết bài này chủ yếu để nhớ kĩ hơn và muốn chia sẻ với mọi người khi mà chiều nay mình đã custom ra mấy cái extend SliderComponentShape, rồi ghép vào SliderTheme mà reload restart hoài UI không nhận :'( Mình rất bối rối muốn ném máy ra cửa sổ thì nhận ra nếu là RangeSlider thì cần extend RangeSliderTrackShape, RangeSliderThumbShape… (thêm Range cơ). Hóa ra do mình không đọc kĩ (tiện muốn kể chuyện InteractiveViewer ghê mà ai muốn nghe ib nhé :v)

    range slider sau khi custom
    import 'dart:math';
    
    import 'package:flutter/material.dart';
    import 'dart:ui' as ui;
    
    class CustomRangeSliderTrack extends RangeSliderTrackShape {
      const CustomRangeSliderTrack();
    
      @override
      Rect getPreferredRect({
        required RenderBox parentBox,
        Offset offset = Offset.zero,
        required SliderThemeData sliderTheme,
        bool isEnabled = false,
        bool isDiscrete = false,
      }) {
        final double overlayWidth = sliderTheme.overlayShape!.getPreferredSize(isEnabled, isDiscrete).width;
        final double trackHeight = sliderTheme.trackHeight!;
    
        final double trackLeft = offset.dx + overlayWidth / 2;
        final double trackTop = offset.dy + (parentBox.size.height - trackHeight) / 2;
        final double trackRight = trackLeft + parentBox.size.width - overlayWidth;
        final double trackBottom = trackTop + trackHeight;
        // If the parentBox'size less than slider's size the trackRight will be less than trackLeft, so switch them.
        return Rect.fromLTRB(min(trackLeft, trackRight), trackTop, max(trackLeft, trackRight), trackBottom);
      }
    
      @override
      void paint(
        PaintingContext context,
        Offset offset, {
        required RenderBox parentBox,
        required SliderThemeData sliderTheme,
        required Animation<double> enableAnimation,
        required Offset startThumbCenter,
        required Offset endThumbCenter,
        bool isEnabled = false,
        bool isDiscrete = false,
        required TextDirection textDirection,
        double additionalActiveTrackHeight = 2,
      }) {
        final ColorTween inactiveTrackColorTween = ColorTween(
          begin: sliderTheme.disabledInactiveTrackColor,
          end: sliderTheme.inactiveTrackColor,
        );
    
        final Rect trackRect = getPreferredRect(
          parentBox: parentBox,
          offset: offset,
          sliderTheme: sliderTheme,
          isEnabled: isEnabled,
          isDiscrete: isDiscrete,
        );
    
        final Paint activePaint = Paint()
          ..shader = ui.Gradient.linear(
            Offset(0, trackRect.top),
            Offset(0, trackRect.bottom),
            [
              Color(0xFF4BC65E),
              Color(0xFF379A46),
            ],
          );
        final Paint inactivePaint = Paint()
          ..color = inactiveTrackColorTween.evaluate(enableAnimation)!;
        final Size thumbSize = sliderTheme.rangeThumbShape!.getPreferredSize(isEnabled, isDiscrete);
        final double thumbRadius = thumbSize.width / 2;
        assert(thumbRadius > 0);
    
        final Radius trackRadius = Radius.circular(trackRect.height / 2);
    
        context.canvas.drawRRect(
          RRect.fromLTRBAndCorners(
            trackRect.left,
            trackRect.top,
            startThumbCenter.dx,
            trackRect.bottom,
            topLeft: trackRadius,
            bottomLeft: trackRadius,
          ),
          inactivePaint,
        );
        context.canvas.drawRect(
          Rect.fromLTRB(
            startThumbCenter.dx,
            trackRect.top - (additionalActiveTrackHeight / 2),
            endThumbCenter.dx,
            trackRect.bottom + (additionalActiveTrackHeight / 2),
          ),
          activePaint,
        );
        context.canvas.drawRRect(
          RRect.fromLTRBAndCorners(
            endThumbCenter.dx,
            trackRect.top,
            trackRect.right,
            trackRect.bottom,
            topRight: trackRadius,
            bottomRight: trackRadius,
          ),
          inactivePaint,
        );
      }
    }
    
    class CustomRangeSliderThumb extends RangeSliderThumbShape {
      const CustomRangeSliderThumb();
      
      @override
      Size getPreferredSize(bool isEnabled, bool isDiscrete) {
        return Size.fromRadius(20);
      }
      
      @override
      void paint(PaintingContext context,
        Offset center, {
        required Animation<double> activationAnimation, 
        required Animation<double> enableAnimation, 
        bool isDiscrete = false,
        bool isEnabled = false,
        bool? isOnTop,
        required SliderThemeData sliderTheme,
        TextDirection? textDirection,
        Thumb? thumb,
        bool? isPressed,
      }) {
        final Canvas canvas = context.canvas;
        final outerPaint = Paint()
          ..style = PaintingStyle.fill
          ..shader = ui.Gradient.linear(
            Offset(0, 0),
            Offset(0, 30),
            [
              Color(0xFF4BC65E),
              Color(0xFF379A46),
            ],
          );
        canvas.drawCircle(center, 10, outerPaint);
        canvas.drawCircle(center, 5, Paint()..color = Colors.white);
      }
    }
    // dùng như này để có 1 range slider như ý
    // có thể custom thêm gì đó tùy ý nha
    SliderTheme(
      data: SliderThemeData(
        trackHeight: 4,
        rangeTrackShape: CustomRangeSliderTrack(),
        rangeThumbShape: CustomRangeSliderThumb(),
      ),
      child: RangeSlider(
        values: priceRange,
        min: minPrice,
        max: maxPrice,
        onChanged: (value) {
          setState(() {
            priceRange = value;
          });
        },
      ),
    ),

    Tham khảo: Flutter Slider widgets: A deep dive with examples – LogRocket Blog

  • Code sướng tay: Bước 2

    Code sướng tay: Bước 2

    Hi, hôm nay mình lại tiếp tục xàm xí đâyyy. Hôm nay sẽ lại tiếp tục về chuyện đặt tên và thêm về cách viết hàm. Hãy bắt đầu với 1 câu nói sến súa nào :3

    Programs must be written for people to read, and only incidentally for machines to execute.

    (Đại khái là code viết cho người đọc, chỉ là vô tình máy chạy được thôi)

    Abelson and Sussman

    Và để người đọc được thì sao không viết code một cách có-thể-đọc-được nhỉ?

    // Kiểu như này
    // "If errors is empty..."
    if (errors.isEmpty) ...
    
    // "Hey, subscription, cancel!"
    subscription.cancel();
    
    // "Get the monsters where the monster has claws."
    monsters.where((monster) => monster.hasClaws);
    // Thay vì như này
    // Telling errors to empty itself, or asking if it is?
    if (errors.empty) ...
    
    // Toggle what? To what?
    subscription.toggle();
    
    // Filter the monsters with claws *out* or include *only* those?
    monsters.filter((monster) => monster.hasClaws);

    Tuy nhiên đừng cố gắng để nó quá dễ đọc đến mức như đang nói, điều này làm code rườm rà và trở nên khó đọc hơn bao giờ hết!

    // has gone too far
    if (theCollectionOfErrors.isEmpty) ...
    
    monsters.producesANewSequenceWhereEach((monster) => monster.hasClaws);

    Cách đặt tên cho câu hỏi yes/no

    Tên trả lời cho câu hỏi yes/no thường được dùng làm các điều kiện rẽ nhánh, so sánh 2 cách viết sau:

    if (window.closeable) ...  // Adjective.
    if (window.canClose) ...   // Verb.

    Chúng ta có thể đặt tên như sau:

    • Bắt đầu với “to be”isEnabledwasShownwillFire. Đây là cách phổ biến nhất (window.closeable cũng có thể được đặt là window.isCloseable)
    • Sử dụng trợ động từhasElementscanCloseshouldConsumemustSave.
    • Các động từ thông thường không hay được dùng trong TH này

    Chúng ta có thể đặt tên bắt đầu với những động từ này mà không bị nhầm lẫn với các hàm do nó không phải là các động từ mệnh lệnh.

    // Nên đặt như này
    // Tuy nhiên, trong một số trường hợp
    // bỏ qua các tiền tố yes/ no này mà nghĩa vẫn rõ ràng thì nên bỏ đi
    isEmpty
    hasElements
    canClose
    closesWindow
    canShowPopup
    hasShownPopup
    // Chứ không phải là như này
    empty         // Adjective or verb?
    withElements  // Sounds like it might hold elements.
    closeable     // Sounds like an interface.
                  // "canClose" reads better as a sentence.
    closingWindow // Returns a bool or a window?
    showPopup     // Sounds like it shows the popup.

    Tiếp theo là về việc lùi dòng và độ phức tạp của code trong phạm vi hàm. Mỗi lần bạn lùi vào 1 tab, vậy là code của bạn đã phức tạp và tốn công sức để đọc thêm 1 bậc. Mình rất thích đọc và xử lí hết lỗi trong tab Problems này cho đến khi nó hiện lên dòng chữ như vầy:

    Không còn lỗi nữa~

    Và khi mình code js, đã có rất nhiều cảnh báo bắn ra pằng pằng khi mình code của mình quá dài, nó sẽ yêu cầu mình tách hàm ra (tính ra IDE giúp được rất nhiều, nó còn có thể detect các đoạn code giống nhau mà mình copy paste sau đó bảo mình tách hàm đi bạn ơi~). IDE sẽ cảnh báo khi bạn lùi vào quá nhiều và yêu cầu bạn refactor code. IDE làm vậy không phải vì nó không đọc được code của bạn, mà vì nó sợ sau này bạn sẽ không đọc được code của bạn đấy :P. Vậy tại sao trong 1 hàm lại cần tab vào nhiều đến thế? Đó là vì hàm đó đang cố gắng làm quá nhiều việc.

    Có một nguyên lí rất nổi tiếng trong giới lập trình viên, gọi là Single Responsibility Principle (nguyên lí đơn nhiệm). Nguyên lí này khi áp dụng cho một hàm thì có nghĩa là: 1 hàm chỉ nên làm 1 việc. Đôi khi 1 việc này sẽ hơi tốn code, nhưng trong hầu hết các trường hợp (nếu không phải bạn đang thực thi một thuật toán gì đó phức tạp) thì nó sẽ có thể viết trong chiều dài màn hình của bạn, và không quá 3 lần tab vào (theo ý kiến của mình, hoặc số lần tab vào này có thể phụ thuộc vào trí nhớ của bạn nhưng với mình 4 là quá nhiều để nhớ, nếu mỗi lần tab vào đi kèm 1 điều kiện thì đã có 16 trường hợp nhỏ nhất đang chờ được mình duyệt qua T_T). Có 1 tip nhỏ để giảm bớt độ phức tạp lùi dòng, đó là sử dụng early return (1 lần duy nhất) trong hàm của bạn. Điều này sẽ giảm tab complexity xuống 1 level rồi đó :3

    Early Return (thoát sớm)

    Tuy nhiên, không phải lúc nào chúng ta cũng nên chia ra quá nhiều hàm với 1 đống param đi kèm, điều này sẽ tạo ra rất nhiều các hàm ăn bám (hàm chỉ dùng được duy nhất 1 lần cho 1 hàm khác) và thừa thãi. Hãy tách hàm sao cho hợp lí để dễ dàng đọc (và đôi khi là dùng) chúng nhé :3

    Chúc mọi người sẽ code sướng cái tay và đọc code sướng con mắt hehe. À với Tết rồi nên chúc ai đọc bài viết này sẽ đẹp trai/xinh gái, nhiều tiền, nhiều sức khoẻ, vui vẻ, hạnh phúc và không bị đồng nghiệp (hoặc sếp) chửi vì code lởm ạ.

    Tham khảo:

    https://dart.dev/guides/language/effective-dart/design

    https://stackoverflow.com/questions/475675/when-is-a-function-too-long

  • Code sướng tay: Bước 1

    Có một vấn đề mà ai code cũng cần làm, đó là đặt tên (cho lớp, hàm, biến…).

    Ai cũng có thể viết code máy hiểu được, nhưng chỉ số ít viết code mà người hiểu được.

    “I’m not a great programmer, I’m just a good programmer with great habits.”

    Martin Fowler (tác giả sách Refactoring)

    Hồi còn đi học, chúng ta hay làm toán và đặt tên các biến là x,y,z,t… các hằng là a,b,c,…m,n…. Chúng ta hay giải lí với các biến là U, I, g, … Tương tự với các môn cần đến toán như hóa, sinh… Và tin học cũng là một môn dùng đến toán như vậy. Chúng ta tiếp tục lặp lại những gì mình từng biết:

    int a, b, c;

    float x, y, z;

    double aa, bb, xx, yy;

    Đặt những cái tên mà chúng ta thấy là đúng đắn, nhưng trông thật vô nghĩa. Trước mình cũng code rất nhiều những thuật toán, mà giờ đọc lại, không còn hiểu được ngày xưa mình code gì thế này, hàm này để làm gì, giải quyết bài toàn gì? Đừng đặt cho biến của bạn những cái tên quá ngắn như thế, vì nó chẳng thể hiện được tí ý nghĩa nào của biến đó hết.

    Đố ai hiểu code đang nghĩ gì :v

    Sau một chút thì mình học được cách thêm comment vào code để dễ hiểu hơn, như thế này:

    code cùng một chút comment (l=left, r=right)

    Tuy nhiên, sau này mấy anh khóa trên đi làm xong đến lớp bảo: “Ở công ty code giờ về lớp code nhìn cứ ngứa mắt. Đi làm code là tên biến nó cứ dài dài cơ. Càng dài càng tốt ae nhề, dài nhìn mới thuận mắt (nói với mấy anh cùng đi làm).” Lúc đấy mình cũng cười trừ thôi, vì thấy code của mình đọc dễ hiểu lắm rồi, đặt tên dài làm gì đâu. Hồi đó mình còn học các cách để rút gọn/ viết tắt tên biến như là lấy chữ cái đầu, bỏ đi nguyên âm, lấy 3 kí tự đầu… Mãi sau mới thấy, IDE gợi ý đến tận răng, tiếc gì mấy phím gõ mà phải học viết tắt quá mức như vậy nhỉ (thật ra học cái này cũng hữu ích khi mà đọc code người khác họ viết lst còn biết là list chả hạn .-.) Đừng cố gắng viết tắt tên biến, trừ khi là những từ viết tắt mà ai cũng biết và hiểu rõ.

    Hóa ra là, hàng tốt thì không cần quảng cáo, code tốt thì không cần comment, tự những cái tên trong code cần phải làm việc của nó khiến đoạn code dễ đọc, dễ hiểu (trừ những logic phức tạp và khó hiểu). Khi tên một hàm thể hiện nó làm gì, người ta sẽ không cần đọc đến đoạn code bên trong nó nữa. Trong cuộc đời 1 coder, thời gian đọc code luôn nhiều hơn thời gian viết ra nó (vì code thường sẽ cần được sửa), nên nếu đặt một cái tên tốt, sau này đọc lại bạn sẽ đỡ áp lực hơn khi đọc lại code của chính mình (và đỡ muốn đấm bản thân hơn, đỡ mất thời gian đọc comment, đỡ phải vò đầu bứt tai suy nghĩ ôi cái đoạn code này dùng để làm gì thế nhỉ).

    Nếu biến của bạn có đơn vị, hãy cố gắng thêm đơn vị vào tên của nó. Ví dụ thay vì hàm startAnimation(int delayTime) hãy đặt là startAnimation(int delayTimeInSeconds) hoặc nếu tốt hơn nữa, hãy thay kiểu của nó thành một kiểu mà sẽ khó bị nhầm lẫn, ví dụ như Duration trong Dart, bạn sẽ không cần quan tâm đến đơn vị của nó nữa. Khi code, nếu bạn phát hiện ra một cái tên khó hiểu hoặc dễ hiểu lầm và bạn có thể đặt cho nó cái tên tốt hơn, hãy đổi nó ngay khi bạn có thể (miễn là nó không ảnh hưởng tới đồng nghiệp của bạn, nếu có ảnh hưởng, bạn có thể hỏi ý kiến họ trước hoặc thêm một chút comment để sau này không tốn thời gian quá nhiều để hiểu nó nữa). Nhỏ thôi nhưng sẽ khiến bạn của sau này phải cảm ơn bạn đấy :3

    Một số cách đặt tên mà bạn nên tham khảo:

    • Nên dùng tiếng anh để đặt tên vì hầu hết các kí hiệu trong code đều là tiếng anh và tiếng anh không dễ bị nhầm lẫn như tiếng việt không dấu
    • Tuân thủ nguyên tắc đặt tên của ngôn ngữ/ dự án mà bạn làm
    • Đặt tên cho hàm bằng các động từ, cho các lớp bằng danh từ
    • Chỉ sử dụng biến bắt đầu bằng is/ are/ has… để đặt cho các biến/ thuộc tính/ hàm trả lời cho câu hỏi yes/no
    • Khi bạn không nghĩ ra được một cái tên cho biến/hàm/lớp bạn cần vì nó dễ bị nhầm lẫn với những biến/hàm/lớp khác, đây là lúc nên đặt lại tên cho cả những biến/hàm/lớp khác kia (chứ đừng thay bằng một từ đồng nghĩa, điều này sẽ khiến mọi người bối rối vì không biết chúng khác nhau ở đâu .-.)

    Tóm tắt các nguyên tắc:

    1. Tuân thủ convention đặt tên của ngôn ngữ/dự án đang làm
    2. Tên biến nên thể hiện ý nghĩa của nó, đừng cố gắng viết tắt nó
    3. Tham khảo một số nguyên tắc chung

    Lần đầu viết bài, mong là sẽ có lần sau hehe~