Java – 在不允许使用@NotNull或@Nullable注释的情况下,在编译时检测NPE的最佳方法

我曾经使用大量的@NotNull/@Nullable注释来使IDE在编译时帮助我找出潜在的NPE 。 但是,我的新团队不允许使用@NotNull/@Nullable注释,也不允许使用那些允许的自定义注释。 因此,我比以前更有可能写出NPE导致的错误。

我尝试了几个解决方案:

  1. 在Java 8中使用Optional<T> 。但是,这不是每个案件都很好。 通常不建议使用Optional<T>作为字段或参数的类型。 Optional<T>实例本身可能为null也是非常令人沮丧的。 另外,调用ifPresent(obj->...)时,很难在lambda表达式中操作控制流(在Java 9中更容易)。 而使用太多的Optional<T>使代码有点冗长。 (更新:不幸的是, Optional<T>也被禁止使用)
  2. 使IDE将每个未注释的实例视为@Nullable 。 这个解决方案确实帮我找到了一些潜在的错误,但是IDE会建议我检查几乎所有的方法调用,这真的很烦人,因为许多方法都是故意不返回null
  3. 检查每个方法调用。 这是一个可行的解决方案,但是它具有严重的影响,即通过方法调用可能导致null 。 在这种情况下,一个方法的每个参数都可能为null ,并且当一个参数被检查为无效时,该方法通常会连续返回null 。 最后,每个方法都被“感染”,并有可能收到null参数并返回null
  4. 调用Objects.requireNonNull()来防止上述问题。 它稍微减少了无处不在的检查的痛苦。 但是,它不能保证当不允许null的情况下,调用者不会向null传递null 。 一旦null传递,抛出的运行时NPE更有可能破坏你的应用程序。
  5. 切换到kotlin。 当然,这是不允许的:)

是否有其他建议在编译时检测NPE(并保存我的工作)? 我认为这个问题的解决方案可以被广泛使用,不仅仅是帮助自己,因为并不是所有的团队都允许使用@NotNull/@Nullable注解和Optional<T>

不是一个确定的答案,而是一些探索的方法:

  • 像findBug , PMD , Coverity这样的工具在这个练习中是相当不错的。
  • 检查你不能在团队中使用注释的原因。 也许你可以让你的团队改变主意? (也可能有一个很好的理由。)
  • Eclipse IDE在这个练习中是非常好的。 您是否尝试了“Windows>首选项> Java>编译器>错误/警告>空分析”选项?
  • 单元测试。 在一些测试案例中涵盖“不应该发生”的情况。 代码覆盖工具(如JaCoCo )也可能是有用的。
  • 防御性编程(普通的'如果'语句,断言…)
  • 以前的项目的混合

希望这可以帮助。

你没有说你正在使用什么IDE,但Eclipse和Intellij支持外部注释。 有了它们,您仍然可以在本地注释您的代码,并让IDE提供空分析。

Intellij文档
Eclipse文档

你也许可以找到一种方法来使用适合你的情况的Optional<T>

假设你必须写一个方法U doStuff(T param) ,并且这个param是由其他人的一个库给你的代码,它可能是空的。

doStuff方法的内部,你可以做

 return Optional.ofNullable(param) .map(notNullParam -> doThing(notNullParam)) .orElse(null); 

通过这种方式,您可以确定您的Optional本身不是空的,人们仍然可以使用您的API参数为null,并且在它们有意义时返回null。

我建议查看SonarLint ,这是一个非常好的工具,可以检测所有类型的错误和错误,包括NPE

你应该做什么 – 提出一个好的API设计,并指定哪些图层允许返回null,哪些不允许。

一般来说,你应该总是尝试返回其他的东西,而不是null – 空集合,一个代表not-found的对象。

所以你应该手动检查空值,返回值可能是空的。 否则,只召集不返回null,只是不检查。

您还可以引入一些实用程序方法来检查空/空集合:

如果collection为null或者不为null,则Check.isEmpty()将返回true,等等…

您也可以使用空对象模式

我可以把你的问题和我的用例联系起来。 在这些情况下,随意提出代码覆盖的工具。

  • JaCoCo非常方便。 如果你使用IntelliJ,内置的Jacoco / IJ的代码覆盖率明确地给你违规和可能的NPE
  • 单元测试,以支持您的域明智的非空对象的情况

毕竟, NPE仍然是基于设计/程序员的错误 。 保持您的代码经过充分测试和适当的检查,以避免NPE并抛出自定义异常或IllegalArgumentException如果您的服务需要一个有效的值。