Guard — зачем же он нужен?

Вы читаете перевод статьи Эрика Керни

Когда я впервые увидел оператор guard во время Apple’s Platform State of the Union, я не мог до конца понять, зачем бы я стал им пользоваться и что же он из себя представляет? Вот короткое описание:

Как и оператор if, guard исполняет код полагаясь на логическое значение выражения. В отличии от if, guard исполняет код только при получении false. Можно думать о нём как об Assert, только программа не будет завершена.

Вливаемся

Возьмём простой пример для сравнения старой техники и использования guard:

func fooManualCheck(x: Int?) {
    if x == nil || x <= 0 {
        // Условия не соблюдены, выходим из функции
        return
    }
    
    // Работаем с x
    x!.description
}

Это самый простой способ (в стиле Objective-C) убедиться, что значение существует и удовлетворяет условию. Хотя, работает оно прекрасно, в нём есть несколько недостатков:

  1. Мы проверяем условие, которое нам не нужно, вместо проверки значения, которое нас интересует. Код становится очень запутанным, если у нас несколько таких проверок. Мы ведь надеемся, что наше условие на самом деле не пройдёт.
  2. Так же нужно “силой развернуть” (force unwrap) опциональное значение.

Swift представил нам более чистый способ сделать это и избавил нас от этих недостатков с помощью Optional Binding:

func fooBinding(x: Int?) {
    if let x = x where x > 0 {
        // Работаем с x
        x.description
    }
    
    // Условия не соблюдены, выходим из функции
}

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

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

И тут на помощь приходит guard:

func fooGuard(x: Int?) {
    guard let x = x where x > 0 else {
        // Условия не соблюдены, выходим из функции
        return
    }
    
    // Работаем с x
    x.description
}

Использование guard решает все 3 проблемы, упомянутые выше:

  1. Идёт проверка условий, которые нам действительно важны. Если условие не соблюдено, запускается блок else, который обязательно выводит из функции. Если вы забудете постаивть return, компилятор сообщит вам об ошибке.
  2. Если условие соблюдено, опциональная переменная автоматически развёрнута и доступна внутри guard.
  3. Мы проверяем условия рано и функция будет просто читаема.

Классно ещё то, что это работает и для не-опциональных значений:

func fooNonOptionalGood(x: Int) {
    guard x > 0 else {
        // Условия не соблюдены, выходим из функции
        return
    }
    
    // Работаем с x
}
 
func fooNonOptionalBad(x: Int) {
    if x <= 0 {
        // Условия не соблюдены, выходим из функции
        return
    }
    
    // Работаем с x
}

Сворачиваемся

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

Автор

Yaroslav Erohin

Yaroslav Erohin

мобильный разработчик, хувиан, одержим музыкой

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

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