在构建应用程序或软件项目之前,请设计一个错误处理系统(第一部分)

考虑应用中的错误处理系统可以成倍提高应用的可靠性和可维护性。 在您的应用中可能发生错误的无限可能中,您实际上只想看到有限的几种情况。 因此,当出现讨厌的错误或在您的应用中引入功能更改时,您会很高兴您拥有一个了不起的错误处理系统,它将准确告诉您哪里出了问题以及出了什么问题。 如果您有时间,以与Web API相似的方式为它们提供修复建议或提供其他提示也是一个好主意。


我将在为企业解决方案制作iOS应用程序的上下文中解释每个概念。 全文中包含特定的代码要点。


业务视角

在调试和拥有可靠软件的基础上。 企业通常希望收集有关可用性的数据并进行分析以改善其服务和产品。 因此,分析报告中的报告错误统计信息将是其产品如何为人们服务的有力指标。

用户角度

用户不希望应用程序崩溃或不响应。 用户想知道出了什么问题,以便对于大多数错误,他们可以尝试对自己进行故障排除,以便他们可以完成当前的任务。

开发人员观点

开发人员将节省构建和维护系统的时间和压力。 开发人员忘记了他们的代码,开发人员团队将处理彼此的代码; 因此,带有好名字,消息和提示的错误处理系统将使使用您代码的任何人都更加容易。


就像蛋糕一样,应用程序中的所有图层都赋予了它结构。 与蛋糕不同,应用程序中的图层只是抽象的。

模型层

这是业务逻辑所在的位置。 在您要解决的业务问题中,允许哪些关系以及什么才有意义。 因此,将在此处模拟诸如允许每个用户获得多少张优惠券的事情。 与业务相关的数据和模型在此处。

基础设施层

您的应用程序将连接到外部服务,例如第三方API和应用程序自己的后端。 以及本地数据库; 用于缓存和存储内容的服务被视为外部服务。 因此,您的网络和缓存类都在这一层。 诸如请求和响应模型之类的解析和补充模型也在此处。

用户界面层

该层将具有诸如ViewController,View和Cell类的内容。 这是锦上添花。 使它看起来不错。


自从制作了这张图。 我意识到那时就可以处理错误,并且在实践中大多数时候都更好。

模型层

您可能会收到诸如“不允许执行此操作”,“不符合要求的条件”之类的错误。 从编程的角度来看,这些可能是我们认为是业务逻辑的自定义规则。 如果有不同类型的用户和授权类型,将在此处确定。

对于开发人员和用户,这些错误通常可能是相同的。

基础设施层

在这里,您可能会遇到诸如“无法访问服务器”,“文件不存在”之类的错误。 从编程的角度来看,这些通常是从API或数据库传播的错误。

如果错误是技术笨拙的,则最好为开发人员和用户单独发送一条消息。 此外,向开发人员提供尽可能多的信息。

用户界面层

在这里,您可能会遇到诸如“无法调整窗口大小”,“视图未加载”之类的错误。 这些将只是UI错误,因此除非对用户而言至关重要。 无需将其显示给用户。 但是,对于开发人员而言,了解这些至关重要,以便它们可以从中恢复。


开发中

作为开发人员或项目经理。 您应该确保正确执行此部分,以避免在可能避免的事情上花费大量时间。 可以避免的事情包括:

  • 调试

每个项目都是不同的。 有很多工具。 但是要做的是,如果您经常做某事。 通常,抽出时间解决问题并提高您对该功能的了解,将加快并修复许多您必须要做的低效的事情。

  • 在错误修复的一方面花费太多时间

查找错误。 重现该错误。 了解错误。 考虑修复程序。 考虑到所有其他部分,它可能会混乱。 测试错误本身。 测试周围的功能仍然有效。 如果这些方法花费的时间太长,则您效率低下。

  • 打印,记录,警告

诸如打印错误和抛出异常之类的小事情都会记录到控制台。 有时您想将其显示在屏幕上。 我通常会制作一个自定义的打印功能,该功能可以执行以下几项操作:检查是否处于开发模式,振动,记录时间和消息,在屏幕上显示,登录到分析并具有断点。

  • 测试中

当独立测试人员测试并遇到错误时。 他们将必须解释在执行了哪些步骤之后发生的位置和发生的情况。 这是低效的。 您需要一个系统来有效地做到这一点。 也许当您向质量检查人员显示错误时,请显示错误编号或代码,以便他们可以告诉您代码并立即找到它。

您可能要考虑的另一件事是,通常您的逻辑中会有一条路径,您不想发生或处理。 在这里,您可能想要包含fatalError(),但这只会使应用程序崩溃。 这不是很好,质量检查人员通常会说应用程序崩溃了,这使您看起来很糟糕。 因此,如果使用显示“ App即将崩溃!”的函数包装崩溃,则将其崩溃。 然后至少看起来像是您打算崩溃的。

  • 选装件

可选是处理错误的好方法。 特别是如果函数返回可选类型。 您的模型应具有失败的初始化程序。 使用可选参数,并确保始终记录为零,甚至可能为什么为零。

  • 赶上

了解您的投掷和接住,尝试,尝试吗? 并尝试! 好。 很好地学习重新投掷和关闭。

在生产中

在此确保您不显示开发人员消息或记录不必要的消息。 您应该向用户显示有用的消息,以便他们大多数情况下可以解决自己的问题,最坏的情况是知道失败的原因并提供报告问题的选项。 但是将次要的,非致命的错误记录到分析中非常有帮助,并考虑您的错误统计信息来决定首先要修复的错误。 也许甚至您如何解决一件事并摆脱多个错误。 Crashlytics,做到这一点。


要考虑的另一件事是您的应用程序可能处于的各种状态。这不仅是诸如InBackground,InForeground之类的标准状态。 您可能需要在决策中包含自定义全局变量。 记录故障时,记录这些错误是一个不错的主意,因为全局状态是易于修复但易于忘记的错误的常见来源。 例如在线,离线,授权,互联网连接类型等。


不要混淆您的术语,这很重要。 不要混淆状态错误,状态可以是错误的来源,但这不是错误类型。 因此,一个好的做法是将状态映射到自定义错误类型,这样当您遇到一个逻辑,该逻辑指示某个状态不应该在某个状态下发生时,您不会抛出该状态,而是代表该状态的错误。

现在,如果您的应用程序是分层的,例如“模型”,“基础结构”和“视图”,或者实际上是MVVM,MVC,MVP中的任何一个,则您将不得不以与应用程序几乎相同的方式来构造错误。 因此,错误将出现在模型层,基础结构层和视图层中。 这意味着先抛出错误,然后再抛出错误并处理错误。

我发现整理所有错误类型的最好方法是创建一个名为AllErrors的文件,并在其中包含所有错误。 现在,引发错误的特定功能将必须在应用程序中无处不在,因此需要一种通用模式来使其更易于管理。 拥有某种管理所有错误抛出和重新抛出功能的中央文件也将很不错。 不用担心,您将在本文结尾看到以简单的方式进行所有操作。 目前,要了解的是一般原因和做某些事情的动机。 实际上,具体实现可能会有很大不同。


所有常规的东西。 但更重要的是,每个初始化程序都应该是有故障的。 让我证明这一点。 如果在任何地方都遵循这种模式,您将习惯它。 但是它有什么作用? 您应该对初始化器中的每个参数应用数据验证。 如果Person对象的初始值设定项的参数Int名为age,并且您的企业需要成熟的受众群体,则对于18岁及以下的年龄,返回nil。 在大多数情况下,您的后端将处理此问题,大约99%的时间一切都会按预期进行,但是在所有位置进行这种不透气的数据验证将使您的系统更好。 如果系统的一部分已经处理了,那么为什么要处理呢?这不是多余的,浪费时间吗? 井系统和零件发生变化。 因此,在大型部分之间交换数据的每个点之间执行此操作,将使系统解耦,并使系统更加健壮,即使其他部分已完全更改。 在软件中,很多时候您总是很懒惰,有时这可能会导致新颖的解决方案和捷径。 但是大多数情况下,这只会导致错误的烂屎应用程序。

在与业务相关的模型上,您应该具有基本的API函数,例如get,set,initialize等。如果您需要具有缓存层以及服务器后端。 然后,您的应用程序应该始终只有一个事实来源。 应用中的所有内容都从本地存储中获取,并且本地存储以某种方式与服务器数据库交互以在可达性允许的情况下同步。

这里的错误应该是:无法读写,未经授权,自定义业务规则等。所有内容都写在模型类中。


通常,我会在数据层中遵循单例模式。 一单件用于网络相关功能,一单用于缓存相关功能。 支持类是这个难题的重要组成部分,它应该冒出很多错误,但是单例在这里是最重要的。 数据缓存类将处理所有持久性,并进一步与网络层同步。

您可能有一个包含两个数据单例的管理器类,该类将管理它们之间同步的所有复杂规则。 在这里,应用程序状态变得很重要,就像应用程序处于联机/脱机状态或登录/注销一样。 但是,缓存层如何与数据库同步的决定很大程度上由业务逻辑决定。

此处的错误应与应用程序状态,同步错误,传播到应用程序的外部错误(来自第三方服务,后端或文件系统/ SQLite数据库)有关。

因此,取决于开发人员,您可以处理UI层中或错误发生处附近的错误。 我个人喜欢保持ViewControllers轻巧,并自动执行许多功能,因此我通常选择在源代码附近进行操作。 如果存在请求和响应模型,则它们的可失败的初始化程序和引发错误的相关方法将被视为此层的一部分。


很好的自动执行大多数功能的方法是通过协议。 我喜欢遵循表示不同功能的不同协议并将其标记在视图控制器上的想法。 可以清楚地了解每个控制器的功能。 此外,通过在协议扩展中声明功能,您可以隐藏一些实现,获得一些可以调用的功能。 但是,能够在最后一层处理错误以一次性完成某件事也很好。 在那种情况下,只需将错误作为完成关闭参数之一进行中继即可。


这只是一种软件项目设计模式,可以解决许多业务问题并优化应用程序的可维护性和可靠性。 这是系统的一般概述。

我希望您能采取一些有用的策略来架构自己的应用程序并解决一些常见问题。 请继续阅读本系列的下一篇文章,在这里我可以详细解释throw,rethrowing,closure throw函数的结构。 它们是错误处理系统的重要组成部分。 然后,下一篇文章将介绍:通用错误/响应结构,RxSwift,异步错误处理…