【问题标题】:Swift do-try-catch syntaxSwift do-try-catch 语法
【发布时间】:2015-08-23 14:09:03
【问题描述】:

我尝试理解 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 可以理解在处理每个案例时它是详尽的。

【问题讨论】:

  • 你没有指定你抛出的错误类型,所以 Swift 无法确定所有可能的选项
  • 有没有办法指定错误的类型?
  • 我在新版 Swift 书籍中找不到任何内容 - 现在只有 throws 关键字
  • 在操场上为我工作,没有错误或警告。
  • Playgrounds 似乎允许 do 块在顶层是非穷尽的 - 如果你将 do 包装在一个非抛出函数中,它将产生错误。

标签: swift swift2


【解决方案1】:

Swift 担心你的 case 语句没有涵盖所有情况,要修复它,你需要创建一个默认 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")
}

【讨论】:

  • 但这不是很尴尬吗?我只有两个案例,它们都列在catch 语句中。
  • 现在是添加func method() throws(YourErrorEnum) 甚至throws(YourEnum.Error1, .Error2, .Error3) 的增强请求的好时机,这样您就知道可以抛出什么了
  • Swift 编译器团队在其中一次 WWDC 会议上明确表示,他们不想要“像 Java”那样的所有可能错误的迂腐列表。
  • 没有默认/默认错误;正如其他海报指出的那样,只需留下一个空白{}
  • @Icaro 这并不能让我安全;如果我以后“在数组中添加一个新条目”,编译器应该对我大喊大叫,因为我没有更新所有受影响的 catch 子句。
【解决方案2】:

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 用户出错。

【讨论】:

  • @rickster:你真的能重现编译器错误吗?对我来说,原始代码编译时没有错误或警告。如果抛出未捕获的异常,程序会以error caught in main() 中止。所以虽然你所说的一切听起来很合理,但我无法重现这种行为。
  • 喜欢你在扩展中分离错误信息的方式。保持代码清洁的好方法!很好的例子!
  • 强烈建议您避免在生产代码中使用强制 - try 表达式,因为它可能会导致运行时错误并导致您的应用程序崩溃
  • @Otar 总体上是好的想法,但这有点偏离主题——答案没有解决使用(或不使用)try! 的问题。此外,对于 Swift 中的各种“强制”操作(解包、尝试等),甚至对于生产代码,可以说存在有效、“安全”的用例——如果通过前置条件或配置,您已经可靠地消除了失败的可能性,它可以短路到即时故障比编写无法测试的错误处理代码更合理。
  • 如果您只需要显示错误消息,将逻辑放在SandwichError 类中是有意义的。但是,我怀疑对于大多数错误,错误处理逻辑不能如此封装。这是因为它通常需要了解调用者的上下文(是否恢复、重试或报告上游失败等)。换句话说,我怀疑最常见的模式无论如何都必须匹配特定的错误类型。
【解决方案3】:

我怀疑这只是尚未正确实施。 Swift Programming Guide 绝对似乎暗示编译器可以“像 switch 语句”一样推断出详尽的匹配。它没有提到需要一个通用的catch 才能做到详尽。

您还会注意到错误出现在try 行,而不是块的末尾,即在某些时候编译器将能够查明块中的哪个try 语句具有未处理的异常类型。

虽然文档有点模棱两可。我浏览了“Swift 中的新功能”视频,但找不到任何线索;我会继续努力的。

更新:

我们现在升级到 Beta 3,没有任何 ErrorType 推断的迹象。我现在相信,如果这是计划好的(我仍然认为是在某个时候),协议扩展上的动态调度可能会扼杀它。

Beta 4 更新:

Xcode 7b4 添加了对Throws: 的文档注释支持,“应该用来记录可以抛出哪些错误以及为什么会抛出错误”。我想这至少提供了 some 机制来将错误传达给 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

又一次更新

Apple 的错误处理原理现已提供 here。在swift-evolution 邮件列表上也有一些有趣的讨论。从本质上讲,John McCall 反对类型错误,因为他认为大多数库最终都会包含一个通用错误案例,并且类型错误不太可能在样板代码之外增加太多代码(他使用了“抱负虚张声势”这个词)。 Chris Lattner 表示,如果 Swift 3 可以与弹性模型一起使用,他对输入错误持开放态度。

【讨论】:

  • 感谢您的链接。虽然约翰不相信:“许多库包含'其他错误'类型”并不意味着每个人都需要“其他错误”类型。
  • 明显的反击是没有简单的方法可以知道函数会抛出什么样的错误,直到它发生,迫使开发人员捕获所有错误并尝试尽可能最好地处理它们。坦率地说,这很烦人。
【解决方案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 的函数的任何其他地方

【讨论】:

  • 不确定我是否理解你被搞砸的原因。你会得到一个编译器错误,这正是你想要的,对吧?如果添加枚举 case,这就是 switch 语句发生的情况。
  • 在某种意义上,在我看来,这似乎最有可能发生在错误枚举/do case 中,但它与枚举/开关中发生的情况完全一样,你是对的。我仍在努力说服自己,Apple 选择不输入我们抛出的内容是好的选择,但您不帮助我解决这个问题! ^^
  • 手动输入抛出的错误最终会在非平凡的情况下变得一团糟。这些类型是函数中所有 throw 和 try 语句的所有可能错误的联合。如果您手动维护错误枚举,这将是痛苦的。不过,每个区块底部的catch {} 可以说更糟。我希望编译器最终会在可能的地方自动推断错误类型,但我无法确认。
  • 我同意编译器理论上应该能够推断出函数抛出的错误类型。但我认为,为了清楚起见,开发人员明确写下它们也是有意义的。在您谈论的非平凡情况下,列出不同的错误类型对我来说似乎没问题: func f() throws ErrorTypeA, ErrorTypeB {}
  • 肯定缺少一个很大的部分,因为没有传达错误类型的机制(除了 doc cmets)。然而,Swift 团队表示他们不想要明确的错误类型列表。我相信过去处理过 Java 检查异常的大多数人都会同意 ?
【解决方案5】:

像这样创建枚举:

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

【讨论】:

  • 关闭但没有雪茄。尝试固定间距并制作枚举驼峰式大小写。
【解决方案6】:
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)
  }

【讨论】:

    猜你喜欢
    • 2016-05-21
    • 2016-09-30
    • 2016-06-06
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-01-22
    • 1970-01-01
    相关资源
    最近更新 更多