引发ArgumentNullException有何帮助?


27

假设我有一个方法:

public void DoSomething(ISomeInterface someObject)
{
    if(someObject == null) throw new ArgumentNullException("someObject");
    someObject.DoThisOrThat();
}

我受过训练,相信抛出的错误ArgumentNullException是“正确的”,但是“对象引用未设置为对象的实例”错误意味着我有一个错误。

为什么?

我知道,如果我要缓存对引用的引用someObject并在以后使用它,那么最好在传入时检查是否为空,并尽早失败。但是,如果我在下一行取消引用,为什么要进行检查?它将以一种方式或另一种方式引发异常。

编辑

这只是我想到的...担心被取消引用的null是否来自不检查您的C ++之类的语言(即,它只是尝试在内存位置0 +方法偏移处执行某些方法)?


明确显示异常有助于确认存在错误的可能性,可以在文档中直接指出。
zzzzBov

Answers:


34

我想,如果您立即取消引用该变量,则可以采用任何一种方法进行辩论,但我仍希望使用ArgumentNullException。
它对正在发生的事情更为明确。异常包含为空的变量名称,而NullReferenceException则不包含。特别是在API级别,ArgumentNullException清楚地表明某些调用者没有遵守方法的约定,并且失败不一定是随机错误或某些更深层次的问题(尽管情况仍然如此)。你应该早点失败。

此外,以后的维护者更有可能做什么?如果他们在第一次取消引用之前添加代码,则添加ArgumentNullException,还是仅添加代码,然后允许抛出NullReferenceException?


通过扩展,如果这是非公共方法,或者是非公共类上的公共方法,那么您认为没有充分的理由进行空检查吗?
Scott Whitlock,

我通常不将这些检查添加到私有方法中,但是为了保持一致,我仍然可以将它们添加到私有类的公共方法中。对于私有方法,我确实倾向于假设参数在某些公开调用级别得到了较早的验证。
Matt H

54

我受过训练,认为抛出ArgumentNullException是“正确的”,但是“对象引用未设置为对象的实例”错误意味着我有一个错误。为什么?

假设我调用M(x)了您编写的方法。我传递空值。我得到一个ArgumentNullException,名称设置为“ x”。那个异常明确地意味着我有一个错误。我不应该为x传递null。

假设我调用了您编写的方法M。我传递空值。我得到一个空的deref异常。我应该怎么知道是否有错误,或者有错误,或者您代表我调用的某些第三方库有错误?我是否需要修复代码还是打电话给您告诉您修复代码,我一无所知。

两种异常都表明存在错误。都不应该在生产代码中抛出任何内容。问题是哪个异常可以更好地向调试人员传达谁的bug?空的deref异常几乎不会告诉您任何信息;参数null异常会告诉您很多信息。善待您的用户;抛出异常,使他们能够从错误中学习。


我不确定我"neither should ever be thrown in production code"是否会使用其他类型的代码/情况?它们应该像这样编译Debug.Assert吗?或者您是说不要将它们扔出API?
StuperUser 2015年

6
@StuperUser:我认为您误解了我的意思,因为我以一种混乱的方式编写了它。你应该throw new ArgumentNullException你的生产代码,它不应该运行测试用例之外。实际不应该抛出异常,因为调用者永远都不会有该错误。
埃里克·利珀特

1
它不仅传达谁的错误,它传达其中的错误是。即使这两个领域都是我的,也很难总是容易地确切了解什么变量为空。
Ohad Schneider

5

关键是,您的代码应该做一些有意义的事情。

A NullReferenceException并不是特别有意义,因为它可能意味着一切。如果不查看引发异常的位置(代码可能对我不可用),我无法弄清楚出了什么问题。

An ArgumentNullException是有意义的,因为它告诉我:操作失败,因为参数someObjectnull。我不需要查看您的代码就可以弄清楚。

引发此异常也意味着,您明确地处理null,即使您执行此操作的唯一方法是说您无法处理它,而让代码崩溃可能意味着您实际上可以处理null ,但忘记执行此案。

异常点是在发生故障的情况下传达信息,可以在运行时进行处理以从故障中恢复,或者在调试时进行处理以更好地了解出了什么问题。


2

我相信唯一的优势是您可以在异常情况下提供更好的诊断。就是说,该函数的调用者正在传递null,而如果让取消引用抛出该错误,则无法确定该调用者是否传递了错误的数据或该函数本身是否存在错误。

如果有人要调用该函数以使其易于使用(如果出现问题,请进行调试)而不在我的内部代码中进行操作,我会这样做,因为运行时仍会对其进行检查。


1

我受过训练,认为抛出ArgumentNullException是“正确的”,但是“对象引用未设置为对象的实例”错误意味着我有一个错误。

如果代码无法按设计工作,则可能会出现错误。NullReferenceException系统会抛出an ,而事实实际上是错误而不是功能。不仅因为您没有定义要处理的特定异常。另外,由于开发人员在阅读您的代码时可能会认为您没有考虑发生的情况。

出于类似的原因,ApplicationException属于您的应用程序,而不是Exception属于操作系统的常规。引发的异常类型指示异常的起源。

如果您考虑了null,则最好通过抛出特定的异常来优雅地处理它,或者如果它是可接受的输入,则不要抛出异常,因为它们很昂贵。通过执行具有良好默认值的操作或根本不执行任何操作(例如,无需更新)来处理它。

最后,不要忽略呼叫者处理情况的能力。通过给他们一个更具体的异常,ArgumentException他们可以构造他们的try / catch以便在更普遍之前处理此错误,NullReferenceException这可能是由其他地方发生的许多其他原因(例如初始化构造函数变量失败)引起的。


1

通过检查可以更清楚地了解正在发生的事情,但是真正的问题来自这样的代码(伪造的)示例:

public void DoSomething(Foo someOtherObject, ISomeInterface someObject)
{
    someOtherObject.DoThisOrThat(this, someObject);
}

这里涉及一个调用链,没有null检查,您只会看到someOtherObject中的错误,而看不到问题首次显现的位置-这立即意味着浪费时间调试或解释了调用堆栈。

通常,最好与这种情况保持一致,并始终添加null检查-这样可以更轻松地发现是否丢失了某个对象,而不是根据个案进行选择(假设您知道null是始终无效)。


1

需要考虑的另一个原因是,如果在使用null对象之前进行了一些处理,该怎么办?

假设您有一个关联的公司ID(Cid)和人员ID(Pid)的数据文件。它被保存为数字对流:

Cid Pid Cid Pid Cid Pid Cid Pid Cid Pid

并且此VB函数将记录写入文件:

Sub WriteRecord(Company As CompanyInfo, Person As PersonInfo)
    Using File As New StringWriter(DataFileName)
        File.Write(Company.ID)
        File.Write(Person.ID)
    End Using
End Sub

如果Person为空引用,则该行将File.Write(Person.ID)引发异常。该软件可以捕获异常并进行恢复,但这会带来问题。编写的公司ID没有关联的人员ID。如果确实如此,那么当您下次调用此函数时,将把公司ID放在期望的上一个人ID的位置。除了该记录不正确之外,每条后续记录还将具有个人ID和公司ID,这是错误的处理方式。

Cid Pid Cid Pid Cid Cid Pid Cid Pid Cid

通过在函数开始时验证参数,可以防止在保证方法失败时进行任何处理。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.