我今天正在阅读Uncle Bob关于异常处理的书,我可以从处理空值中回忆起那些方法不应该处理空值,因为它会使代码混乱。我有点困惑。 我一直认为一个方法应该始终确保它的依赖关系不是null(除非它们在构造函数和构造函数中注入了可空性)。 例如,如果我有方法
public void SendMessage(IEmailSender emailSender, contactList list)
{
if(emailSender == null)
{
throw new ArgumentNullException("Failed to send
message.",MethodBase.GetCurrentMethod().GetParameters[0].Name);
}
if(list == null)
{
throw new ArgumentNullException("Failed to send
message.",MethodBase.GetCurrentMethod().GetParameters[1].Name);
}
// rest of code goes here
}
我错过了什么吗?
答案 0 :(得分:3)
我还没有看过这本书,但我只能想象Bob叔叔主张使用Null Object Pattern而不是显式空引用处理。
例如,,而不是
if(log != null)
log.Write("My log message");
您可以创建一个包含ILogger
方法的Write
接口,并创建两个实现此接口的类:NullLogger
和FileLogger
。对于NullLogger
方法的实现,Write
将有一个空主体。
在我看来,这与您在示例中的明确的前置条件验证不同
答案 1 :(得分:3)
有两种观点:
一方面,通过调用您的方法,您可以通过自己的方法告诉来电者他究竟做错了什么。这对调用者来说非常好,因为他可以在他获得异常时立即修复它。如果你编写第三方使用的API代码,这将是真实有效的。
另一方面,如果你自己调用你的方法,抛出异常的合理参数是,因为你希望能够在你的调用代码中使用catch块来处理这种情况!如果你没有理由在某个地方处理它,为什么要抛出异常呢?我看到的唯一原因是,通过在GlobalExceptionHandler中捕获这些异常来进行详细的错误记录。
所以你看到我们在这里有两类异常:一个用于开发人员,避免错误使用API,另一个用作方法的错误结果。
如果您正在编写将由他人使用的API代码,您的选择将是,而不是听Bob; - )
对于那些没有阅读过CleanCode的人,Bob建议做两件事:
1.您不应该编写返回null 的方法(以避免之后进行不必要的检查)。所以不要写这个:
var myObject = GetObjectThatDoesSomthing();
if(myObject != null)
{
myObject.DoSomething();
}
......你应该写下这个:
var myObject = GetObjectThatDoesSomething();
myObject.DoSomething();
清洁。
2.您不应该将null 传递给您的方法,以避免在方法开头进行不必要的检查,例如:
public Point Add(Point p1, Point p2)
{
if(p1 == null) throw ArgumentException();
if(p2 == null) throw ArgumentException();
...
}
这些规则的要点是:如果你坚持使用它,你知道你不必编写这些空检查,你的代码变得更清晰,更容易阅读。但是,在您使用第三方代码的那一刻,您无法判断他们是否在其API中应用了相同的规则,因此您将开始再次进行预检或后检查。当你为其他人编写API时,同样的事情:你的API的消费者如何知道你已经用Bobs规则编码了......
答案 2 :(得分:1)
这取决于您编写的代码类型。 如果您的公共方法旨在供不熟悉使用的各种开发人员使用,那么检查参数并抛出冗长的异常总是有意义的。
如果您正在编写一个私有方法,该方法仅在同一个类中使用,或者由您或您的协作者编写的另一个友好类调用的内部函数,则进行偏执无效检查则不太合理。您的注射设计和测试必须确保您的内部不会获得空值。
如果私有/内部方法参数仍然为空,那么它总是太迟了。从私有/内部方法抛出ArgumentNull异常无助于外部用户修复原因,因此对他来说无法获得ArgumentNull或NullReference异常。