error handling理论?

大多数有关error handling的build议归结为一些提示和技巧(例如,看这篇文章 )。 这些提示是有帮助的,但我认为他们不回答所有问题。 我觉得应该按照一定的哲学思想来devise我的应用,这个哲学思想为我们提供了一个坚实的基础。 error handling的主题有没有这样的理论?

以下是一些实际问题:

  • 如何判断错误是应该在本地处理还是传播到更高级别的代码?
  • 如何决定是logging一个错误,还是将错误信息显示给用户?
  • logging应该只在应用程序代码中完成的东西吗? 或者可以从库代码做一些日志logging。
  • 在例外的情况下,你应该在哪里抓住他们? 在低级或更高级别的代码?
  • 如果您想通过所有的代码层来争取统一的error handling策略,或者尝试开发一个能够适应各种error handling策略的系统(以便能够处理来自第三方库的错误)。
  • 创build错误代码列表是否有意义? 还是这些老式的?

在很多情况下,制定一个足够好的策略来处理错误条件是常识。 但是,我想知道是否有更正式的“学术”方法?

PS:这是一个普遍的问题,但也欢迎C ++特定的答案(C ++是我工作的主要编程语言)。

几年前,我认为完全一样的问题:)

在search和阅读了几件事情之后,我认为我发现的最有趣的参考是Andy Longshaw和Eoin Woods的“ 生成模式,error handling和pipe理” 。 这是一个简短而系统的尝试,以涵盖你提到的基本成语和其他一些。

这些问题的答案颇具争议性,但上述作者勇于在会议上揭穿自己,然后将自己的想法写在纸上。

logging应该只在应用程序代码中完成的东西吗? 或者可以从库代码做一些日志logging。

只是想对此发表评论。 我的观点是永远不要直接login库代码,而是在应用程序代码中提供钩子或callback来实现这一点,所以应用程序可以决定如何处理来自日志的输出(如果有的话)。

介绍

为了理解error handling需要做什么,我认为需要清楚地理解所遇到的错误types以及遇到错误的环境。

对我来说,考虑两种主要types的错误是非常有用的:

  1. 错误不应该发生,通常是由于代码中的错误。

  2. 在正常操作中预期并且不能阻止的错误,例如由于应用程序无法控制的数据库问题导致数据库连接断开。

应该处理错误的方式很大程度上取决于它是哪种types的错误。

影响如何处理错误的不同的上下文是:

  • 应用程序代码

  • 库代码

库代码中的error handling与应用程序代码中的处理有所不同。

下面讨论处理两种主要types错误的原理。 还讨论了图书馆代码的特殊考虑。 最后,原文中的具体实际问题是在所提出的哲学的背景下处理的。

错误的types

编程错误 – 错误 – 以及其他永远不会发生的错误

许多错误是编程错误的结果。 这些错误通常不能被纠正,因为不能预期具体的编程错误。 这意味着我们不能预先知道错误是什么情况,所以我们不能从这种情况中恢复,也不应该尝试。

最终,对这种错误的修复就是修复编程错误。 为了方便起见,错误应该尽快出现。 理想情况下,程序应在识别出这样的错误并提供相关信息后立即退出。 快速而明显的退出减less了完成debugging和重新testing周期所需的时间,允许在相同的testing时间内修复更多的错误。 这反过来导致应用程序更强大,而且需要部署的时候更less的bug。

处理这种types的错误的另一个主要目标应该是提供足够的信息,以便于识别错误。 例如,在Java中,抛出一个RuntimeException通常会在堆栈跟踪中提供足够的信息来立即识别错误; 在干净的代码中,通过检查堆栈跟踪,通常可以识别即时修复。 在其他语言中,可能会logging调用堆栈或保留必要的信息。 为了简洁起见,不压制信息是至关重要的。 不要担心这种types的错误发生时您将占用多less日志空间。 提供的信息越多,bug的修复速度越快,应用程序投入生产时会留下较less的bug来污染日志。

服务器应用

现在,在某些服务器应用程序中,服务器具有足够的容错能力,即使偶尔出现编程错误也能继续运行。 在这种情况下,最好的方法是在必须继续运行的服务器代码和可以允许失败的任务处理代码之间有非常明确的分隔。 例如,任务可以被降级到线程或subprocess,就像在许多Web服务器中所做的那样。

在这样的服务器体系结构中,处理任务的线程或subprocess可以被视为可能失败的应用程序。 所有上面的考虑都适用于这样一个任务:通过干净地退出任务,尽可能快地出现错误,并且应该logging足够的信息以便容易地find并修复错误。 例如,当这样一个任务退出Java时,通常应该logging导致退出的任何RuntimeException的整个堆栈跟踪。

尽可能多的代码应该在处理任务的线程或进程中执行,而不是在主服务器线程或进程中执行。 这是因为主服务器线程或进程中的任何错误都会导致整个服务器closures。 将代码(包含它所包含的错误)推送到任务处理代码中,这样会更好,在代码出现时不会导致服务器崩溃。

在正常操作中预期和不能防止的错误

在正常操作中预期的和不能防止的错误,例如来自数据库或与应用程序分离的其他服务的exception,需要非常不同的处理。 在这些情况下,目标不是修复代码,而是在有意义时让代码处理错误,并告知用户或操作员谁可以解决问题。

例如,在这些情况下,应用程序可能希望丢弃迄今积累的任何结果,然后重试操作。 在数据库访问中,使用事务可以帮助确保累积的数据被丢弃。 在其他情况下,考虑这样的重试来编写代码会很有用。 幂等性的概念在这里也是有用的。

当自动重试不能充分解决问题时,应通知人员。 应告知用户操作失败; 用户通常可以select重试。 然后用户可以判断是否需要重试,并且还可以对input进行改变,以便重试时可以帮助更好地进行。

对于这种types的错误,可以使用logging和可能的电子邮件通知来通知系统操作员。 与编程错误的logging不同,在正常操作中预期的错误的logging应该更简洁,因为错误可能发生多次并且在日志中出现多次; 运营商往往会分析许多错误的模式,而不是集中在一个人的错误。

图书馆和应用程序

以上关于错误types的讨论直接适用于应用程序代码。 error handling的另一个主要上下文是库代码。 库代码仍然具有相同的两种基本types的错误,但是它通常不能或不应该直接与用户通信,并且对应用程序上下文知之甚less,包括立即退出是否可接受,应用程序代码是否可接受。

因此,图书馆应该如何处理日志,他们应该如何处理在正常操作中可能出现的错误,以及他们应该如何处理编程错误和其他不应该发生的错误。

关于日志logging,如果可能的话,库应该支持以客户端应用程序代码所需的格式进行日志logging。 一种有效的方法是根本不做任何日志logging,并允许应用程序代码根据库提供给应用程序代码的错误信息进行所有日志logging。 另一种方法是使用可configuration的日志logging接口,允许客户端应用程序为日志logging提供实现,例如,当首次加载库时。 例如,在Java中,库可能使用logback日志logging接口,并允许应用程序担心要configurationlogback以使用哪个日志logging实现。

对于不应该发生的错误和其他错误,库仍然不能简单地退出应用程序,因为这可能不被应用程序接受。 相反,图书馆应该退出图书馆的电话,为来电者提供足够的信息来帮助诊断问题。 信息可以以堆栈跟踪的例外forms提供,或者如果正在使用可configuration的日志logging方法,则库可以logging信息。 然后,应用程序可以像处理这种types的任何其他错误,通常通过退出或在服务器中一样,通过允许任务进程或线程退出,并使用相同的日志logging或错误报告来处理应用程序代码。

正常操作中预期的错误也应该报告给客户端代码。 在这种情况下,与在客户端代码中遇到的这种types的错误一样,与错误相关的信息可以更简洁。 通常,图书馆应该减less对这类错误的本地处理,更多地依靠客户端代码来决定是否重试以及重复多less次。 如果需要,客户端代码可以将重试决定传递给用户。

实际问题

现在我们有哲学了,让我们把它应用到你提到的实际问题上。

  • 如何判断错误是应该在本地处理还是传播到更高级别的代码?

如果在正常操作中出现错误,请重试或者可能在本地咨询用户。 否则,将其传播到更高级别的代码。

  • 如何决定是logging一个错误,还是将错误信息显示给用户?

如果是正常操作期望的错误,并且用户input将有助于确定要采取的操作,则获取用户input并logging简洁的消息; 如果这似乎是编程错误,请向用户提供简要通知并logging更多的信息。

  • logging应该只在应用程序代码中完成的东西吗? 或者可以从库代码中进行一些日志logging。

从库代码中logging应该在客户端代码的控制之下。 该库至多应该login到客户端提供实现的接口。

  • 在例外的情况下,你应该在哪里抓住他们? 在低级或更高级别的代码?

在正常操作中预期的exception可以在本地捕获,操作重试或以其他方式处理。 在所有其他情况下,应允许exception传播。

  • 如果您想通过所有的代码层来争取统一的error handling策略,或者尝试开发一个能够适应各种error handling策略的系统(以便能够处理来自第三方库的错误)。

第三方库中的错误types与应用程序代码中出现的错误types相同。 错误应主要根据它们代表的错误types进行处理,并对库代码进行相关调整。

  • 创build错误代码列表是否有意义? 还是这些老式的?

应用程序代码应提供编程错误情况下的错误的完整描述,以及在正常操作中可能发生错误的情况下的简要描述; 在任何情况下,描述通常比错误代码更合适。 库可以提供一个错误代码作为描述错误是编程还是其他内部错误,或者错误是否可以在正常操作中发生的错误代码的方式,后一种types可能细分得更细; 然而,在可能的语言中,exception层次结构比错误代码更有用。 请注意,从命令行运行的应用程序可能充当shell脚本的库,但是。

免责声明:我不知道关于error handling的任何理论,但是,当我探索各种语言和编程范例,以及编程语言devise(并讨论它们)时,我反复思考了这个主题。 因此,下面是我迄今为止的经验总结。 有客观的论据。

注意:这应该涵盖所有的问题,但我甚至没有尝试按顺序解决它们,而是select结构化的表示。 为了清楚起见,在每节结束时,我会对所回答的问题提出一个简洁的答案。


介绍

作为一个前提,我想指出,无论讨论如何,在devise库(或可重用代码)时都必须牢记一些参数。

作者不能指望如何使用这个图书馆,因此应避免使整合变得比应该更困难的策略。 最明显的缺陷将依赖于全球共享的国家; 线程本地共享状态也可能是与协程/绿色线程交互的噩梦。 这样的协程和线程的使用也强调了同步最好留给用户,在单线程代码中,它将意味着没有(最佳性能),而在协程和绿色线程中,用户最适合实现(或使用现有的)专用同步机制的实现。

也就是说,当库仅供内部使用时,全局variables或线程局部variables可能是方便的; 如果使用的话,应作为技术限制明确logging。


logging

有很多方法可以logging消息:

  • 与额外的信息,如时间戳,进程ID,线程ID,服务器名称/ IP,…
  • 通过同步调用或asynchronous机制(和溢出处理机制)
  • 在文件,数据库,分布式数据库,专用的日志服务器,…

作为图书馆的作者,日志应该集成在客户端基础设施(或closures)中。 最好通过允许客户端提供钩子来处理日志本身,我的build议是:

  • 提供2个挂钩:一个决定是否login,另一个实际login(消息被格式化,而后者只有在客户端决定login时才被调用)
  • 在消息之上提供:严重性(aka级别),文件名,行和函数名称,如果开放源代码或其他逻辑模块 (如果有多个)
  • 默认情况下,写入stdoutstderr (取决于严重性),直到客户端明确说不logging

我会注意到,按照介绍中所描述的准则,同步留给了客户。

关于是否logging错误:不要logging(作为错误)你通过你的API报告的内容; 但您仍然可以以较低的严重程度logging详细信息。 客户可以在处理错误时决定是否报告,例如,如果这只是一个推测性的调用,select不报告。

注意:有些信息不应该logging到日志中,而其他一些信息最好是混淆的。 例如,不应该logging密码,信用卡或护照/社会安全号码最好是混淆的(至less部分)。 在为这种敏感信息devise的库中,这可以在logging过程中完成; 否则应用程序应该照顾这一点。

logging应该只在应用程序代码中完成的东西吗? 或者可以从库代码做一些日志logging。

应用程序代码应该决定策略。 图书馆是否logging取决于是否需要。


出错后继续?

在我们谈论报告错误之前,我们应该问的第一个问题是错误是应该报告(处理)还是如果事情是错误的,放弃当前stream程显然是最好的策略。

这当然是一个棘手的话题。 一般来说,我会build议devise这样一个选项,如果需要,可以进行清除/重置。 如果在某些情况下不能实现,那么这些情况就会引起stream程的堕胎。

注意:在某些系统上,有可能获得进程的内存转储。 如果应用程序处理敏感数据(密码,信用卡,护照等),则最好在生产中停用(但可以在开发过程中使用)。

注意:有一个debugging开关将一部分错误报告调用转换为带有内存转储的中止stream程以帮助在开发过程中进行debugging可能会很有趣。


报告错误

发生错误意味着函数/接口的合约不能被满足。 这有几个后果:

  • 客户端应该被警告,这就是为什么应该报告错误
  • 没有部分正确的数据应该在野外逃脱

后一点将在稍后处理; 现在让我们专注于报告错误。 客户不应该无意中忽略这个报告。 这就是为什么使用错误代码是如此可憎的原因(在可以忽略返回值的语言中):

 ErrorStatus_t doit(Input const* input, Output* output); 

我知道有两个scheme需要在客户端部分采取明确的行动:

  • 例外
  • 结果types( optional<T>either<T, U> ,…)

前者是众所周知的,后者在function语言中是非常多的,并且在std::future<T>的伪装下被引入到C ++ 11中,尽pipe存在其他的实现。

我build议在可能的情况下更好地select后者,因为它更易于理解,但是如果没有预期的结果,则会回到例外状态。 对比:

 Option<Value&> find(Key const&); void updateName(Client::Id id, Client::Name name); 

在“只写”操作(如updateName ,客户端对结果没有任何用处。 它可以被引入,但是很容易忘记检查。

如果结果types不切实际,或者不足以传达详细信息,则还原为exception:

 Option<Value&> compute(RepositoryInterface&, Details...); 

在这种外部定义的callback的情况下,潜在的故障几乎是无限的。 实现可以使用networking,数据库,文件系统,在这种情况下,为了准确地报告错误:

  • 当接口不足以(或不切实际)传达错误的全部细节时,应该期望外部定义的callback通过exception来报告错误。
  • 基于这个抽象callback的函数应该对这些exception是透明的(让它们通过,而不是修改)

我们的目标是让这个exception起泡到接口实现的层(至less),因为只有在这个层次上才有机会正确解释抛出的exception。

注意:外部定义的callback并不是强制使用exception,我们应该期望它可能会使用一些。


使用错误

为了使用错误报告,客户需要足够的信息来做出决定。 结构化信息(例如错误代码或exceptiontypes)应该是首选(用于自动操作),并且可以以非结构化的方式提供附加信息(消息,堆栈等)(供人类调查)。

如果一个function清楚地logging了所有可能的失效模式,那么最好是:何时发生以及如何报告。 但是,特别是在执行任意代码的情况下,客户端应该准备处理未知代码/exception。

当然,一个明显的例外是结果types: boost::variant<Output, Error0, Error1, ...>提供了一个编译器检查的已知失败模式的详尽列表…尽pipe返回这种types的函数仍然可能抛出,当然。

如何决定是logging一个错误,还是将错误信息显示给用户?

当订单无法履行时,应始终提醒用户,但应显示用户友好(可理解)的信息。 如果可能的话,还应提供build议或解决方法。 详情是调查小组。


从错误中恢复?

最后,但也是最重要的一点,就是错误的真正可怕的部分:恢复。

这是数据库(真实的)非常适合的事情:类似事务的语义。 如果有任何意外发生,交易就会中止,就像什么都没发生过一样。

在现实世界中,事情并不简单。 取消发送的电子邮件的一个简单的例子让人想起:太迟了。 协议可能存在,取决于您的应用程序域,但是这是没有这个讨论。 但是,第一步是恢复一个健全的内存状态的能力; 这在大多数语言中都不是简单的(而STM现在只能做到这一点)。

首先,说明这个挑战:

 void update(Client& client, Client::Name name, Client::Address address) { client.update(std::move(name)); client.update(std::move(address)); // Throws } 

现在,更新地址失败后,我剩下一半更新的client 。 我能做什么 ?

  • 试图撤销发生的所有更新几乎是不可能的(撤消可能会失败)
  • 在执行任何单个更新之前复制状态是一个性能问题(假设我们甚至可以把它换回来)

无论如何,所要求的簿记是这样的,错误会蔓延。

而最糟糕的是,对腐败程度没有一个安全的假设(除了client现在已经被玷污了)。 或者至less,没有假设,将忍受时间(和代码的变化)。

往往,赢的唯一途径就是不玩。


可能的解决scheme:事务

只要有可能,关键的想法是定义macrosfunction,这将失败或产生预期的结果。 这些是我们的交易 。 他们的forms是不变的:

 Either<Output, Error> doit(Input const&); // or Output doit(Input const&); // throw in case of error 

事务不会修改任何外部状态,因此如果它不能产生结果:

  • 外部世界并没有改变(没有任何回滚)
  • 没有部分结果要观察

任何不是事务的函数都应该被认为是破坏了所触及的任何东西,因此处理非事务性函数错误的唯一方法是让它冒泡直到达到事务层。 任何事先处理错误的尝试最终都注定要失败。

如何判断错误是应该在本地处理还是传播到更高级别的代码?

在例外的情况下,你应该在哪里抓住他们? 在低级或更高级别的代码?

只要这样做是安全的,就要处理它们,这样做是有价值的。 最值得注意的是,可以捕捉错误,检查是否可以在本地处理,然后处理或传递。


如果您想通过所有的代码层来争取统一的error handling策略,或者尝试开发一个能够适应各种error handling策略的系统(以便能够处理来自第三方库的错误)。

我以前没有提到这个问题,但是我相信,我所强调的方法已经是双重的了,因为它包括了结果types和例外。 因此,处理第三方库应该是一件小事,尽pipe我build议无论如何都要出于其他原因包装它们(第三方代码更好地被隔离,而不是面向以阻抗自适应为主的业务接口)。

如何判断错误是应该在本地处理还是传播到更高级别的代码?

如果exception破坏了方法的运行,那么把它抛到更高的层次是一个好方法。 如果您熟悉MVC,则必须在Controller中评估exception。

如何决定是logging一个错误,还是将错误信息显示给用户? logging错误和所有关于错误的信息是一个好方法。 如果错误中断了操作,或者用户需要知道发生错误,则应将其显示给用户。 请注意,在Windows服务日志中非常重要。

logging应该只在应用程序代码中完成的东西吗? 或者可以从库代码中进行一些日志logging。

我没有看到有任何理由loggingDLL中的错误。 它只应该抛出错误。 当然可能有一个具体的理由。 在我们公司一个dlllogging有关过程的信息(不仅是错误)

在例外的情况下,你应该在哪里抓住他们? 在低级或更高级别的代码? 类似的问题:在什么时候你应该停止传播一个错误并处理它?

在一个控制器。

编辑:我需要解释这一点,如果你不熟悉MVC。 模型视图控制器是一种devise模式。 在Model中开发应用程序逻辑。 在视图中,您向用户显示内容。 在Controller中,您可以获取用户事件并调用相关函数的Model,然后调用View来将结果显示给用户。

假设你有一个有两个文本框和一个标签和一个名为Add的button的表单。 你可能猜到这是你的观点。 Button_Click事件在Controller中定义。 模型中定义了添加方法。 当用户点击时,Button_Click事件被触发,Controller调用add方法。 这里的文本框的值可以是空的,也可以是字母而不是数字。 add函数中发生exception,并引发此exception。 控制器处理它。 并在标签中显示错误信息。

如果您想通过所有的代码层来争取统一的error handling策略,或者尝试开发一个能够适应各种error handling策略的系统(以便能够处理来自第三方库的错误)。

我更喜欢第二个。 这会更容易。 我不认为你可以做一个error handling的一般东西。 特别针对不同的图书馆

创build错误代码列表是否有意义? 还是这些老式的?

这取决于你将如何使用它。 在一个单一的应用程序(一个网站,一个桌面应用程序),我不认为这是必要的。 但是,如果您开发一个Web服务,您将如何通知用户错误? 提供错误代码在这里总是很重要。

 If (error.Message == "User Login Failed") { //do something. } If (error.Code == "102") { //do something. } 

你更倾向哪个?

现在还有另一种错误代码的方式:

 If (error.Code == "LOGIN_ERROR_102") // wrong password { //do something. } 

其他可能是:LOGIN_ERROR_103(例如:这是用户过期)等…

这个也是人类可读的。

我从库代码的日志(或其他行为)的观点是从来没有。

图书馆不应对其用户施加政策,并且用户可能意图发生错误。 也许这个程序是故意恳求一个特定的错误,期望它到来,来testing一些条件。 logging这个错误将是误导。

日志logging(或其他)对调用者施加策略,这是不好的。 而且,如果一个无害的错误条件(例如,主叫方会忽略或重新调用无害的错误条件),日志量可能会掩盖任何合法错误或导致健壮性问题(使用过多的IO填充光盘等等)

  1. 总是尽快处理。 越接近事件的发生,就越有可能做一些有意义的事情,或者至less弄清楚事情发生的地点和原因。 在C ++中,这不仅仅是上下文的问题,在许多情况下不可能确定。

  2. 一般来说,你应该总是停止应用程序,如果发生错误是一个真正的错误(不是像找不到一个文件,这不应该算作一个错误,但被标记为这样)。 它不会自行排除,一旦应用程序被破坏,将会导致无法debugging的错误,因为它们与发生的区域无关。

  3. 为什么不?

  4. 见1。

  5. 见1。

  6. 你需要保持简单,否则你会后悔的。 在运行时处理错误更重要的是testing以避免它们。

  7. 这就好像是说集中还是集中不好。 在某些情况下,这可能是很有意义的,但在其他情况下浪费时间。 对于某些可加载的lib /模块,这些模块可能有与数据有关的错误(垃圾进入,垃​​圾进出),这使得很多东西都有意义。 对于更一般的error handling或灾难性错误,更less。

error handling没有伴随着forms化的理论。 对于一个被认为是科学领域的话题来说,这也太“具体实现”了(公平地说,编程本身就是一门科学,这是一个很大的争论)。

尽pipe如此,这是开发者工作(也就是他/她的生活)的一个很好的部分,所以在这个话题上已经开发出了实用的方法和技术指导。

A. Alexandrescu 在C ++系统中的error handling系统中提出了一个很好的观点

我在GitHub中有一个存储库,其中提供的技术已经实现。

基本上,AA做的是实现一个类

 template<class T> class Expected { /* Implementation in the GitHub link */ }; 

这意味着被用作返回值。 这个类可以保存Ttypes的返回值或者一个exception(指针)。 这个exception可以被明确地抛出,也可以根据请求抛出,但是丰富的错误信息总是可用的。 一个示例用法就是这样

 int foo(); // .... Expected<int> ret = foo(); if (ret.valid()) { // do the work } else { // either use the info of the exception // or throw the exception (eg in an exception "friendly" codebase) } 

在构build这个error handling框架的同时,AA会带领我们完成技术和devise,这些技术和devise会产生成功或错误的error handling,以及哪些工作或什么不工作。 他还给出了“错误”和“error handling”的定义,

我的两分钱

如何判断错误是应该在本地处理还是传播到更高级别的代码? 处理您可以处理的错误。 让错误传播,你不能。

如何决定是logging一个错误,还是将错误信息显示给用户? 两个正交的问题,这不是相互排斥的。 logging错误最终是为了你,开发者。 如果您对此感兴趣,请logging下来。 如果它对用户是可操作的,则显示给用户(“错误:无networking连接!”)。

logging应该只在应用程序代码中完成的东西吗? 或者可以从库代码中进行一些日志logging。 我看不出为什么图书馆不能login。

在例外的情况下,你应该在哪里抓住他们? 在低级或更高级别的代码? 你应该抓住他们,你可以处理它们(插入你的定义的句柄)。 如果你不能处理他们,不要理他们(也许有人在堆栈可以处理他们..)。

你当然不应该把try / catch块放在你调用的每个抛出函数的周围。

类似的问题:你应该在什么时候停止传播错误并处理它? 如果您想通过所有的代码层来争取统一的error handling策略,或者尝试开发一个能够适应各种error handling策略的系统(以便能够处理来自第三方库的错误)。 首先,你可以处理它。 这一点可能不存在,你的应用程序可能会崩溃。 然后你会得到一个很好的崩溃转储,并可以更新你的error handling。

创build错误代码列表是否有意义? 还是这些老式的? 还有一点争议。 我实际上会说不是:所有错误代码的一个超级列表意味着这个列表总是最新的,所以当它不是最新的时候,你实际上可能会造成伤害。 最好让每个函数logging它可以返回的所有错误代码,而不是有一个超级列表。

这里是一个很棒的博客文章,它解释了error handling应该如何完成。 http://damienkatz.net/2006/04/error_code_vs_e.html

如何判断错误是应该在本地处理还是传播到更高级别的代码? 就像马丁·贝克特(Martin Becket)在另一个答案中所说的那样,这是一个错误是否可以在这里解决的问题。

如何决定是logging一个错误,还是将错误信息显示给用户? 如果你这么想的话,你应该不会向用户显示错误。 相反,给他们一个forms良好的信息来解释情况,而不会提供太多的技术信息。 然后logging技术信息,尤其是在处理input时出现错误。 如果你的代码不知道如何处理错误的input,那么这个必须是固定的。

logging应该只在应用程序代码中完成的东西吗? 或者可以从库代码中进行一些日志logging。 login库代码是没有用的,因为你甚至可能没有写它。 但是,应用程序可以logging与库代码的交互,甚至可以通过统计信息检测错误。

在例外的情况下,你应该在哪里抓住他们? 在低级或更高级别的代码? 见问题一。

类似的问题:在什么时候你应该停止传播一个错误并处理它? 见问题一。

如果您想通过所有的代码层来争取统一的error handling策略,或者尝试开发一个能够适应各种error handling策略的系统(以便能够处理来自第三方库的错误)。 在大多数繁重的语言中,抛出exception是一个代价昂贵的操作,所以在整个程序stream被打断的情况下使用它们。 另一方面,如果可以预测一个函数的所有结果,则将所有数据通过一个作为参数传入的引用variables,并返回一个错误代码(成功时为0,错误为1+)。

创build错误代码列表是否有意义? 还是这些老式的? 为特定函数列出错误代码,并将其logging在可能的返回值列表中。 请参阅上一个问题以及链接。

如何判断错误是应该在本地处理还是传播到更高级别的代码?

error handling应该在受影响最严重的级别上完成。 如果只影响较低级别的代码,则应该在那里处理。 如果错误影响较高级别的代码,则该错误需要在较高级别处理。 这是为了防止一些错误导致其行为不正确时,某些更高级别的代码继续其快乐的方式。 它应该知道发生了什么,只要它受到影响。

如何决定是logging一个错误,还是将错误信息显示给用户?

你应该总是logging错误。 当用户受到影响时,您应该向用户显示错误。 如果这是他们永远不会注意到的事情,也没有直接的影响(例如,在第三个terminal打开之前,两个套接字未能打开,导致用户不应报告的很短的延迟),那么不应该通知他们。

logging应该只在应用程序代码中完成的东西吗? 或者可以从库代码做一些日志logging。

太多的日志logging很less是一件坏事。 当你需要search一个库错误时,你会后悔没有logging的东西,而在追查其他错误的时候,你会因额外的日志而感到沮丧。

在例外的情况下,你应该在哪里抓住他们? 在低级或更高级别的代码?

与上面的error handling类似,应该在影响的地方捕捉错误,并且可以有效地纠正/处理错误。 这将因案例而异。

如果您想通过所有的代码层来争取统一的error handling策略,或者尝试开发一个能够适应各种error handling策略的系统(以便能够处理来自第三方库的错误)。

这在很大程度上是个人决定。 我的内部error handling与我使用任何涉及第三方库的error handling大不相同。 我对我的代码有一个总体的想法,但是第三方的东西可能会发生任何事情。

创build错误代码列表是否有意义? 还是这些老式的? 取决于你期望有多less错误抛出。 如果您花费大量时间寻找错误,您可能会喜欢您的错误代码列表,因为它们可以帮助您指出正确的方向。 然而,花在构build这些代码上的时间花在编码/缺陷修复上的时间减less了,所以它是一个混杂的包。 这主要归结为个人偏好。

第一个问题可能是你怎么处理这个错误?

你可以修复它(在这种情况下,你需要告诉用户)还是用户可以修复它?

如果没有人能解决这个问题,你将退出,有没有什么价值的报告给你(通过崩溃转储或错误代码)?

我正在改变我的devise和编码理念,以便:

  1. 如果一切顺利,如预期的那样,不会产生错误。
  2. 抛出一个exception,如果有不同的事情发生或意外发生; 让调用者处理它。
  3. 如果解决不了,就把它传播一个更高的层次。

希望通过这种技术,传播给用户的问题将非常重要。 否则程序会尝试解决它们。

我目前正在遇到返回代码丢失的问题; 或者创build新的返回码。

由Krzysztof Cwalina和Brad Abrams撰写的“框架devise指南:可重用.NET库的惯例,习语和模式”一书有一些很好的build议。 有关例外的第7章。 例如,它倾向于抛出exception来返回错误代码。

-Krip