在Swift中选择正确的失败方法

Swift的一个主要重点是编译时安全性-使我们(作为开发人员)可以轻松地专注于编写可预测性更高,更不易出现运行时错误的代码。 但是,有时事情确实由于各种原因而失败-因此,本周,让我们看一下如何适当地处理此类失败,以及我们可以使用哪些工具来做到这一点。

几周前,我们在“在Swift中处理非可选的可选内容”中介绍了如何处理不是真正可选的可选内容 在那篇文章中,我介绍了将preconditionFailure()guard结合使用的情况,而不是强制展开,并介绍了微框架Require,它提供了方便的API。

从那篇文章开始,许多人问到preconditionFailure()assert()之间有什么区别,以及它与Swift的throwing功能之间的关系。 因此,在这篇文章中,让我们仔细研究一下所有这些语言功能以及何时使用它们。

让我们从清单开始

据我所知,这是所有可以处理Swift中错误的方法:

  • 返回 nil 或错误枚举值。 错误处理的最简单形式是从遇到错误的函数中简单地返回nil (如果使用Result枚举作为返回类型,则返回.error情况)。 尽管这在许多情况下确实很有用,但将其过度用于所有错误处理会迅速导致使用API​​麻烦,并且还存在隐藏错误逻辑的风险。
  • 引发错误 (使用throw MyError ),这要求调用者使用do, try, catch模式处理潜在的错误。 另外,可以使用try?忽略错误try? 在呼叫站点。
  • 使用 assert() assertionFailure()来验证某个条件为真。 默认情况下,这会在调试版本中导致致命错误,而在发行版本中会被忽略。 因此, 不能保证触发断言后执行就会停止,因此有点像严重的运行时警告。
  • 使用 precondition() preconditionFailure()代替断言。 关键区别在于,即使在发行版中,也总是*对它们进行评估。 这意味着您可以保证,如果不满足条件,执行将永远不会继续。
  • 调用 fatalError() -在子类化符合NSCoding系统类(例如UIViewController fatalError() ,您可能已经在Xcode生成的init(coder:)实现中看到了此NSCoding 。 直接调用它会杀死您的进程。
  • 调用 exit() ,使用代码将其存在于您的进程中。 当您可能想退出全局范围时(例如在main.swift ),这在命令行工具和脚本中非常有用。

*除非您使用 Ounchecked 优化模式 进行编译

可恢复与不可恢复

选择正确的失败方法时,要考虑的关键是确定所发生的错误是否可恢复

例如,假设我们正在调用服务器,并且收到错误响应。 无论我们是多么出色的程序员,以及我们服务器的基础架构如何牢固,这都是必然发生的。 因此,将这些类型的错误视为致命的和不可恢复的错误通常是一个错误。 相反,我们想要的是恢复并可能向用户显示某种形式的错误屏幕。

那么,在这种情况下如何选择合适的失败方法呢? 如果我们看一下上面的列表,我们可以将其分为可恢复不可恢复的技术,如下所示:

可恢复的

  • 返回nil或错误枚举情况
  • 引发错误

不可恢复

  • 使用assert()
  • 使用precondition()
  • 调用fatalError()
  • 调用exit()

在这种情况下,由于我们正在处理异步任务,因此返回nil或错误枚举大小写可能是最佳选择,如下所示:

对于同步API,抛出是一个不错的选择-因为它“迫使”我们的API用户以适当的方式处理错误:

但是,有时错误是无法恢复的。 例如,假设我们需要在应用启动期间加载配置文件。 如果缺少该配置文件,它将使我们的应用程序处于未定义状态 -因此在这种情况下,崩溃比继续执行程序更好。 为此,使用更强大,不可恢复的故障方式之一更为合适。

在这种情况下,如果缺少配置文件,我们将使用preconditionFailure()停止执行:

程序员错误与执行错误

另一个重要的区分是错误是由错误的逻辑或错误的配置引起的,还是应将错误视为应用程序流程合法部分 。 基本上,程序员是引起错误还是外部因素引起了错误。

当防止程序员错误时,您几乎总是想使用不可恢复的技术。 这样一来,您就不必在整个应用程序中都围绕异常情况进行编码,并且有一套很好的测试程序可以确保尽快发现这些类型的错误。

例如,假设我们正在构建一个视图,该视图需要在使用视图模型之前将其绑定。 视图模型在我们的代码中将是可选的,但是我们不想每次使用它时都必须对其进行拆包。 但是,如果视图模型以某种方式丢失了,我们不一定要在生产中使应用程序崩溃—在调试中获得有关它的错误就足够了。 这是使用断言的一种情况:

请注意,由于assertionFailure()在发布版本中将以静默方式失败,因此我们必须在上述guard语句中return

结论

我希望这篇文章有助于弄清Swift中可用的各种错误处理技术之间的区别。 我的建议不仅是坚持一种技术,还要根据情况选择最合适的一种。 通常,我也建议尽可能尝试从错误中恢复,以免破坏用户体验,除非应将错误视为致命错误。

另外,请记住print(error)不是错误处理😉

如果您有任何疑问,建议或反馈,请随时在Twitter上与我联系。 如果您有任何想让我在即将发布的每周博客文章中涉及的主题,我也希望能收到您的来信。

谢谢阅读! 🚀