Функциональный C#. Часть 2. Одержимость примитивами
6К открытий6К показов
Мы продолжаем цикл статей о функциональном программировании на языке C#:
- Функциональный C#: Неизменяемые объекты
- Функциональный C#: Одержимость примитивами
- Функциональный C#: Ненулевые ссылочные типы
- Функциональный C#: Обработка исключений
Представьте себе ситуацию, что вам потребовалось описать некий класс Customer
, содержащий определенные данные — скажем, имя и электронную почту. К сожалению, многие программисты подумают, что использовать для имени и почты поля элементарных типов данных куда проще, чем написать базовый класс.
Если вы тоже так думаете, то вашим результатом будет подобный код:
Данный метод является правильным ровно до того момента, пока вам не понадобится проверять значения полей на корректность. Но вас так просто не убедить и вы пишете уйму проверок. В итоге класс увеличивается и обрастает различными условиями:
Да и ладно, если бы эти проверки оставались внутри класса. Но ведь они выходят на уровень выше — в основной класс приложения:
Согласитесь, такой подход, мягко говоря, не совсем корректный. А как же принцип DRY и единый источник истины? В приведенном выше примере по меньшей мере 3 таких источника, что совсем не оправдано.
Именно эта ситуация и называется состоянием одержимости примитивами. В следующей главе мы покажем вам, как обойти эту “болезнь”.
Как избавиться от одержимости примитивами?
Очень просто! Мы всего-навсего должны ввести два новых класса, в которых мы и будем проверять значения на валидность. Это и будет единым источником истины, о котором говорилось выше.
Красота такого подхода заключается в том, что если вы захотите изменить логику проверки значений, вам нужно будет подкорректировать код ровно в одном месте. Чем меньше дублирования кода в вашей программе, тем меньше ошибок вы допустите и тем счастливее ваши клиенты!
Обратите внимание на то, что конструктор класса Email
приватный. А новый экземпляр мы можем создать при помощи метода Create
, который прогоняет входное значение через множество фильтров, проверяя его на валидность. Это сделано для того, чтобы значение объекта было корректным с самого начала его существования.
А вот и пример применения таких классов:
Обратите внимание, что экземпляры Result<Email>
и Result<CustomerName>
явно говорят нам, что метод Create
может вызвать ошибку. И если это произойдет, то информацию об ошибке можно узнать из свойства Error
.
А теперь давайте взглянем на класс Customer
после того, как мы ввели два маленьких побочных класса:
Практически все проверки были перемещены в классы Email
и CustomerName
. Остались только условия с проверками на null
, но их мы рассмотрим в следующей статье.
Так какие преимущества мы получили, избавившись от одержимости примитивами?
- Мы создали единый авторитетный источник знаний для каждого объекта и избавились от дублирования кода.
- Теперь невозможно по ошибке присвоить объекту
Email
илиCustomerName
такое значение, которое привело бы к ошибке компилятора. - Нет необходимости в дополнительной проверке корректности электронной почты или имени покупателя. Если объекты класса
Email
илиCustomerName
существуют, то мы точно знаем, что данные в них хранятся абсолютно верные.
Есть одна деталь, на которой бы хотелось остановиться поподробней. Дело в том, что некоторые программисты избавляются от одержимости элементарных типов не полностью. Например:
Нужно помнить, что использовать элементарные типы стоит только тогда, когда объект покидает пределы программы. То есть в тех случаях, когда значения заносятся в базу данных или экспортируются во внешний файл. Но в своем приложении старайтесь использовать написанные вами классы-обертки настолько часто, насколько это возможно. Это сделает ваш код более чистым. Убедитесь в этом сами:
Обратная сторона: ограничения
К сожалению, создание пользовательских типов данных в C# реализовано не так безупречно, как в функциональных языках: F#, например. Возможно, ситуацию исправит новая версия языка: C# 7.0.
Именно поэтому я считаю, что в некоторых ситуациях использование примитивов лучше, чем создание простого класса-обертки. Например, для представления денег. Они могут быть выражены при помощи элементарного типа данных с одной лишь проверкой знака числа. Да, вам придется продублировать это условие, но это решение проще, даже в долгосрочной перспективе.
Как и всегда, скажу вам, чтобы вы перед тем, как что-то написать, взвесили все “за” и “против” и только потом принимали решение. И не бойтесь менять свое мнение несколько раз.
Вывод
С неизменяемыми и не примитивными типами данных мы становимся все ближе и ближе к решению задач в парадигме функционального программирования. А в следующей статье мы попробуем избавиться от многочисленных проверок на null
.
Исходные коды
Перевод статьи «Functional C#: Primitive obsession»
6К открытий6К показов