Статьи

Что такое глобальное состояние?


Безумие: делать одно и то же снова и снова и ожидать разных результатов.
— приписывается Альберту Эйнштейну

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

Например:

new SomeClass()->printId();
new SomeClass()->printId();
// output: 1, 2

имеет некоторое глобальное состояние (статический счетчик), влияющее на поле внутри экземпляров SomeClass. Поэтому может быть нелегко копировать сценарии (как в тестах) несколько раз.

Примеры

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

Говоря об объектах, синглетонах и статических классах, содержащих поля, это еще один пример глобального состояния. Более тонкие случаи — это скрытые локализации, такие как переводы вывода и символов (LC_ALL?)

Параметризация усложняется глобальным состоянием, потому что шов для соавторов скрыт (файлы конфигурации и переменные среды / глобальные переменные) или недоступен (одиночные).

способность быть свидетелем в суде

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

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

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

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

Принимая этот подход до крайности, мы замечаем, что глобальное состояние часто скрыто от нашего взгляда, потому что оно воспринимается как должное. Базовые классы, предлагаемые языком (например, String), не могут быть смоделированы или заменены тестовыми двойниками, даже если в них есть достаточно логики; все классы и функции, содержащиеся в наших приложениях, находятся в глобальном состоянии, поскольку их реализация не может быть заменена, но мы не считаем их проблемой как синглтонами.

постоянная

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

Фактически само определение состояния (например, из аппаратных логических сетей) — это определение компонента, который может со временем изменять свое поведение, сохраняя информацию о предыдущих входных данных. Короче говоря, любые вычисления, к которым часто обращаются, но которые не имеют возможности изменить свой результат или вызвать побочные эффекты, не являются состоянием (это глобально). Вместо этого ПЗУ рассматривается как чисто комбинаторная сеть, которая не является настоящей «памятью», а просто функцией преобразования адресов в слова.

Синглтон, изменяющий свои ответы после некоторых вызовов, является глобальным состоянием, которое затрудняет тестирование; статический класс, содержащий только чистые функции, может сделать тесты слишком сложными и нарушить объектно-ориентированную парадигму, но это не так опасно, как первая.

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

class Array
  def sum
    inject {|sum, x| sum + x }
  end
end

Когда вы видите вызов этого метода, вы должны задать несколько вопросов:

  • где это было добавлено в базу кода? Какой исходный файл мне посмотреть?
  • Когда он был добавлен в базу кода? Уверен ли я, что определения могут быть включены только при запуске, и я не вызываю метод до его определения?
  • Существуют ли множественные переопределения метода? Может быть из других библиотек или кода для интеграции?

Те же проблемы произошли с prototype.js, который модифицирует объект-прототип базовых объектов JavaScript, таких как Array, эффективно переопределяя и добавляя методы. Результатом является небольшая совместимость с другими библиотеками.

Но даже исправление обезьяны должно быть хорошо, пока модификации действительно постоянны и окончательны. Если вы используете единый фреймворк (например, Ext Js) и всегда используете его на всех страницах приложения и в тестах, тогда он может безопасно обезопасить базовые классы языка без необходимости отладки метода, работающего в одной среде. но не в другом.

Вывод

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

Создание постоянных глобальных вещей — это первый шаг к упрощению рассуждений о дизайне и повышению тестируемости. Если вы можете запустить модульный тест дважды одним и тем же методом, вы официально свободны от глобального состояния в этом сценарии.

Будьте внимательны при использовании открытых классов, редактируемых прототипов, встроенных вызовов реестров и файлов и т. Д.: Они могут добавить несколько переменных к переменным, которые могут повлиять на результат фрагмента кода. Они будут скрывать зависимость, но не заставят ее исчезнуть.