null对象与空对象

[这是最佳实践的结果:函数应该返回null还是空对象? 但我想要非常一般。 ]

在我看过的许多遗留(生产)C ++代码中,有一种趋势是编写大量NULL (或类似)检查来测试指针。 在添加NULL时,许多这些都会在发布周期结束时添加 – 检查可以快速解决由指针引用引起的崩溃问题 – 而且没有太多时间进行调查。

为了解决这个问题,我开始编写采用( const )引用参数的代码,而不是传递指针的(更多)常见技术。 没有指针,不想检查NULL (忽略实际具有空引用的极端情况)。

在C#中,存在相同的C ++“问题”:希望检查每个未知引用对nullArgumentNullException )并通过添加null检查来快速修复NullReferenceException

在我看来,防止这种情况的一种方法是首先使用空对象( String.EmptyEventArgs.Empty )来避免空对象。 另一种方法是抛出exception而不是返回null

我刚开始学习F#,但看起来那个环境中的空对象要少得多。 那么也许你真的不需要有很多null引用?

我在这里吠叫错了吗?

我倾向于怀疑具有大量NULL的代码,并尝试在可能的情况下将它们重构为exception,空集合等等。

Martin Fowler的重构 (第260页)中的“引入空对象”模式也可能有所帮助。 Null对象响应真实对象的所有方法,但是以“做正确的事”的方式。 因此,不要总是检查Order以查看order.getDiscountPolicy()是否为NULL,请确保在这些情况下Order具有NullDiscountPolicy。 这简化了控制逻辑。

为了避免NullReferenceException而传递非null是一个直接的,易于解决的问题(“它爆炸,因为它为空”),这是一个更加微妙,难以调试的问题(“几个调用堆栈的东西”不是表现得像预期的那样,因为早些时候它有一些没有有意义的信息但不是空的对象“)。

NullReferenceException是一件很棒的事情 ! 它失败,大声,快速,几乎总是快速,容易地识别和修复。 这是我最喜欢的例外,因为我知道当我看到它时,我的任务只需要大约2分钟。 将此与令人困惑的质量保证或客户报告进行对比,试图描述必须复制并追溯到原点的奇怪行为。 呸。

这一切都归结为你作为一种方法或一段代码,可以合理地推断你所谓的代码。 如果你被提交了一个空引用,并且你可以合理地推断出调用者可能意味着什么(例如,可能是一个空集合?)那么你绝对应该只处理空值。 但是,如果您无法合理地推断如何处理null,或者调用者的意思是null(例如,调用代码告诉您打开文件并将该位置指定为null),则应抛出ArgumentNullException 。

在每个“网关”点维护这样的正确编码实践 – 代码中function的逻辑界限 – NullReferenceExceptions应该更加罕见。

Null得到我的投票。 再说一遍,我是’快速失败’的心态。

String.IsNullOrEmpty(…)也非常有用,我想它可以捕获这两种情况:null或空字符串。 您可以为您传递的所有课程编写类似的function。

如果您正在编写将null作为错误条件返回的代码,那么请不要:通常,您应该抛出exception – 更难以错过。

如果您正在使用您担心可能返回null的代码,那么大多数这些都是愚蠢的exception :也许在调用者处执行一些Debug.Assert检查以在开发期间检测输出。 你不应该在生产中真正需要大量null检查,但是如果某些第三方库以不可预测的方式返回大量的null ,那么确定:执行检查。

在4.0中,您可能希望查看代码合同; 这给了你更好的控制来说“这个参数永远不应该作为null传递”,“这个函数永远不会返回null”等等 – 让系统在静态分析期间(即你建立时)validation这些声明。

关于null的事情是它没有意义。 它只是缺少一个对象。

所以,如果你真的是一个空字符串/集合/什么,总是返回相关的对象,永远不会为null 。 如果有问题的语言允许您指定,请执行此操作。

如果您想要返回的内容不是静态类型可指定的值,那么您有许多选项。 返回null是一个答案,但没有意义是有点危险的。 抛出exception可能实际上就是你的意思。 您可能希望扩展具有特殊情况的类型(可能具有多态性,即特殊情况模式(其特殊情况是Null对象模式))。 您可能希望将返回值包装在具有更多含义的类型中。 或者您可能想传入一个回调对象。 通常有很多选择。

我会说这取决于。 对于返回单个对象的方法,我通常会返回null。 对于返回集合的方法,我通常会返回一个空集合(非null)。 不过,这些更符合指导方针而非规则。

如果你真的想要在“ null ”环境中编程,考虑更频繁地使用扩展方法,它们不受NullReferenceExceptions的影响,并且至少“假装” null不再存在:

 public static GetExtension(this string s) { return (new FileInfo(s ?? "")).Extension; } 

可以称为:

 // this code will never throw, not even when somePath becomes null string somePath = GetDataFromElseWhereCanBeNull(); textBoxExtension.Text = somePath.GetExtension(); 

我知道,这只是方便而且许多人正确地认为它违反了OO原则(虽然OO的“创始人”Bertrand Meyer认为null并完全放弃了他的OO设计,这适用于Eiffel语言,但是那是另一个故事)。 编辑:丹提到比尔瓦格纳( 更有效的C# )认为这是不好的做法,他是对的。 曾经考虑过IsNull扩展方法;-)?

为了使代码更具可读性,可能会有另一个提示:当对象为null时,更频繁地使用null-coalescing运算符来指定默认值:

 // load settings WriteSettings(currentUser.Settings ?? new Settings()); // example of some readonly property public string DisplayName { get { return (currentUser ?? User.Guest).DisplayName } } 

这些都不会偶尔检查null (并且??只是一个隐藏的if-branch)。 我更喜欢尽可能少的代码null ,因为我相信它使代码更具可读性。 当我的代码混乱使用if语句为null ,我知道设计中有一些错误,我重构。 我建议任何人都这样做,但我知道这个问题的意见差异很大。

(更新)与例外的比较

到目前为止,讨论中没有提到与exception处理的相似性。 当你发现自己无处不在时忽略null只要你认为它是在你的方式,它基本上与写作相同:

 try { //...code here... } catch (Exception) {} 

它具有删除任何exception跟踪的效果,只是为了找到它会在代码中引发不相关的exception。 虽然我认为避免使用null是好的,如前面提到的那样,在特殊情况下为null是好的。 只是不要将它们隐藏在null-ignore-blocks中,它最终会产生与catch-all-exceptions块相同的效果。

对于例外主角,他们通常源于交易编程和强大的exception安全保证或盲目指导。 在任何体面的复杂性,即。 异步工作流,I / O,尤其是网络代码,它们根本就不合适。 您之所以在C ++中看到关于此问题的Google风格文档,以及所有良好的异步代码“没有强制执行它”(也想想您最喜欢的托管池)。

它还有更多内容,虽然它可能看起来像一个简化,但它确实很简单。 对于一个你会得到很多例外的东西,不是为了重大的例外使用..无论如何我离题,从世界顶级图书馆设计师那里阅读,通常的地方是提升(只是不要混淆它与另一个喜欢exception的阵营,因为他们不得不写音乐软件:-)。

在你的实例中,这不是Fowler的专业知识,一个有效的“空对象”成语只有在C ++中才有可能由于可用的转换机制(可能但肯定不是总是通过支配 )。另一方面,在你的null 类型中你有能力抛出exception并做任何你想做的事情,同时保留干净的呼叫站点和代码结构。

在C#中,您的选择可以是一个好的或错误的类型的单个实例; 因此它能够投掷接受或简单地按原样运行。 所以它可能会或可能不会违反其他合同(根据您所面临的代码质量,您认为最好的是合适的)。

最后,它确实清理了调用站点,但是不要忘记你将面临与许多库的冲突(特别是从容器/词典返回,结束迭代器的想法,以及任何其他’接口’代码到外部世界)。 null-as-value检查是非常优化的机器代码片段,需要记住,但我会同意任何一天野生指针使用而不理解constness,引用和更多将导致不同类型的可变性,别名和性能问题。

要添加,没有银弹,并且在空引用上崩溃或在托管空间中使用空引用,或抛出和不处理exception是一个完全相同的问题,尽管托管和exception世界会试图向您推销。 任何体面的环境都可以提供保护(你可以在你想要的任何操作系统上安装任何filter,你认为虚拟机还做什么),而且还有很多其他的攻击媒介,这个被过分苛刻。 再次从谷歌进入x86validation,他们自己的做法更快更好’IL’,’动态’友好代码等。

本着你的直觉,权衡利弊和本地化效果..将来你的编译器将优化所有检查,并且比任何运行时或编译时人工方法更有效(但不是很容易交叉 – 模块交互)。

您不能总是返回一个空对象,因为并不总是定义’empty’。 例如,int,float或bool为空是什么意思?

返回NULL指针不一定是坏习惯,但我认为返回(const)引用(当然这样做有意义)是一种更好的做法。

最近我经常使用Fallible类:

 Fallible theName = obj.getName(); if (theName) { // ... } 

有这样一个类的各种实现(检查谷歌代码搜索), 我也创建了自己的 。

空对象如何比空对象更好 ? 你只是重命名症状。 问题是你的函数的契约定义太松散了“这个函数可能会返回一些有用的东西,或者它可能会返回一个虚拟值”(其中虚拟值可能为null,一个“空对象”或一个魔术常量,如-1但无论你如何表达这个虚拟值,呼叫者仍然必须在使用返回值之前检查它。

如果要清理代码,解决方案应该是缩小函数范围,使其首先不返回虚拟值。

如果你有一个可能返回一个值的函数,或者什么都不返回,那么指针就是一种表达它的常用(和有效)方式。 但通常情况下,您的代码可以重构,以便消除这种不确定性。 如果你能保证函数返回一些有意义的东西,那么调用者可以依赖它返回有意义的东西,然后他们就不必检查返回值。

我尽量避免从方法中返回null。 通常有两种情况 – 当null结果是合法的,何时不应该发生。

在第一种情况下,当没有结果是合法的时,有几种解决方案可用于避免空结果和与它们相关联的空检查: 空对象模式和特殊情况模式用于返回不执行任何操作的替代对象,或执行某些特定操作在特定情况下的事情。

如果没有返回任何对象是合法的,但仍然没有合适的Null对象或特殊情况替代品,那么我通常使用Optionfunction类型 – 我可以在没有合法结果时返回一个空选项。 然后由客户决定什么是处理空选项的最佳方法。

最后,如果从方法返回任何对象是不合法的,只是因为如果缺少某些东西,该方法无法产生结果,那么我选择抛出exception并进一步执行。