有 Java 编程相关的问题?

你可以在下面搜索框中键入要查询的问题!

断言在Java良好实践中主动抛出断言错误?

翻阅Joshua Bloch的“有效Java-第二版”,我在第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,而是如果我们想抛出ExceptionError,应该是哪一个?积极抛出AssertionError是一种良好的做法吗?文档中说抛出表示断言失败,所以我觉得我们不应该主动抛出它。对吗


第二次编辑:明确的问题:积极抛出AssertionError是一种良好的做法,还是应该避免,即使这是可能的?(我猜读文件是后者)


共 (6) 个答案

  1. # 1 楼答案

    我宁愿

        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;
        }
    }
    
    1. 我们不应该抛出AssertionError,因为它应该为实际断言保留
    2. 除了断言和一些catch块之外,不应该有一位代码无法实际访问

    但我更喜欢https://stackoverflow.com/a/41324246/348975

  2. # 2 楼答案

    我认为无论是断言者还是非法者在这里都不是很好。断言错误不符合Matt的回答。这里的参数没有错,它们只是在错误的this操作中传递给了一个方法。因此,IAE可能也不太好。当然,这也是一个基于意见的问答

    另外,我不确定启用断言对于抛出AssertionError是强制性的,或者AssertionError表示启用了断言

  3. # 3 楼答案

    据我所知,您的方法是枚举对象的方法。在大多数情况下,当有人添加新的枚举值时,他也应该修改“apply”方法。在这种情况下,您应该抛出UnsupportedOperationException

  4. # 4 楼答案

    我同意布洛赫先生的看法——备选方案(IllegalArgumentExceptionIllegalStateExceptionUnsupportedOperationException)没有恰当地传达问题的严重性,打电话的人可能会错误地试图抓住和处理这个案件。事实上,如果这一行曾经到达,所讨论的程序将被破坏,唯一明智的做法是退出

    这里的要点是,enum有一组有限的值,因此不可能到达throw行-只有当enum的定义发生了更改而没有修复此实例方法时,才会发生这种情况。抛出RuntimeException表明调用方犯了错误,而实际上方法(和枚举)本身已经损坏。显式地引发AssertionError正确地表示该方法期望的不变量已被违反

    番石榴有一篇有用的文章,它把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是处理这些案件的推荐方法。他们的^{}类中的注释还提供了一些关于选择异常的有用见解。在AssertionError似乎太强的情况下,提出VerifyException可能是一个很好的折衷方案

    至于ErrorRuntimeException的具体问题,这其实并不重要(两者都是未检查的,因此可能会沿着调用堆栈向上移动而不会被捕获),但调用方更有可能尝试从RuntimeException恢复。在这种情况下使应用程序崩溃是一个功能,因为否则我们将继续运行(此时)明显不正确的应用程序。当然,调用方捕获和处理AssertionError(或ErrorThrowable)的可能性较小,但是调用方当然可以做任何他们想做的事情

  5. # 5 楼答案

    在我看来,在这里使用AssertionError是不正确的

    From the docs,断言错误扩展基类^{}

    An Error is a subclass of Throwable that indicates serious problems that a reasonable application should not try to catch.

    错误应该是致命的,而我希望您的程序能够处理此错误,并向用户显示有关未知操作的警告消息

    如果这里有什么问题的话,我希望抛出一个^{},并在调用堆栈中的其他地方进行处理

    Thrown to indicate that the requested operation is not supported.

    考虑不使用计算器的情况,而是使用EnNS:

    的任何代码流。

    如果开发人员要向现有枚举添加新值,我不希望使用此现有枚举的函数调用错误,因为新值不受支持

  6. # 6 楼答案

    关于错误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.

    此外,《指南》规定:

    Do not use assertions for argument checking in public methods.

    所以我认为例外是检查这种情况的正确方法

    我建议使用new UnsupportedOperationException("Operator " + name() + " is not supported.");,因为我认为它可以更好地描述问题(即开发人员添加了一个enum值,但忘记实现所需的案例)

    然而,我认为这个示例案例应该使用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之前,此代码不会编译