多少空值检查就足够了?
当不需要检查空值时,有哪些准则?
我最近一直在处理的很多继承代码都有空检查和恶心。对琐碎函数进行空检查,对状态为非空返回的 API 调用进行空检查,等等。在某些情况下,空检查是合理的,但在许多情况下,空值不是合理的期望。
我听说过很多论点,从“你不能信任其他代码”到“总是防御性地编程”,再到“直到语言保证我有一个非空值,我总是会检查。我当然在某种程度上同意其中的许多原则,但我发现过度的空检查会导致其他通常违反这些原则的问题。顽强的空检查真的值得吗?
我经常观察到具有过多空检查的代码实际上质量较差,而不是质量更高。大部分代码似乎都集中在空检查上,以至于开发人员忽略了其他重要的品质,例如可读性、正确性或异常处理。特别是,我看到很多代码忽略了std::bad_alloc异常,但对.new
在C++中,我在某种程度上理解这一点,这是由于取消引用空指针的不可预测的行为;null 取消引用在 Java、C#、Python 等中处理得更优雅。我刚刚看到警惕的空检查的糟糕例子,还是真的有一些东西?
这个问题旨在与语言无关,尽管我主要对C++,Java和C#感兴趣。
我看到的一些似乎过多的空检查示例包括以下内容:
此示例似乎考虑了非标准编译器,因为C++规范说失败的新编译器会引发异常。除非您明确支持不合规的编译器,否则这有意义吗?这在 Java 或 C# 等托管语言(甚至C++/CLR)中是否有任何意义?
try {
MyObject* obj = new MyObject();
if(obj!=NULL) {
//do something
} else {
//??? most code I see has log-it and move on
//or it repeats what's in the exception handler
}
} catch(std::bad_alloc) {
//Do something? normally--this code is wrong as it allocates
//more memory and will likely fail, such as writing to a log file.
}
另一个例子是在处理内部代码时。特别是,如果是一个小团队可以定义自己的开发实践,这似乎没有必要。在某些项目或遗留代码上,信任文档可能不合理...但是对于您或您的团队控制的新代码,这真的有必要吗?
如果一个你可以看到并且可以更新(或者可以对负责的开发人员大喊大叫)的方法有一个合约,是否仍然需要检查空值?
//X is non-negative.
//Returns an object or throws exception.
MyObject* create(int x) {
if(x<0) throw;
return new MyObject();
}
try {
MyObject* x = create(unknownVar);
if(x!=null) {
//is this null check really necessary?
}
} catch {
//do something
}
在开发私有函数或其他内部函数时,当合约仅调用非 null 值时,是否真的有必要显式处理 null?为什么空检查比断言更可取?
(显然,在您的公共API上,空检查至关重要,因为对用户不正确使用API大喊大叫被认为是不礼貌的)
//Internal use only--non-public, not part of public API
//input must be non-null.
//returns non-negative value, or -1 if failed
int ParseType(String input) {
if(input==null) return -1;
//do something magic
return value;
}
与:
//Internal use only--non-public, not part of public API
//input must be non-null.
//returns non-negative value
int ParseType(String input) {
assert(input!=null : "Input must be non-null.");
//do something magic
return value;
}