Удалите из кода If-Else и Switch Case

Расширение репертуара подходов и методов для устранения ветвления – один из быстрых способов улучшить проект. Сайт proglib.io опубликовал сокращенный перевод статьи «Remove Your If-Else and Switch Cases», в которой поясняется, как можно сделать свой код чище и приятнее.

Image by Darkmoon_Art from Pixabay

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

Автор всех скриншотов в статье — Nicklas Millard

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]

Оставьте комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *

Прокрутить вверх