Быстрый синтаксис do-try-catch

Я попытаюсь понять новую вещь обработки ошибок в swift 2. Вот что я сделал: я сначала объявил 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 ENUM.

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

7 ответов

Решение

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

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

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

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

Обновить:

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

Бета 4 Обновление:

Xcode 7b4 добавил поддержку комментариев к документам для 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-evolution. По сути, Джон МакКолл против типизированных ошибок, потому что он считает, что большинство библиотек в конечном итоге будет содержать общий случай ошибок, и что типизированные ошибки вряд ли добавят много кода, кроме стандартного (он использовал термин "желательный блеф"). Крис Латтнер сказал, что он открыт для опечаток в Swift 3, если он может работать с моделью устойчивости.

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

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

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

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

Ошибку можно обработать с помощью переключателя case в catch

      func  checkAge(age:Int) throws {

    guard !(age>0 && age < 18) else{
        throw Adult.child
    }

    guard !(age >= 60) else{
        throw Adult.old
    }
    
    guard (age>0) else{
        throw Adult.notExist
    }
    
}


   do{
       try checkAge(age:0)
      
    }
    catch let error {
        switch error{
        case Adult.child : print("child")
        case Adult.old : print("old")
        case Adult.notExist : print("not Exist")
        default:
            print("default")
        }
    }

enum Adult:Error {
    case child
    case old
    case notExist
}

Создайте перечисление следующим образом:

//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...")
}
Другие вопросы по тегам