通过Joshua Bloch的“Effective Java - Second Edition”,我偶然发现了第152页的以下代码:
double apply(double x, double y) {
switch(this) {
case PLUS: return x + y;
case MINUS: return x - y;
case TIMES: return x * y;
case DIVIDE: return x / y;
}
throw new AssertionError("Unknown op: " + this);
}
现在令我困惑的是,AssertionError
被积极抛出。这被认为是好习惯吗?
根据我的理解,断言用于不干扰代码,以便在没有启用断言的情况下启动java编程并且因此不执行断言语句时,行为不会改变。
如果我在没有启用断言的情况下运行程序时得到AssertionException
,我会感到很困惑。
即使我理解示例案例可能经常发生,你分析了几个不同的选项,如果它们都不是,你应该抛出异常。
那么在这里抛出一个AssertionException
是好的做法,还是抛出一个不同的更好?如果是这样,哪一个最适合?也许IllegalArgumentException
?
编辑以澄清:我的问题不在于我们是否应该在此处抛出Error
,但如果我们想要抛出Exception
或Error
,那么它应该是哪一个?积极抛出AssertionError
是一种好习惯吗?文档说抛出表示断言失败,所以我觉得我们不应该主动抛出它。这是对的吗?
第二次编辑:明确的问题:积极抛出AssertionError
是好的做法,还是应该避免,即使有可能? (我的猜测是阅读文档是后者)
答案 0 :(得分:19)
我同意布洛赫先生的意见 - 替代方案(IllegalArgumentException
,IllegalStateException
和UnsupportedOperationException
)无法正确传达问题的严重性,并且来电者可能会错误地尝试抓住并处理这个案子。事实上,如果到达这条线,那么有问题的程序是破解,唯一明智的做法就是退出。
这里的要点是枚举有一组有限的值,因此它不可能到达throw
行 - 只有在枚举的定义发生变化而不修复此实例方法时才会出现。抛出RuntimeException
表示调用者犯了一个错误,实际上方法(和枚举)本身就被破坏了。明确提出AssertionError
正确表示此方法预期违反的不变量。
番石榴有一篇有用的文章,分解when to raise different types of exceptions。他们写道:
传统的断言是一种只有在类本身(包含检查)以某种方式被破坏时才会失败的检查。 (在某些情况下,这可以扩展到包。)这些可以采取各种形式,包括后置条件,类不变量和内部前置条件(在非公共方法上)。
不可能条件检查是一个不可能失败的问题,除非稍后修改周围的代码,或者严重违反我们对平台行为的最深层假设。这些应该是不必要的,但通常是强制的,因为编译器无法识别语句无法访问,或者因为我们对编译器无法推断的控制流有所了解。
该页面显示AssertionError
是处理这些案例的推荐方法。他们Verify
课程中的评论也提供了一些有关选择例外的有用见解。如果AssertionError
似乎过于强大,那么VerifyException
可能是一个很好的妥协。
至于Error
或RuntimeException
的具体问题,它并不重要(两者都未经检查,因此可能会在未被捕获的情况下向上移动调用堆栈),但调用者更有可能尝试从RuntimeException
恢复。在这种情况下崩溃应用程序是一个功能,因为否则我们将继续运行一个(在这一点上)明显不正确的应用程序。调用者捕获和处理AssertionError
(或Error
或Throwable
)的可能性当然不大,但当然来电者可以随心所欲。
答案 1 :(得分:5)
在我看来,AssertionError
在这里使用会不正确。
From the docs, AssertionError 扩展基类Error
错误是Throwable的子类,表示合理的应用程序不应该试图捕获的严重问题。
错误应该是致命的,而我希望你的程序能够处理这个错误,并向用户显示有关未知操作的警告消息。
如果有任何内容,我希望抛出UnsupportedOperationException
,并在调用堆栈的其他地方处理。
抛出以指示不支持所请求的操作。
考虑这样的情况:不是在计算器中,而是使用ENUM的任何代码流:
如果开发人员要向现有枚举添加新值,我不希望使用此现有枚举的函数调用错误,只是因为不支持新值。
答案 2 :(得分:4)
关于错误,Java Tutorial州:
第二种例外是错误。这些是对应用程序外部的特殊条件,并且应用程序通常无法预期或从中恢复。
此外,Programming With Assertions指南指出:
不要在公共方法中使用断言进行参数检查。
所以我认为异常是检查此类案件的正确方法。
我建议使用new UnsupportedOperationException("Operator " + name() + " is not supported.");
,因为我认为它更好地描述了问题(即开发人员添加了枚举值但忘记实现所需的案例)。
但是我认为这个示例案例应该使用AbstractEnum
设计模式而不是开关:
PLUS {
double apply(double x, double y) {
return x + y;
}
},
MINUS {
double apply(double x, double y) {
return x - y;
}
},
TIMES {
double apply(double x, double y) {
return x * y;
}
},
DIVIDE {
double apply(double x, double y) {
return x / y;
}
};
abstract double apply(double x, double y);
它不易出错,因为在每个案例实现apply
之前,此代码都不会编译。
答案 3 :(得分:2)
我更喜欢
double apply(double x, double y) {
switch(this) {
case PLUS: return x + y;
case MINUS: return x - y;
case TIMES: return x * y;
default: assert this==DIVIDE: return x / y;
}
}
AssertionError
,因为它应该保留给实际的断言。答案 4 :(得分:1)
我认为AssertionError或IllegalAE在这里都不是很好。如Matt的答案所示,断言错误并不好。这里的参数没有错,那些只是传递给错误this
操作的方法。所以IAE可能也不好。当然,这也是基于意见的问答。
此外,我不确定启用断言是否必须抛出AssertionError或断言错误意味着断言已启用。
答案 5 :(得分:0)
据我所知,你的方法是一个枚举对象的方法。在大多数情况下,当有人添加新的枚举值时,他应该修改" apply"方法也是如此。在这种情况下,您应该抛出UnsupportedOperationException。