20

我正在阅读优秀的清洁代码

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

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;
}

我更喜欢断言方法,但我不喜欢断言默认关闭的事实。

书最后说:

在大多数编程语言中,没有很好的方法来处理调用者意外传递的 null。因为是这种情况,理性的做法是默认禁止传递null。

它并没有真正涉及您将如何执行此限制?

无论哪种方式,你们中的任何人都有强烈的意见吗?

4

16 回答 16

8

一般规则是,如果您的方法不需要null参数,那么您应该抛出System.ArgumentNullException。正确投掷Exception不仅可以保护您免受资源损坏和其他不良事件的影响,还可以为您的代码用户提供指导,从而节省调试代码的时间。

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

于 2008-08-28T13:45:29.040 回答
4

也不是立即使用,但与 Spec# 的提及有关……有一个提议将“null-safe types”添加到 Java 的未来版本:“Enhanced null handling - Null-safe types”

根据该提案,您的方法将变为

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

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

于 2008-08-28T16:20:36.127 回答
4

使用断言和抛出异常都是有效的方法。任何一种机制都可以用来指示编程错误,而不是运行时错误,就像这里的情况一样。

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

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

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

当然,如果你不能为你的代码强制执行策略(例如,如果你正在创建一个库,那么这取决于其他开发人员如何运行你的代码),你应该选择安全的方法,为那些抛出 NullPointerException作为库 API 一部分的方法。

于 2008-08-28T22:26:27.197 回答
3

它并没有真正涉及您将如何执行此限制?

如果它们传入 null,您可以通过抛出ArgumentExcexception来强制执行它。

if (p1 == null || p2 == null) {
    throw new IllegalArgumentException("Invalid argument for xProjection");
}
于 2008-08-28T13:42:07.863 回答
3

在大多数编程语言中,没有很好的方法来处理调用者意外传递的 null。因为是这种情况,理性的做法是默认禁止传递null。

我发现JetBrains@Nullable@NotNullannotations 处理这个问题的方法是迄今为止最巧妙的。不幸的是,它是特定于 IDE 的,但非常干净和强大,IMO。

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

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

于 2008-08-28T17:51:58.263 回答
2

我更喜欢使用断言。

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

于 2008-08-28T13:45:10.460 回答
2

Spec# 看起来很有趣!

当这样的东西不可用时,我通常使用运行时空检查和内部方法的断言来测试非私有方法。我没有在每个方法中显式编码 null 检查,而是将其委托给具有检查 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);
   **/

   /** ... **/
} 

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

于 2008-08-28T16:05:14.757 回答
1

我通常都不喜欢这样做,因为它只会减慢速度。无论如何,稍后都会抛出 NullPointerExceptions,这将很快导致用户发现他们正在将 null 传递给该方法。我曾经检查过,但我的 40% 的代码最终都是检查代码,此时我认为它不值得漂亮的断言消息。

于 2008-08-28T13:38:50.337 回答
1

我同意或不同意wvdschel 的帖子,这取决于他具体说什么。

在这种情况下,这个方法肯定会崩溃,null所以这里可能不需要显式检查。

但是,如果该方法只是存储传递的数据,并且您稍后会调用其他方法来处理它,那么尽早发现错误输入是更快修复错误的关键。在那之后,可能有无数种方式将坏数据碰巧提供给您的班级。这有点像试图弄清楚老鼠是如何在事后进入你的房子的,试图在某个地方找到洞。

于 2008-08-28T13:43:06.113 回答
1

@Chris Karcher 我会说绝对正确。我唯一要说的是分别检查参数并让异常报告为空的参数,因为它使跟踪空值的来源变得更加容易。

@wvdschel 哇!如果编写代码对您来说太费力了,您应该研究类似PostSharp(或 Java 等价物,如果可用)之类的东西,它可以对您的程序集进行后处理并为您插入参数检查。

于 2008-08-28T13:45:56.467 回答
1

虽然它不是严格相关的,但您可能想看看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#。

于 2008-08-28T14:22:28.220 回答
1

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

通过对代码进行静态分析,findbugs可以指出调用该方法的位置,其中可能存在 null 值。

这有两个优点:

  1. 注释描述了您应该如何调用该方法的意图,帮助文档
  2. FindBugs 可以指向该方法的潜在问题调用者,允许您追踪潜在的错误。

仅当您可以访问调用您的方法的代码时才有用,但通常是这种情况。

于 2008-08-28T22:34:06.913 回答
1

由于跑题似乎已成为话题,Scala 对此采取了一种有趣的方法。所有类型都假定不为空,除非您明确地将其包装在 anOption中以指示它可能为空。所以:

//  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"
}
于 2008-10-29T22:43:20.370 回答
0

在方法的开头抛出 C#ArgumentException或 Java在我看来是最清晰的解决方案。IllegalArgumentException

应该始终小心运行时异常 - 未在方法签名上声明的异常。由于编译器不会强制您捕获这些内容,因此很容易忘记它们。确保您有某种“包罗万象”的异常处理,以防止软件突然停止。这是用户体验中最重要的部分。

于 2008-08-28T16:53:18.263 回答
0

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

于 2008-08-28T16:59:05.923 回答
0

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

如果 null 可能来自实际的异常情况,但您不想为此使用受检异常,那么您肯定希望在方法的开头使用 IllegalArgumentException 路线。

于 2008-08-28T23:55:19.217 回答