eFusion 02 июня 2021

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

Расширение репертуара подходов и методов для устранения ветвления – один из быстрых способов улучшить проект. Рассказываем, как вы можете сделать свой код чище и приятнее.
❗ Удалите из кода If-Else и Switch Case

Перевод публикуется с сокращениями, автор оригинальной статьи Nicklas Millard.

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

Cкорее всего, if-else и switch – ваш обычный подход к ветвлению кода, но в нем нет необходимости. Вы можете полностью исключить ключевое слово else из своего словаря по программированию. Некоторые матерые кодеры говорят, что if-else – полиморфизм новичков.

Что плохого в традиционном ветвлении?

Много чего. Традиционное ветвление быстро разрастается. Вам придется изменять существующий код каждый раз при добавлении новой функции. Это нарушает принцип Open-Closed. Функции должны быть реализованы с помощью новых классов.

В идеале нужно изменять существующие классы только при рефакторинге, исправлении ошибок или для обновления бизнес-логики.

Какие есть альтернативы?

Существует масса альтернатив, но мы рассмотрим три типовых подхода, которые часто применяются при удалении традиционного ветвления из кода:

  • концепции моделей с классами;
  • использование полиморфного выполнения при работе с изменяющимися состояниями объектов;
  • инкапсуляция стратегии в отдельные классы.

Эти 3 подхода легко справятся с большинством повседневных ситуаций, с которыми вы можете столкнуться.

Все методы имеют общие черты:

  • Новая функциональность реализуется с помощью новых классов. Добавление кода, а не его изменение, обычно является более безопасным вариантом. Каждый раз, когда изменяется уже используемый код, вы подвергаетесь огромному риску все сломать.
  • Проще тестировать специализированные классы – это огромное преимущество. Простые классы и методы легче рассматривать – легче понять множество небольших и сплоченных классов, чем несколько монолитных.
  • Управление концепциями на низком уровне лучше, чем расширение обязанностей одного класса.

Моделирование концепций с помощью простых классов

Любому знакомому с DDD (domain-driven design) программисту известно, как важно избегать фиксирования бизнес-логики в небольших специализированных классах.

Допустим, у нас есть класс User и в нем имя пользователя. Оно является строкой и имеет два условия: не может быть нулем или пустой строкой и не может превышать 50 символов.

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

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

Если что-то изменится и возникнет необходимость в валидации специальных символов, таких как «æøå», придется найти каждое место, где получено имя пользователя, и добавить новую проверку.

Гораздо лучшим подходом является перенос концепции имени пользователя и создание небольшого специализированного объекта, как показано ниже.

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

Этот фрагмент кода, несомненно, чище. Теперь каждый раз, когда потребуется обновление, его нужно будет делать только в одном месте.

Изменение реализации метода объекта в зависимости от его состояния

Иногда необходимо, чтобы объект вел себя по-разному в зависимости от его внутреннего состояния. Типичный ленивый способ реализации этого – традиционное ветвление, как в приведенном ниже примере.

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

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

Одной из ловушек традиционного ветвления является вложенная условная логика. Любая форма «рождественской елки» в вашем коде – это усложнение следования логике и рассуждениям, ветви if/else начинают отдаляться друг от друга, что затрудняет чтение и обслуживание.

Паттерн состояния – это очень крутой подход к удалению сумасшедшей вложенной условной логики. Пытайтесь добиться, чтобы наш код был похож на следующий:

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

Видите, какая плоская конструкция?

Теперь каждая ветвь инкапсулирована в собственный класс, а класс account делегирует ответственность специализированному AccountState.

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

Удвойте количество кода и повысьте читабельность.

У нас есть базовый класс, от которого наследуется каждый объект состояния. Если мы получим новый запрос на добавление состояния RequiresValidation или что-то еще, будет легко реализовать эту функцию, не касаясь существующих классов.

Внимательный читатель заметил, что нет никакого перехода в состояние. Очевидно, что классы не полностью реализованы, т. к. переход из одного состояния в другое выходит за рамки статьи.

Рефакторинг ветвлений на отдельные классы

Наиболее часто используемый способ устранения условных ветвлений – объекты стратегии.

Вы постоянно будете видеть (или видели) шаблон стратегии, реализованный в процедурном стиле с помощью if-elseif и switch/case.

Допустим, мы хотим преобразовать любой тип в формат CSV и указать, как преобразуется каждое свойство типа, но тип не должен определять это сам. [Csv Info] – это атрибут, который по сути является метаинформацией о типе. Затем эта метаинформация считывается с помощью крошечной рефлексии внутри метода ToCsv().

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

Ниже приведен фрагмент класса CsvInfoAttribute. Этот код не является полным мусором, но он не очень расширяемый и не очень гибкий. Каждый раз, когда нужно будет добавить новый параметр преобразования, придется добавлять и дополнительный enum, а затем реализовывать преобразование для него в методе Format(). Это означает, что вам нужно изменить существующий код.

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

❗ Удалите из кода If-Else и Switch Case
Более правильный подход заключается в том, чтобы позволить каждой команде изменять необходимое в рамках своего проекта.

Мы можем сделать это, разделив каждую ветку switch/case на специализированные классы, что делает enum ненужным.

Каждая стратегия должна реализовывать общий интерфейс, а CsvInfoAttribute больше не должен иметь собственного метода Format(). Вместо этого он делегирует ответственность за форматирование специализированным объектам.

Рассмотрим приведенный ниже код:

❗ Удалите из кода If-Else и Switch Case
Теперь, в любое время, когда понадобится новый тип форматирования, это просто вопрос создания нового класса. Больше никаких изменений в нескольких существующих классах.

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

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

Заключение

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

Использование if-else и switch влечет за собой отказ от многих объектно-ориентированных практик.

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

Дополнительные материалы:

Источники

Комментарии

 
 
25 июня 2021

зачем так все усложнять, читабильность ухудшается в разы, тут даже с функциями надо быть осторожнее ибо если увлечься можно ветку так разбить и распихать по разным местам исходника что потом концов не найдешь))

14 июня 2021

Если можно сделать проще не городи всяку хрень - завещал старина Оккам.

10 июня 2021

Может я просто не понимаю, но не могу найти преимущество вынесения username в отдельный класс. И так и так валидация проходит при создании экземпляра класса User. Просто стало как-то все запутанно (через одно место).

08 июня 2021

Судя по всему автор никогда не имел дело с проектами содержащими 500+ классов отвечающих за различные сервисы, логику и представление и не работал в распределённой команде. 500 классов при таком подходе превратятся в 2000+ классов и для того чтобы человеку вне контеста понять код придётся "разматывать" все эти зависим ости и наследования. Особо одарённые еще и интерфейсы к этому хозяйству прикручивают. А ещё часто приходится менять логику или вводить/убирать параметры. И, если используя if можно было обойтись изменением пары тройки строк, в такой конструкции придётся менять иерархию.

И снова спрошу - как-то же распределенные разработчики разбираются в 100500 классах в случае с котлином, джавой и андроидом? Ничего им не приходится разматывать и создавать 2000 классов. Если человек вне контекста, то на такую должность он подходит т.к. для работы вне контекста нужен опыт работы с внеКонтекстом, опыт работы в распределенных командах и просто опыт работы, а не после 4го курса (или не дай бог без универа вообще)... Код должен быть нормальным, должна быть документация (если это уважающий себя проект, а не 500 классов мусора) и его нужно в дальнейшем адекватно поддерживать, а не говнокодить, кто во что горазд.

Господа! О чем вы все здесь вообще?!! ООП появилось не вчера и все ООП-ные ЯП юзаются не один десяток лет. Как вы считаете, если бы с ООП было что-то не так, как тут считают, "мировые правящие IT-головы" не решили бы это все хозяйство упразднить, переживая о 2000 классах или о неокрепшей психике молодых-зеленых??!!!!! Придите в себя! Если вы пришли в ОО-язык из мультипарадигмального или вообще из процедурного, то не болтайте чепухой, а соблюдайте правила этого языка, писанные 10-летиями и будет вам умиротворение и покой.

Документация ?? спасибо посмеялся... Актуальной можно поддерживать только ограниченную доку которая даёт общее представление об архитектуре и интерфейсах больших модулей. Иначе, она только будет запутывать. Принцип KISS наше всё, не усложняйте без нужды, иначе привет отладчик для каждого нового разработчика в команду. И причём здесь ООП и IF? Разве они контрят друг-друга? Да, если портянка ifov не помещается в экран, то что-то надо менять и переходить на стейты. Я опечален, что вы восприняли мои слова так буквально

До появления ООП появились идеи Дейкстры о структурном программировании. If-else там - одна из главных управляющих структур. Даже в макроассемблерах применяется, где никакого ООП нет.

Не могу согласиться с подобным подходом. Главное в программе - это её наглядность. Добавился новый блок - добавляем условие if-else, ставим комментарий в начале программы - и вс видно с первого взгляда. Здесь объектный подход проигрывает со сравнением со структурным. Не рекомендую следовать советам данной статьи.

Посадите обратно в клетку автора и не выпускайте. ООП зло во плоти и тут это просто на лицо. ООП это не только путаница в коде, но и жутчайшие тормоза, а также горы ненужного кода. Лучше использовать if-else/switch чем гору ООП.

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

06 июня 2021

Если считать три столпа: инкапсуляция, наследование, полиморфизм злом, то можно смело обратиться в Google, Apple, Windows и сказать, чтобы не юзали C++, JS, Swift, Java, Objective C, Go и гору прочих ЯП, используемых и создаваемых. Пишем все в процедурном стиле и плодим простыни из неподдерживаемого кода! Если необходимо написать код для вывода "Hello world!", то, конечно нафиг ООП, а если вы собрались наваять приложение в телефон - попробуйте это сделать на процедурке...

05 июня 2021

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

Кода больше - больше тормозов. Да и наглядней if-else ничего нет.

03 июня 2021

"Использование if-else и switch влечет за собой отказ от многих объектно-ориентированных практик"

а если я пишу на фп-языке(лисп), на языке без направленности (си) или на языке с неполной или неявной реализацией ооп (go)?

04 июня 2021

Тогда забейте на эту дурную статью и уменьшайте вложенность условий другими способами (не вижу, кстати, ничего плохого в одноуровневом условии без else)

Просто этих Java-проггереров хлебом не корми лишь бы написать тысячу-другую лишних классов и интерфейсов, чтобы код стал совсем не читаемым за толстым слоем абстракции и перенаследования.

03 июня 2021

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

То-то за последние 25 лет объём кода в софте вырос в 1000 раз. В Win 95 было примерно 20 МБ, а в Win 7 - уже примерно 20 ГБ. И тем не менее, код всё равно "ломается".

Не нужно забывать, что каждый фреймворк и каждая фича весит в 1000 раз больше, чем в 95й винде. В Win7 напихано в 1000 раз больше всякого-ненужного, ну, и говнокод, бесконтрольно писанный индусами, а также заплатка на заплатке и патчем погоняет - поэтому всё "ломается". Windows будет всегда ломаться потому, что это Windows... В статье речь не о Windows (как минимум потому, что неизвестно, какой там код), а об общем подходе написания правильного кода, типс&трикс, так сказать.

03 июня 2021

Сильный разработчик будет код писать, а не думать, как бы ему лишний раз от if-else/switch избавиться.

А потом другой сильный разработчик, он же тимлид, заставит первого рефакторить код, дабы оптимизировать эти горы условий... Сразу нужно избегать ветвлений (на пример, как в статье) и строить свой проект в таком ключе.

Да ну не, сильный разработчик заметит, что ветвлений слишком много и сразу задизайнит всё так, чтобы и читалось хорошо, и масштабировалось нормально. Тут опыт порешает, а не стремление "избавляться от if-else/switch ради ООП". Да и, думаю, такие вещи заранее обсуждаются и чаще всего учитываются в общей архитектуре. Естественно, не без исключений.

07 июня 2021

Горы условий в переключателе не подлежат оптимизации. Это - как стрелки на железной дороге. Сколько надо, столько и ветвятся.

ЛУЧШИЕ СТАТЬИ ПО ТЕМЕ

LIVE >

Подпишись

на push-уведомления