我目前正在进行代码审查,并且我注意到的一件事是,异常消息似乎只是在重申发生异常的地方的异常数量。例如,

throw new Exception("BulletListControl: CreateChildControls failed.");


该消息中的所有三个项目我都可以从异常中解决。我从堆栈跟踪中了解了类和方法,并且知道它失败了(因为我有一个异常)。首先,出于一般原因(例如PropertyNotFoundException-why),我创建了一个异常类(如果尚不存在),然后当我将其抛出时,该消息指示出了什么问题(例如,“在Node上找不到属性'IDontExist' 1234“-什么)。在StackTrace中。时间可能会在日志中显示(如果适用)。开发人员如何解决(和修复)

您还有其他引发异常的技巧吗?特别是在创建新类型和异常消息方面。

评论

这些是用于日志文件还是呈现给用户?

仅用于调试。它们可能最终以日志记录。它们不会显示给用户。我不喜欢向用户显示异常消息。

#1 楼

我将把答案更多地指向异常之后发生的事情:这有什么好处,软件应该如何工作,用户应该如何处理异常?我在职业生涯初期遇到的一项很棒的技术是始终按三个部分报告问题和错误:上下文,问题和解决方案。使用此准则可以极大地改变错误处理方式,并使软件极大地方便操作员使用。

这里有一些示例。

Context: Saving connection pooling configuration changes to disk.
Problem: Write permission denied on file '/xxx/yyy'.
Solution: Grant write permission to the file.


在在这种情况下,操作员确切知道该怎么办以及必须影响哪个文件。他们还知道连接池更改没有发生,应该重复。

Context: Sending email to 'abc@xyz.com' regarding 'Blah'.
Problem: SMTP connection refused by server 'mail.xyz.com'.
Solution: Contact the mail server administrator to report a service problem.  The email will be sent later. You may want to tell 'abc@xyz.com' about this problem.


我写服务器端系统,而我的操作员通常都是精通技术的第一线支持。对于听众不同但包含相同信息的台式机软件,我会写不同的消息。

如果使用这种技术,将会发生很多奇妙的事情。软件开发人员通常最擅长了解如何解决他们自己的代码中的问题,因此,在编写代码时以这种方式编码解决方案对于处于劣势的最终用户来说是巨大的收益,因为他们经常缺少有关以下方面的信息该软件到底在做什么。曾经读过Oracle错误消息的任何人都会知道我的意思。

想到的第二件事是,当您发现自己试图描述例外情况中的解决方案时,您正在编写“检查X,如果A则B否则C”。这是一个非常明显的迹象,表明在错误的位置检查了您的异常。您程序员有能力比较代码中的内容,因此“如果”语句应在代码中运行,为什么要让用户参与一些可以自动化的事情?很有可能是代码深处的原因,有人做了懒惰的事情,并从任意数量的方法中抛出了IOException,并在无法充分描述出什么问题,具体上下文是什么的调用代码块中捕获了所有这些方法的潜在错误。以及如何解决。这鼓励您编写更精细的纹理错误,并在代码中的正确位置捕获并处理它们,以便您可以正确地阐明操作员应采取的步骤。

在一家公司,我们拥有一流的操作员,他们真正了解该软件,并保留了自己的“操作手册”,从而丰富了我们的错误报告和建议的解决方案。为了认识到这一点,该软件开始在例外情况下包括指向运行手册的Wiki链接,以便提供基本的说明以及操作员随着时间推移可以进行更高级的讨论和观察的链接。有了尝试这项技术的准则,那么在创建自己的代码时应该在代码中命名异常就变得更加明显。 NonRecoverableConfigurationReadFailedException成为您将要更详细地描述给操作员的简写。我喜欢冗长,我认为这对于下一个接触我的代码以进行解释的开发人员来说更容易。

评论


+1这是一个很好的系统。哪个更重要:确保信息能够传达或使用简短的单词?

– Michael K
2010-12-23 15:04

我喜欢+1的解决方案,包括,上下文,问题,解决方案

– WebDev
2010-12-23 17:03

这项技术非常有用。我肯定会利用它。

–孩子钻石
15年3月30日在9:11

上下文是不必要的数据。它已经存在于堆栈跟踪中。有解决方案是可取的,但并非总是可行/有用的。大多数问题都是温和地停止应用程序或忽略挂起的操作,然后返回到应用程序主执行循环,以希望下次您成功...异常类的名称应使解决方案变得显而易见,对于FileNotFound或ConnectException,您知道该怎么做))

– gavenkoa
17-2-28在11:40



@ThomasFlinkow在您的示例跟踪中,堆栈跟踪中将包含init(),execute()和cleanup()。借助库中良好的命名架构和简洁/易于理解的API,您无需字符串解释。并且快速失败,不要在整个系统中都处于损坏状态。具有唯一ID的跟踪和日志记录可以解释应用程序的流程/状态。

– gavenkoa
18年6月18日在14:09

#2 楼

在这个新近提出的问题中,我指出,异常根本不应该包含任何消息。我认为,他们这样做的事实是一个巨大的误解。我要提出的是,该异常的“消息”是该异常的(完全限定的)类名称。


异常应在其自己的成员变量中包含尽可能多的有关所发生事件的详细信息;例如,IndexOutOfRangeException应该包含被发现无效的索引值,以及在引发异常时有效的上限值和下限值。这样,通过反射,您可以自动构造一条消息,内容如下:IndexOutOfRangeException: index = -1; min=0; max=5和此消息以及堆栈跟踪信息,应该是解决问题所需的所有客观信息。将其格式化为漂亮的消息(例如“索引-1不在0到5之间”)不会添加任何值。

在您的特定示例中,NodePropertyNotFoundException类将包含不属于该属性的名称。找到,并且引用了不包含该属性的节点。这很重要:它不应包含节点的名称;它应该包含对实际节点的引用。在您的特定情况下,这可能不是必需的,但这是一个原则问题,是一种首选的思维方式:构造异常时,主要的考虑是它必须可由可能捕获到它的代码使用。人类的可用性是一个重要的问题,但仅是次要问题。

这可以解决您在职业生涯中某个时候可能遇到的令人沮丧的情况,在这种情况下,您可能会捕获到一个异常,该异常包含有关消息文本内发生的重要信息,但未包含在其成员变量内,因此您为了找出发生了什么,不得不对文本进行字符串解析,希望消息文本在基础层的将来版本中保持不变,并祈祷当您的程序是在其他国家/地区运行。

当然,由于异常的类名是异常的消息,(并且异常的成员变量是特定的详细信息),这意味着您需要大量还有很多不同的异常来传达所有不同的消息,这很好。陈述并继续编写我们的代码,而不必中断我们正在做的事情,创建一个新的异常类,以便我们可以将其扔在那里。对于这些情况,我有一个throw类,实际上该类确实接受字符串消息作为构造时参数,但是此异常类的构造函数上有一个很大的巨大的明亮紫色GenericException注释,指出该类的每个实例在发布软件系统之前,最好在提交代码之前,必须用一些更特殊的异常类的实例化替换。

评论


如果您使用的语言没有GC(例如C ++),则在将对任意数据的引用放入发送到堆栈的异常中时应格外小心。很有可能,在捕获异常之前,您引用的所有内容都已被销毁。

–塞巴斯蒂安·雷德尔(Sebastian Redl)
2015年4月14日15:13



@SebastianRedl是的。如果节点对象由using-disposable(在C#中)或try-with-resources(在Java中)子句保护,则这同样适用于C#和Java:将存储/关闭异常的对象,使其成为对象。为了在处理异常的地方从中获取有用信息而非法访问它。我想在这种情况下,应该在异常中存储某种对象的摘要,而不是对象本身。我想不出一种万无一失的方法来针对所有情况进行通用处理。

–迈克·纳基斯(Mike Nakis)
15年4月14日在18:42

#3 楼

通常,异常应该通过提供有用的信息(期望值,实际值,可能的原因/解决方案等)来帮助开发人员查明原因。内置类型很有意义。特定类型使其他开发人员可以捕获特定异常并进行处理。如果开发人员知道如何处理您的异常,但类型为Exception,那么他将无法正确处理它。

评论


+1-期望值与实际值非常有用。在问题给出的示例中,您不应该简单地说一个方法失败,而应该说它为什么失败(基本上是失败的确切命令以及导致失败的情况。)

– Felix Dombek
2010-12-23 13:45

#4 楼

在.NET中,永远不要throw new Exception("...")(就像问题的作者所显示的那样)。 Exception是根Exception类型,它不应该直接抛出。而是抛出一种派生的.NET异常类型,或者创建自己的从Exception(或另一种异常类型)派生的自定义异常。为什么不抛出Exception?因为抛出Exception并不能描述您的异常,并且会迫使您的调用代码编写类似catch(Exception ex) { ... }的代码,这通常不是一件好事! :-)。

#5 楼

您想要寻找的“添加”到异常的东西是那些不在异常或堆栈跟踪中的数据元素。是否这些是“消息”的一部分还是在登录时是否需要附加一个有趣的问题。 “为什么”可能会涉及更多(应该是,一个希望),而不只是一两行凝视并说“道!当在生产代码中记录错误时,情况更是如此-我经常被不良数据所困扰,这些不良数据被发现进入了我们的测试系统中不存在的实时系统。只需知道导致错误的数据库中记录的ID是什么,就可以节省大量时间。

所以...对于.NET,要么列出,要么列出。 ,添加到已记录的异常数据收集中(cf @Plip!):序列化,有时单个参数可能会令人惊讶地复杂)
ADO.NET或Linq返回到SQL或类似的附加数据(这也会变得有些有趣!)。 。

当然,有些事情直到您在初始错误报告/日志中没有它们时,您才知道需要。直到发现需要它们时,您才会意识到某些事情。

#6 楼

什么是例外?

(1)告诉用户出了什么问题?

这应该是万不得已的方法,因为您的代码应该进行干预,并向他们展示比“异常”更“有趣”的东西。

“错误”消息应清楚,简洁地指出出了什么问题以及用户可以采取哪些措施来从错误状态中恢复。

例如“请不要再按此按钮”。

(2)告诉开发人员何时出错?

这是您登录到文件中以便进行后续分析的那种东西。该消息应再次指出出了什么问题。

(3)告诉Exception处理程序(即代码)出了什么问题?

异常的类型将决定查看哪个异常处理程序,并且在异常对象上定义的属性将允许处理程序对其进行处理。

异常消息完全无关。

#7 楼

如果可以,请不要创建新类型。它们可能引起额外的混乱,复杂性并导致需要维护的代码更多。它们可能使您的代码不得不扩展。设计异常层次结构需要进行大量的测试。这不是事后的想法。通常最好使用内置的语言异常层次结构。

异常消息的内容取决于消息的接收者-因此您必须置自己于那个人的内。 br />支持工程师将需要能够尽快识别错误的来源。包括简短的描述性字符串以及任何有助于解决问题的数据。始终包括堆栈跟踪(如果可以)-这将是唯一的真实信息源。

向系统的普通用户显示错误取决于错误的类型:如果用户可以解决问题(例如,通过提供不同的输入),则需要简洁的描述性消息。如果用户无法解决问题,则最好声明已发生错误并记录/发送错误以提供支持(使用上述准则)。

也-不要抛出大的“ HALT ERROR!”图标。这是一个错误-它不是世界末日。

所以总而言之:考虑一下系统的参与者和用例。将自己放在这些用户的鞋子上。有所帮助。对人好点。在系统设计中先考虑一下这一点。从用户的角度来看,这些异常情况以及系统如何处理它们,与系统中的正常情况一样重要。

评论


我不同意。当语言API不能满足您的确切需求时,有很多充分的理由来实现您自己的异常。一个原因是,在一种方法中,有很多事情可能会失败,您可以针对不同类型的异常编写不同的catch子句,从而可以对确切的问题做出反应。另一个是,您可以将代表不同抽象层的多层异常分开,其中可以将异常所属的确切层按其类型编码。仅使用“ Exception”或“ IllegalStateException”和消息字符串对那里没有多大帮助。

– Felix Dombek
2010-12-23 13:51

我也不同意。异常类型对将使用它的调用代码有意义。例如,如果我正在调用框架,并且这在内部导致FileDoesNotExistException,那么作为框架的调用者,这对我来说可能没有任何意义。相反,可能最好创建一个自定义异常并将传入的异常作为内部异常传入。

– bytedev
16-10-10在15:51