Расширение репертуара подходов и методов для устранения ветвления – один из быстрых способов улучшить проект. Сайт proglib.io опубликовал сокращенный перевод статьи «Remove Your If-Else and Switch Cases», в которой поясняется, как можно сделать свой код чище и приятнее.
Количество строк кода никогда не было хорошим показателем его качества. Подобный приведенному ниже сумасшедший код не должен использоваться. Он не читабелен и делает проект неуправляемым, к тому же теряется гибкость.
Cкорее всего, if-else
и switch
– ваш обычный подход к ветвлению кода, но в нем нет необходимости. Вы можете полностью исключить ключевое слово else
из своего словаря по программированию. Некоторые матерые кодеры говорят, что if-else
– полиморфизм новичков.
Что плохого в традиционном ветвлении?
Много чего. Традиционное ветвление быстро разрастается. Вам придется изменять существующий код каждый раз при добавлении новой функции. Это нарушает принцип Open-Closed. Функции должны быть реализованы с помощью новых классов.
В идеале нужно изменять существующие классы только при рефакторинге, исправлении ошибок или для обновления бизнес-логики.
Какие есть альтернативы?
Существует масса альтернатив, но мы рассмотрим три типовых подхода, которые часто применяются при удалении традиционного ветвления из кода:
- концепции моделей с классами;
- использование полиморфного выполнения при работе с изменяющимися состояниями объектов;
- инкапсуляция стратегии в отдельные классы.
Эти 3 подхода легко справятся с большинством повседневных ситуаций, с которыми вы можете столкнуться.
Все методы имеют общие черты:
- Новая функциональность реализуется с помощью новых классов. Добавление кода, а не его изменение, обычно является более безопасным вариантом. Каждый раз, когда изменяется уже используемый код, вы подвергаетесь огромному риску все сломать.
- Проще тестировать специализированные классы – это огромное преимущество. Простые классы и методы легче рассматривать – легче понять множество небольших и сплоченных классов, чем несколько монолитных.
- Управление концепциями на низком уровне лучше, чем расширение обязанностей одного класса.
Моделирование концепций с помощью простых классов
Любому знакомому с DDD (domain-driven design) программисту известно, как важно избегать фиксирования бизнес-логики в небольших специализированных классах.
Допустим, у нас есть класс User
и в нем имя пользователя. Оно является строкой и имеет два условия: не может быть нулем или пустой строкой и не может превышать 50 символов.
Можно с уверенностью предположить, что нам понадобятся имена пользователей в других частях приложения. Каждый раз, когда мы получаем имя, придется выполнять одни и те же проверки, которые будут разбросаны по всему проекту.
Если что-то изменится и возникнет необходимость в валидации специальных символов, таких как «æøå», придется найти каждое место, где получено имя пользователя, и добавить новую проверку.
Гораздо лучшим подходом является перенос концепции имени пользователя и создание небольшого специализированного объекта, как показано ниже.
Этот фрагмент кода, несомненно, чище. Теперь каждый раз, когда потребуется обновление, его нужно будет делать только в одном месте.
Изменение реализации метода объекта в зависимости от его состояния
Иногда необходимо, чтобы объект вел себя по-разному в зависимости от его внутреннего состояния. Типичный ленивый способ реализации этого – традиционное ветвление, как в приведенном ниже примере.
Всякий раз, когда нам нужно вывести деньги, в аккаунте следует проверить внутреннее состояние и вести себя соответственно.
Одной из ловушек традиционного ветвления является вложенная условная логика. Любая форма «рождественской елки» в вашем коде – это усложнение следования логике и рассуждениям, ветви if/else начинают отдаляться друг от друга, что затрудняет чтение и обслуживание.
Паттерн состояния – это очень крутой подход к удалению сумасшедшей вложенной условной логики. Пытайтесь добиться, чтобы наш код был похож на следующий:
Видите, какая плоская конструкция?
Теперь каждая ветвь инкапсулирована в собственный класс, а класс account
делегирует ответственность специализированному AccountState
.
Удвойте количество кода и повысьте читабельность.
У нас есть базовый класс, от которого наследуется каждый объект состояния. Если мы получим новый запрос на добавление состояния RequiresValidation
или что-то еще, будет легко реализовать эту функцию, не касаясь существующих классов.
Внимательный читатель заметил, что нет никакого перехода в состояние. Очевидно, что классы не полностью реализованы, т. к. переход из одного состояния в другое выходит за рамки статьи.
Рефакторинг ветвлений на отдельные классы
Наиболее часто используемый способ устранения условных ветвлений – объекты стратегии.
Вы постоянно будете видеть (или видели) шаблон стратегии, реализованный в процедурном стиле с помощью if-elseif и switch/case.
Допустим, мы хотим преобразовать любой тип в формат CSV и указать, как преобразуется каждое свойство типа, но тип не должен определять это сам. [Csv Info]
– это атрибут, который по сути является метаинформацией о типе. Затем эта метаинформация считывается с помощью крошечной рефлексии внутри метода ToCsv()
.
Ниже приведен фрагмент класса CsvInfoAttribute
. Этот код не является полным мусором, но он не очень расширяемый и не очень гибкий. Каждый раз, когда нужно будет добавить новый параметр преобразования, придется добавлять и дополнительный enum, а затем реализовывать преобразование для него в методе Format()
. Это означает, что вам нужно изменить существующий код.
Предположим, что класс живет в какой-то общей библиотеке, от которой зависит много проектов в вашей организации. Если одной команде нужен новый вариант преобразования, ей придется обратиться к сопровождающим проекта, чтобы они добавили функциональность, создали новую версию и опубликовали ее.
Более правильный подход заключается в том, чтобы позволить каждой команде изменять необходимое в рамках своего проекта.
Мы можем сделать это, разделив каждую ветку switch/case
на специализированные классы, что делает enum
ненужным.
Каждая стратегия должна реализовывать общий интерфейс, а CsvInfoAttribute
больше не должен иметь собственного метода Format()
. Вместо этого он делегирует ответственность за форматирование специализированным объектам.
Рассмотрим приведенный ниже код:
Теперь, в любое время, когда понадобится новый тип форматирования, это просто вопрос создания нового класса. Больше никаких изменений в нескольких существующих классах.
Чтобы это сработало, необходимо определить некоторые ограничения: любой форматер должен реализовать IValueFormatter
и иметь конструктор по умолчанию.
Представьте, как легко сейчас протестировать и исправить ошибки. Вы всегда будете точно знать, что проверять и где искать. Больше никаких следов безумной switch-логики.
Заключение
К сожалению, традиционное ветвление используется очень широко. Многие разработчики придерживаются своего испытанного и верного способа ветвления кода, не осознавая, что обслуживание и добавление функциональности превратилось в кошмар.
Использование if-else и switch влечет за собой отказ от многих объектно-ориентированных практик.
Если вы действительно хотите стать сильным разработчиком, начните искать способы устранения традиционного ветвления или используйте описанные в статье.
[customscript]techrocks_custom_after_post_html[/customscript]
[customscript]techrocks_custom_script[/customscript]