5

我在一个开发人员使用不同 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) { ... do something ... }

例如,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 更具可读性。由于代码的阅读频率高于编写频率,因此我想确保我(以及我们团队中的其他人)编写的所有代码都尽可能具有可读性。

4

2 回答 2

6

我会推荐以下内容:

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

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

于 2014-01-27T10:03:41.180 回答
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,否则程序员现在必须每次都查找该函数以检查他是否必须进行 null 检查。看到 Java 中的每个框架在某些情况下都可以在某些函数中返回 null,您并没有减少程序员的工作量。

你说你希望在他们进行空检查时出现警告,因为你的@Nonnull-annotation 是不必要的。所以他们要做的就是输入整个空检查,然后收到警告,必须再次删除它。明白了吗?

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

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

于 2014-01-27T08:47:21.923 回答