Education

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

Тем не менее, конструкция if-else де-факто стала решением для ветвления кода, что имеет смысл. Это одна из первых тем, которую изучают начинающие разработчики. К несчастью, многие так никогда и не переходят на более подходящие стратегии ветвления. 

Некоторые живут по правилу: if-else молоток, всё остальное — гвозди.

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

Я продемонстрирую несколько приёмов и шаблонов, которые помогут положить конец этой неприятной практике. Сложность будет возрастать с каждым примером.

1. Совсем лишние блоки else

Это самая распространённая ошибка начинающих разработчиков. Ниже яркий пример того, как вы проигрываете, используя if-else:

Простой if-else

Выражение легко упростить, просто убрав блок else:

Без блока else

Выглядит более профессионально, не так ли?

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

2. Присвоение значений

Если вам нужно присвоить значение переменной на основе некоторого ввода, бросьте эту возню с if-else — существует намного более читаемый подход:

Присвоение значения с использованием if-else

Выглядит ужасно, даже несмотря на простоту. Прежде всего, if-else легко заменяется оператором выбора. Но можно ещё упростить код, удалив else if и else.

Оператор if с быстрым return

Убираем else if и else и получаем чистый читаемый код. Обратите внимание, что я изменил стиль быстрого возвращаемого значения — просто не имеет смысла проверять значение, когда верное уже получено.

3. Проверка входных условий

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

Скажем, у нас есть вышеописанный метод DetermineGender с требованием, чтобы входное значение равнялось 0 или 1:

Метод без проверки значений

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

Применив граничные операторы метода безопасного программирования, сначала проверяем входные значения и только потом выполняем метод if:

Проверка входных условий с помощью граничных операторов

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

Операторы if также заменены на тернарные, так как больше нет смысла возвращать значение “Unknown”.

4. Превращение if-else в словарь — полностью избегаем if-else

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

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

Зная, что впоследствии нужно будет добавлять ещё операции, превратим if-else в словарь:

Читаемость выросла, код проще понять. 

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

5. Расширение приложений — полностью избегаем if-else

Немного более продвинутый пример

Необходимо уточнить — это более “корпоративный” подход. Нетипичный сценарий “давайте заменим if-else”. Теперь можно продолжить.

If можно полностью заменить объектами.

Довольно часто необходимо расширить какую-то часть приложения. Как начинающий разработчик вы можете просто использовать дополнительный оператор if-else (или else-if).

Рассмотрим пример: нам нужно представить экземпляр Order в виде строки. Во-первых, у нас есть только два типа представления строки — JSON и простой текст. Использование if-else на этом этапе не является проблемой, но мы с лёгкостью можем заменить else if на if, как было показано выше.

Однако этот подход определённо не приемлем, если эту часть приложения нужно будет расширять.

Мало того, что код выше не соответствует принципу открытости/закрытости, его трудно читать и поддерживать в долгосрочной перспективе.

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

Рефакторинг этого беспорядочного куска кода выглядит так: 

  1. Извлекаем каждую ветвь в отдельный стратегический класс с общим интерфейсом.
  2. Динамически находим все классы, реализующие общий интерфейс. 
  3. На основе входных данных решаем, какую стратегию использовать. 

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

Но динамическое расширение приложений — сложная тема.

Я продемонстрировал только часть, заменяющую пример с if-else. Здесь можно увидеть все вовлечённые объекты.

В идеале обнаружение типов и словарь предоставляются снаружи метода PrintOrder. Но в любом случае давайте пробежимся по коду выше: 

  1. Подпись метода не изменилась, так как вызывающей программе не нужно знать о рефакторинге.
  2.  Для начала получим все типы в блоке, реализующие общий интерфейс IOrderOutputStrategy
  3. Затем соберём словарь, в котором displayName форматировщика является ключом, а тип — значением. 
  4. Затем тип форматировщика выбирается из словаря, мы пытаемся создать экземпляр стратегического объекта.
  5. И наконец вызывается ConvertOrderToString стратегического объекта.

Читайте также:


Перевод статьи Nicklas Millard: Better Software Without If-Else

Предыдущая статья10 трюков для мастеров Python
Следующая статьяПрекратите использовать конфигурационные файлы JSON