Урок 3.4 Инструкция Guard

Урок 3.4 Инструкция Guard

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

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


Что Вы Узнаете

  • Как использовать guard для написания более читаемого кода
  • Как создать функцию, которая защищает от недопустимых аргументов

Словарь


Связанные Ресурсы

Руководство по языку программирования Swift: Ранний выход

 

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

Вы узнали, что каждый оператор if оценивает булево выражение. Если результат истинный, выполняется код, определенный в операторе; в противном случае, он пропускается. Но что, если у вас есть несколько операторов if, вложенных друг в друга? Ваш код начинает формировать то, что программисты называют "пирамидой ужаса":

 

func singHappyBirthday() {
  if birthdayIsToday {
      if !invitedGuests.isEmpty {
          if cakeCandlesLit {
              print(”Happy Birthday to you!”)
          } else {
              print(”The cake’s candles haven’t been lit.”)
          }
      } else {
          print(”It’s just a family party.”)
    }
  } else {
      print(”No one has a birthday today.”)
  }
}

 

Что проблематичного в этом примере? С каждым оператором if код перемещается всё дальше и дальше от начала каждой строки, делая код трудным для чтения. И каждый оператор else перемещается всё дальше от своего соответствующего оператора if, так что трудно понять, как они соотносятся друг с другом. Вы можете переработать эту логику, чтобы уменьшить эффект пирамиды, при этом все еще используя операторы if:

 

func singHappyBirthday() {
    if !birthdayIsToday {
        print("No one has a birthday today.")
        return
    }
 
    if invitedGuests.isEmpty {
        print("It's just a family party.")
        return
    }
 
    if cakeCandlesLit == false {
        print("The cake's candles haven't been lit.")
        return
    }
 
    print("Happy Birthday to you!")
}

 

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

 

func singHappyBirthday() {
  guard birthdayIsToday else {
    print(”No one has a birthday today.”)
    return
  }
 
  guard !invitedGuests.isEmpty else {
    print(”It’s just a family party.”)
    return
  }
 
  guard cakeCandlesLit else {
    print(”The cake’s candles haven’t been lit.”)
    return
  }
 
  print(”Happy Birthday to you!”)
}

 

Блок else оператора guard выполняется только в том случае, если выражение оценивается как false. Это противоположно оператору if, который выполняет блок, если выражение оценивается как true.

 

guard condition else {

   // false: выполняется этот код

}

// true: выполняется этот код

 

С таким подходом вы можете написать функцию, которая возвращает значение сразу, если не может выполнить свою задачу. Оператор guard требует, чтобы в случае else вы вышли из области видимости функции, используя ключевое слово return. Устраняя все нежелательные условия с помощью операторов guard и вызывая return, вы можете переместить проверку условий в начало функции и разместить код, который вы ожидаете выполнить, внизу. Таким образом, ожидаемый код больше не окружен неожиданными условиями. Это также ясно показывает читателю намерения и логическое обоснование проверки условий, в отличие от использования операторов if, которые не требуют return.

Вот два варианта функции деления: один с использованием оператора if, а другой — с использованием оператора guard. Поскольку нельзя делить на ноль, каждая версия функции выводит результат, если делитель не равен нулю.

 

func divide(_ number: Double, by divisor: Double) {
  if divisor != 0.0 {
    let result = number / divisor
    print(result)
  }
}
 
func divide(_ number: Double, by divisor: Double) {
  guard divisor != 0.0 else {
    return
  }
  let result = number / divisor
  print(result)
}

 

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

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

 

func divide(_ number: Double, by divisor: Double) {
  if divisor == 0.0 { return }
  let result = number / divisor
  print(result)
}

 

Guard с опционалами

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

 

if let eggs = goose.eggs {

   print("Гусь снес \(eggs.count) яиц.")

}

// `eggs` здесь недоступен

 

Вместо этого вы можете использовать guard let, чтобы привязать значение внутри опционала к константе, которая будет доступна за пределами фигурных скобок.

guard let eggs = goose.eggs else { return }
// `eggs` доступен ниже 
print("Гусь снес \(eggs.count) яиц.")

 

Обратите внимание на расположение оператора else после условия. Такое расположение означает, что guard проверяет истинное или не-опциональное условие. Когда значение, которое вы пытаетесь раскрыть, равно nil, выполняется код внутри блока else — в противном случае выполнение продолжается до строки после закрывающей скобки, и раскрытое значение доступно для использования.

И if let, и guard let позволяют раскрывать несколько опционалов в одном операторе. Однако, делая это с guard let, все раскрытые значения становятся доступными на протяжении оставшейся части функции, а не только внутри фигурных скобок управляющей конструкции.

 

func processBook(title: String?, price: Double?, pages: Int?) {
  if let theTitle = title,
    let thePrice = price,
    let thePages = pages {
    print(”\(theTitle) costs $\(thePrice) and has \(thePages
       pages.”)
  }
}
 
func processBook(title: String?, price: Double?, pages: Int?) {
  guard let theTitle = title,
    let thePrice = price,
    let thePages = pages else {
      return
    }
    print(”\(theTitle) costs $\(thePrice) and has \(thePages) pages.”)
}

 

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

Лабораторная работа

Откройте и завершите упражнения в Lab—Guard.playground.


Отрывок из книги
Develop in Swift Fundamentals
Apple Education
https://books.apple.com/ru/book/develop-in-swift-fundamentals/id1581182804

 

Information

Apple, the Apple logo, Apple Books, Apple TV, Apple Watch, Cocoa, Cocoa Touch, Finder, Handoff, HealthKit, iPad, iPad Pro, iPhone, iPod touch, Keynote, Mac, macOS, Numbers, Objective-C, Pages, Photo Booth, Safari, Siri, Spotlight, Swift, tvOS, watchOS, and Xcode are trademarks of Apple Inc., registered in the U.S. and other countries. App Store and iBooks Store are service marks of Apple Inc., registered in the U.S. and other countries. ​
The Bluetooth® word mark and logos are registered trademarks owned by Bluetooth SIG, Inc. and any use of such marks by Apple is under license. ​
IOS is a trademark or registered trademark of Cisco in the U.S. and other countries and is used under license. ​
Other product and company names mentioned herein may be trademarks of their respective companies.