将null传递给方法

时间:2020-03-05 18:44:22  来源:igfitidea点击:

我正在阅读优秀的《清洁规范》

一个讨论是关于将空值传递给方法。

public class MetricsCalculator {
    public double xProjection(Point p1, Point p2) {
        return (p2.x - p1.x) * 1.5;
    }
}
...
calculator.xProjection(null, new Point(12,13));

它代表处理此问题的不同方法:

public double xProjection(Point p1, Point p2) {
    if (p1 == null || p2 == null) {
        throw new IllegalArgumentException("Invalid argument for xProjection");
    }
    return (p2.x - p1.x) * 1.5;
}

public double xProjection(Point p1, Point p2) {
    assert p1 != null : "p1 should not be null";
    assert p2 != null : "p2 should not be null";
    return (p2.x - p1.x) * 1.5;
}

我更喜欢断言方法,但是我不喜欢断言默认情况下处于关闭状态的事实。

该书最后指出:

In most programming languages there is no good way to deal with a null that is passed by a caller accidentally. Because this is the case, the rational approach is to forbid passing null by default.

它实际上并没有涉及如何实施此限制?

无论哪种方式,我们中的任何人都有强烈的意见。

解决方案

回答

我通常不喜欢任何一种,因为这只是在减慢速度。无论如何,稍后都会引发NullPointerException,这将迅速导致用户发现他们正在将null传递给该方法。我曾经检查过,但是我40%的代码最终还是要检查代码,这时我认为这不值得漂亮的断言消息。

回答

It doesn't really go into how you would enforce this restriction?

如果它们传入null,则通过抛出ArgumentExcexception来强制实施。

if (p1 == null || p2 == null) {
    throw new IllegalArgumentException("Invalid argument for xProjection");
}

回答

我同意还是不同意wvdschel的帖子,这取决于他的具体说法。

当然,在这种情况下,此方法将在null上崩溃,因此此处可能不需要显式检查。

但是,如果该方法仅存储传递的数据,并且我们稍后会调用其他一些方法来处理该数据,则尽早发现错误的输入是更快修复错误的关键。在此之后的某个时刻,可能会有无数种方式将不良数据恰巧提供给班级。这是在试图弄清事实之后老鼠如何进入房子,并试图在某个地方找到孔。

回答

我更喜欢使用断言。

我有一条规则,我只能在公共方法和受保护的方法中使用断言。这是因为我相信调用方法应确保将有效参数传递给私有方法。

回答

一般规则是,如果方法不希望使用" null"参数,则应抛出System.ArgumentNullException。抛出适当的"异常"不仅可以保护我们免受资源破坏和其他不良影响,而且还可以为代码用户提供指导,从而节省了调试代码的时间。

另请阅读有关防御性编程的文章

回答

@克里斯·卡歇尔,我会说完全正确。我要说的唯一一件事是分别检查参数,并让exeption报告参数为空的参数,因为这样可以更轻松地跟踪零值的来源。

@wvdschel哇!如果编写代码对我们来说太费力,则应考虑使用PostSharp(或者Java等效语言,如果可用的话)之类的东西,可以对程序集进行后处理并为我们插入参数检查。

回答

尽管它不是严格相关的,但我们可能要看一下Spec#。

我认为它仍在开发中(由Microsoft),但是可以使用一些CTP,并且看起来很有希望。基本上,我们可以执行以下操作:

public static int Divide(int x, int y)
    requires y != 0 otherwise ArgumentException; 
  {
  }

或者

public static int Subtract(int x, int y)
    requires x > y;
    ensures result > y;
  {
    return x - y;
  }

它还提供了诸如Notnull类型的其他功能。它建立在.NET Framework 2.0之上,并且完全兼容。如我们所见,语法是C#。

回答

Speclooks非常有趣!

当类似的东西不可用时,我通常会通过运行时空检查和内部方法的断言来测试非私有方法。我没有在每个方法中显式地编写null检查,而是将其委托给具有check null方法的实用程序类:

/**
 * Checks to see if an object is null, and if so 
 * generates an IllegalArgumentException with a fitting message.
 * 
 * @param o The object to check against null.
 * @param name The name of the object, used to format the exception message
 *
 * @throws IllegalArgumentException if o is null.
 */
public static void checkNull(Object o, String name) 
    throws IllegalArgumentException {
   if (null == o)
      throw new IllegalArgumentException(name + " must not be null");
}

public static void checkNull(Object o) throws IllegalArgumentException {
   checkNull(o, "object");
} 

// untested:
public static void checkNull(Object... os) throws IllegalArgumentException {
   for(Object o in os) checkNull(o);  
}

然后检查变成:

public void someFun(String val1, String val2) throws IllegalArgumentException {
   ExceptionUtilities.checkNull(val1, "val1");
   ExceptionUtilities.checkNull(val2, "val2");

   /** alternatively:
   ExceptionUtilities.checkNull(val1, val2);
   **/

   /** ... **/
}

可以与编辑器宏或者代码处理脚本一起添加。
编辑:详细检查也可以通过这种方式添加,但是我认为自动添加单行要容易得多。

回答

同样不是立即使用,而是与Spec#的提及有关。有一个建议在Java的未来版本中添加"空安全类型":"增强的空处理Null安全类型"。

根据提案,方法将变为

public class MetricsCalculator {
    public double xProjection(#Point p1, #Point p2) {
        return (p2.x - p1.x) * 1.5;
    }
}

其中,#Point是对类型为Point的对象的非null类型的引用。

回答

我认为,在方法开始时立即抛出CArgumentException或者JavaIllegalArgumentException是最清晰的解决方案。

对于方法签名中未声明的Runtime Exceptions异常,应始终保持谨慎。由于编译器不会强迫我们捕获这些错误,因此很容易忘记它们。确保我们具有某种"全部捕获"异常处理,以防止软件突然停止。那是用户体验中最重要的部分。

回答

处理此问题的最佳方法实际上是使用异常。最终,断言最终将给最终用户提供类似的体验,但是在向最终用户显示异常之前,开发人员无法调用代码来处理这种情况。 Ultimatley,我们希望确保尽早测试无效输入(尤其是在面向公众的代码中),并提供调用代码可以捕获的适当异常。

回答

In most programming languages there is no good way to deal with a null that is passed by a caller accidentally. Because this is the case, the rational approach is to forbid passing null by default.

我发现,到目前为止,JetBrains的@ Nullable@ NotNull注释方法可用于处理此问题。不幸的是,它是特定于IDE的,但确实干净且功能强大,IMO。

http://www.jetbrains.com/idea/documentation/howto.html

将此(或者类似的东西)作为java标准会非常好。

回答

在这里,使用断言和引发异常都是有效的方法。任何一种机制都可以用来指示编程错误,而不是运行时错误,如此处的情况。

  • 断言具有性能优势,因为它们通常在生产系统上被禁用。
  • 异常具有安全性的优点,因为始终执行检查。

选择实际上取决于项目的开发实践。整个项目需要确定一个断言策略:如果选择在所有开发过程中启用断言,那么我会说使用断言来检查生产系统中的这种无效参数,由于无论如何,编程错误都不太可能以有意义的方式被捕获和处理,因此它的行为就像断言一样。

但是实际上,我知道很多开发人员不相信断言会在适当的时候启用,因此选择了抛出NullPointerException的安全性。

当然,如果我们不能为代码实施策略(例如,如果我们正在创建库,并且这取决于其他开发人员如何运行代码),则应该选择对这些代码抛出NullPointerException的安全方法库API的一部分的方法。

回答

稍微偏离主题,但我认为findbug的一个非常有用的功能是能够注释方法的参数,以描述哪些参数不应传递空值。

使用代码的静态分析,findbug可以随后指出使用潜在的空值调用该方法的位置。

这有两个优点:

  • 注释描述了如何调用该方法的意图,并帮助了文档
  • FindBugs可以指向该方法的潜在问题调用者,从而使我们可以跟踪潜在的错误。

仅当我们有权访问调用方法的代码时才有用...但是通常是这种情况。

回答

以Java方式,假设null来自编程错误(即,永远不要超出测试阶段),然后让系统将其抛出,或者如果有副作用达到该点,请在开始时检查null,然后抛出IllegalArgumentException或者NullPointerException。

如果null可能来自实际的特殊情况,但我们不想为此使用检查的异常,那么我们肯定想在方法开始时使用IllegalArgumentException路由。

回答

由于离题似乎已成为话题,Scala为此采取了一种有趣的方法。假定所有类型都不为null,除非我们将其显式包装在Option中以指示其可能为null。所以:

//  allocate null
var name : Option[String]
name = None

//  allocate a value
name = Any["Hello"]

//  print the value if we can
name match {
  Any[x] => print x
  _ => print "Nothing at all"
}