Чем плох оператор else
Содержание
В предыдущей статье мы говорили про оператор switch/case. Думаю с этим более-менее понятно. Менее очевидный оператор — else. Пожалуй, в некоторых случаях он оправдан, но чаще всего лучшим решением будет избежать использование этого оператора.
Проблема #
В самом операторе проблем нет. Проблема в его использовании: зачастую мы имеем массу вложенных условий и ветвлений, что ухудшает читаемость кода и порождает ошибки при написании тестов. Наверняка каждый встречался с подобным кодом:
def check_violation(car):
if car.type != 'military' and speed > 120:
driver = get_driver(car)
if driver.is_officials():
skip_car()
else:
fine()
else:
skip_car(car)
В данном примере лишь 2 уровня вложенности, но их может быть сильно больше. И это не единственная проблема: наш метод делает слишком много действий. Он проверяет тип машины, проверяет кем является водитель, проверяет его водительское удостоверение и т.д. Можно прийти к пониманию, что множественные условия — это всегда раздутые методы, нарушающие принцип единственной ответственности. Кроме того, такой подход порождает потенциальные места, где код можно расширить, и это не обязательно будете вы, возможно, ваш коллега добавит еще один else.
Решение #
Как можно избежать использование else в большинстве случаев? На самом деле достаточно просто, и это точно не займет у вас много времени. Эта техника называется Guard Clause (защитное условие) или ранний возврат.
Вынесем логику проверки условий остановки машины в отдельные методы:
def should_stop(car, speed):
driver = get_driver(car)
return speed > 120 and car.type != 'military' and not driver.is_officials()
def check_car(car):
if should_stop(car):
fine()
return
skip_car(car)
Разумеется, в реальном мире мы бы вынесли проверку нарушений ПДД в отдельную абстракцию, но в данном случае я решил опустить этот момент для простоты.
Хочу отметить, что в функции check_car мы не использовали else. Вместо этого мы использовали return из первого условия. Такой подход избавляет нас от еще одного потенциального ветвления и вложенности кода.
Однако, в некоторых ситуациях это может привести к дублированию кода очистки. Например, если после завершения основной логики мы хотим закрыть соединение с базой данных, нам пришлось бы писать закрытие перед каждым return.
Примечание: Если у вас возникает необходимость дублировать код закрытия ресурсов, скорее всего, стоит посмотреть в сторону контекстных менеджеров (оператор
withв Python) илиtry/finallyблоков, а также декораторов, которые возьмут эту рутину на себя.
Также я рекомендую использовать инверсную логику (Guard Clauses). Например, код наподобие этого:
def handle_response(response):
if response.status_code == 200:
process_data(response.data)
Я бы предложил заменить на это:
def handle_response(response):
if response.status_code != 200:
return
process_data(response.data)
В данном случае мы избавляемся от ветвления при обработке основной логики и можем писать код линейно, без лишних отступов.