对照《Refactoring》精读英文Wikipedia Null Object Pattern词条中Criticism一节
英文Wikipedia的词条Null Object Pattern中的Criticism一节摘录如下(en.wikipedia.org/wiki/Null_o…)。
这里给每句话前面加上了序号。
Criticism
① This pattern should be used carefully as it can make errors/bugs appear as normal program execution.[7]
② Care should be taken not to implement this pattern just to avoid null checks and make code more readable, since the harder-to-read code may just move to another place and be less standard—such as when different logic must execute in case the object provided is indeed the null object. ③ The common pattern in most languages with reference types is to compare a reference to a single value referred to as null or nil. ④ Also, there is additional need for testing that no code anywhere ever assigns null instead of the null object, because in most cases and languages with static typing, this is not a compiler error if the null object is of a reference type, although it would certainly lead to errors at run time in parts of the code where the pattern was used to avoid null checks. ⑤ On top of that, in most languages and assuming there can be many null objects (i.e., the null object is a reference type but doesn't implement the singleton pattern in one or another way), checking for the null object instead of for the null or nil value introduces overhead, as does the singleton pattern likely itself upon obtaining the singleton reference.
*7. Fowler, Martin (1999). Refactoring pp. 216
译文
先简单翻译一下(在ChatGPT的翻译结果上做了调整)。
批评
① 这个模式应该谨慎使用,因为它可能导致即便发生了错误或代码中存在缺陷,程序也能正常执行。*7
② 请注意,不要仅仅为了避免检查变量/引用的值是否为null,并使代码更好读就使用此模式。因为更难读的代码可能会出现在其他地方,而这些代码的写法又缺少规范,特别是当要对函数/方法返回的null object(空对象)进行特殊处理时。
③ 在大多数带有引用类型的语言中,通常的模式是将引用与称为null或nil的单一值进行比较。
④ 此外,(如果正在使用该模式)还需要额外的测试来确保,所有需要返回null object的地方都没有返回null,以及所有需要用null object给变量赋值(包括参数传递)的地方,都没有赋值为null。因为在大多数静态类型的语言中,如果null object是引用类型,赋值为null是不会引起编译错误的,但是在代码的某些地方可能会产生运行时错误。
⑤ 除此之外,在大多数语言中,如果存在许多null object(null object是引用类型又未实现单例模式),检查变量/引用的值是否为null object比只是检查是否为null或nil的开销要大。可就算实现了单例模式,在获取单例对象时也依然有额外的开销。
*7. Fowler, Martin (1999). 《Refactoring》原书第216页
下面逐句分析这段文字。
逐句分析
① This pattern should be used carefully as it can make errors/bugs appear as normal program execution.
对Null调用方法通常会报错(是的,存在不会报错的特殊情况),而用仅带有空值和空方法的对象——称为null object(空对象)——来取代Null表示不存在、未知或无意义等概念后,更容易吞掉错误。不过,程序倒是能继续运行了。
TODO PHP之父Rasmus Lerdorf在2017年上海的PHPCon上,好像说过类似这么一句话,“在PHP中,有时错误的代码也能继续执行”
注:本文用首字母大写的Null泛指各种语言中的空指针或空引用;用代码体的
null(PHP、Java)或nil(Go)特指对应语言中的空指针或空引用。
这类似于用抛异常来替代返回Null以表示异常情况,但又过早捕捉了异常类层级结构中最顶层的Throwable,早早吞掉了异常信息。
另外,用户也难以区分返回的空结果是本来就没有结果,还是因为因缺失某些要素导致的(作为默认值的)空结果。
② Care should be taken not to implement this pattern just to avoid null checks and make code more readable, since the harder-to-read code may just move to another place and be less standard—such as when different logic must execute in case the object provided is indeed the null object.
③ The common pattern in most languages with reference types is to compare a reference to a single value referred to as
nullornil.
②和③两句话可以一起看。
可能对Null Object设计模式持批评态度的人认为,if reference == null/nil才是公认的方式(common pattern),而程序员们未对所谓“更难读的代码(harder-to-read code)”的写法达成共识。
这里“更难读的代码”(至少是“之一”)应该指的就是“such as”指出的情况。要对函数/方法返回的null object进行特殊处理之前,需要先判断返回值是不是null object。
《Refactoring》一书给出了两种判断方法。
方法一,实现Nullable接口,暴露boolean isNull()方法。
interface Nullable {
boolean isNull();
}
class NullCustomer extends Customer {
public boolean isNull() {
return true;
}
}
class Customer...
public boolean isNull() {
return false;
}
...
方法二,只有NullCustomer实现充当标签的Null接口(称为Testing Interface)。这种方式适用于无法修改Customer类时。
interface Null {}
class NullCustomer extends Customer implements Null...
...
if (aCustomer instanceof Null) {
...
}
也可以不用Nullable接口和Null接口,而是直接使用instanceof来判断:if (aCustomer instanceof NullCustomer)。
这可能就是批评者认为的“缺少规范(less standard)”。
④ Also, there is additional need for testing that no code anywhere ever assigns null instead of the null object, because in most cases and languages with static typing, this is not a compiler error if the null object is of a reference type, although it would certainly lead to errors at run time in parts of the code where the pattern was used to avoid null checks.
这句说的是这种情况,
class Site...
Customer getCustomer() {
return (_customer == null) ? // <--
Customer.newNull() :
_customer;
}
class Customer...
static Customer newNull() {
return new NullCustomer();
}
...
使用了Null Object设计模式以后,getCustomer()本该返回new NullCustomer(),但一旦返回了null,虽然不会引起编译时错误,但是在类似下面的代码中
Customer customer = site.getCustomer();
String customerName = customer.getName(); // <-- NullPointerException here
还是会产生NullPointerException。
也就是说,一旦本该返回null object的地方返回了null,就会绕过编译器的类型检查,因为null是NullObjectClass nullObject的有效取值。这就导致后续代码又有可能对Null调用方法,进而是不是还要再检查一下是不是Null呢,这不就又回到了最初Null Object要解决的问题!
《Refactoring》的作者Fowler Martin认为把返回null的地方统统替换为null object是一个“messy step”。
There's no doubt that this is the trickiest part of this refactoring. For each source of a null I replace, I have to find all the times it is tested for nullness and replace them. If the object is widely passed around, these can be hard to track. I have to find every variable of type customer and find everywhere it is used. It is hard to break this process into small steps. Sometimes I find one source that is used in only a few places, and I can replace that source only. But most of the time, however, I have to make many widespread changes. The changes aren't too difficult to back out of, because I can find calls of isNull without too much difficulty, but this is still a messy step.
⑤ On top of that, in most languages and assuming there can be many null objects (i.e., the null object is a reference type but doesn't implement the singleton pattern in one or another way), checking for the null object instead of for the null or nil value introduces overhead, as does the singleton pattern likely itself upon obtaining the singleton reference.
最后一个长句。
这句说的是性能开销的问题。总共有两种开销。
第一种开销在于,无论是aCustomer.isNull()还是aCustomer instanceof ...显然都要比if (aCustomer == null)开销大。
第二种开销来自项目中存在各种引用类型的null object,但均未实现单例模式。但为NullCustomer,NullOrder……等都实现了单例模式,这种开销就消失了吗?
我认为没有消失,因为创建新的null object的开销转变成了获取单例对象的开销(通常涉及到锁)。所以最后一个分句的as应该是表示“就算……也……”的让步关系,而不是通常类比或表示因果关系的含义。
好了,英文Wikipedia Null Object Pattern词条中Criticism这一节就精读完了。