Синтаксис Swift do-try-catch

Я пытаюсь понять новую вещь для обработки ошибок в swift 2. Вот что я сделал: я впервые объявил об ошибке перечисления:

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 перечислены только два случая.

Для регулярных операторов switch swift может понять, что он является исчерпывающим, когда обрабатывается каждый случай.

Ответ 1

В модели обработки ошибок 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.

Ответ 2

Я подозреваю, что это просто еще не реализовано. Swift Programming Guide определенно означает, что компилятор может вывести исчерпывающие соответствия "как оператор switch". Он не упоминает о необходимости общего catch, чтобы быть исчерпывающим.

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

Документация немного неоднозначна. Ive просмотрел "Что нового в Swift видео и не нашел никаких подсказок; Я продолжаю пытаться.

Update:

Теперь были до Beta 3 без намека на вывод ErrorType. Теперь я верю, что если это когда-либо планировалось (и я все еще думаю, что это было в какой-то момент), динамическая отправка по расширению протокола, вероятно, убила его.

Обновление Beta 4:

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

Другое обновление:

Проведя некоторое время, надеясь на автоматический вывод ErrorType и выяснив, какие ограничения будут для этой модели, я изменил свое мнение - this это то, что я надеюсь, что 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

Еще одно обновление

Явное объяснение обработки ошибок теперь доступно здесь. Также были интересные обсуждения в списке рассылки swift-evolution. По сути, Джон Макколл выступает против типизированных ошибок, потому что он считает, что большинство библиотек в конечном итоге будет включать в себя общий случай ошибки, и что типизированные ошибки вряд ли добавят много кода помимо шаблона (он использовал термин "желательный блеф" ). Крис Лэттнер сказал, что он готов ввести типичные ошибки в Swift 3, если он может работать с моделью устойчивости.

Ответ 3

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

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")
}

Ответ 4

Я был также разочарован отсутствием типа, который может бросить функция, но теперь я получаю его благодаря @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

Ответ 5

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)
  }

Ответ 6

Создайте 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...")
}