Перейти к содержанию

Чем плох оператор else

·404 слова·2 минуты
Содержание

В предыдущей статье мы говорили про оператор 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)

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