为我的图书馆实施的“异常数量”是多少?


20

我一直想知道应该为软件的各个部分实现并抛出多少个不同的异常类。我的特定开发通常与C ++ / C#/ Java有关,但是我认为这是所有语言的问题。

我想了解抛出很多不同异常的情况,以及开发人员社区对好的库的期望。

我看到的权衡包括:

  • 更多的异常类可以为API用户提供非常精细的错误处理级别(容易发生用户配置或数据错误,或者找不到文件)
  • 更多的异常类允许将特定于错误的信息嵌入到异常中,而不仅仅是字符串消息或错误代码
  • 更多的异常类可能意味着更多的代码维护
  • 更多的异常类可能意味着该API对用户的访问性降低

我希望了解异常用法的场景包括:

  • 在“配置”阶段,可能包括加载文件或设置参数
  • 在“操作”类型阶段,库可能正在运行任务并正在做一些工作,也许在另一个线程中

不使用异常或较少异常(作为比较)的其他错误报告模式可能包括:

  • 更少的异常,但是嵌入了可以用作查找的错误代码
  • 直接从函数返回错误代码和标志(有时无法从线程返回)
  • 发生错误时实施事件或回调系统(避免堆栈展开)

作为开发人员,您希望看到什么?

如果有很多异常,您是否还要分别处理错误?

根据操作阶段,您是否倾向于错误处理类型?



5
“ API对用户而言不太容易使用”-可以通过具有多个异常类来处理这些异常类,这些异常类都从该API的通用基类继承而来。然后,就像在开始一样,您可以查看异常来自哪个API,而不必担心所有细节。在使用API​​完成第一个程序时,如果您需要有关该错误的更多信息,那么您将开始研究不同异常的实际含义。当然,您必须同时很好地使用所使用语言的标准异常层次结构。
史蒂夫·杰索普

相关点史蒂夫
模糊

Answers:


18

我保持简单。

库具有从std ::: runtime_error扩展的基本异常类型(来自C ++,适用于其他语言)。这个异常需要一个消息字符串,所以我们可以登录;每个抛出点都有一个唯一的消息(通常具有唯一的ID)。

就是这样

注意1:在有人捕获到异常的情况下,可以修复异常并重新启动操作。对于可能可以唯一地固定在远程位置的事物,我将添加派生异常。但这是非常非常罕见的(记住捕手不太可能接近投掷点,因此解决问题将很困难(但一切都取决于情况))。

注2:有时库是如此简单,以至于不值得给它自己的异常,而std :: runtime_error会这样做。只有将异常与std :: runtime_error区别开来的能力可以为用户提供足够的信息来对其进行处理的情况下,异常才重要。

注3:在一个类中,我通常更喜欢错误代码(但是这些错误代码永远不会在我的类的公共API中转义)。

权衡一下:

我看到的权衡包括:

更多的异常类可以为API用户提供非常精细的错误处理级别(容易发生用户配置或数据错误,或者找不到文件)

是否有更多例外确实可以使您更好地控制谷物?问题就变成了捕获的代码是否可以基于异常真正地修复错误。我确信在某些情况下,在这种情况下,您应该有另一个例外。但是,上面列出的所有例外,唯一有用的更正是生成一个大警告并停止该应用程序。

更多的异常类允许将特定于错误的信息嵌入到异常中,而不仅仅是字符串消息或错误代码

这是使用异常的重要原因。但是信息对于缓存它的人必须是有用的。他们可以使用该信息执行某些纠正措施吗?如果对象在您的库内部,并且不能用于影响任何API,则该信息将无用。您需要非常具体地说明所抛出的信息对于可以抓住它的人具有有用的价值。捕获该信息的人通常不在您的公共API范围内,因此请对您的信息进行定制,以使其可以与您的公共API中的内容一起使用。

如果他们所能做的只是记录异常,那么最好只抛出一条错误消息,而不是大量数据。由于捕获程序通常会使用数据生成错误消息。如果您生成错误消息,那么它将在所有捕获程序中保持一致,如果允许捕获程序生成错误消息,则根据谁在调用和捕获消息,您可能会得到不同报告的相同错误。

更少的异常,但是嵌入了可以用作查找的错误代码

您必须确定天气错误代码可以有意义地使用。如果可以,那么您应该有自己的例外。否则,您的用户现在需要在catch内实现switch语句(这使catch自动处理内容的整个观点变得无效)。

如果不能,那么为什么不在异常中使用错误消息(无需拆分代码和消息,这会使查找变得很麻烦)。

直接从函数返回错误代码和标志(有时无法从线程返回)

在内部返回错误代码非常有用。它允许您在那里修复错误,然后必须确保修复所有错误代码并解决它们。但是,通过公共API泄漏它们是一个坏主意。问题是程序员经常忘记检查错误状态(至少作为例外,未经检查的错误将迫使应用程序退出未处理的错误,通常会破坏您的所有数据)。

发生错误时实施事件或回调系统(避免堆栈展开)

此方法通常与其他错误处理机制结合使用(而不是替代方法)。想想您的Windows程序。用户通过选择菜单项来启动动作。这将在事件队列上生成一个动作。事件队列最终分配一个线程来处理该操作。该线程应该处理该操作,并最终返回到线程池并等待另一个任务。在这里,必须由任务执行的线程在基础处捕获异常。捕获异常的结果通常会导致为主循环生成一个事件,最终将导致向用户显示错误消息。

但是,除非面对异常可以继续进行,否则堆栈即将解散(至少对于线程而言)。


+1; Tho“否则,您的用户现在需要在catch内实现switch语句(这使catch自动处理内容的整个观点破了)。” -取消调用堆栈(如果仍然可以使用它)和强制错误处理仍然是很大的好处。但是,当您必须在中间将其捕获并重新抛出时,它肯定会降低调用堆栈展开的能力。
Merlyn Morgan-Graham

9

我通常从以下开始:

  1. 参数错误的异常类。例如,“参数不允许为空”,“参数必须为正”,等等。Java和C#为它们提供了预定义的类。在C ++中,我通常只创建一个从std :: exception派生的类。
  2. 前置错误的异常类。这些用于更复杂的测试,例如“索引必须小于大小”。
  3. 断言错误的异常类。那些用于检查一致性中途方法的状态。例如,当遍历列表以对负,零或正的元素进行计数时,最后这三个元素的总和应为大小。
  4. 库本身的一个基本异常类。一开始,只需要上这个课。仅在需要时才开始添加子类。
  5. 我不希望包装异常,但我知道在这一点上意见不同(并且与使用的语言非常相关)。如果要包装,则需要其他包装异常类

由于前3种情况的类是调试辅助工具,因此它们不应由代码处理。相反,它们应该仅由显示信息的顶级处理程序捕获,以便用户可以将其复制粘贴到开发人员(甚至更好:按“发送报告”按钮)。因此,请包含对开发人员有用的信息:文件,函数,行号以及一些清楚标识哪些检查失败的消息。

由于前三个案例对于每个项目都是相同的,因此在C ++中,我通常只是从先前的项目中复制它们。由于许多人做的完全一样,因此C#和Java的设计人员将针对这些情况的标准类添加到标准库中。[更新:]对于懒惰的程序员:一个类可能就足够了,如果幸运的话,您的标准库已经具有合适的异常类。我更喜欢添加信息,例如文件名和行号,而C ++中的默认类未提供这些信息。[结束更新]

根据库的不同,第四种情况可能只有一个类,或者可能变成一小类。我更喜欢敏捷方法从简单开始,在需要时添加子类。

有关我的第四种情况的详细论点,请参阅Loki Astari的答案。我完全同意他的详细回答。


+1; 应如何在框架中编写写异常与应如何在应用程序中编写异常之间有明确的区别。区别有点模糊,但您确实提到过(对于库的情况,请遵循Loki的规定)。
Merlyn Morgan-Graham
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.