Nghệ thuật viết code đẹp - Phần IV: Sử dụng biến một cách hợp lý

Hôm nay mình xin được giới thiệu tới các bạn phần 4 của series Nghệ thuật viết code đẹp với tựa đề Sử dụng biến một cách hợp lý. Link 3 phần trước các bạn có thể tham khảo ở đây:

Nghệ thuật viết code đẹp - Phần I: Viết flow điều kiện và vòng lặp dễ hiểu Nghệ thuật viết code đẹp - Phần II: Nên viết comment như thế nào? Nghệ thuật viết code đẹp - Phần III: Đơn giản, dễ đọc hoá biểu thức

Trong thực tế có lẽ các bạn đã từng gặp những project mà trong source code sử dụng biến số một cách tuỳ tiện khiến cho việc đọc hiểu và fix bug trở nên rất khó khăn. Dưới đây là 3 vấn đề thường gặp khi sử dụng biến số.

  1. Số lượng biến số sử dụng nhiều, dẫn tới việc theo dấu một biến nhất định nào đó khó khăn hơn
  2. Scope của biến lớn nên việc nắm bắt được biến được sử dụng ở những nơi nào tốn nhiều thời gian
  3. Biến số bị thay đổi giá trị nhiều nên khó nắm bắt được giá trị hiện tại của nó là gì

Trong bài biết hôm nay chúng ta sẽ đưa ra những giải pháp cần thiết để đối phó với từng vấn đề trong đó.

I. Xoá bớt biến số đi

1. Xoá biến số không cần thiết

Ví dụ chúng ta có đoạn code Python sau:

now = datetime.datetime.now()
root_message.last_view_time = now

Biến now ở đây hoàn toàn không cần thiết bởi vì

  • Nó không giúp biểu thức nhìn đỡ phức tạp hơn
  • Không giúp code đọc dễ hiểu hơn, vì datetime.datetime.now() đã đủ nói lên tất cả rồi
  • Bởi vì nó chỉ được dùng một lần nên lãng phí bộ nhớ

2. Xoá biến lưu kết quả trung gian

Ví dụ chúng ta có hàm xoá một phần tử với giá trị xác định trong mảng viết bằng JavaScript như dưới đây:

var remove_one = function (array, value_to_remove) {
    var index_to_remove = null;
    for (var i = 0; i < array.length; i += 1) {
        if (array[i] === value_to_remove) {
            index_to_remove = i;
            break;
        }
    }
    if (index_to_remove !== null) {
        array.splice(index_to_remove, 1);
    }
};

Biến index_to_remove là Biến trung gian (lưu kết quả trung gian trong quá trình tính toán và xử lý). Ở trong những trường hợp như vậy chúng ta hoàn toàn có thể viết code để loại bỏ biến này, giảm thiểu sự phức tạp của hàm số.

var remove_one = function (array, value_to_remove) {
    var index_to_remove = null;
    for (var i = 0; i < array.length; i += 1) {
        if (array[i] === value_to_remove) {
            array.splice(i, 1);
            return;
        }
    }
};

3. Xoá biến điều khiển flow

Chúng ta cùng xem xét vòng lặp sau, trong thực tế không ít người viết thế này:

boolean done = false;
while (/* Điều kiện */ &&  !done) {
    ...
    if (...) {
        done = true;
        continue;
    }
}

Trong vòng lặp trên có thể có rất nhiều chỗ set cho done = true khiến chúng ta khó tracking lúc đọc và sửa code. Mục đích của việc này vốn chỉ là để đảm bảo sẽ không có chỗ nào cho phép thoát ra khỏi vòng lặp giữa chừng. Tuy nhiên quy tắc này không hề tồn tại. Giải pháp nhanh gọn nhất là tìm cách xoá nó đi.

while (/* Điều kiện */) {
    ...
    if (...) {
        break;
    }
}

II. Thu nhỏ scope của biến

Có lẽ ai cũng từng nghe tới lời khuyên của các bậc đàn anh "Hãy cố tránh việc sử dụng biến global!". Vì ở bất cứ đâu biến này đều có thể sử dụng nên rất khó kiểm soát giá trị của nó và sửa chữa khi chương trình gặp lỗi. Nguyên công dò qua từng file thôi đã mệt rồi chứ đừng nói tới việc đặt debug ở từng vị trí đó. Mặt khác biến global cũng khiến cho namespace bị Ô nhiễm, vì có thể có sự xung đột với biến local. Để thu nhỏ scope của biến thì Key là:

Cố gắng giảm tối đa số lượng dòng code mang biến đó

Ví dụ đơn giản cho việc thu nhỏ scope của biến chúng ta cùng xem trong ví dụ với một class lớn với nhiều function như sau:

class LargeClass {
    string str_;
    
    void Method1() {
        str_ = "example";
        Method2();
    }
    
    void Method2() {
        // Sử dụng biến str_
    }
    
    // Ngoài ra có rất nhiều method không sử dụng biến str_
};

Trong trường hợp này biến str_ đã trở trành biến Mini global. Chúng ta sẽ hạ cấp nó xuống mức biến local như sau:

class LargeClass {

    void Method1() {
        string str = "example";
        Method2(str);
    }
    
    void Method2(string str) {
        // Sử dụng biến str
    }
    
};

Có một tips nữa là nếu scope của biến đã đủ nhỏ, thì nên đặt định nghĩa của biến ở gần nơi sử dụng biến đó. Thường thì trong C hay một số ngôn ngữ cũ khác hay có quy định đặt định nghĩa biến ở đầu file. Tuy nhiên nếu đặt như vậy thì người đọc code khá là khó khăn trong việc tìm xem giá trị khởi tạo của biến là bao nhiêu khi đọc tới đoạn code chứa biến đó. Lúc đấy lại phải quay lại đầu file với một list dài khai báo các biến. Đúng là rất bất tiện.

III. Hạn chế những dòng code thay đổi giá trị biến

Hiển nhiên là càng ít dòng code thay đổi giá trị của biến thì code càng dễ đọc và dễ sửa. Lý tưởng nhất là 1 lần =)). Tuy nhiên thực tế không được thì mình cố gắng giảm xuống mức tối thiểu thôi. Ví dụ với hàm số set giá trị cho phần tử empty đầu tiên tìm thấy.

var setFirstEmptyInput = function (new_value) {
    var i = 1;
    var elem = document.getElementById('input' + i);
    while (elem !== null) {
         if (elem.value === '') {
             elem.value = new_value;
             return elem;
         }
         i++;
         elem = document.getElementById('input' + i);
    }
    return null;
};

Nhìn vào biến elem chúng ta thấy trong nó được khởi tạo ở ngoài và trong mỗi vòng lặp lại xuất hiện một lần khiến việc theo dõi trở lên khó khăn hơn. Nhưng biến này lại biến đổi đồng thời với biến i, nên chúng ta có thể chuyển vòng lặp while thành for như sau:

var setFirstEmptyInput = function (new_value) {
    for (var i = 1; true; i++) {
         var elem = document.getElementById('input' + i);
         if (elem === null) return null;
         
         if (elem.value === '') {
             elem.value = new_value;
             return elem;
         }
    }
};

Với cách sửa này chúng ta đã hạn chế cả ielem chỉ biến đổi ở một dòng code duy nhất.