Перейти к основному содержимому

Изменяемость, побочные эффекты и неизменяемые данные

Разработчику
Теория данных (раздел 3)

Структуры в коде — Структуры данных; persistence — ORM.

Изменяемость — частый источник непредсказуемости. В духе MAPPER объект меняет только то состояние, которое имеет смысл в предметной области, и делает это явно.


Зачем снижать изменяемость

ПроблемаПоследствие
Общие мутабельные структурыГонки, "пропавшие" обновления в async
Сеттеры "на всё"Инварианты легко нарушить извне
Ленивая инициализация полейСкрытые зависимости и порядок вызовов
Побочные эффекты в "чистых" функцияхТесты зависят от окружения

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


Практики по языкам

JavaScript / TypeScriptconst для ссылок, Object.freeze для конфигов, spread вместо мутации массива:

const next = [...items, newItem]; // вместо items.push в shared state

C#init, readonly поля, record для DTO и value object:

public sealed record Money(decimal Amount, string Currency);

Python@dataclass(frozen=True), неизменяемые кортежи для координат.

На практике: замена var на const где возможно; не объявлять переменные переменными (одна let на разные роли — запах); отказ от "изменяемых констант" (объект в const, но поля мутируются без дисциплины).


Существенные и несущественные изменения

Существенные характеристики задаются при создании (OrderId, дата рождения). Несущественные могут меняться через явные методы (rename, updateAddress). Случайный setEmail на сущности User — признак анемии (статья 6).


Побочные эффекты

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

Полезные приёмы:

  • передавать зависимости явно (порты/адаптеры);
  • отделять команды от запросов (CQS, см. статью 1);
  • I/O выносить на край гексагона.

Ленивая инициализация

Паттерн "если поле null — создать список" внутри геттера скрывает стоимость и ломает потокобезопасность. Лучше инициализировать в конструкторе или фабрике; ленивость — осознанно, с документированной синхронизацией.


Связь с параллелизмом

В параллельных вычислениях неизменяемые структуры снижают блокировки. В UI и вебе иммутабельное состояние (Redux, React state) упрощает отладку.

Функциональные языки (Haskell) делают неизменяемость нормой; в императивных — это дисциплина команды.


Краткий чек-лист

  • const / readonly / frozen там, где состояние не должно меняться.
  • Существенные атрибуты задаются при создании, не через случайные сеттеры.
  • Ленивая инициализация — только осознанно.
  • Побочные эффекты вынесены на границы модуля.

Дальше: Декларативный стильУсловия и null. Указатель тем — Справочник тем чистого кода.