Функциональный C#: Неизменные объекты

Мы начинаем цикл статей, в которых покажем вам, как программировать на языке C# в парадигме функционального программирования. Нами будут рассмотрены темы:

  1. Функциональный C#: Неизменные объекты
  2. Функциональный C#: Одержимость примитивами
  3. Функциональный C#: Ненулевые ссылочные типы
  4. Функциональный C#: Обработка исключений

Почему мы рассматриваем неизменные объекты?

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

Давайте рассмотрим такой пример:

Будет ли изменен объект запроса к тому моменту, когда мы выполняем второй поиск? Может быть, да. А, может быть, нет. Это зависит от того, найдем ли мы что-нибудь, осуществив первый поиск. А еще и от того, изменятся ли критерии поиска после выполнения метода AdjustSearchCriteria. Проще говоря, мы не можем знать заранее, изменится ли объект запроса во втором поиске.

А теперь рассмотрим следующий код:

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

Итак, какие существуют проблемы в работе с подвергающимися изменениям структурами данных?

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

Как описать неизменные объекты?

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

Так как же нам описать неизменные объекты? Давайте рассмотрим пример: у нас есть класс ProductPile, представляющий некоторые продукты, которые мы выставили на продажу:

Чтобы сделать поля класса ProductPile неизменными, мы отметим их доступными только для чтения и создадим конструктор:

Итак, чего же мы добились такой организацией класса?

  1. Теперь мы можем не волноваться о корректности данных — проверять значение мы будем только один раз в конструкторе.
  2. Мы абсолютно уверены в том, что значения объектов корректны всегда.
  3. Объекты автоматически становятся потокобезопасными.
  4. Увеличилась читаемость кода, поскольку теперь нет необходимости проверять, не изменились ли значения объектов.

Ограничения в использовании

Конечно, все имеет свою цену. Мы можем применить нашу идею в маленьких и простых классах, однако она совсем не применима к большим.

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

Хорошим примером здесь являются неизменные коллекции (immutable collections). Их авторы учли проблемы с производительностью и добавили класс Builder, который позволяет «мутрировать», изменять коллекцию:

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

Вывод

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

Перевод статьи «Functional C#: Immutability»