Швидкий синтаксис do-try-catch


162

Я намагаюся зрозуміти нову річ, що стосується помилок, швидко. Ось що я зробив: я спершу оголосив enum про помилку:

enum SandwichError: ErrorType {
    case NotMe
    case DoItYourself
}

І тоді я оголосив метод, який видає помилку (не виняток, люди. Це помилка.) Ось такий метод:

func makeMeSandwich(names: [String: String]) throws -> String {
    guard let sandwich = names["sandwich"] else {
        throw SandwichError.NotMe
    }

    return sandwich
}

Проблема - із сторони, що телефонує. Ось код, який викликає цей метод:

let kitchen = ["sandwich": "ready", "breakfeast": "not ready"]

do {
    let sandwich = try makeMeSandwich(kitchen)
    print("i eat it \(sandwich)")
} catch SandwichError.NotMe {
    print("Not me error")
} catch SandwichError.DoItYourself {
    print("do it error")
}

Після того , як doговорить рядок компілятор Errors thrown from here are not handled because the enclosing catch is not exhaustive. Але, на мою думку, це вичерпно, оскільки у SandwichErrorпереліку є лише два випадки .

Для звичайних операторів перемикання швидкий може зрозуміти, що це вичерпно, коли кожен випадок обробляється.


3
Ви не вказуєте тип помилки, яку ви кидаєте, тому Свіфт не може визначити всі можливі варіанти
Farlei Heinen

Чи є спосіб вказати тип помилки?
мустафа

Я не можу нічого знайти в новій версії книги Swift - тільки ключове слово зараз кидає
Farlei Heinen

Працює для мене на дитячому майданчику без помилок чи попереджень.
Fogmeister

2
Здається, майданчики для ігор дозволяють doблокувати на найвищому рівні блоки, які не є вичерпними - якщо ви перетворите функцію на функцію, що не кидає, це призведе до помилки.
Сем

Відповіді:


267

У моделі поводження з помилками Swift 2 є два важливих моменти: вичерпність та стійкість. Разом вони зводяться до вашої do/ catchзаяви, потребуючи виявити всі можливі помилки, а не лише ті, які ви знаєте, що можете викинути.

Зверніть увагу, що ви не декларуєте, які типи помилок може накидати функція, лише чи вона взагалі видає. Це проблема нуля однієї нескінченності: коли хтось визначає функцію для інших (включаючи майбутнє «я»), ви не хочете, щоб кожен клієнт вашої функції пристосовувався до кожної зміни в реалізації вашої функція, включаючи, які помилки вона може викликати. Ви хочете, щоб код, який викликає вашу функцію, був стійким до таких змін.

Оскільки ваша функція не може сказати, які помилки вона викидає (чи може бути викинута в майбутньому), catchблоки, які вловлюють її помилки, не знають, які типи помилок вони можуть видавати. Отже, крім обробки типів помилок, про які ви знаєте, вам потрібно попрацювати з тими, яких ви не маєте, з універсальним catchтвердженням - таким чином, якщо ваша функція змінить набір помилок, які вона викидає в майбутньому, абоненти все одно будуть вловлювати її помилки.

do {
    let sandwich = try makeMeSandwich(kitchen)
    print("i eat it \(sandwich)")
} catch SandwichError.NotMe {
    print("Not me error")
} catch SandwichError.DoItYourself {
    print("do it error")
} catch let error {
    print(error.localizedDescription)
}

Але не будемо зупинятися на цьому. Подумайте ще про цю ідею стійкості. Те, як ви створили свій сендвіч, ви повинні описувати помилки в кожному місці, де ви їх використовуєте. Це означає, що щоразу, коли ви змінюєте набір випадків помилок, вам доведеться змінювати кожне місце, яке їх використовує ... не дуже весело.

Ідея визначення власних типів помилок полягає в тому, щоб ви могли централізувати подібні речі. Ви можете визначити descriptionметод для своїх помилок:

extension SandwichError: CustomStringConvertible {
    var description: String {
        switch self {
            case NotMe: return "Not me error"
            case DoItYourself: return "Try sudo"
        }
    }
}

І тоді ваш код обробки помилок може попросити ваш тип помилок описати себе - тепер кожне місце, де ви обробляєте помилки, може використовувати той самий код і також обробляти можливі майбутні випадки помилок.

do {
    let sandwich = try makeMeSandwich(kitchen)
    print("i eat it \(sandwich)")
} catch let error as SandwichError {
    print(error.description)
} catch {
    print("i dunno")
}

Це також відкриває шлях для типів помилок (або розширень на них) для підтримки інших способів повідомлення про помилки - наприклад, ви можете мати розширення щодо свого типу помилки, яке знає, як подати UIAlertControllerповідомлення про помилку користувачеві iOS.


1
@rickster: Невже ви могли б відтворити помилку компілятора? Оригінальний код складається без помилок чи попереджень для мене. І якщо буде викинуто незвалений виняток, програма припиняє роботу error caught in main().- Отже, хоча все, що ви сказали, звучить розумно, я не можу відтворити таку поведінку.
Мартін Р

5
Любіть, як ви роз’єднували повідомлення про помилки в розширенні. Дійсно приємний спосіб зберегти чистий код! Чудовий приклад!
Konrad77

Настійно рекомендується уникати використання вимушеного tryвираження у виробничому коді, оскільки це може спричинити помилку виконання та спричинити збій вашої програми
Otar

@ Загальна думка загалом, але це трохи поза темою - відповідь не стосується використання (або не використання) try!. Також, напевно, є дійсні "безпечні" випадки використання для різних "силових" операцій у Swift (відкручування, спробу тощо) навіть для виробничого коду - якщо за допомогою попередньої умови чи конфігурації ви надійно усунули можливість виходу з ладу, це може бути більш розумним для короткого замикання до миттєвого виходу з ладу, ніж написання коду керування помилками, який не можна визначити.
рикстер

Якщо все, що вам потрібно, це відображення повідомлення про помилку, введення цієї логіки всередині SandwichErrorкласу має сенс. Однак я підозрюю, що в більшості помилок логіка обробки помилок не може бути настільки інкапсульована. Це тому, що зазвичай потрібно знати контекст абонента (відновити чи повторити, чи повідомити про помилку вгору за течією тощо). Іншими словами, я підозрюю, що найпоширеніша модель у будь-якому випадку повинна відповідати конкретним типам помилок.
макс

29

Я підозрюю, що це ще не було належним чином реалізовано. Swift Керівництво по програмуванню , безумовно , має на увазі , що компілятор може вивести вичерпні сірники як перемикач висловлювання ". Тут не згадується необхідність генерала catch, щоб бути вичерпним.

Ви також помітите, що помилка знаходиться в tryрядку, а не в кінці блоку, тобто в якийсь момент компілятор зможе точно визначити, яке tryвисловлення в блоці має незроблені типи винятків.

Документація, правда, трохи неоднозначна. Я проглянув відео "Що нового у Свіфті" і не знайшов підказки; Я буду намагатися.

Оновлення:

Зараз ми переходимо до бета-версії 3, без натяку на помилку ErrorType. Зараз я вважаю, що якщо це колись планувалося (і я все ще думаю, що це було в якийсь момент), динамічна відправка на розширення протоколу, ймовірно, знищила його.

Оновлення бета-версії 4:

Xcode 7b4 додав підтримку для коментарів doc Throws:, яка "повинна використовуватися для документування, які помилки можна викинути і чому". Я думаю, що це принаймні забезпечує певний механізм передачі помилок споживачам API. Кому потрібна система типу, коли у вас є документація!

Ще одне оновлення:

Провівши деякий час, сподіваючись на автоматичний ErrorTypeвисновок, і розробивши, які обмеження будуть у цій моделі, я передумав - саме це я сподіваюся, що Apple реалізує натомість. По суті:

// allow us to do this:
func myFunction() throws -> Int

// or this:
func myFunction() throws CustomError -> Int

// but not this:
func myFunction() throws CustomErrorOne, CustomErrorTwo -> Int

Ще одне оновлення

Інформація про помилки від Apple, тепер доступна тут . Також було проведено кілька цікавих дискусій у списку розсилки швидкої еволюції . По суті, Джон МакКолл виступає проти введених помилок, тому що він вважає, що більшість бібліотек все одно закінчиться, включаючи загальний випадок помилок, і ці введені помилки навряд чи додадуть багато коду, крім котлової панелі (він використовував термін "домагальний блеф"). Кріс Леттнер сказав, що він відкритий для введення помилок у Swift 3, якщо він може працювати з моделлю стійкості.


Дякуємо за посилання. Не переконує Джон, хоча: "багато бібліотек включають" інший тип помилки "не означає, що всім потрібен тип" інша помилка ".
Франклін Ю.

Очевидний лічильник полягає в тому, що немає простого способу дізнатися, яку помилку буде викидати функція, поки це не зробить, змусивши розробника вловити всі помилки і спробувати впоратися з ними якнайкраще. Це досить прикро, бути досить відвертим.
Вільям Т Фроггард

4

Свіфт переживає, що виписка вашої справи не охоплює всіх випадків, щоб виправити це, вам потрібно створити регістр за замовчуванням:

do {
    let sandwich = try makeMeSandwich(kitchen)
    print("i eat it \(sandwich)")
} catch SandwichError.NotMe {
    print("Not me error")
} catch SandwichError.DoItYourself {
    print("do it error")
} catch Default {
    print("Another Error")
}

2
Але хіба це не незручно? У мене є лише два випадки, і всі вони перераховані у catchзаявах.
мустафа

2
Зараз настав час для запиту на вдосконалення, який додає func method() throws(YourErrorEnum)або навіть throws(YourEnum.Error1, .Error2, .Error3)ви знаєте, що можна кинути
Маттіас Баух

8
Команда-компілятор Swift на одному з сесій WWDC дала зрозуміти, що вони не хочуть педантичних списків усіх можливих помилок "на зразок Java".
Сем

4
Немає помилки за замовчуванням / за замовчуванням; просто залиште порожній улов {}, як вказували інші афіші
Opus1217

1
@Icaro Це не робить мене безпечним; якщо в майбутньому я "додаю новий запис у масив", компілятор повинен кричати на мене за те, що не оновлював усі зачіпаючі пропозиції про вилов.
Франклін Ю

3

Мене також розчарувало відсутність типу, яку може викинути функція, але я отримую її тепер завдяки @rickster, і я підсумую це так: скажімо, ми могли б вказати тип, який викидає функція, у нас буде щось подібне:

enum MyError: ErrorType { case ErrorA, ErrorB }

func myFunctionThatThrows() throws MyError { ...throw .ErrorA...throw .ErrorB... }

do {
    try myFunctionThatThrows()
}
case .ErrorA { ... }
case .ErrorB { ... }

Проблема полягає в тому, що навіть якщо ми нічого не змінимо в myFunctionThatThrows, якщо ми просто додамо випадок помилки до MyError:

enum MyError: ErrorType { case ErrorA, ErrorB, ErrorC }

нас обгризають, тому що наш do / try / catch вже не вичерпний, як і будь-яке інше місце, де ми назвали функції, які кидають MyError


3
Не впевнений, що я слідкую за тим, чому тебе накрутили. Ви отримаєте помилку компілятора, чого ви хочете, правда? Ось що відбувається з переключенням операторів, якщо ви додаєте перелік обліку.
Сем

У певному сенсі мені здавалося, що це найімовірніше, що це станеться з перерахунками помилок / дій, але це точно так, як це станеться в enums / switch, ви маєте рацію. Я все ще намагаюся переконати себе, що вибір Apple не вводити те, що ми кидаємо, є гарним, але ти мені не допоможеш! ^^
greg3z

Ручне введення закинутих помилок в нетривіальних випадках закінчиться великим безладом. Типи - це об'єднання всіх можливих помилок з усіх викидів і спроб операцій у функції. Якщо ви вручну підтримуєте перерахунки помилок, це буде болісно. А catch {}внизу кожного блоку, мабуть, гірше. Я сподіваюся, що компілятор врешті автоматично зробить типи помилок, де це можливо, але мені не вдалося підтвердити.
Сем

Я погоджуюсь, що теоретично компілятор повинен мати можливість виводити типи помилок, які викидає функція. Але я думаю, що має сенс також для розробника чітко записати їх для ясності. У нетривіальних випадках, про які ви говорите, перерахування різних типів помилок мені здається нормальним: func f () кидає ErrorTypeA, ErrorTypeB {}
greg3z

Очевидно, велика частина відсутня в тому, що немає механізму передачі типів помилок (крім коментарів док). Однак команда Swift заявила, що не хоче чітких списків типів помилок. Я впевнений, що більшість людей, які раніше мали справу з винятками Java, перевіряли винятки would
Сем

1
enum NumberError: Error {
  case NegativeNumber(number: Int)
  case ZeroNumber
  case OddNumber(number: Int)
}

extension NumberError: CustomStringConvertible {
         var description: String {
         switch self {
             case .NegativeNumber(let number):
                 return "Negative number \(number) is Passed."
             case .OddNumber(let number):
                return "Odd number \(number) is Passed."
             case .ZeroNumber:
                return "Zero is Passed."
      }
   }
}

 func validateEvenNumber(_ number: Int) throws ->Int {
     if number == 0 {
        throw NumberError.ZeroNumber
     } else if number < 0 {
        throw NumberError.NegativeNumber(number: number)
     } else if number % 2 == 1 {
         throw NumberError.OddNumber(number: number)
     }
    return number
}

Тепер номер підтвердження:

 do {
     let number = try validateEvenNumber(0)
     print("Valid Even Number: \(number)")
  } catch let error as NumberError {
     print(error.description)
  }

-2

Створіть enum так:

//Error Handling in swift
enum spendingError : Error{
case minus
case limit
}

Створіть такий метод, як:

 func calculateSpending(morningSpending:Double,eveningSpending:Double) throws ->Double{
if morningSpending < 0 || eveningSpending < 0{
    throw spendingError.minus
}
if (morningSpending + eveningSpending) > 100{
    throw spendingError.limit
}
return morningSpending + eveningSpending
}

Тепер перевірте, чи є помилка чи ні, і вирішіть:

do{
try calculateSpending(morningSpending: 60, eveningSpending: 50)
} catch spendingError.minus{
print("This is not possible...")
} catch spendingError.limit{
print("Limit reached...")
}

Близько, але сигари немає. Спробуйте виправити відстань і зробити футляр верблюда.
Алек
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.