我什么时候应该提防null
争论?理想情况下,我会在任何地方都提防null
,但这会变得非常臃肿和乏味。我还注意到人们并没有在AsyncCallback
s 之类的东西中设置警卫。
为了避免用大量单一的代码惹恼其他人,是否有任何公认的标准来说明我应该在哪里防范null
?
谢谢。
我什么时候应该提防null
争论?理想情况下,我会在任何地方都提防null
,但这会变得非常臃肿和乏味。我还注意到人们并没有在AsyncCallback
s 之类的东西中设置警卫。
为了避免用大量单一的代码惹恼其他人,是否有任何公认的标准来说明我应该在哪里防范null
?
谢谢。
我经常使用的一种方法是空对象模式。 例如,如果一个工厂类根据参数返回接口的不同实现,并且提供的参数未映射到任何实现,我将返回 NullObject,例如
public interface IFoo{
void Bar();
}
public class NullFoo{
public void Bar(){
//null behaviour
}
}
public class FooFactory{
public IFoo CreateFoo(int i){
switch(i){
case 1:
return new OneFoo();
break;
case 2:
return new TwoFoo();
break;
default:
return new NullFoo();
break;
}
}
}
当我想得到一个IFoo
fromCreateFoo
时,我不必检查返回的对象是否为空。
显然,这只是众多方法中的一种。没有“一刀切”,因为 null 可能意味着不同的东西。
防止空参数的另一种方法是使用CodeContract 前置条件。例如
public void Foo(Bar x){
Contract.Requires<ArgumentNullException>( x != null, "x" );
//access x
}
使用代码契约允许您对代码运行静态代码分析并捕获诸如Foo(null)
. (更多在这里)
这样做的另一个原因是使用一种非常简单的通用扩展方法:
public static class Ex
{
public static void EnsureNotNull<T>(this T t, string argName) where T:class
{
if(t == null)
{
throw new ArgumentNullException(argName);
}
}
}
然后你可以像这样检查你的论点:
public void Foo(Bar x, Bar y){
x.EnsureNotNull("x");
y.EnsureNotNull("y");
}
我什么时候应该防范空参数?
我假设您正在谈论传递给您编写的代码的公共方法或构造函数的空参数。请注意,当您调用任何可能返回 null 的外部依赖项时,您可能还必须“防范” null,除非您的代码可以优雅地处理这些情况。
您应该在向用户公开的任何公共方法(包括构造函数和属性设置器)中防止 null 值没有有用和明确的含义。如果空值对您的代码没有特殊意义(例如数组结尾、“未知”等),那么您不应接受该值,而应抛出一个ArgumentNullException
。
这条规则也不是唯一的null
。您应该始终检查传递给您的公共方法的参数。
例如,假设您正在编写某种 Web 服务方法,该方法确实采用用户 ID,并对用户执行某些操作(例如删除它们)。在您的方法对该用户执行任何其他操作之前,您应该验证它是一个有效的用户 ID。另一个示例是,如果您正在编写一个公共方法,该方法采用集合或数组的索引。您应该预先检查索引是否在允许的范围内 - 索引不大于集合或小于零。
我还注意到人们并没有在 AsyncCallbacks 之类的东西中设置警卫。
如果您知道传递给您的方法的参数会警惕地保留您的方法前置条件(因为如果不是,您会抛出异常),那么您可以在私有和内部方法或私有和内部方法中自由跳过这些检查类。
但正如您所指出的,您仍然必须小心不要相信来自您未编写的 API 的任何返回值,或传递给您的回调方法的任何值。将它们视为“脏”,并假设它们可以是空值或无效值。
这变得非常臃肿和乏味
为您的公共方法指定和跟踪前置条件并不臃肿——它是编译文档。这是您确保代码正确的方式。这是预先告知您的代码的用户他们做错了什么的方式。让它在你的方法中间失败(或者可能在一些模糊相关的类中的另一个方法中)使得调试你的问题变得更加困难。
现在这似乎没什么大不了的。但是,一旦您开始收到客户的投诉,NullReferenceException
堆栈中的级别降低了 5 个,之后调用了 20 个方法,那么我认为您将开始看到好处:)
为了避免用大量单一的代码惹恼其他人,是否有任何公认的标准来说明我应该在哪里防范 null?
通常人们只是if ... throw
在他们的方法的顶部编写代码。这是最惯用的语法,即使是初学者也很容易理解。有点像Lisp中的括号,一旦你使用了那个模式,你就可以非常快速地浏览它,而不用考虑它。
您可以使用 Visual Studio Code Snippets加快编写这些检查的速度。
您可以通过使用或构建一些支持断言语法的共享代码来稍微缩短此代码。代替if ... throw
语法,你会写一行像Assert.NotNull(arg1, "arg1");
. 如果您想要一些灵感,您可以查看NUnit框架的断言和约束。
您可能还想查看Code Contracts API。它设计用于检查前置条件、后置条件和不变量(它们是这些“保护条件”的正式名称)。它还可以将某些验证从运行时转移到编译时,因此您甚至可以在运行程序之前发现自己犯了错误。我还没有真正看过它,但它也可能会给你更简洁的语法。 编辑:另请参阅Pencho Ilchev 的回答,了解使用部分 API 的简短示例。
公共 API 的空值检查是必须的。如果开发人员立即知道由于 null 参数而出现错误,而不是尝试调试由于该 null 参数而导致的一些晦涩的其他异常,那么对于开发人员来说会更容易(也更安全)。
在内部,构造函数是检查空值的好地方,原因相同 - 在构造函数中捕获它比在类上调用方法时更容易。
有人认为到处检查是件好事,但我倾向于认为在实际阅读代码时,它会筛选更多代码。
有时我会创建一个Preconditions
包含静态方法的类,用于检查一些常用方法的先决条件,即null
参数——例如:
public static <T> T checkNull(T arg) {
if (arg == null)
throw new IllegalArgumentException();
return arg;
}
这使您的代码更简洁。
至于您应该在哪里检查null
,应该在它不是有效值的地方进行检查。
编辑
注意到这个问题被标记为 C#,但你明白了......
如果您在谈论方法参数,您可以选择。您可以检查参数并 throw ArgumentNullException
,或者您可以忽略检查并让其他东西NullReferenceException
进一步抛出。在不检查参数的情况下运行的风险是,您的代码可能会在NullReferenceException
抛出之前更改某些全局状态,从而使程序处于未知状态。
一般来说,最好检查你的方法抛出的参数和文档ArgumentNullException
。是的,这需要更多的工作,并且在方法开始时通过这些检查可能会很烦人。你必须问问自己,你得到的更健壮的代码是否是一个很好的权衡。
有关此问题的详细讨论,请参阅此链接。