显然,在代码中绝大多数错误是空引用异常。有没有一些通用的技巧可以避免遇到空引用异常?
除非我搞错了,我知道像 F# 这样的语言不可能存在 null 值。但这并不是问题,我想问的是如何在 C# 等语言中避免空引用错误。
显然,在代码中绝大多数错误是空引用异常。有没有一些通用的技巧可以避免遇到空引用异常?
除非我搞错了,我知道像 F# 这样的语言不可能存在 null 值。但这并不是问题,我想问的是如何在 C# 等语言中避免空引用错误。
if (customer == null) {throw new ArgumentNullException("customer");}
Contract.Requires(customer != null);
1.0非空类型 现代程序中的许多错误表现为空引用错误,这表明提供区分可能评估为空值和确定不为空值的表达式的编程语言的重要性(有关实验证据,请参见[24、22])。事实上,我们希望消除所有空引用错误。
这是Microsoft内部熟悉此研究的人员的可靠来源。该文章可在Spec#网站上获取。
我复制了下面的引用22和24,并包含ISBN以方便您查看。
Manuel Fahndrich和K. Rustan M. Leino。在面向对象语言中声明和检查非空类型。在2003年面向对象编程、系统、语言和应用程序的ACM会议论文集(OOPSLA 2003)中,第38卷,第11号SIGPLAN通知中,页码为302-312。ACM,2003年11月。ISBN = {1-58113-712-5},
Cormac Flanagan,K. Rustan M. Leino,Mark Lillibridge,Greg Nelson,James B. Saxe和Raymie Stata。Java的扩展静态检查。在2002年ACM SIGPLAN编程语言设计和实现(PLDI)会议论文集中,第37卷,第5号SIGPLAN通知中,页码为234-245。ACM,2002年5月。
我已经审查了这些参考资料。第一个参考资料指出他们对自己的代码进行了一些实验,以检查可能存在的空引用缺陷。他们不仅发现了几个问题,而且在许多情况下,潜在的空引用问题指示了更广泛的设计问题。
第二个参考资料没有提供任何具体证据来支持空引用错误是问题的说法。但作者确实指出,在他们的经验中,这些空引用错误是软件缺陷的重要来源。然后,论文进一步解释了他们如何试图消除这些错误。
我还记得在ISE最近发布的Eiffel版本中看到了关于这个问题的公告。他们称之为“void safety”,就像Bertrand Meyer博士所启发或开发的许多东西一样,他们对问题及如何在其语言和工具中防止该问题有着简明扼要的描述。我建议你阅读他们的文章http://doc.eiffel.com/book/method/void-safety-background-definition-and-tools以了解更多信息。除了上述的空对象、空集合,还有一些通用技术,即C++中的资源获取即初始化(Resource Acquisition is Initialization,RAII)和Eiffel语言中的契约式设计(Design By Contract)。它们归结为以下几点:
我看过很多这样的代码:
if ((value != null) && (value.getProperty() != null) && ... ) doSomethingUseful();
很多时候这是完全不必要的,大部分测试可以通过更严格的初始化和更紧密的合同定义来减少。
如果您的代码库中存在此问题,则需要了解每种情况下null代表什么:
1. 如果null代表空集合,请使用空集合。
2. 如果null代表异常情况,请抛出异常。
3. 如果null代表意外未初始化的值,请明确初始化它。
4. 如果null代表合法值,请进行测试-或者更好地使用NullObject执行null操作。
实际上,在设计水平上达到这种清晰度的标准是不容易的,并需要努力和自律才能一致地应用于您的代码库中。你不需要做任何特殊的操作来尝试在C#中“防止”NRE。大多数情况下,NRE只是某种逻辑错误。您可以通过检查参数并编写大量代码来隔离接口边界。
void Foo(Something x) {
if (x==null)
throw new ArgumentNullException("x");
...
}
在这里使用空对象模式是关键。
确保在集合未被填充时要求它们为空,而不是 null。在不必要的情况下使用空集合而不是 null 集合会令人困惑。
最后,在构造对象时,尽可能让它们断言非 null 值。这样以后就不会怀疑值是否为 null,并且只需要在必要的情况下执行 null 检查。对于大多数字段和参数,我可以假定基于之前的断言值不为 null。
在引发异常之前,您可以轻松检查空引用,但通常这并不是真正的问题,因此您最终仍然会抛出异常,因为没有数据,代码实际上无法继续执行。
通常,主要问题不是您有一个空引用,而是您首先获得了一个空引用。如果引用不应为空,则在初始化引用时还没有正确的引用,就不应该通过该点。
我见过的最常见的空引用错误之一来自字符串。通常会进行如下检查:
if(stringValue == "") {}
if(string.IsNullOrEmpty(stringValue){}
如果你的语言中有空值,那么这种情况是不可避免的。空引用错误来自应用程序逻辑错误 - 因此,除非你能避免所有这些错误,否则你必然会遇到一些问题。
避免NullReferenceExceptions的最简单方法之一是在类构造函数/方法/属性设置器中积极检查null引用并引起注意。
例如:
public MyClass
{
private ISomeDependency m_dependencyThatWillBeUsedMuchLater
// passing a null ref here will cause
// an exception with a meaningful stack trace
public MyClass(ISomeDependency dependency)
{
if(dependency == null) throw new ArgumentNullException("dependency");
m_dependencyThatWillBeUsedMuchLater = dependency;
}
// Used later by some other code, resulting in a NullRef
public ISomeDependency Dep { get; private set; }
}
List<Client> GetAllClients()
{
List<Client> returnList = new List<Client>;
/* insert code to go to data base and get some data reader named rdr */
for (rdr.Read()
{
/* code to build Client objects and add to list */
}
return returnList;
}
null
。 不要将其作为“错误代码”强行塞入,不要像它是真实对象一样乱扔,事实上,除非你可以为在这种特定情况下使用它进行辩解,否则根本不要考虑写x = null
。哦,对了,如果你在调用你无法控制的代码,请查看文档并查看它是否也可能会返回 null。 如果能返回 null 就一定要检查它。 - Anon.