代码之家  ›  专栏  ›  技术社区  ›  ACHC

为空检查断言

  •  7
  • ACHC  · 技术社区  · 7 年前

    人们似乎普遍认为,assert语句应该保留用于测试,并在生产中禁用,因为错误应该在那时解决,启用断言会影响性能。不过,对于if语句的空检查也可以这样说。为什么这个代码被认为适合生产

    if(x != null) {
        x.setId(idx);
        if (y != null) {
            if (y.id == x.id) {
                x.doSth();
            }
        } else {
            //handle error
        }
    } else {
        //handle error
    }
    

    但这密码不是吗?(假设已启用断言)

    try {
        assert(x != null); 
        x.setId(idx);
        assert(y != null);
        if (y.id == x.id) {
            x.doSth();
        }
    } catch (AssertionError e) {
        //handle error
    }
    

    我理解在预期变量可能未初始化时使用if语句。然而,当它用于防御性编码时,assert看起来更优雅、更可读。

    我还用以下方法测试了每种方法的性能:

    public class AssertTest {
    
        static final int LOOPS = 10000000;
    
        public static void main(String[] args) {
    
                String testStr = "";
    
                long startNotEqualsTest = System.currentTimeMillis();
                for (int i = 0; i < LOOPS; i++) {
                    if (testStr != null) {
                        testStr = System.currentTimeMillis() + "";
                    } else {
                        throw new RuntimeException("We're doomed");
                    }
                }
                long notEqualsTestDuration = System.currentTimeMillis() - startNotEqualsTest;
    
                testStr = "";
    
                long startAssertTest = System.currentTimeMillis();      
                for (int i = 0; i < LOOPS; i++) {
                    try {
                        assert(testStr != null);
                        testStr = System.currentTimeMillis() + "";
                    } catch (AssertionError e) {
                        throw new RuntimeException("We're doomed");
                    }
                }
                long assertTestDuration = System.currentTimeMillis() - startAssertTest;
    
                System.out.println("Duration with if : " + notEqualsTestDuration + "ms");
                System.out.println("Duration with assert : " + assertTestDuration + "ms");  
        }
    }
    

    两种方法的时间安排大致相同:

    Duration with if : 1234ms
    Duration with assert : 1313ms
    

    禁用断言几乎没有什么区别:

    Duration with if : 1391ms
    Duration with assert : 1375ms
    

    我是否遗漏了任何令人信服的理由,为什么空检查条件比断言更可取?

    3 回复  |  直到 7 年前
        1
  •  5
  •   Jan B. HDR    7 年前

    问问你自己 怎样 你会处理的 null 案子?如果一个物体是 无效的 那不应该。在大多数情况下,根本不处理它是绝对可以的。让它产生一个 NullPointerException 在执行过程中的某个时候。无论如何,这很可能是一个编程错误,所以被 ExceptionHandler 最终写入日志。

    当然,有些情况下你需要做出反应 无效的 物体。 if-else 是为这种情况而做的。这很简单,自我解释,每个程序员都知道这个结构,所以为什么不使用它。 assert 无论如何,在生产中是不受欢迎的。见 this post 例如,就这样。而且,对我来说,try/catch块很麻烦。

    还有其他的选择。例如,davidw建议使用注释,这是非常好的,只要您确保这些注释被解释(例如,在迁移代码时可能会出现问题)。

    另一种方法是 验证器 上课。例如, 阿帕奇公地 库中有一个validate类,该类检查某些条件,如果条件未满,则会引发相应的异常。当然,你可以自己写 验证器 也会抛出您的自定义异常。最后你会得到一个简短的一行

    Validate.notNull(myObj) 
    Validate.hasEmailCorrectSyntax("foo@bar.com");
    

    还要看看Java的 Object.requireNotNull

        2
  •  2
  •   Makoto    7 年前

    使用 assert 必须假定 -ea 标志已启用,但在许多情况下…它不是。 断言 将完成与空检查相同的任务,其明显的副作用是,如果没有以特定方式配置环境,代码将无法按预期工作。

    因此,回避是有道理的。 断言 并在必要时利用空检查。更简洁,如果 可以 ,最好将所有可能为空的内容包装在 Optional 使用实例并对其进行操作 ifPresent (或) orElseThrow 因为如果这些值为空,您似乎希望指示错误)。

        3
  •  0
  •   DavidW    7 年前

    以我的经验,如果 x y 事实上,如果为空,那么(而不是第一个示例)将被编码的错误是:

    void someFunction(AnObject x, AnObject y) {
        if (x == null || y == null) {
            throw new IllegalStateException("Contract violation: x and y must be non-null");
        }
        // rest of method can be run without null checks.
    }
    

    正如我在下面的一些讨论中所指出的,如果您预先知道某些参数值正在中断(比如 null NaN 在浮点/双精度运算中),那么快速失败就很有意义。它比 无效的 值放入 Map 例如,npe被抛出到另一个线程上。

    (注意:已编辑以删除使用批注的有争议的建议。)