为什么'抛出'在Swift中不安全?

Swift中对我最大的误解是throws关键字。 考虑下面的一段代码:

 func myUsefulFunction() throws 

我们不能真正理解它会抛出什么样的错误。 我们唯一知道的是它可能会抛出一些错误。 了解错误的唯一方法是查看文档或在运行时检查错误。

但这不是反对斯威夫特的性质吗? Swift具有强大的generics和一个types系统来使代码具有performance力,但是它的感觉就好像是完全相反,因为你不能从函数签名中得到有关错误的任何信息。

为什么? 还是我错过了一些重要的东西,并误解了这个概念?

select是一个有意的devise决定。

他们不希望在Objective-C,C ++和C#中不需要声明exception抛出的情况,因为这使得调用者必须假设所有的函数都抛出exception,并且包含样板来处理可能不会发生的exception,或者只是忽略了例外的可能性。 这两种方法都不是理想的,第二种方法使得exception不可用,除非你想终止程序,因为你不能保证当堆栈解开时调用堆栈中的每个函数都能正确地释放资源。

另一个极端是你所倡导的观点,并且每一种抛出的exception都可以被声明。 不幸的是,人们似乎反对这样的结果,那就是你有大量的catch块,所以你可以处理每种types的exception。 所以,比如说,在Java中,他们会抛出Exception减lessSwift或更糟糕的情况,他们使用未经检查的exception,所以你可以完全忽略这个问题。 GSON库是后一种方法的一个例子。

我们select使用未经检查的exception来指示parsing失败。 这主要是因为通常客户端不能从错误的input中恢复,因此强迫他们捕获一个检查的exception导致在catch()块中的草率代码。

https://github.com/google/gson/blob/master/GsonDesignDocument.md

这是一个非常糟糕的决定。 “嗨,你不能信任你自己的error handling,所以你的应用程序应该崩溃”。

就个人而言,我认为斯威夫特得到了正确的平衡。 你必须处理错误,但是你不必写大量的catch语句来完成。 如果他们走得更远,人们会想办法颠覆这个机制。

devise决定的完整基本原理是在https://github.com/apple/swift/blob/master/docs/ErrorHandlingRationale.rst

编辑

似乎有些人在我所说的一些事情上有问题。 所以这里是一个解释。

程序可能抛出exception的原因有两大类。

  • 程序外部环境中的意外情况,例如文件IO错误或格式错误的数据。 这些是应用程序通常可以处理的错误,例如向用户报告错误并允许他们select不同的行为。
  • 编程中的错误,如空指针或数组绑定错误。 解决这些问题的正确方法是让程序员改变代码。

第二种types的错误通常不应该被捕获,因为它们表明对环境的错误假设,这可能意味着程序的数据是错误的。 我无法安全地继续下去,所以你必须放弃。

第一种types的错误通常可以被恢复,但是为了安全地恢复,每个堆栈帧都必须被正确解开,这意味着对应于每个堆栈帧的函数必须意识到它所调用的函数可能会抛出exception并采取步骤以确保在抛出exception的情况下,一切都得到一致的清除,例如,使用finally块或等价物。 如果编译器不提供对告诉程序员的支持,他们已经忘记计划exception,程序员不会总是计划exception,并会编写泄漏资源或使数据处于不一致状态的代码。

gson的态度如此令人震惊的原因是因为他们说你无法从parsing错误中恢复(实际上,更糟的是,他们告诉你,你缺乏从parsing错误中恢复的技能)。 这是一个荒谬的事情来断言,人们总是试图parsing无效的JSON文件。 如果有人错误地select了一个XML文件,我的程序崩溃是不是一件好事? 不,不是。 它应该报告问题并要求他们select一个不同的文件。

顺便提一句,只是一个例子,说明为什么使用未检查的exception来处理可以从中恢复的错误是不好的。 如果我想从select一个XML文件的人中恢复,我需要捕获Java运行时exception,但是哪些? 那么我可以看看Gson的文档来找出,假设他们是正确的和最新的。 如果他们已经检查了exception,那么API会告诉我哪些exception会出现,编译器会告诉我是否处理这些exception。

我曾经是Swiftinput错误的早期支持者。 这就是Swift团队说服我的错误。

严重types的错误是脆弱的,可能会导致API进化不佳。 如果API承诺只抛出3个错误中的一个,那么在稍后的版本中出现第四个错误时,我有一个select:我把它埋在现有的3中,或者强制每个调用者重写它们的error handling代码处理它。 由于它不是原来的3,所以它可能不是一个非常普遍的情况,这给API带来了很大的压力,不会扩大错误列表,特别是一旦框架长期被广泛使用(想想:Foundation )。

当然,对于开放的枚举,我们可以避免,但是一个开放的枚举不能实现强types错误的目标。 这基本上是一个无types的错误,因为你仍然需要一个“默认”。

你可能仍然会说“至less我知道错误来自于一个开放的枚举”,但是这往往会让事情变得更糟。 说我有一个日志系统,它试图写和获得一个IO错误。 它应该返回什么? Swift没有代数数据types(我不能说() -> IOError | LoggingError ),所以我可能不得不将IOError封装到LoggingError.IO(IOError) (这会强制每个图层显式地重新包装;您可以不经常rethrows )。 即使它有ADT,你真的想IOError | MemoryError | LoggingError | UnexpectedError | ... IOError | MemoryError | LoggingError | UnexpectedError | ... IOError | MemoryError | LoggingError | UnexpectedError | ... ? 一旦你有了几层,我就会一层一层地缠绕着一些根本的“根本原因”,而这些根本原因必须被解开才能解决。

你将如何处理呢? 在绝大多数情况下,抓块是什么样的?

 } catch { logError(error) return } 

cocoa程序(即“应用程序”)深入挖掘错误的根本原因,并基于每个精确的情况执行不同的操作,这是非常罕见的。 可能有一两个病例有复原,其余的则是无论如何也无能为力。 (这是一个常见的问题,在Java中,检查exception不仅仅是Exception ,它不像以前没有人沿着这条路走下去,我喜欢Yegor Bugayenko在Java中检查exception的论点,他基本上认为是他首选的Java实践,迅速的解决scheme。)

这并不是说没有强types错误会非常有用的情况。 但是有两个答案:首先,你可以自由地用enum自己实现强types的错误,并且得到很好的编译器执行。 不完美(你仍然需要一个在switch语句之外的默认捕获,但不在里面 ),但是如果你自己遵循一些约定,那么它是相当不错的。

其次,如果这个用例变得很重要(也可能),那么稍后为这些情况添加强types错误并不难,而不会破坏那些需要相当一般的error handling的常见情况。 他们只会添加语法:

 func something() throws MyError { } 

来电者将不得不把它视为强大的types。

最后,对于强types的错误有很大的用处,基金会需要抛出它们,因为它是系统中最大的错误产生者。 (相对于处理由Foundation生成的一个,你真的从头开始创build一个NSError )这将是基础的大规模检修,很难与现有的代码和ObjC保持兼容。 所以input错误将需要绝对精彩的解决非常常见的cocoa问题值得考虑作为默认行为。 它不能只是一个更好的(更不用说有上述问题)。

所以这些都不是说在所有情况下,无types错误都是error handling的完美解决scheme。 但是这些观点让我相信,今天在斯威夫特走的路是正确的。

Interesting Posts