我正在处理的项目的一部分已更新,并且在某个时刻有人开始发送空集合,而不是发送null
作为方法的参数。
这首先导致了一个错误,然后导致我将if (null == myCollection)
更改为if (CollectionUtils.isEmpty(myCollection))
,最终导致了多个错误的级联。这样,我发现很多代码对这些集合的处理方式不同:
null
时(即用户在此处未提及任何内容)因此,我的问题是:这是好的还是坏的设计实践?
答案 0 :(得分:9)
约书亚·布洛赫(Joshua Bloch)在他的《非常有效的Java》( Effective Java )一书中,将这个问题当作方法返回值的问题(不是像您的问题那样“一般”):
(关于使用null )这很容易出错,因为程序员在写 客户可能会忘记编写特殊情况代码来处理null 返回。
(...)
有时会认为空返回值比 空数组,因为它避免了分配数组的开销。 该论点有两个方面失败。首先,不宜担心 除非性能分析表明 有问题的方法是造成性能问题的真正原因(项目 55)。其次,可以从每次不返回任何项目的调用中返回相同的零长度数组,因为 零长度数组是不可变的,并且不可变的对象可以共享 自由地(第15项)。
(...)
总而言之,没有理由从 数组或集合值方法,而不是返回空数组 或集合。(...)
就我的代码而言,我个人将这种推理作为经验法则。当然,在某些情况下,将null和empty区分是有意义的,但是以我的经验,这是非常罕见的。
尽管如此,如BionicCode在评论部分所述,如果方法返回null而不是空以表示出了问题,则您总是有可能抛出异常
答案 1 :(得分:2)
我知道这个问题已经有一个可以接受的答案。但是由于进行了讨论,我决定编写自己的文章来解决这个问题。
NULL的发明人本·托尼·霍尔(Tony Hoare)先生说
我称之为我十亿美元的错误。这是在1965年发明空引用的。那时,我正在设计第一个全面的类型系统,用于面向对象语言(ALGOL W)的引用。我的目标是确保所有引用的使用都绝对安全,并由编译器自动执行检查。但是我无法抗拒引入空引用的诱惑,仅仅是因为它是如此易于实现。这导致了无数的错误,漏洞和系统崩溃,在过去的40年中可能造成十亿美元的痛苦和破坏。
首先,在更新已经发布和测试的旧代码时引入重大更改是非常不好的做法。在这种情况下进行重构总是很危险的,应该避免。我知道发布丑陋或愚蠢的代码可能会受到伤害,但是如果发生这种情况,那么这显然是缺乏质量管理工具(例如,代码审查或约定)的迹象。但是显然没有单元测试,否则由于测试失败,更改的作者会立即撤消更改。主要问题是您的团队没有约定如何将NULL作为参数或结果进行处理。更新作者将NULL参数切换为空集合的意图是绝对正确的,应该得到支持,但是他不应该为工作代码而这样做,而应该与团队讨论,以便其余团队可以遵循以使其更有效。您的团队绝对必须团结一致,并同意放弃NULL作为参数或结果值,或者至少找到一个标准。最好将NULL发送到地狱。唯一的解决方案是还原更新作者所做的更改(我假设您正在使用版本控制)。然后重做更新,但是像以前一样使用NULL进行老式且讨厌的更新。不要在新代码中使用NULL-为了美好的未来。尝试修复更新的版本肯定会升级情况,因此会浪费时间。 (我假设我们正在谈论一个更大的项目)。如果可能,请回滚到以前的版本。
简而言之,如果您不想继续阅读:是的,这是非常的错误做法。您自己可以从当前的情况得出这个结论。您现在看到了非常不稳定且不可预测的代码。不合理的错误是您的代码变得不可预测的证明。如果您至少没有针对业务逻辑的单元测试,那么代码就是热门。导致此代码的做法永远不会是好的。如果有可能获得中立的结果或参数(例如集合的情况),则NULL对API用户没有直觉的意义。空集合是中性的。您可以期望一个集合为空或包含至少一个元素。您还可以从集合中期待其他东西。您想指出一个错误并且操作无法终止?然后,更喜欢抛出一个具有良好名称的异常,该异常可以清楚地将错误的根源传达给调用方。
NULL是历史遗物。绊倒NULL值意味着程序员编写了草率的代码。他忘记为指针分配内存地址来初始化指针。为了创建有效的指针,编译器必须知道一个内存地址作为参考。如果您想要一个指针但不浪费内存仅用于声明指针或不知道指向该点的地址,则NULL是一种约定,使指针指向无处,这意味着不必为指针的指针分配任何内存。引用(指针本身除外)。如今,在具有垃圾回收和大量可用内存的现代面向对象语言中,NULL在编程中已变得无关紧要。在某些情况下,它会用来表达缺少像SQL中那样的数据。但是在OO编程中,您可以绝对避免使用它,从而使您的应用程序更强大。
您可以选择应用Null-Object Pattern。另外,最好的做法是使用默认参数(如果您的语言(如C#)支持此参数)或使用重载。例如。如果method参数是可选的,则使用重载(或默认参数)。如果该参数是强制性的,但是您不能提供该值,则只需不调用该方法。如果参数是一个集合,那么在没有值的情况下请始终使用空集合。方法的作者必须处理这种情况,因为他必须处理所有可能的情况或参数状态。这包括NULL。因此,方法的作者有责任检查NULL并决定如何处理它。这是约定俗成的惯例。如果您的团队同意不再使用NULL,则不再需要这种烦人且丑陋的NULL检查。某些框架提供了@NotNull
属性。作者可以使用它来修饰方法参数,以指示NULL不是有效值。编译器现在将执行NULL检查,并向程序员显示错误,该错误(错误)使用该方法,并且根本不会进行编译。除了代码审查外,这还可以帮助防止或识别违规情况,并导致更健壮的代码。
大多数库都提供帮助程序类,例如Array.Empty()
或Enumerable.Empty()
(C#)创建空集合,这些集合提供类似IsEmpty()
的方法。这使意图在语义上清晰,因此代码易于阅读。如果您的标准库中不存在自己的帮助程序,那是值得的。
尝试将质量管理整合到团队的日常工作中。进行代码审查以确保要发布的代码符合您的质量标准(单元测试,无NULL值,对于语句主体,命名等始终使用花括号)
希望您能解决您的问题。我知道这是清理别人的混乱局面的压力所在。这就是团队沟通如此重要的原因。
答案 2 :(得分:1)
这取决于您的需求。 Null是定值而不是空集合。我想说,分别处理空集合和不空集合是一种不好的做法。 空值表示缺少数据。我想说,如果这种情况是合法的,并且您使用的是Java 8或更高版本,则应该使用Optional。在这种情况下,Optional.empty()表示没有集合,而Optional.of(collection)表示即使集合为空,集合也位于此处。
答案 3 :(得分:1)
在收集的情况下,建议像这样使用。
if(myCollection !=null && !myCollection.isEmpty()) {
//Process the logic
}
但是,作为设计的一部分,Joshua Bloch建议在有效Java中使用空集合。
引用他的声明
没有理由从数组值方法中返回null 而不是返回零长度的数组。
您可以找到有效Java的链接。 https://www.amazon.com/dp/0321356683
答案 4 :(得分:1)
好吧,通过访问null值,您可以直接获取NullPointerException,并且大多数时候(根据我的经验),在更深层次的逻辑层次结构中区分null值和空集合是一种不好的做法。它应该只是空而不是null。
答案 5 :(得分:0)
让我们在这里放一些选票诱饵吧?据我了解,您有类似的方法
public void foo(Collection myCollection) {
if (CollectionUtils.isEmpty(myCollection)) {
// ...
}
// ...
}
我还了解到,该方法在很多地方都被调用过,因此如果您不需要更改所有调用方法,那么该方法将最为实用。而且我知道在传递null和向其传递空集合之间存在语义差异。空集合意味着我们实际上知道没有任何元素。 Null表示未指定是否存在(我假设您的方法在这种情况下也可以做有用的工作)。
好的设计将有两种方法:
/** @param myCollection a possibly empty collection; not null */
public void foo(Collection myCollection);
/** Call this method if you don’t want to specify elements */
public void foo();
但是,考虑到您现有的代码库,您不想引入突然提到的非null要求,并且中断到现在一直有效的代码。一种前进的方式是对1-arg方法进行有效的评论
不建议将null传递给此方法。它有效,但可能是 在以后的版本中禁止使用。
这将使您有时间在未来几个月甚至几年内更改代码库,并且仍然可以让您在某个时候达到更好的设计。
同时,您可以将1-arg方法更改为委托给no-arg方法(如果它接收到空值)。
注意事项:您需要在文档(Javadoc)中非常清楚地说明未指定元素的语义(最好调用no-arg方法)以及传递空集合所带来的语义差异。