在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上与我联系。 如果您有任何想让我在即将发布的每周博客文章中涉及的主题,我也希望能收到您的来信。
谢谢阅读! 🚀