Как избавиться от if else c
Перейти к содержимому

Как избавиться от if else c

  • автор:

Перестаньте использовать операторы if-else

Наверняка вы видели огромное количество уроков, в которых используются операторы if-else. Скорее всего вы прочитали множество книг, продвигающих использование if-else, а по сути техническое разветвление в программировании.

Возможно вы даже используете if-else по умолчанию, но давайте положим этому конец и заменим его объектами состояния.

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

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

Для тех, кто все еще в неведении, вот очень краткое вступление:

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

Применяя шаблон состояний, вы просто изменяете поведение объектов, используя специализированные объекты состояний вместо операторов If-Else.

Прошли те дни, когда код выглядел так:

Вы, конечно, уже писали более сложные ветки. Уверен, что несколько лет назад.

Логика ветвления выше даже не очень сложна — но попробуйте добавить новые условия, и вы увидите, как эта штука взорвётся.

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

Еще лучше, это сделает вашу кодовую базу более солидной.

Мы рассмотрим, как я заменю If-Else ветвление в коде, готовом к производству. Это вымышленный пример, но этот подход я использовал, работая с крупными клиентами.

Давайте создадим очень простой Booking class, в котором есть пара состояний.Также у него будет два открытых метода: Accept( ) и Cancel( ).

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

Рефакторинг логики ветвления из нашего кода — это трехступенчатый процесс:

1.Создайте абстрактный базовый класс состояния

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

3.Пусть класс «Booking» имеет частный или внутренний метод, который принимает в качестве параметра базовый класс состояния

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

Обратите внимание, что в этом базовом классе также есть два метода, Accept и Cancel — хотя здесь они помечены как внутренние.

Кроме того, базовое состояние имеет «специальный» метод EnterState . Он вызывается всякий раз, когда объекту Booking присваивается новое состояние.

Во-вторых, мы делаем отдельные классы для каждого состояния, которое мы хотим представить.

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

Наконец, сам класс Booking.

Видите, как класс Booking просто делегирует реализацию Accept and Cancel своему объекту состояния?

Это позволяет снять большую часть условной логики и позволяет каждому состоянию сосредоточиться только на том, что важно для него самого — текущее состояние также имеет возможность перевести booking в новое состояние.

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

Это так же просто. Вам больше не придется иметь дело с громоздкими операторами if-else.

Объект состояния не важен при сохранении объекта, например, в базу данных SQL или NoSQL. Важно только знать состояние объекта и то, как он должен быть отображен в столбце.

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

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

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

Еще больше полезной и нужной информации вы найдете в нашем Телеграм-канале по ссылке:

Замена вложенных условных операторов граничным оператором

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

Решение

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

Причины рефакторинга

«Условный оператор из ада» довольно просто отличить. Отступы каждого из уровней вложенности формируют в нем отчётливую стрелку, указывающую вправо:

Разобраться в том, что и как делает такой оператор довольно сложно, так как «нормальный» ход выполнения в нем не очевиден. Такие операторы появляются эволюционным путём, когда каждое из условий добавляется в разные промежутки времени без мыслей об оптимизации остальных условий.

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

Порядок рефакторинга

Постарайтесь избавиться от «побочных эффектов» в условиях операторов. Разделение запроса и модификатора может в этом помочь. Такое решение понадобится для дальнейших перестановок условий.

Выделите граничные условия, которые приводят к вызову исключения или немедленному возвращению значения из метода. Переместите эти условия в начало метода.

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

Устали читать?

Сбегайте за подушкой, у нас тут контента на 7 часов чтения.

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

Этот рефакторинг — малая часть интерактивного онлайн курса по рефакторингу.

Удалите из кода 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 влечет за собой отказ от многих объектно-ориентированных практик.

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

Добавить комментарий

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