Функциональный C#: Одержимость примитивами

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

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

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

Если вы тоже так думаете, то вашим результатом будет подобный код:

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

Да и ладно, если бы эти проверки оставались внутри класса. Но ведь они выходят на уровень выше — в основной класс приложения:

Согласитесь, такой подход, мягко говоря, не совсем корректный. А как же принцип DRY и единый источник истины? В приведенном выше примере по меньшей мере 3 таких источника, что совсем не оправдано.

Именно эта ситуация и называется состоянием одержимости примитивами. В следующей главе мы покажем вам, как обойти эту «болезнь».

Как избавиться от одержимости примитивами?

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

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

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

А вот и пример применения таких классов:

Обратите внимание, что экземпляры Result<Email> и Result<CustomerName> явно говорят нам, что метод Create может вызвать ошибку. И если это произойдет, то информацию об ошибке можно узнать из свойства Error.

А теперь давайте взглянем на класс Customer после того, как мы ввели два маленьких побочных класса:

Практически все проверки были перемещены в классы Email и CustomerName. Остались только условия с проверками на null, но их мы рассмотрим в следующей статье.

Так какие преимущества мы получили, избавившись от одержимости примитивами?

  1. Мы создали единый авторитетный источник знаний для каждого объекта и избавились от дублирования кода.
  2. Теперь невозможно по ошибке присвоить объекту Email или CustomerName такое значение, которое привело бы к ошибке компилятора.
  3. Нет необходимости в дополнительной проверке корректности электронной почты или имени покупателя. Если объекты класса Email или CustomerName существуют, то мы точно знаем, что данные в них хранятся абсолютно верные.

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

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

Обратная сторона: ограничения

К сожалению, создание пользовательских типов данных в C# реализовано не так безупречно, как в функциональных языках: F#, например. Возможно, ситуацию исправит новая версия языка: C# 7.0.

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

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

Вывод

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

Исходные коды

  1. Код с одержимостью примитивами
  2. Код без одержимости примитивами

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