Глобальные состояния: зачем и как их избегать

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

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

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

  1. Во-первых, я опишу то, что мы чаще всего называем глобальными состояниями. Этот термин не всегда применяется точно, поэтому требует пояснения.
  2. Далее мы узнаем, чем глобалы вредны для нашей кодовой базы.
  3. Затем я объясню, как урезать область видимости глобалов, чтобы превратить их в локальные переменные.
  4. И наконец, я расскажу об инкапсуляции и о том, почему крестовый поход против глобальных переменных является лишь частью большой проблемы.

Надеюсь, эта статья объяснит вам всё, что нужно знать о глобальных состояниях. Если вам покажется, что я многое упустил и вы меня за это ненавидите и не хотите меня больше видеть, напишите об этом в комментариях. Это будет приятно мне, моим читателям и всем, кто неожиданно оказался на этой странице.

Ты готов, дорогой читатель, вскочить на коня и познать своего врага? Вперёд, найдём эти глобалы и заставим их отведать вкус стали наших мечей!

Что такое состояние?

Начнём с основ, чтобы вы, разработчики, понимали друг друга.

Состояние (state) — это определение системы или сущности. Состояния встречаются в реальной жизни:

  • Когда компьютер выключен, его состояние — выключен.
  • Когда чашка чая горячая, её состояние — горячая.

В разработке ПО некоторые конструкции (например переменные) могут иметь состояния. Скажем, строка «hello» или число 11 не считаются состояниями, они значения. Они становятся состоянием, когда прикрепляются к переменной и помещаются в память.

<?php  echo "hello"; // No state here! $lala = "hello"; // The variable $lala has the state 'hello'.

Можно выделить два вида состояний:

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

<?php  $lala = "hello"; // Initialisation of the variable. $lala = "hallo"; // The state of the variable $lala can be changed at runtime.

Неизменяемые состояния: не могут меняться в ходе исполнения. Вы присваиваете своей переменной первое состояние, и его значение впоследствии уже не меняется. «Константами» в обиходе называют примеры неизменяемых состояний:

<?php  define("GREETING", "hello"); // Constant definition. echo GREETING;  GREETING = "hallo"; // This line will produce an error!

Теперь давайте послушаем гипотетическую беседу между Денисом и Василием, вашими коллегами-разработчиками:

— Дэн! Ты везде насоздавал глобальные переменные! Их нельзя поменять без того, чтобы всё не сломалось! Я тебя прибью!
— Нифига, Васёк! Мои глобальные состояния офигенные! Я вложил в них душу, это шедевры! Я обожаю свои глобалы!

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

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

<?php  namespace App\Ecommerce;  $global = "I'm a mutable global variable!"; // global variable  class Shipment {     public $warehouse; // local variable existing in the whole class      public function __construct()     {         $info = "You're creating a shipment object!"; // local variable bound to the constructor scope         echo $info;     } }  class Product {     public function __construct()     {         global $global;         $global = "I change the state now 'cause I can!";          echo "You're creating a product object!"; // no state here     } }

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

Если вы и правда так подумали, очень рекомендую продолжить чтение.

Глобальные состояния хуже чумы и холеры?

Самая большая диаграмма связей

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

Почему?

Допустим, у вас большое приложение с глобальными переменными. Каждый раз, когда вам нужно что-то поменять, вам придётся:

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

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

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

Это ещё не всё. Если вам нужно поменять состояние глобалов, то вы не будете представлять, на какую область видимости это повлияет. Приведёт ли это к неожиданному поведению другого класса, метода или функции? Успехов в поиске.

Короче, вы объедините все классы, методы и функции, использующие одинаковые глобальные состояния. Не забывайте: зависимости сильно повышают сложность. Вас это пугает? Должно бы. Маленькие определённые области видимости очень полезны: вам не нужно держать в голове всё приложение, достаточно помнить лишь о тех областях, с которыми работаете.

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

Коллизии имён глобалов

Есть сложности с использованием сторонних библиотек. Представим, что вы хотите использовать вон ту суперкрутую библиотеку, которая случайным образом раскрашивает каждый символ с эффектом мерцания. Мечта каждого разработчика! Если эта библиотека тоже использует глобалы, у которых те же имена, что и у ваших собственных, то вы насладитесь коллизиями имён. Ваше приложение обрушится и вы будете гадать о причинах, вероятно, долго:

  • Во-первых, вам понадобится выяснить, что ваша библиотека использует глобальные переменные.
  • Во-вторых, вам понадобится вычислить, какая переменная использовалась в ходе исполнения — ваша или библиотеки? Это не так просто, имена-то одинаковые!
  • В-третьих, раз вы не можете самостоятельно изменить библиотеку, придётся переименовать свои глобальные изменяемые переменные. Если они использованы по всему приложению, вы будете рыдать.

На каждом этапе вы будете рвать волосы от ярости и отчаяния. Скоро вам уже не понадобится расчёска. Вряд ли вас соблазняет такой сценарий. Возможно, кто-то вспомнит, что JavaScript-библиотеки Mootools, Underscore и jQuery всегда конфликтовали друг с другом, если их не помещать в более мелкие области видимости. А, и знаменитый глобальный объект $ в jQuery!

Тестирование превратится в кошмар

Если я вас ещё не убедил, давайте посмотрим на ситуацию с точки зрения модульного тестирования: каким образом вы пишете тесты при наличии глобальных переменных? Поскольку тесты могут менять глобалы, вы не знаете, при каком тесте какое было состояние. Вам нужно изолировать тесты друг от друга, а глобальные состояния связывают их вместе.

У вас когда-нибудь было так, что в изоляции тесты работают нормально, а когда запускаешь весь пакет, они сбоят? Нет? А у меня было. Каждый раз, когда об этом вспоминаю, я страдаю.

Проблемы с параллелизмом

Изменяемые глобальные состояния могут доставить много проблем, если вам необходим параллелизм (concurrency). Когда вы меняете состояние глобалов в нескольких потоках исполнения, то по уши вляпаетесь в мощное состояние гонки.

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

Избегание глобальных изменяемых состояний

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

Возьмём REST API: конечные точки получают какие-то HTTP-запросы с параметрами и отправляют ответы. Эти HTTP-параметры, отправленные на сервер, могут быть востребованы на многих уровнях вашего приложения. Очень соблазнительно сделать эти параметры глобальными при получении HTTP-запроса, модифицировав их перед отправкой ответа. Добавляем сверху в каждый запрос параллелизм, и рецепт катастрофы готов.

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

Если у вас откуда-то взялись глобалы, то как с ними быть? Как рефакторить приложение Дениса, вашего коллеги-разработчика, который создал глобалы везде, где только можно, потому что за последние 20 лет он ничего не читал по разработке?

Аргументы функций

Простейший способ избежать глобалов заключается в передаче переменных с помощью аргументов функции. Возьмём простой пример:

<?php  namespace App;  use Router\HttpRequest; use App\Product\ProductData; use App\Exceptions;  class ProductController {     public function createAction(HttpRequest $httpReq)     {         $productData = $httpReq->get("productData");          if (!$this->productModel->validateProduct($productData)) {             return ValidationException(sprintf("The product %d is not valid", $productData["id"]));         }          $product = $this->productModel->createProduct($productData);     } }  class Product {     public function createProduct(array $productData): Product     {         $productData["name"] = "SuperProduct".$productData["name"]; // This is not what you should do; I talk about it later in the article.          try {             $product = $this->productDao->find($productData["id"]);             return product;         } catch (NotFoundException $e) {             $product = $this->productDao->save($productData);             return $product;         }     } }  class ProductDao {     private $db;      public function find(int $id): array     {         return $this->db->find(['product' => $id]);     }      public function save(array $productData): array     {         return $this->db->saveProduct($productData);     } }

Как видите, массив $productData из контроллера, через HTTP-запрос, проходит через разные уровни:

  1. Контроллер получил HTTP-запрос.
  2. Параметры переданы в модель.
  3. Параметры переданы в DAO.
  4. Параметры сохранены в базе данных приложения.

Мы могли бы сделать этот массив параметров глобальным, когда извлекли его из HTTP-запроса. Кажется, что так проще: не нужно передавать данные в 4 разные функции. Однако передача параметров в качестве аргументов функций:

  • Очевидно покажет, что эти функции используют массив $productData.
  • Очевидно покажет, что какие функции используют какие параметры. Видно, что для ProductDao::find из массива $productData нужен только $id, а не всё подряд.

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

Вы уже слышите, как Денис протестует: «А если у функции уже три и более аргументов? Если нужно добавить ещё больше, то вырастет сложность функции! И что насчёт переменных, объектов и других конструкций, которые везде нужны? Будете передавать их каждой функции в приложении?».

Вопросы справедливые, дорогой читатель. Как хороший разработчик, вы должны объяснить Денису, использовав свои навыки общения, вот что:

«Денис, если в твоих функциях слишком много аргументов, то проблемой могут быть сами функции. Вероятно, они делают слишком много, отвечают за слишком много вещей. Ты не думал разделить их на более мелкие функции?».

Ощущая себя оратором в афинском Акрополе, вы продолжаете:

«Если тебе нужны переменные во многих областях видимости, то это проблема, и скоро мы об этом поговорим. Но если они и правда тебе нужны, то что плохого в передаче их через аргументы функций? Да, тебе придётся их набрать на клавиатуре, но мы же разработчики, это наша работа — писать код».

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

Контекстные объекты

Контекстными называют те объекты, которые содержат данные, определённые каким-то контекстом. Обычно эти данные хранятся в виде конструкции «ключ-пара», как например ассоциативный массив в PHP. У такого объекта нет поведения, только данные, аналогично объекту-значению.

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

Контекстом будет сам запрос: другой запрос — другой контекст — другой набор данных. Затем контекстный объект будет передан любому методу, которому понадобятся эти данные.

Вы скажете: «Это офигенно и всё такое, но что это даёт?»

  • Данные инкапсулированы в объекте. Чаще всего вашей задачей будет сделать данные неизменяемыми, то есть чтобы вы не могли изменить состояние — значение данных в объекте после инициализации.
  • Очевидно, что контексту нужны данные контекстного объекта, поскольку они передаются всем функциям (или методам), которым эти данные нужны.
  • Это решает проблему параллелизма: если у каждого запроса будет собственный контекстный объект, вы можете безопасно записывать или считывать их в их собственных потоках исполнения.

Но у всего в разработке есть цена. Контекстные объекты могут вредить:

  • Глядя на аргументы функции, вы не будете знать, какие данные лежат в контекстном объекте.
  • В контекстный объект можно положить что угодно. Осторожнее, не положите слишком много, например, всю пользовательскую сессию, или даже большую часть данных вашего приложения. А то может получиться такое: $context->getSession()->getUser()->getProfil()->getUsername(). Нарушите закона Деметры, и вашим проклятием станет безумная сложность.
  • Чем больше контекстный объект, тем сложнее узнать, какие данные и в какой области видимости он использует.

В общем, я бы избегал использования контекстных объектов по мере сил. Они могут повлечь немало сомнений. Неизменяемость данных — большой плюс, но нельзя забывать и о недостатках. Если используете контекстный объект, убедитесь, что он достаточно маленький, и передавайте его в маленькую и тщательно определённую область видимости.

Если перед исполнением программы вы понятия не имеете, сколько состояний будет передано вашим функциям (например, параметры из HTTP-запроса), то контекстные объекты могут быть полезны. Поэтому их некоторые фреймворки их используют, вспомните, к примеру, объект Request в Symfony.

Внедрение зависимостей

Другой хорошей альтернативой глобальным изменяемым состояниям будет прямое внедрение нужных вам данных в объект прямо при его создании. Это определение внедрения зависимости: набора методик для внедрения объектов в ваши компоненты (классы).

Почему именно внедрение зависимостей?

Цель — ограничить использование ваших переменных, объектов или иных конструктов, поместить их в ограниченную область видимости. Если у вас есть зависимости, которые внедрены, а следовательно могут действовать только внутри области видимости объекта, то вам будет проще узнать, в каком контексте они используются и почему. Никакой тоски и мучений!

Внедрение зависимостей делит жизненный цикл приложения на две важные фазы:

  1. Создание объектов приложения и внедрение их зависимостей.
  2. Использование объектов для достижения ваших целей.

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

Многие фреймворки используют внедрение зависимостей, иногда в довольно сложных схемах, с конфигурационными файлами и Dependency Injection Container (DIC). Но вовсе не обязательно всё усложнять. Вы можете просто создавать зависимости на одном уровне и внедрять их уровнем ниже. Например, в мире Go я не знаю никого, кто использовал бы DIC. Ты просто создаёшь зависимости в основном файле с кодом (main.go), а затем передаёшь их на следующий уровень. Можно также инстанцировать всё подряд в разные пакеты, чтобы чётко обозначить, что «фаза внедрения зависимостей» должна выполняться только на этом конкретном уровне. В Go области видимости пакетов могут сделать какие-то вещи проще, чем в PHP, в котором DIC’и широко применяются в каждом известном мне фреймворке, в том числе в Symfony и Laravel.

Внедрение через конструктор или сеттеры

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

  • Если вам нужно знать, какие есть зависимости у класса, вам достаточно найти конструктор. Не нужно искать разрозненные по всему классу методы.
  • Настройка зависимостей при установке даст вам уверенность в безопасности использования объекта.

Немножко поговорим о последнем пункте: это называется «применение инварианта» (enforcing invariant). Создавая экземпляр объекта и внедряя его зависимости, вы знаете: что бы ни понадобилось вашему объекту, он настроен правильно. А если вы используете сеттеры, как вы узнаете, что ваши зависимости уже заданы в момент использования объекта? Можете пойти в стек и попробовать выяснить, вызывались ли сеттеры, но я уверен, что вам не хочется этим заниматься.

Нарушение инкапсуляции

В конце концов, единственное различие между локальными и глобальными состояниями — это их области видимости. Они ограничены у локальных состояний, а для глобальных доступно всё приложение. Однако вы можете столкнуться с проблемами, характерными для глобальных состояний, если используете состояния локальные. Почему?

Ты сказал «инкапсуляция»?

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

Начнём с начала. Что нам говорит Википедия про определение инкапсуляции? Языковой механизм ограничения прямого доступа к каким-то компонентам объекта. Ограничение доступа? Зачем?

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

Растущая область видимости и утечки состояний

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

Возьмём пример: Anemic Domain Model может увеличивать область видимости ваших изменяемых моделей. По сути, Anemic Domain Model делит данные и поведение ваших доменных объектов на две группы: модели (объекты только с данными) и сервисы (объекты только с поведением). Чаще всего эти модели будут использоваться во всех сервисах. Следовательно, есть вероятность, что какой-то модели будет всё время расти область видимости. Вы не будете понимать, какая модель в каком контексте используется, их состояние изменится, и на вас обрушатся все те же проблемы.

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

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

Как определить такие области видимости? Следуйте бизнес-модели вашей компании. Приложение должно быть зеркалом бизнеса, а значит области видимости должны содержать состояния и поведения, отражающие бизнес-модель.

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

Возможности копирования состояний

Во многих случаях хорошим решением будет копирование состояний без их прямого изменения. Вернёмся к нашему примеру с Product, точнее, к этому методу:

class Product {     public function createProduct(array $productData): Product     {         $productData["name"] = "SuperProduct".$productData["name"]; // This is not what you should do; I talk about it later in the article.          try {             $product = $this->productDao->find($productData["id"]);             return product;         } catch (NotFoundException $e) {             $product = $this->productDao->save($productData);             return $product;         }     } }

Массиву $productData лучше оставаться неизменяемым. Если вы напрямую поменяете его состояние, а затем передадите другим функциям, то вскоре просто не сможете узнать, какое состояние принял этот массив.

В нашем примере значение имени изменено лишь один раз, это приемлемо. Но что произойдёт, когда ваши коллеги-разработчики увидят этот код? Они могут подумать, что вполне нормально напрямую менять состояния изменяемых переменных. И тогда можно ждать большого бардака с неизвестными состояниями.

Когда мы пишем код, мы одновременно пишем и руководства для тех, кто будет его читать. Об этом нужно помнить.

Лучше сделать так:

class Product {     public function createProduct(array $productData): Product     {         // Since $productData is passed to other variable, it has to be immutable.         $name = "SuperProduct".$productData["name"];          try {             $product = $this->productDao->find($productData["id"]);             return product;         } catch (NotFoundException $e) {             $product = $this->productDao->save($name, $productData);             return $product;         }     } }

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

Вы даже можете изолировать это изменение состояние в отдельном методе, чтобы было ещё очевиднее: «Внимание, я сейчас меняю это состояние».

Что насчёт глобальных неизменяемых состояний?

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

Безопасно ли их использовать?

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

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

К примеру, константа ShipmentDelay будет использована, надеюсь, только там, где реализована логика отгрузки товара. А если Денис, ваш коллега-разработчик, начнёт использовать ShipmentDelay для другой задержки, не относящейся к отгрузкам, то ваш глобал будет использоваться там, где он не имеет смысла. Глупо? Я видел много разработчиков, которые делают подобные странные вещи во имя священного принципа DRY.

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

Нужно ли находить и уничтожать все глобальные состояния?

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

Однако приложениям свойственно увеличиваться. Поэтому помните:

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

В разработке ПО не бывает однозначно хороших и однозначно плохих решений. Всё бывает полезным в зависимости от ситуации, и одна из самых трудных задач — принять верное решение в ваших условиях. Поэтому экспериментируйте, создавайте прототипы и думайте о последствиях.

Однако я считаю, что чаще всего глобальные изменяемые состояния приносят больше вреда, чем пользы.

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


ссылка на оригинал статьи https://habr.com/ru/company/mailru/blog/454946/

Добавить комментарий

Ваш e-mail не будет опубликован. Обязательные поля помечены *