Nếu bạn muốn hỏi một lập trình viên kỳ cựu cho một lời khuyên về các phương pháp lập trình tốt, sau khi suy nghĩ, câu trả lời rất có thể sẽ là, “Tránh các biến toàn cục!”. Và với lý do chính đáng: biến toàn cục là một trong những khái niệm bị lạm dụng nhiều nhất trong ngôn ngữ. Mặc dù chúng có vẻ vô hại trong các chương trình học nhỏ, nhưng chúng thường có vấn đề ở những chương trình lớn hơn.

Các lập trình viên mới thường bị cám dỗ để sử dụng nhiều biến toàn cục, vì chúng dễ làm việc với chúng, đặc biệt là khi có nhiều lệnh gọi đến các hàm khác nhau (truyền dữ liệu qua các tham số hàm là một điều khó khăn). Tuy nhiên, đây nhìn chung là một ý tưởng tồi. Nhiều developer tin rằng nên tránh hoàn toàn các biến toàn cục không phải const!

Nhưng trước khi đi vào lý do tại sao, chúng ta nên làm rõ. Khi các developer nói với bạn rằng các biến toàn cục là xấu, họ thường không nói về tất cả các biến toàn cầu. Họ chủ yếu nói về các biến toàn cục không phải const.

1. Tại sao các biến toàn cục (không phải const) không tốt

Cho đến nay, lý do lớn nhất mà các biến toàn cục không phải const nguy hiểm là vì giá trị của chúng có thể bị thay đổi bởi bất kỳ hàm nào được gọi và không có cách nào dễ dàng để lập trình viên biết rằng điều này sẽ xảy ra. Hãy xem xét chương trình sau:

/*
Cafedev.vn - Kênh thông tin IT hàng đầu Việt Nam
@author cafedevn
Contact: cafedevn@gmail.com
Fanpage: https://www.facebook.com/cafedevn
Group: https://www.facebook.com/groups/cafedev.vn/
Instagram: https://instagram.com/cafedevn
Twitter: https://twitter.com/CafedeVn
Linkedin: https://www.linkedin.com/in/cafe-dev-407054199/
Pinterest: https://www.pinterest.com/cafedevvn/
YouTube: https://www.youtube.com/channel/UCE7zpY_SlHGEgo67pHxqIoA/
*/

int g_mode; // declare global variable (will be zero-initialized by default)
 
void doSomething()
{
    g_mode = 2; // set the global g_mode variable to 2
}
 
int main()
{
    g_mode = 1; // note: this sets the global g_mode variable to 1.  It does not declare a local g_mode variable!
 
    doSomething();
 
    // Programmer still expects g_mode to be 1
    // But doSomething changed it to 2!
 
    if (g_mode == 1)
        std::cout << "No threat detected.\n";
    else
        std::cout << "Launching nuclear missiles...\n";
 
    return 0;
}

Lưu ý rằng lập trình viên đặt biến g_mode thành 1, và sau đó gọi doSomething (). Trừ khi lập trình viên có kiến ​​thức rõ ràng rằng doSomething () sẽ thay đổi giá trị của g_mode, họ có thể không mong đợi doSomething () thay đổi giá trị! Do đó, phần còn lại của main () không hoạt động như lập trình viên mong đợi.

Nói tóm lại, các biến toàn cục làm cho trạng thái của chương trình không thể đoán trước được. Mọi lệnh gọi hàm đều trở nên nguy hiểm tiềm ẩn và lập trình viên không có cách nào dễ dàng để biết được lệnh nào nguy hiểm và lệnh nào không! Các biến cục bộ an toàn hơn nhiều vì các hàm khác không thể ảnh hưởng trực tiếp đến chúng.

Có rất nhiều lý do chính đáng khác để không sử dụng các hình cầu không hằng số.

Với các biến toàn cục, không có gì lạ khi tìm thấy một đoạn code giống như sau:

void someFunction()
{
    // useful code
 
    if (g_mode == 4) // do something good
}

Sau khi gỡ lỗi, bạn xác định rằng chương trình của bạn không hoạt động chính xác vì g_mode có giá trị 3, không phải 4. Bạn làm cách nào để khắc phục nó? Bây giờ bạn cần tìm tất cả các vị trí mà g_mode có thể được đặt thành 3 và theo dõi cách nó được đặt ngay từ đầu. Có thể đây là một đoạn code hoàn toàn không liên quan!

Một trong những lý do chính để khai báo biến cục bộ càng gần với nơi chúng được sử dụng càng tốt là vì làm như vậy sẽ giảm thiểu lượng code bạn cần xem qua để hiểu biến đó làm gì. Các biến toàn cục nằm ở đầu- bởi vì chúng có thể được truy cập ở mọi nơi, bạn có thể phải xem toàn bộ chương trình để hiểu cách sử dụng của chúng. Trong các chương trình nhỏ, đây có thể không phải là vấn đề. Trong những cái lớn, nó sẽ gặp vấn đề.

Ví dụ: bạn có thể thấy g_mode được tham chiếu 442 lần trong chương trình của bạn. Trừ khi g_mode được ghi chép đầy đủ, bạn có thể sẽ phải xem qua từng lần sử dụng g_mode để hiểu cách nó được sử dụng trong các trường hợp khác nhau, các giá trị hợp lệ của nó và chức năng tổng thể của nó.

Các biến toàn cục cũng làm cho chương trình của bạn ít mô-đun hơn và kém linh hoạt hơn. Một hàm không sử dụng gì ngoài các tham số của nó và không có tác dụng phụ là một mô đun hoàn hảo. Tính mô-đun giúp hiểu được hàm của một chương trình cũng như khả năng tái sử dụng. Các biến toàn cục làm giảm đáng kể tính mô đun.

Đặc biệt, tránh sử dụng các biến toàn cục cho các biến “điểm quyết định” quan trọng (ví dụ: các biến bạn muốn sử dụng trong câu lệnh điều kiện, như biến g_mode trong ví dụ trên). Chương trình của bạn không có khả năng bị hỏng nếu một biến toàn cục giữ giá trị thông tin thay đổi (ví dụ: như tên của người dùng). Nó có nhiều khả năng bị hỏng hơn nếu bạn thay đổi một biến toàn cục ảnh hưởng đến cách chương trình của bạn thực sự hoạt động.

Bạn nên

Sử dụng biến cục bộ thay vì biến toàn cục bất cứ khi nào có thể.

Vì vậy, những lý do rất tốt để sử dụng các biến toàn cục không phải const là gì?

Không có nhiều. Trong hầu hết các trường hợp, có những cách khác để giải quyết vấn đề mà tránh sử dụng các biến toàn cục không phải const. Nhưng trong một số trường hợp, việc sử dụng hợp lý các biến toàn cục không phải hằng số thực sự có thể làm giảm độ phức tạp của chương trình và trong những trường hợp hiếm hoi này, việc sử dụng chúng có thể tốt hơn các lựa chọn thay thế.

Một ví dụ điển hình là file nhật ký, nơi bạn có thể kết xuất thông tin lỗi hoặc gỡ lỗi. Có lẽ nên xác định điều này là toàn cầu vì bạn có thể chỉ có một bản ghi trong một chương trình và nó có thể sẽ được sử dụng ở mọi nơi trong chương trình của bạn.

Đối với giá trị của nó, các đối tượng std :: cout và std :: cin được triển khai dưới dạng các biến toàn cục (bên trong không gian tên std).

Theo nguyên tắc chung, bất kỳ việc sử dụng biến toàn cục nào phải đáp ứng ít nhất hai tiêu chí sau: Chỉ nên có một trong những thứ mà biến đại diện trong chương trình của bạn và việc sử dụng nó phải phổ biến trong suốt chương trình của bạn.

Nhiều lập trình viên mới mắc sai lầm khi nghĩ rằng một cái gì đó có thể được triển khai như một toàn cầu vì chỉ cần một cái ngay bây giờ. Ví dụ: bạn có thể nghĩ rằng vì đang triển khai trò chơi một người chơi nên bạn chỉ cần một người chơi. Nhưng điều gì xảy ra sau đó khi bạn muốn thêm chế độ nhiều người chơi (so với hoặc ghế nóng)?

2. Bảo vệ bạn khỏi sự hủy diệt toàn cục

Nếu bạn thấy một cách sử dụng tốt cho một biến toàn cục không phải const, một vài lời khuyên hữu ích sẽ giảm thiểu mức độ rắc rối mà bạn có thể gặp phải. Lời khuyên này không chỉ dành cho các biến toàn cục không phải hằng số mà có thể hữu ích với tất cả các biến toàn cục.

Đầu tiên, đặt tiền tố cho tất cả các biến toàn cục không có vùng chứa tên bằng “g” hoặc “g_”, hoặc tốt hơn, đặt chúng vào vùng tên (được thảo luận trong bài  – Namespace do người dùng định nghĩa), để giảm nguy cơ đặt tên trùng.

Ví dụ, thay vì:

constexpr double gravity { 9.8 }; // unclear if this is a local or global variable from the name
 
int main()
{
    return 0;
}

Làm như vậy:

namespace constants
{
    constexpr double gravity { 9.8 };
}
 
int main()
{
    return 0;
}

Thứ hai, thay vì cho phép truy cập trực tiếp vào biến toàn cục, cách tốt hơn là “đóng gói” biến. Trước tiên, hãy đảm bảo rằng biến chỉ có thể được truy cập từ bên trong tệp mà nó được khai báo, ví dụ: bằng cách làm cho biến tĩnh hoặc const. Thứ hai, cung cấp các “hàm truy cập” toàn cục bên ngoài để làm việc với biến. Các hàm này có thể đảm bảo duy trì việc sử dụng thích hợp (ví dụ: xác thực đầu vào, kiểm tra phạm vi, v.v.). Ngoài ra, nếu bạn quyết định thay đổi triển khai cơ bản (ví dụ: di chuyển từ cơ sở dữ liệu này sang cơ sở dữ liệu khác), bạn chỉ phải cập nhật các hàm truy cập thay vì mọi đoạn code sử dụng trực tiếp biến toàn cục.

Ví dụ, thay vì:

namespace constants
{
    extern const double gravity { 9.8 }; // has external linkage, is directly accessible by other files
}

Bạn nên làm vậy:

namespace constants
{
    const double gravity { 9.8 }; // has internal linkage, is accessible only by this file
}
 
double getGravity() // this function can be exported to other files to access the global outside of this file
{
    // We could add logic here if needed later
    // or change the implementation transparently to the callers
    return constants::gravity;
} 

Lưu ý

Các biến const có liên kết nội bộ theo mặc định, lực hấp dẫn không cần phải tĩnh.

Thứ ba, khi viết một hàm độc lập khác sử dụng biến toàn cục, không sử dụng biến trực tiếp trong nội dung hàm của bạn. Thay vào đó, hãy chuyển nó vào như một đối số. Bằng cách đó, nếu hàm của bạn cần sử dụng một giá trị khác cho một số trường hợp, bạn có thể chỉ cần thay đổi đối số. Điều này giúp duy trì tính mô-đun.

Thay vì:

#include <iostream>
 
namespace constants
{
    constexpr double gravity { 9.8 };
}
 
// This function is only useful for calculating your instant velocity based on the global gravity
double instantVelocity(int time)
{
    return constants::gravity * time;
}
 
int main()
{
    std::cout << instantVelocity(5);
}

Nên làm vậy:

#include <iostream>
 
namespace constants
{
    constexpr double gravity { 9.8 };
}
 
// This function can calculate the instant velocity for any gravity value (more useful)
double instantVelocity(int time, double gravity)
{
    return gravity * time;
}
 
int main()
{
    std::cout << instantVelocity(5, constants::gravity); // pass our constant to the function as a parameter
}

Cài ứng dụng cafedev để dễ dàng cập nhật tin và học lập trình mọi lúc mọi nơi tại đây.

Nguồn và Tài liệu tiếng anh tham khảo:

Tài liệu từ cafedev:

Nếu bạn thấy hay và hữu ích, bạn có thể tham gia các kênh sau của cafedev để nhận được nhiều hơn nữa:

Chào thân ái và quyết thắng!

Đăng ký kênh youtube để ủng hộ Cafedev nha các bạn, Thanks you!