我什么时候应该防范null
论点?理想情况下,我会在任何地方防范null
,但这会变得非常臃肿和乏味。我还注意到,人们并没有像AsyncCallback
s那样放置警卫。
为了避免让那些带有大量单一代码的人烦恼,有什么可以接受的标准,我应该防范null
吗?
感谢。
答案 0 :(得分:5)
我经常使用的一种方法是null object pattern. 例如,如果a有一个工厂类,它基于一个参数返回一个接口的不同实现,并且提供的参数没有映射到任何实现,我会返回一个NullObject, e.g。
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
获取CreateFoo
时,我不必检查返回的对象是否为空。
显然,这只是众多方法中的一种。没有“一刀切”,因为null可能意味着不同的东西。
防止空参数的另一种方法是使用CodeContract preconditions。 e.g。
public void Foo(Bar x){
Contract.Requires<ArgumentNullException>( x != null, "x" );
//access x
}
使用代码约定可以对代码运行静态代码分析并捕获Foo(null)
等错误。 (more here)
另外一个原因是使用一个非常简单的通用扩展方法:
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");
}
答案 1 :(得分:4)
我什么时候应该防范空参数?
我假设您正在讨论传递给公共方法或您编写的代码构造函数的null参数。请注意,每当调用任何可能返回null的外部依赖项时,您也可能必须“防范”null,除非您的代码可以正常处理这些情况。
您应该在任何公共方法(包括构造函数和属性设置器)中防止null,这些方法是在null值没有有用和显式含义的用户处公开的。如果一个空值对你的代码没有什么特别的意义(例如数组结尾,“未知”等)那么你就不应该接受那个值,而应该抛出一个ArgumentNullException
。
此规则也不是null
唯一的。您应该始终检查传递给公共方法的参数。
例如,假设您正在编写某种带有用户ID的Web服务方法,并对用户执行某些操作(例如删除它们)。在方法对该用户执行任何其他操作之前,您应该验证它是否是有效的用户ID。另一个例子是,如果您正在编写一个将索引作为集合或数组的公共方法。您应该检查索引是否在允许的范围内 - 索引不大于集合,或者小于零。
我还注意到人们并没有像AsyncCallbacks那样放置警卫。
如果您知道传递给您的方法的参数会保持您的方法前置条件(因为如果不是则抛出异常),那么您可以在私有和内部方法中跳过这些检查,或者私人和内部课程。
但是正如您所指出的那样,您仍然必须小心不要相信您未编写的API的任何返回值,或者传递给回调方法的任何值。将它们视为“脏”,并假设它们可以为空或无效值。
变得非常臃肿和乏味
指定和跟踪公共方法的前提条件并不是膨胀 - 它是编译文档。这是您确保代码正确的方式。这是你的代码的用户如何被告知他们做错了什么。让这个在你的方法中间失败(或者在一些模糊相关的类中的另一个方法中)会使调试你的问题变得更加困难。
现在这似乎不是什么大问题。但是,一旦你开始收到堆栈中NullReferenceException
5级的客户的投诉,之后会有20个方法调用,那么我认为你会开始看到这些好处:)
为了避免让那些有大量单一代码的人烦恼,有什么可以接受的标准,我应该防范null吗?
通常,人们只需在其方法的顶部编写if ... throw
代码。这是最习惯的语法,即使对于初学者也很容易理解。有点像Lisp中的parens,一旦你使用了这种模式,你就可以很快地浏览它,而不用考虑它。
使用Visual Studio Code Snippets编写这些检查可以更快。
您可以通过使用或构建一些支持断言语法的共享代码来缩短此代码。您可以编写类似if ... throw
的行,而不是Assert.NotNull(arg1, "arg1");
语法。如果您需要一些灵感,可以查看NUnit框架的assertions和constraints。
您可能还想查看Code Contracts API。它设计用于检查前置条件,后置条件和不变量(这些是“保护条件”的正式名称)。它还可以将一些验证从运行时间转移到编译时,这样您就可以在运行程序之前发现自己犯了错误。我没有真正看过它,但它也可能会给你更简洁的语法。 修改:另请参阅Pencho Ilchev's answer,了解使用此API的一部分的简短示例。
答案 2 :(得分:1)
必须检查公共API。对于开发人员来说,如果他们知道他们因为null参数而导致错误而不是尝试通过该null参数调试一些模糊的其他异常,那么开发人员会更容易(也更安全)。
在内部,构造函数是一个检查空值的好地方,原因相同 - 在构造函数中捕获它比在类上调用方法时更容易。
有些人认为在任何地方都可以检查,但我倾向于认为在实际阅读代码时会有更多代码需要筛选。
答案 3 :(得分:1)
有时我创建一个Preconditions
类,其中包含用于检查一些常见方法前置条件的静态方法,即null
参数 - 例如:
public static <T> T checkNull(T arg) {
if (arg == null)
throw new IllegalArgumentException();
return arg;
}
这可以让你的代码更清洁。
至于 ,你应该检查null
,它应该在不是有效值的地方完成。
修改强>
注意到这个问题被标记为C#,但你明白了......
答案 4 :(得分:1)
如果您正在讨论方法参数,那么您可以选择。您可以检查参数并抛出ArgumentNullException
,或者您可以忽略该检查并让其他内容进一步抛出NullReferenceException
。在不检查参数的情况下运行的风险是您的代码可能会在抛出NullReferenceException
之前更改某些全局状态,从而使程序处于未知状态。
通常,检查参数并记录您的方法抛出ArgumentNullException
可能更好。是的,这是一个更多的工作,在一个方法的开头趟过这些检查可能会很烦人。你必须问自己,你得到的更强大的代码是否是一个很好的权衡。
有关此问题的详细讨论,请参阅This link。