显然,代码中的绝大多数错误都是空引用异常.是否有任何一般技术可以避免遇到空引用错误?
除非我弄错了,否则我知道在F#这样的语言中,不可能有空值.但这不是问题,我问如何避免C#等语言中的空引用错误.
当向用户显示空引用异常时,这表示代码中由于开发人员的错误而导致的缺陷.以下是有关如何防止这些错误的一些想法.
我对那些关心软件质量并且也在使用.NET编程平台的人的最佳建议是安装和使用Microsoft代码合同(http://msdn.microsoft.com/en-us/devlabs/dd491992.aspx) .它包括执行运行时检查和静态验证的功能.将这些合同构建到代码中的基本功能包含在.NET框架的4.0版本中.如果您对代码质量感兴趣,并且听起来像您,您可能真的很喜欢使用Microsoft代码合同.
使用Microsoft代码协定,您可以通过添加类似"Contract.Requires(customer!= null);"的前提条件来保护您的方法免受空值的影响.添加这样的前提条件等同于许多其他人在上述评论中推荐的做法.在代码合同之前,我建议你做这样的事情
if (customer == null) {throw new ArgumentNullException("customer");}
现在我推荐
Contract.Requires(customer != null);
然后,您可以启用运行时检查系统,该系统将尽早捕获这些缺陷,从而引导您诊断和纠正有缺陷的代码.但是,不要让我给你的印象是代码契约只是一种替换参数null异常的奇特方式.它们比那更强大.使用Microsoft代码协定,您还可以运行静态检查程序,并要求它调查代码中可能发生空引用异常的可能站点.静态检查器需要更多经验才能轻松使用.我不会先为初学者推荐它.但随意尝试一下,亲眼看看吧.
关于空引用错误是否是一个重要问题,在这个主题中存在一些争论.一个冗长的答案如下.对于那些不想涉及的人,我将总结一下.
微软领先的Spec#和代码合同项目程序正确性的研究人员认为这是一个值得解决的问题.
Bertrand Meyer博士和ISE软件工程师团队开发并支持Eiffel编程语言,他们也认为这是一个值得解决的问题.
在我自己开发普通软件的商业经验中,我经常看到空引用错误,我想在我自己的产品和实践中解决这个问题.
多年来,微软一直致力于旨在提高软件质量的研究.他们的一项努力是Spec#项目.我认为.NET 4.0框架中最令人兴奋的发展之一就是引入了Microsoft代码契约,这是Spec#研究团队早期工作的产物.
关于你的评论"代码中的绝大多数错误都是空引用异常",我相信它是"绝大多数"的限定符会引起一些分歧.短语"绝大多数"表明大概有70-90%的故障将空引用异常作为根本原因.这对我来说似乎太高了.我更喜欢引用Microsoft Spec#的研究.在他们的文章The Spec#programming system:一个概述中,由Mike Barnett,K.Rustan M. Leino和Wolfram Schulte撰写.在CASSIS 2004,LNCS vol.3362,Springer,2004,他们写道
1.0非空类型现代程序中的许多错误表现为空解除错误,这表明编程语言的重要性提供了区分可能评估为空的表达式和那些肯定不会出现的表达式的能力(对于某些实验证据,见[24,22]).实际上,我们希望根除所有null解除引用错误.
对于熟悉此研究的Microsoft人员来说,这可能是一个很好的来源.本文可在Spec#站点获得.
我复制了下面的参考文献22和24,并包含了ISBN以方便您使用.
Manuel Fahndrich和K. Rustan M. Leino.以面向对象的语言声明和检查非null类型.在2003年ACM会议上面向对象编程,系统,语言和应用的会议论文集,OOPSLA 2003,第38卷,SIGPLAN通告第11期,第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卷,SIGPLAN通知第5页,第234-245页.ACM,2002年5月.
我查看了这些参考文献 第一个参考文献表明他们一些实验审查了他们自己的代码,可能存在空参考缺陷.他们不仅找到了几个,而且在很多情况下,潜在的零参考的识别表明设计存在更广泛的问题.
第二个引用没有提供任何关于空引用错误是问题的断言的具体证据.但作者确实表示,根据他们的经验,这些空引用错误是软件缺陷的重要来源.然后,本文继续解释他们如何试图消除这些缺陷.
我还记得ISE在最近发布的Eiffel上发布的一则声明中看到了一些相关信息.他们将这个问题称为"无效安全",就像Bertrand Meyer博士启发或开发的许多事情一样,他们对这个问题有着雄辩和教育性的描述,以及他们如何用他们的语言和工具来预防这个问题.我建议您阅读他们的文章 http://doc.eiffel.com/book/method/void-safety-background-definition-and-tools 以了解更多信息.
如果您想了解有关Microsoft代码合同的更多信息,最近会出现大量文章.您还可以在http:SLASH SLASH codecontracts.info上查看我的博客,该代码主要用于通过使用合同编程来讨论软件质量.
除了上述(空对象,空集合)之外,还有一些通用技术,即资源获取是来自C++的初始化(RAII)和来自Eiffel的Design By Contract.归结为:
使用有效值初始化变量.
如果变量可以为null,则检查null并将其视为特殊情况或期望空引用异常(并处理该异常).断言可用于测试开发构建中的合同违规.
我看过很多看起来像这样的代码:
if((value!= null)&&(value.getProperty()!= null)&& ... &&(... doSomethingUseful())
很多时候这是完全没必要的,大多数测试可以通过更严格的初始化和更严格的合同定义来删除.
如果这是您的代码库中的问题,那么有必要在每种情况下理解null表示的内容:
如果null表示空集合,请使用空集合.
如果null表示异常情况,则抛出异常.
如果null表示意外未初始化的值,则显式初始化它.
如果null表示合法值,则测试它 - 或者甚至更好地使用执行null操作的NullObject.
实际上,这种设计级别的清晰度标准非常重要,需要付出努力和自律才能始终如一地应用于您的代码库.
你没有.
或者更确切地说,尝试在C#中"防止"NRE并没有什么特别之处.在大多数情况下,NRE只是某种类型的逻辑错误.您可以通过检查参数和拥有大量代码来在接口边界处对这些进行防火墙处理
void Foo(Something x) { if (x==null) throw new ArgumentNullException("x"); ... }
在整个地方(大部分.Net Framework都这样做),所以当你搞砸了,你会得到一个更具信息性的诊断(尽管堆栈跟踪更有价值,NRE也提供了这一点).但你仍然只是一个例外.
(旁白:像这样的例外 - NullReferenceException,ArgumentNullException,ArgumentException,... - 通常不应该被程序捕获,而只是意味着"这个代码的开发者,有一个错误,请修复它."我指的是这些作为"设计时间"异常;将这些与运行时环境(例如FileNotFound)导致的真正"运行时"异常进行对比,并且可能被程序捕获和处理.)
但是在一天结束时,你只需要正确编码即可.
理想情况下,大多数NRE永远不会发生,因为'null'对于许多类型/变量来说是一个荒谬的值,理想情况下,静态类型系统会禁止'null'作为这些特定类型/变量的值.然后编译器会阻止您引入这种类型的意外错误(排除某些类型的错误是编译器和类型系统最擅长的).这是某些语言和类型系统擅长的地方.
但是如果没有这些功能,您只需测试代码以确保没有此类错误的代码路径(或者可能使用一些可以为您进行额外分析的外部工具).
使用Null对象模式是关键.
确保在未填充集合时要求集合为空,而不是为null.当空集合执行时使用空集合会让人感到困惑并且通常是不必要的.
最后,我尽可能在构造时使我的对象断言为非null值.这样我以后无疑会知道值是否为null,并且只需要在必要时执行空值检查.对于我的大多数字段和参数,我可以假设基于先前的断言,值不为空.
您可以在导致异常之前轻松检查空引用,但通常这不是真正的问题,因此您最终会抛出异常,因为代码在没有任何数据的情况下无法真正继续.
通常主要的问题不是你有一个空引用,而是你首先得到一个空引用.如果引用不应该为null,则在没有适当引用的情况下,不应超过引用初始化的点.