在 Java 良好实践中积极抛出 AssertionError 吗?
Is actively throwing AssertionError in Java good practice?
浏览 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
,我会很困惑。
尽管我知道示例案例可能经常发生,但您分析了几个不同的选项,如果是 none 个选项,您应该抛出异常。
所以在这里抛出一个 AssertionException
是好习惯,还是抛出一个不同的更好?如果是这样,哪一个最适合?也许 IllegalArgumentException
?
编辑澄清:我的问题不是关于我们是否应该在这里抛出一个 Error
,而是如果我们想抛出一个 Exception
或一个 Error
,应该抛出哪个是?主动抛出 AssertionError
是好习惯吗?文档上说Thrown表示一个断言失败,所以我觉得我们不应该主动抛出它。对吗?
第二次编辑:明确的问题:主动抛出 AssertionError
是一种好习惯,还是应该避免这种情况,即使这是可能的? (我阅读文档的猜测是后者)
在我看来,AssertionError
在这里使用是不正确的。
From the docs, an AssertionError extends base class Error
An Error is a subclass of Throwable that indicates serious problems that a reasonable application should not try to catch.
错误应该是致命的,而我希望您的程序能够处理这个错误,并向用户显示有关未知操作的警告消息。
如果这里有任何问题,我希望抛出一个 UnsupportedOperationException
,并在调用堆栈的其他地方处理。
Thrown to indicate that the requested operation is not supported.
考虑这样一种情况,不是在计算器中,而是在任何使用 ENUM 的代码流中:
如果开发人员要向现有枚举添加新值,我不希望使用此现有枚举的函数调用错误,只是因为不支持新值。
我认为 AssertionError 或 IllegalAE 在这里都不是很好。断言错误并不像马特的回答中所指出的那样好。这里的参数没有错,那些只是在错误的 this
操作时传递给方法。所以 IAE 可能也不是很好。当然,这也是一个基于意见的问答。
此外,我不确定是否必须启用断言才能抛出 AssertionError 或 assertionError 表示已启用断言。
我同意 Bloch 先生的观点 - 备选方案(IllegalArgumentException
、IllegalStateException
和 UnsupportedOperationException
)没有正确传达问题的严重性,来电者可能会错误地设法抓住并处理这个案子。事实上,如果到达这一行,则有问题的程序 broken,唯一明智的做法是退出。
这里的要点是枚举有一组有限的值,因此应该不可能到达 throw
行 - 只有当枚举的定义发生变化而没有修复此实例方法时才会发生。抛出一个 RuntimeException
表明调用者犯了一个错误,而实际上方法(和枚举)本身被破坏了。明确提出 AssertionError
正确表示此方法预期的不变量已被违反。
Guava 有一篇有用的文章分解了 when to raise different types of exceptions。他们写道:
A conventional assertion is a check that should only fail if the class itself (that contains the check) is broken in some way. (In some cases this can extend to the package.) These can take various forms including postconditions, class invariants, and internal preconditions (on non-public methods).
An impossible-condition check is one that cannot possibly fail unless surrounding code is later modified, or our deepest assumptions about platform behavior are grossly violated. These should be unnecessary but are often forced because the compiler can't recognize that a statement is unreachable, or because we know something about the control flow that the compiler cannot deduce.
页面上说 AssertionError
是处理这些情况的推荐方法。他们 Verify
class 中的评论也提供了一些关于选择例外的有用见解。在 AssertionError
似乎太强的情况下,提高 VerifyException
可能是一个很好的折衷方案。
至于 Error
或 RuntimeException
的具体问题,这并不重要(两者都未选中,因此可能会在不被捕获的情况下向上移动调用堆栈),但调用者是更有可能尝试从 RuntimeException
中恢复。在这种情况下使应用程序崩溃是一个 特征 ,否则我们将继续 运行 一个(在这一点上)明显不正确的应用程序。调用者捕获并处理 AssertionError
(或 Error
或 Throwable
)的可能性肯定较小,但调用者当然可以为所欲为。
据我了解,您的方法是枚举对象的方法。在大多数情况下,当有人添加新的枚举值时,他也应该修改 "apply" 方法。在这种情况下,您应该抛出 UnsupportedOperationException。
关于错误,Java Tutorial 指出:
The second kind of exception is the error. These are exceptional conditions that are external to the application, and that the application usually cannot anticipate or recover from.
此外,Programming With Assertions 指南指出:
Do not use assertions for argument checking in public methods.
所以我认为异常是检查此类情况的正确方法。
我建议使用 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
.
后才能编译
我更愿意
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
因为它应该保留给实际断言。
- 除了断言和一些 catch 块之外,不应该有任何无法实际访问的代码。
但我更喜欢
浏览 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
,我会很困惑。
尽管我知道示例案例可能经常发生,但您分析了几个不同的选项,如果是 none 个选项,您应该抛出异常。
所以在这里抛出一个 AssertionException
是好习惯,还是抛出一个不同的更好?如果是这样,哪一个最适合?也许 IllegalArgumentException
?
编辑澄清:我的问题不是关于我们是否应该在这里抛出一个 Error
,而是如果我们想抛出一个 Exception
或一个 Error
,应该抛出哪个是?主动抛出 AssertionError
是好习惯吗?文档上说Thrown表示一个断言失败,所以我觉得我们不应该主动抛出它。对吗?
第二次编辑:明确的问题:主动抛出 AssertionError
是一种好习惯,还是应该避免这种情况,即使这是可能的? (我阅读文档的猜测是后者)
在我看来,AssertionError
在这里使用是不正确的。
From the docs, an AssertionError extends base class Error
An Error is a subclass of Throwable that indicates serious problems that a reasonable application should not try to catch.
错误应该是致命的,而我希望您的程序能够处理这个错误,并向用户显示有关未知操作的警告消息。
如果这里有任何问题,我希望抛出一个 UnsupportedOperationException
,并在调用堆栈的其他地方处理。
Thrown to indicate that the requested operation is not supported.
考虑这样一种情况,不是在计算器中,而是在任何使用 ENUM 的代码流中:
如果开发人员要向现有枚举添加新值,我不希望使用此现有枚举的函数调用错误,只是因为不支持新值。
我认为 AssertionError 或 IllegalAE 在这里都不是很好。断言错误并不像马特的回答中所指出的那样好。这里的参数没有错,那些只是在错误的 this
操作时传递给方法。所以 IAE 可能也不是很好。当然,这也是一个基于意见的问答。
此外,我不确定是否必须启用断言才能抛出 AssertionError 或 assertionError 表示已启用断言。
我同意 Bloch 先生的观点 - 备选方案(IllegalArgumentException
、IllegalStateException
和 UnsupportedOperationException
)没有正确传达问题的严重性,来电者可能会错误地设法抓住并处理这个案子。事实上,如果到达这一行,则有问题的程序 broken,唯一明智的做法是退出。
这里的要点是枚举有一组有限的值,因此应该不可能到达 throw
行 - 只有当枚举的定义发生变化而没有修复此实例方法时才会发生。抛出一个 RuntimeException
表明调用者犯了一个错误,而实际上方法(和枚举)本身被破坏了。明确提出 AssertionError
正确表示此方法预期的不变量已被违反。
Guava 有一篇有用的文章分解了 when to raise different types of exceptions。他们写道:
A conventional assertion is a check that should only fail if the class itself (that contains the check) is broken in some way. (In some cases this can extend to the package.) These can take various forms including postconditions, class invariants, and internal preconditions (on non-public methods).
An impossible-condition check is one that cannot possibly fail unless surrounding code is later modified, or our deepest assumptions about platform behavior are grossly violated. These should be unnecessary but are often forced because the compiler can't recognize that a statement is unreachable, or because we know something about the control flow that the compiler cannot deduce.
页面上说 AssertionError
是处理这些情况的推荐方法。他们 Verify
class 中的评论也提供了一些关于选择例外的有用见解。在 AssertionError
似乎太强的情况下,提高 VerifyException
可能是一个很好的折衷方案。
至于 Error
或 RuntimeException
的具体问题,这并不重要(两者都未选中,因此可能会在不被捕获的情况下向上移动调用堆栈),但调用者是更有可能尝试从 RuntimeException
中恢复。在这种情况下使应用程序崩溃是一个 特征 ,否则我们将继续 运行 一个(在这一点上)明显不正确的应用程序。调用者捕获并处理 AssertionError
(或 Error
或 Throwable
)的可能性肯定较小,但调用者当然可以为所欲为。
据我了解,您的方法是枚举对象的方法。在大多数情况下,当有人添加新的枚举值时,他也应该修改 "apply" 方法。在这种情况下,您应该抛出 UnsupportedOperationException。
关于错误,Java Tutorial 指出:
The second kind of exception is the error. These are exceptional conditions that are external to the application, and that the application usually cannot anticipate or recover from.
此外,Programming With Assertions 指南指出:
Do not use assertions for argument checking in public methods.
所以我认为异常是检查此类情况的正确方法。
我建议使用 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
.
我更愿意
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
因为它应该保留给实际断言。 - 除了断言和一些 catch 块之外,不应该有任何无法实际访问的代码。
但我更喜欢