带有不同IDE的@Nonnull - 警告不必要的空检查

时间:2014-01-27 08:17:14

标签: java intellij-idea findbugs jsr305

我正在开发人员使用不同IDE的环境中工作 - Eclipse,Netbeans和IntelliJ。我正在使用@Nonnull注释(javax.annotation.Nonnull)来表明方法永远不会返回null:

@Nonnull
public List<Bar> getBars() {
  return bars;  // this.bars is a final, initialized list
}

如果他们执行以下操作之一,我希望其他开发人员收到警告:

  1. 将方法更改为返回null而不删除@Nonnull注释
  2. 对于使用@Nonnull明确定义的方法,不必检查null:if(foo.getBars()== null){...做某事......}
  3. 支持第一种方案,例如通过IntelliJ。第二个不是;客户端不会被警告不需要检查null。

    我们无论如何都计划转向收集集合的克隆而不是集合本身,所以最好忘记@Nonnull并改为:

    public List<Bar> getBars() {
      return new ArrayList<Bar>(bars);
    }
    

    修改

    为了澄清,我不打算为此更改IDE。我想知道上面提到的IDE是否支持我上面描述的内容 - 或者,如果有充分理由说明为什么它不受支持。

    我明白了不要太依赖合同。但是,如果我用最后一段中的样式编写getBars()(返回列表的克隆),那么例如IntelliJ标记任何代码

    的警告
    if (foo.getBars() == null) { ... }
    

    如果您选择遵循此警告并删除空检查,则您似乎同样依赖于不更改的getBars()实现。但是,在这种情况下,您似乎依赖于实现细节而不是显式合同(如@Nonnull的情况)。

    编辑#2:

    我不关心执行速度,空检查确实非常快。我担心代码可读性。请考虑以下事项:

    选项A:

    if ((foo.getBars() == null || foo.getBars().size() < MAXIMUM_BARS) && 
        (baz.getFoos() == null || baz.getFoos().size() < MAXIMUM_FOOS)) { 
      // do something
    }
    

    选项B:

    if (foo.getBars().size() < MAXIMUM_BARS && 
        baz.getFoos().size() < MAXIMUM_FOOS) {
      // do something
    }
    

    我认为选项B比选项A更具可读性。由于代码的读取频率高于编写代码,因此我希望确保所有代码I(以及我们团队中的其他代码)编写尽可能可读。

3 个答案:

答案 0 :(得分:6)

我会推荐以下内容:

  • 坚持使用@Nonnull@Nullable注释,就像您已经这样做一样。
  • 定义并强制执行默认值。默认值是什么并不重要,但在整个代码库中它应该是相同的。
  • 使用FindBugs检查您的案例1和2. FindBugs有大多数IDE的插件,我看到了Eclipse,Netbeans和IntelliJ,但还有更多。 (RCN_REDUNDANT_NULLCHECK_OF_NONNULL_VALUE规则涵盖了2号案例。我对其进行了测试。我认为在阅读本页其他地方的DavidHarkness评论之后我应该提一下。)

这种方法与IDE无关,也适用于Hudson或Jenkins等外部构建环境。

答案 1 :(得分:2)

添加到已接受答案的一件事 - 如果您使用jsr305中的注释 - 请使用@CheckForNull而不是@Nullable。更多细节在这里: https://today.java.net/pub/a/today/2008/09/11/jsr-305-annotations.html 用两个词 - @Nullable的意图 - 只是说null在这里完全没问题,不需要额外的检查。 @CheckForNull表示必须检查标记字段的每个用法是否为空并且将由findbugs报告(作为冗余空值检查并将此值分配给标记为@Nonnull的值)。

P.S。对不起,这必须是评论,而不是答案,但我仍然没有足够的声誉来发表评论......

答案 2 :(得分:0)

我想,你的注释要做两件事:

  • 让程序更快
  • 使程序员必须减少工作量(因为他们不必输入空值检查)

对于第一个:Null-check几乎不消耗任何资源。如果您运行此示例:

public static void main(String[] args) {
    long ms = System.currentTimeMillis();
    for (int i=0;i<10000;i++) {
        returnsNonNull(i);
        doNothing();
    }
    System.out.println("Without nullcheck took "+(System.currentTimeMillis()-ms)+" ms to complete");

    ms = System.currentTimeMillis();
    for (int i=10000;i<20000;i++) {
        if (returnsNonNull(i)!=null) {
            doNothing();
        }
    }
    System.out.println("With nullcheck took "+(System.currentTimeMillis()-ms)+" ms to complete");
}

public static String returnsNonNull(int i) {
    return "Foo "+i+" Bar";
}
public static void doNothing() {

}

你会看到,这两个测试之间几乎没有任何区别。第二个(使用空检查)有时甚至比第一个更快,这意味着它们在资源使用方面几乎无法区分。

现在到第二点: 实际上,你正在让程序员更多地工作,而不是更少。除非你的整个项目和框架中绝对没有函数返回null,程序员现在必须每次查找函数以检查是否必须进行空检查。看到Java中的每个框架都可以在某些条件下在某些函数中返回null,而不是减少程序员的工作量。

你说当你因为你的@Nonnull-annotation进行无效检查而你需要一个警告。所以他们会做的是他们输入整个空检查然后得到警告并且必须再次删除它。明白我的意思?

如果您在代码中出错并将某些内容标记为可以返回null的@Nonnull,该怎么办?

我要做的是将它写入@return下的Javadoc。然后程序员可以决定他做了什么,这通常比强迫别人用你的风格编写代码更好。