我们是针对特定问题还是一般性例外编写例外?

时间:2020-03-05 18:41:52  来源:igfitidea点击:

我有一些将用户ID赋予实用程序的代码,该实用程序随后将电子邮件发送给该用户。

emailUtil.sendEmail(userId, "foo");

public void sendEmail(String userId, String message) throws MailException {
    /* ... logic that could throw a MailException */
}

可能由于多种原因引发MailException,电子邮件地址问题,邮件模板问题等。

我的问题是这样的:我们是为这些异常中的每一个创建一个新的异常类型,然后分别对其进行处理?还是创建一个MailException,然后将某些内容存储在该异常中(计算机可读的内容,而不是描述文字),从而允许我们根据实际发生的事情做不同的事情。

编辑:为澄清起见,异常不适用于日志,而不适用于日志,这与代码对它们的反应方式有关。为了继续处理邮件示例,假设我们发送邮件时,它可能会因为我们没有电子邮件地址而失败,或者可能因为我们没有有效的电子邮件地址而失败,或者可能失败。

我的代码将希望对每个问题做出不同的反应(主要是通过更改返回给客户端的消息,但也要更改实际逻辑)。

最好为这些问题中的每一个都具有一个异常实现,或者为一个内部具有某种含义(例如枚举)的总括性异常,以使代码区分其类型是什么。

解决方案

回答

这取决于应用程序在做什么。在以下情况下,我们可能想抛出个别异常

  • 该应用程序具有高可用性
  • 发送电子邮件尤为重要
  • 该应用程序的范围很小,并且发送电子邮件占了很大一部分
  • 该应用程序将部署到一个远程站点,我们将仅获得调试日志
  • 我们可以从封装在mailException中的某些异常子集中恢复,但不能从其他异常中恢复

在大多数情况下,我会说只记录异常的文本,而不会浪费时间来细化本来就很细微的异常。

回答

在我的代码中,我发现MOST异常遍及整个UI层,在那里我的异常处理程序捕获了它们,这些异常处理程序仅向用户显示一条消息(并写入日志)。毕竟,这是一个意外的例外。

有时,我确实想捕获特定的异常(就像我们似乎想做的那样)。但是,我们可能会发现这种情况很少见,这表明使用异常来控制逻辑是无效的(缓慢的),并且经常被皱眉。

因此,以示例为例,如果我们想在未配置电子邮件服务器时运行某些特殊的逻辑,则可能需要向emailUtil对象添加一个方法,例如:

公共布尔isEmailConfigured()

...先调用它,而不是查找特定的异常。

当确实发生异常时,这实际上意味着情况是完全出乎意料的,并且代码无法处理它-因此,我们能做的最好的就是将其报告给用户(或者将其写入日志或者重新启动)

至于具有异常层次结构与其中的带有错误代码的异常,我通常会执行后者。如果只需要定义一个新的错误常量而不是一个全新的类,则添加新的异常会更容易。但是,只要我们在整个项目中保持一致就没关系。

回答

我倾向于不使用异常,而是倾向于从可能存在执行问题的方法中返回状态对象的列表。状态对象包含严重性枚举(信息,警告,错误等),状态对象名称(如"电子邮件地址")和用户可读的消息(如"格式错误的电子邮件地址")

然后,调用代码将确定要过滤到UI的内容以及要处理自身的代码。

就我个人而言,我认为异常严格用于无法实现常规代码解决方案的情况。性能冲击和处理限制对我来说有点过分。

使用状态对象列表的另一个原因是,识别多个错误(例如在验证期间)更加容易。毕竟,我们只能抛出一个必须继续处理的异常。

假设有用户提交了一封电子邮件,该电子邮件的目标地址格式不正确,并且包含我们要阻止的语言。我们是否抛出格式错误的电子邮件异常,然后在他们解决并重新提交之后,抛出了错误的语言异常?从用户体验的角度来看,一次处理所有这些都是更好的方法。

更新:合并答案

@Jonathan:我的意思是我可以评估操作,在这种情况下,发送电子邮件,并发送回多个失败原因。例如,"错误的电子邮件地址","空白邮件标题"等。

除了一个例外,我们仅限于解决一个问题,然后要求用户重新提交,他们在第二点发现第二个问题。这确实是不良的UI设计。

重新发明轮子..可能。但是,大多数应用程序应分析整个事务,以便向用户提供最佳信息。想象一下,如果编译器在第一个错误时停止了运行。然后,我们修复错误并再次点击编译,只是因为另一个错误而使它再次停止。屁股好痛。对我来说,这正是抛出异常的问题,因此也是我说使用其他机制的原因。

回答

@ Chris.Lively

我们知道我们可以在例外情况甚至"状态码"中传递消息。我们在这里重新发明轮子。

回答

尽管不是真正的OO方法,但我倾向于使用较少的Exception类型。取而代之的是,我对自定义的Exceptions进行了枚举,该枚举对Exc​​eption进行了分类。大多数时候,我有一个自定义的基础Exception,它保留着几个成员,这些成员可以在派生Exception类型中重写或者自定义。

几个月前,我在博客上发表了关于如何使异常国际化的想法。它包括上面提到的一些想法。

回答

我会过去

throw new exception("WhatCausedIt")

如果要处理异常,可以传递代码而不是" WhatCausedIt",然后使用switch语句对不同的答案做出反应。

回答

虽然我们可以区分代码执行,但看起来异常是由" catch exceptionType层次结构模式"还是" if(...)else ...异常代码模式"完成的,这无关紧要

但是,如果我们正在开发将被其他人使用的软件,例如库,我认为创建自己的异常类型很有用,以提醒其他人软件可以抛出除正常之外的其他异常,并且它们更好地捕获并捕获了异常。解决它们。

当我使用一个库并且它们的方法只是启动一个"异常"时,我总是想知道:是什么导致了这个异常?我的程序必须如何反应?不是javadoc或者没有解释异常。使用WellChossenExceptionTypeName可以避免过多的开销巫婆

回答

这取决于捕获异常的代码是否需要在异常之间进行区分,还是取决于我们是否仅使用异常来故障转移到错误页面。如果需要在调用堆栈中区分NullReference异常和自定义MailException,请花点时间编写它。但是大多数时候,程序员只是将异常用作捕获所有异常,从而在网页上引发错误。在这种情况下,我们只是在浪费精力编写新的异常。

回答

我认为以上各项的结合将为我们带来最佳效果。

我们可以根据问题抛出不同的异常。例如缺少电子邮件地址= ArgumentException。

但是,然后在UI层中,我们可以检查异常类型以及消息(如果需要),然后向用户显示适当的消息。我个人倾向于仅在引发某种类型的异常(在我的应用程序中为UserException)时向用户显示参考消息。当然,我们应该尽可能多地清理和验证用户输入,以确保在真正不太可能的情况下生成任何异常,而不是作为格式不正确的电子邮件的过滤器,可以使用正则表达式轻松对其进行检查。

我也不会担心从用户输入中捕获异常对性能的影响。我们唯一一次会从异常中看到性能问题的时间是当异常被抛出并陷入循环或者类似情况时。

回答

我通常从一个通用异常开始,并根据需要对其进行子类化。如果需要,我总是可以捕获一般异常(以及所有子类异常),也可以捕获特定异常。

Java-API的一个示例是IOException,它具有子类,例如FileNotFoundException或者EOFException(以及更多)。

这样,我们就可以同时获得两者的优势,而无需像以下这样的抛出子句:

throws SpecificException1, SpecificException2, SpecificException3 ...

一般

throws GeneralException

足够。但是,如果我们希望对特殊情况有特殊的反应,则可以随时捕获特定的异常。

回答

我发现,如果需要让CODE根据返回的异常来决定要做什么,请创建一个命名良好的异常,该异常将普通的基本类型作为子类。传递的消息应被视为"仅人眼可见",并且太脆弱而无法做出决定。让编译器完成工作!

如果我们需要通过不知道检查异常的机制将此消息传递到更高的层,则可以将其包装在RuntimeException(MailDomainException)的合适的命名子类中,该子类可能会引起问题,并且可以解决原始原因。