Глобальные переменные часто используются в программировании для хранения данных, доступных во всех частях программы. Однако такой подход имеет множество недостатков, которые могут привести к серьезным проблемам в коде. В этом уроке мы рассмотрим основные причины, почему использование глобальных переменных считается плохой практикой, и предоставим примеры кода, демонстрирующие проблемы, которые могут возникнуть при их использовании.

1. Неопределенность и трудности в отладке

Глобальные переменные могут быть изменены в любой части программы, что делает их текущее состояние непредсказуемым. Это затрудняет отслеживание изменений и выявление ошибок.

Пример:


  #include <iostream>
  int globalVar = 10;
  
  void foo() {
    globalVar += 5;
  }
  
  void bar() {
    globalVar *= 2;
  }
  
  int main() {
    std::cout << "Initial value: " << globalVar << std::endl;
    foo();
    std::cout << "After foo(): " << globalVar << std::endl;
    bar();
    std::cout << "After bar(): " << globalVar << std::endl;
    return 0;
  }
  

В данном примере функция foo() изменяет глобальную переменную globalVar, а затем функция bar() изменяет её снова. Это приводит к сложности в отслеживании значений переменной и возможным ошибкам при масштабировании кода.

2. Скрытые зависимости

Глобальные переменные создают скрытые зависимости между функциями, что делает код менее модульным и сложным для понимания.

Пример:


  #include <iostream>
  int globalCounter = 0;
  
  void increment() {
    ++globalCounter;
  }
  
  void printCounter() {
    std::cout << "Counter: " << globalCounter << std::endl;
  }
  
  int main() {
    increment();
    increment();
    printCounter();
    return 0;
  }
  

Функции increment() и printCounter() зависят от глобальной переменной globalCounter, что не очевидно из их сигнатур. Это затрудняет понимание и тестирование кода.

3. Проблемы с потоками (многопоточность)

В многопоточных приложениях использование глобальных переменных может привести к состояниям гонки, когда несколько потоков одновременно изменяют одну и ту же переменную.

Пример:


  #include <iostream>
  #include <thread>
  int globalSum = 0;
  
  void addToSum(int value) {
    globalSum += value;
  }
  
  int main() {
    std::thread t1(addToSum, 5);
    std::thread t2(addToSum, 10);
    t1.join();
    t2.join();
    std::cout << "Global sum: " << globalSum << std::endl;
    return 0;
  }
  

В этом примере два потока одновременно изменяют значение globalSum, что может привести к некорректным результатам. Порядок выполнения потоков не определен, и итоговое значение переменной может быть неожиданным.

4. Затруднение в тестировании

Код, использующий глобальные переменные, сложнее тестировать, так как тесты должны учитывать возможные изменения глобального состояния. Это усложняет создание независимых и изолированных тестов.

Пример:


  #include <iostream>
  int globalFlag = 0;
  
  void setFlag() {
    globalFlag = 1;
  }
  
  void resetFlag() {
    globalFlag = 0;
  }
  
  bool isFlagSet() {
    return globalFlag == 1;
  }
  
  int main() {
    setFlag();
    std::cout << "Flag is set: " << std::boolalpha << isFlagSet() << std::endl;
    resetFlag();
    std::cout << "Flag is set: " << std::boolalpha << isFlagSet() << std::endl;
    return 0;
  }
  

Чтобы протестировать функции setFlag() и resetFlag(), необходимо учитывать текущее состояние глобальной переменной globalFlag, что усложняет написание тестов.

Заключение

Использование глобальных переменных в программе может привести к множеству проблем, таких как неопределенность состояния, скрытые зависимости, проблемы с многопоточностью и трудности в тестировании. Рекомендуется избегать использования глобальных переменных и предпочитать локальные переменные, параметры функций и другие подходы, которые улучшают читаемость, модульность и надежность кода.

Следите за этими рекомендациями, чтобы ваш код был чистым, понятным и легким в сопровождении.

Перейти к следующему уроку →