是否有一个空指针异常代码气味?

时间:2010-04-06 16:00:23

标签: java nullpointerexception

最近我的一个同事用一些代码写了一个代码来捕获整个方法的空指针异常,并返回一个结果。我指出了空指针可能有多种原因,所以我们将其改为对一个结果的防御性检查。

但是,捕获NullPointerException似乎对我来说不对。在我看来,Null指针异常是错误代码的结果,而不是系统中的预期异常。

是否有任何情况下捕获空指针异常是有意义的?

18 个答案:

答案 0 :(得分:36)

是的,抓住任何RuntimeException几乎总是代码味道。 C2 Wiki似乎同意。

异常可能是一些特别防御的代码,它们运行来自其他模块的大量随机代码。这种防御性结构的例子是EDT,ThreadPools / Executors和插件系统。

答案 1 :(得分:23)

我可以想到一个用于捕获NullPointerException的唯一用途:

catch (NullPointerException) {
    ApplyPainfulElectricShockToProgrammer();
}

答案 2 :(得分:15)

由于第三方库中存在错误,我有时必须捕获nullpointer异常。我们使用的库抛出了这个异常,我们无能为力。

在这种情况下,确定可以捕获它,否则不会。

答案 3 :(得分:7)

取决于。

这位同事的经验如何?他这样做是为了无知/懒惰还是有一个真正的理由呢? (比如这是主线,高于一切,永远不应该死?)

90%的时间捕获运行时异常是错误的,99%捕获NullPointerException是错误的(如果原因是“我得到了很多......”那么整个程序员是错的,你应该看看他正在做的其余代码

但在某些情况下,捕获NullPointerException可能是可以接受的。

答案 4 :(得分:3)

一般来说,我认为这是代码味道;在我看来,防御性检查更好。我会扩展它以涵盖大多数未经检查的异常,除了事件循环等,它们希望捕获报告/日志记录的所有错误。

我能想到的异常就是调用一个无法修改的库,并且可能会生成一个空指针异常,以响应一些难以主动检查的断言失败。

答案 5 :(得分:3)

滑稽

我刚刚找到了一些不应该在工作中完成的事情:

public static boolean isValidDate(final String stringDateValue) {
    String exp = "^[0-9]{2}/[0-9]{2}/[0-9]{4}$";
    boolean isValid = false;
    try {
        if (Pattern.matches(exp, stringDateValue)) {
            String[] dateArray = stringDateValue.split("/");
            if (dateArray.length == 3) {
                GregorianCalendar gregorianCalendar = new GregorianCalendar();
                int annee = new Integer(dateArray[2]).intValue();
                int mois = new Integer(dateArray[1]).intValue();
                int jour = new Integer(dateArray[0]).intValue();

                gregorianCalendar = new GregorianCalendar(annee, mois - 1,
                        jour);
                gregorianCalendar.setLenient(false);
                gregorianCalendar.get(GregorianCalendar.YEAR);
                gregorianCalendar.get(GregorianCalendar.MONTH);
                gregorianCalendar.get(GregorianCalendar.DAY_OF_MONTH);
                isValid = true;
            }
        }
    } catch (Exception e) {
        isValid = false;
    }
    return isValid;
}

baaad:)

开发人员希望日历能够引发此类异常:

java.lang.IllegalArgumentException: DAY_OF_MONTH
    at java.util.GregorianCalendar.computeTime(GregorianCalendar.java:2316)
    at java.util.Calendar.updateTime(Calendar.java:2260)
    at java.util.Calendar.complete(Calendar.java:1305)
    at java.util.Calendar.get(Calendar.java:1088)

使值无效...

是的它有效,但这不是一个很好的做法......

提高异常(特别是填充堆栈跟踪)比仅手动检查数据要花费更多......

答案 6 :(得分:3)

这很糟糕,但它可以产生优化的字节码。

如果大多数时间内整数i不是null,那么检查会降低整体效果。支票本身需要3条指令(0-4)。然后整个案例需要7条指令(0-14)。

public class IfNotNull {

    public Integer i;

    public String getIAsString() {
        if (i != null) {
            return i.toString();
        } else {
            return "";
        }
    }
}

  public java.lang.String getIAsString();
    Code:
       0: aload_0       
       1: getfield      #2                  // Field i:Ljava/lang/Integer;
       4: ifnull        15
       7: aload_0       
       8: getfield      #2                  // Field i:Ljava/lang/Integer;
      11: invokevirtual #3                  // Method java/lang/Integer.toString:()Ljava/lang/String;
      14: areturn       // <- here we go
      15: ldc           #4                  // String 
      17: areturn 

遵循EAFP方法,这在Python世界中很常见。 null案例代价很高,但not null案例只需4条指令(0-7)。

public class TryCatch {

    public Integer i;

    public String getIAsString() {
        try {
            return i.toString();
        } catch (NullPointerException npe) {
            return "";
        }
    }
}

  public java.lang.String getIAsString();
    Code:
       0: aload_0       
       1: getfield      #2                  // Field i:Ljava/lang/Integer;
       4: invokevirtual #3                  // Method java/lang/Integer.toString:()Ljava/lang/String;
       7: areturn       // <- here we go
       8: astore_1      
       9: ldc           #5                  // String a
      11: areturn       
    Exception table:
       from    to  target type
           0     7     8   Class java/lang/NullPointerException

谁知道,如果JIT编译器可以优化这个?

答案 7 :(得分:2)

当然是。

大多数情况下,您的变量一开始不应为null。许多新语言都内置了对非可空引用类型的支持 - 也就是说,保证永远不会为null的类型。

对于允许传入值为null的时间,您需要进行检查。但是,例外绝对是一种不好的方法。

if语句可能需要执行三条指令,并且是本地检查(这意味着,您需要在需要保证的同一位置进行检查)。

另一方面,使用异常可能需要更多指令 - 系统会尝试查找方法,失败,查看异常表中的相应异常处理程序,跳转到那里,执行处理程序和跳转再次。此外,检查可能是非本地的。如果您的代码是这样的:

try
  return contacts.find("Mom").getEmail()
catch (NullPointerException e)
  return null

您不知道NPE是在“getEmail”还是“find”中引发的。

对于以更混淆的方式编写的非常非常常见的模式的技术上更糟糕的解决方案?它不是排名,但它肯定闻起来很糟糕:/

答案 8 :(得分:2)

你应该捕获NullPointerException(或者特别是任何Throwable)的唯一地方是在某个顶层或系统边界,这样你的程序就不会完全崩溃并且可以恢复。例如,在web.xml中设置错误页面提供了一个全面的功能,以便Web应用程序可以从异常中恢复并通知用户。

答案 9 :(得分:1)

很久以前我有一次使用。当按键检索集合中的对象并且找不到对象时,一个特别愚蠢的库会抛出NullPointerException。没有其他方法可以通过键查找,也无法检查对象是否存在。

一段时间后,我们启动了供应商并开始修改库。现在,库会抛出一个更好的异常(我的更改),并且有一个检查功能(其他人的更改)。

当然,我总是在try块中只有一行。不管怎么说,我自己也会犯错误的代码。

答案 10 :(得分:1)

捕获NULL指针异常实际上取决于上下文...应该努力避免严格的绝对规则...应该在上下文中应用规则 - 想要捕获此异常并将整个软件置于某种STABLE状态 - 执行什么都没有或几乎没有。所有这些编码规则都应该被很好地理解

此时,您可以查看您的软件AUDIT TRACE ...您应该做的事情并发现此异常的来源。

永远不会发生NULL指针异常的想法必须是可验证的。首先进行静态分析...(如果第三方代码/组件进入则更难),然后使用相关工具进行详尽的状态空间搜索。

X

答案 11 :(得分:1)

为了干净地终止基于Swing-GUI的应用程序,可能需要捕获NPE(实际上是任何RTE)。

编辑:在这种情况下,它通常通过UncaughtExceptionHandler完成。

答案 12 :(得分:1)

这个怎么样:

try
{
    foo.x = bar;
}
catch (NullPointerException e)
{
    // do whatever needs to be done
}

作为微优化,当foo可能为null时,但几乎从不是?

这个想法是这样的:

  • 显式NULL检查需要一条机器指令

  • 另一方面,第二个版本中的NULL检查可以通过允许NULL访问,捕获SIGSEGV和抛出NullPointerException来完成。如果对象不是NULL,则这是免费的。

答案 13 :(得分:0)

我尝试保证我的接口的结果,但是如果某些库或某些代码可以产生null,并且我期望保证捕获它可能是可行的。当然,一旦你抓到它,你所做的就取决于你。有时检查null是没有意义的,如果你抓住了它,你还有其他一些解决问题的方法可能不会那么好但是可以完成工作。

我所说的是使用例外,它是一个非常好的语言功能。

答案 14 :(得分:0)

如果您的方法调用外部接口(或SOAP API)并且返回的值可能为Null,则捕获NullPointerException非常有用。除此之外,捕获这些例外没有太大的好处。

答案 15 :(得分:0)

这实际上取决于接口定义。非结构化NPE处理与捕获异常或Throwable一样糟糕。

Null对于识别未初始化状态非常有用,而不是使用空字符串或max_int或其他任何东西。一旦我经常使用null的地方就是回调对象不相关的地方。

我非常喜欢Guice提供的@Nullable注释。

http://code.google.com/docreader/#p=google-guice&s=google-guice&t=UseNullable

  

消除NullPointerExceptions   你的代码库,你必须受到纪律处分   关于空引用。我们去过   通过以下方式获得成功   执行一个简单的规则:

     

每个参数都是非null,除非   明确指定。谷歌   集合库和JSR-305都有   简单的API来获取空值   控制。 Preconditions.checkNotNull   如果为null,则可用于快速失败   找到引用,@ Nullable可以   用于注释参数   允许空值。

     

Guice禁止默认为null。它会   拒绝注入null,失败了   而是ProvisionException。如果为null   你的班级允许,你可以   用。注释字段或参数   @Nullable。 Guice承认任何   @Nullable注释,就像   edu.umd.cs.findbugs.annotations.Nullable   或javax.annotation.Nullable。

答案 16 :(得分:0)

在Java中是的,需要检查NullPointerException。

当应用程序在需要对象的情况下尝试使用null时抛出。其中包括:

调用null对象的实例方法。 访问或修改空对象的字段。 将null的长度视为数组。 访问或修改null的槽,就像它是一个数组一样。 抛出null就好像它是一个Throwable值。

应用程序应抛出此类的实例以指示null对象的其他非法使用。

读取文本文件(即XML)时其他语言的NullPointerException,其记录未经验证为正确的ASCII字符和记录格式。

答案 17 :(得分:0)

如果程序员是初学者,他可能有习惯捕捉每一个阻止他获得最终输出的异常。代码审查员不应该接受这一点。

捕获任何RuntimeException都很糟糕。但是如果真的有必要,那么代码中的注释对将来使用该代码的程序员来说真的很有帮助。如果你不能写出合理的评论来捕捉它们,那么你必须避免它们。周期。