Главная

If-Else — полиморфизм для бедных

Как из 6 строк кода сделать 92.

Поделиться:
 

7+

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

Еще я слышу очень много разных дискуссий по поводу enum`ов и других дискретных величин. Некоторые разработчики раздражаются, когда им говорят не использовать конструкцию if-then-else. Как вы думаете, каковы последствия использования enum`ов в конструкциях if-then-else? Разветвление по дискретным значениям затрудняет изменение программного обеспечения. Каждая новая фича нуждается в том, чтобы вы отслеживали, где происходит ветвление, и соответствующим образом изменяли существующий код.

Это определенно не лучший способ разработки программного обеспечения. Возможно, это хорошо на первых этапах, когда нужно, чтобы код просто заработал, но с развитием проекта, switch и if-then-else должны исчезнуть. Традиционный подход к ветвлению с использованием if-else и switch устарел. Это не SOLID. Это не гибко. Это просто ужасно.

В традиционном подходе нет ничего, что касается объектно-ориентированного стиля, тем не менее, он процветает. Многие разработчики начинают думать, что это правильный подход или даже наиболее целесообразный. Несмотря на то, что if-then-else/switch код работает, можно добиться лучшего результата. Целью должно быть выполнение нового условия путем создания нового класса.

Давайте визуализируем проблему.

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

Image for post

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

Image for post

Единственное, что можно сказать об этом коде, — это попытка частично реализовать CQS-шаблон проектирования.
Если вы хотите сказать: «Тут стоит использовать switch!» вам следует подумать о том, что важно в разработке программного обеспечения. Switch тут или if-else — это совершенно не имеет значения. Вы в любом случае столкнетесь с новыми условиями. Вы все время будете сталкиваться с новыми условиями или требованиями. Допустим, условия теперь выглядят так:

Image for post

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

Image for post

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

Есть еще одна проблема. Сигнатура метода обманывает нас, поскольку это не просто обновление пользователя. Этот код также выбирает, какой алгоритм выполнять, в зависимости от причины обновления и даже знает каждую реализацию. Очевидно, что этот метод несет в себе множество обязанностей. Этот пример явно подчеркивает все ужасное, что есть в if-else и switch.

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

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

Я упоминал ранее, что мы можем добиться большего. Под «большего» я подразумеваю написание кода, который
1) читабельный
2) поддерживаемый
3) гибкий

Заменяя традиционное ветвление полиморфным исполнением, возникает четкая связь между классом и требованиями, которыми он управляет. Простые, очень связанные классы с четкими обязанностями легко поддерживать. Обнаружение и исправление дефектов становится простым и приятным заданием.

Вдобавок ко всему, ваше программное обеспечение легко встраивает новые функции без необходимости изменять существующий класс. Да начнется рефакторинг.
Вы увидите, как далеко мы можем продвинуться без единого оператора if-then-else или switch.
UpdateAsync (Reason, User) теперь стал таким простым.

Image for post

Обратите внимание, что теперь вы принимаете аргумент интерфейса вместо enum`a. Теперь метод делегирует ответственность за знание того, как выполнить обновление, специализированному объекту.
Конкретные реализации IUpdateReason выглядят следующим образом. Подробности об аргументах конструктора и реализации метода оставлю на ваше усмотрение.

Image for post

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

Но есть еще последняя проблема. Ваш UpdateAsync(Reason, User) теперь несколько избыточен.
Чтобы решить эту проблему, мы больше не будем делать рефакторинг. Мы переделаем отдельные части системы.
В этом случае имеет смысл создать объекты команд и обработчики команд. Это упростит вызывающий код, поскольку он просто обработает команду, например UpdateUserAddress, и в следствии будет вызвано соответствующее действие обработчика. Вот ссылка на еще одну полезную статью по этому поводу:

Update: here’s a newer article where I describe the commands and handlers approach.

Заключение

Традиционное ветвление часто является инструментом новичков.
Однако есть более изящный способ ветвления — полиморфный подход. if-then-else и switch значительно затрудняют чтение, поддержку и настройку вашего кода. В следующий раз, когда вы будете внедрять функцию с использованием традиционного if-then-else ветвления, уделите время и проанализируйте, как вы можете использовать полиморфизм.
Спасибо!

7+