让我们考虑您提供以下代码的情况:
public void delete() throws IOException, SQLException { // Non-Compliant
/* ... */
}
这里的危险是您编写的要调用的代码delete()
如下所示:
try {
foo.delete()
} catch (Exception e) {
/* ... */
}
这也是不好的。并且它将被另一条标记捕获基本Exception类的规则捕获。
关键是不要编写使您想在其他地方编写错误代码的代码。
您遇到的规则是很常见的。 Checkstyle的设计规则中包含以下内容:
抛出计数
将throws语句限制为指定的计数(默认为1)。
原理:异常构成方法接口的一部分。声明一个方法以引发太多不同根源的异常会使异常处理繁重,并导致不良的编程实践,例如编写诸如catch(Exception ex)之类的代码。这种检查迫使开发人员将异常放入层次结构中,以便在最简单的情况下,调用者只需要检查一种类型的异常,但是可以在需要时专门捕获任何子类。
这准确地描述了问题,问题所在以及为什么不应该这样做。许多静态分析工具将识别并标记这是一个公认的标准。
虽然你可以根据语言的设计做到这一点,有可能是时候,这是应该做的正确的事情,这是东西,你应该看到,马上走“嗯,我为什么这样做呢?” 对于内部代码来说,每个人都受过足够严格的训练以至于永远不会接受,这是可以接受的catch (Exception e) {}
,但是我经常看到人们偷工减料,尤其是在内部情况下。
不要让使用您的班级的人想要编写错误的代码。
我应该指出,在Java SE 7和更高版本中,这的重要性降低了,因为单个catch语句可以捕获多个异常(从Oracle 捕获改进的类型检查并捕获多个异常类型和重新抛出异常)。
使用Java 6及更低版本,您将获得如下代码:
public void delete() throws IOException, SQLException {
/* ... */
}
和
try {
foo.delete()
} catch (IOException ex) {
logger.log(ex);
throw ex;
} catch (SQLException ex) {
logger.log(ex);
throw ex;
}
要么
try {
foo.delete()
} catch (Exception ex) {
logger.log(ex);
throw ex;
}
Java 6的这些选项都不是理想的。第一种方法违反了DRY。多个块一次又一次地执行相同的操作-每个异常一次。您要记录异常并重新抛出它吗?好。每个异常的代码行相同。
由于多种原因,第二种选择更糟。首先,这意味着您正在捕获所有异常。空指针在那里被捕获(并且不应该)。此外,你重新抛出Exception
这意味着该方法的签名是deleteSomething() throws Exception
这只是让一个烂摊子进一步上涨的堆栈使用你的代码的人现在被迫到catch(Exception e)
。
在Java 7,这是不是因为,因为你可以做,而不是重要的:
catch (IOException|SQLException ex) {
logger.log(ex);
throw ex;
}
此外,类型检查是否确实捕获了抛出的异常的类型:
public void rethrowException(String exceptionName)
throws IOException, SQLException {
try {
foo.delete();
} catch (Exception e) {
throw e;
}
}
类型检查器将认识到,e
可能仅是类型IOException
或SQLException
。我仍然不太热衷于使用这种样式,但是它并没有像Java 6那样导致糟糕的代码(在这种情况下,它将迫使您将方法签名作为异常扩展的超类)。
尽管进行了所有这些更改,但是许多静态分析工具(Sonar,PMD,Checkstyle)仍在执行Java 6样式指南。这不是一件坏事。我倾向于同意仍然强制执行这些警告,但是您可以根据您的团队对它们的优先级排序,将其优先级更改为主要或次要。
如果异常,应选中或取消选中...这是一个事摹[R Ë 一个牛逼 的辩论,人们可以很容易地发现无数的博客文章占用的说法两侧。但是,如果使用检查的异常,则至少在Java 6下,应该避免抛出多个类型。