提问人:Nathan W 提问时间:10/14/2008 最后编辑:razlebeNathan W 更新时间:5/24/2011 访问量:1547
在函数中使用参数之前,我是否应该确保参数不为空?
Should I make sure arguments aren't null before using them in a function?
问:
这个标题可能并不能真正解释我真正想表达的意思,真的想不出一种方法来描述我的意思。
我想知道在使用函数之前检查函数接受的参数是否为空或空是否是一种好的做法。我有这个函数,它只是像这样包装一些哈希创建。
Public Shared Function GenerateHash(ByVal FilePath As IO.FileInfo) As String
If (FilePath Is Nothing) Then
Throw New ArgumentNullException("FilePath")
End If
Dim _sha As New Security.Cryptography.MD5CryptoServiceProvider
Dim _Hash = Convert.ToBase64String(_sha.ComputeHash(New IO.FileStream(FilePath.FullName, IO.FileMode.Open, IO.FileAccess.Read)))
Return _Hash
End Function
正如你所看到的,我只是接受一个IO。Fileinfo 作为参数,在函数开始时,我正在检查以确保它不是一无是处。
我想知道这是好的做法,还是我应该让它到达实际的哈希器,然后抛出异常,因为它是空的。?
谢谢。
答:
如果 NULL 是不可接受的输入,则引发异常。你自己,就像你在示例中所做的那样,这样消息就很有帮助。
处理 NULL 输入的另一种方法是依次使用 NULL 进行 repont。取决于函数的类型 -- 在上面的示例中,我将保留异常。
如果它是面向外部的 API,那么我会说您要检查每个参数,因为输入不可信。
但是,如果它只打算在内部使用,那么输入应该能够被信任,您可以为自己节省一堆不会为软件增加价值的代码。
一般来说,我建议在使用公共函数/方法之前验证所有参数是一种很好的做法,并且尽早失败,而不是在执行一半的函数之后失败。在这种情况下,抛出异常是正确的。
根据您的方法正在做什么,尽早失败可能很重要。如果您的方法正在更改类上的实例数据,则不希望它更改一半的数据,然后遇到 null 并引发异常,因为对象的数据可能处于中间状态,并且可能处于无效状态。
如果您使用的是 OO 语言,那么我建议验证公共方法的参数是必不可少的,但对于私有和受保护的方法则不那么重要。我在这里的理由是,你不知道公共方法的输入是什么——任何其他代码都可以创建你的类的实例并调用它的公共方法,并传入意外/无效的数据。但是,私有方法是从类内部调用的,并且该类应该已经验证了内部传递的任何数据。
大多数时候,让它抛出异常是相当合理的,只要你确定异常不会被忽略。
但是,如果您可以向其添加某些内容,那么用更准确的异常包装并重新抛出它并没有什么坏处。解码“NullPointerException”将比“IllegalArgumentException(”FilePath MUST be supplied“)”(或其他什么)花费更长的时间。
最近我一直在开发一个平台,在测试之前,你必须运行一个混淆器。每个堆栈跟踪看起来都像猴子在随机输入废话,所以我养成了一直检查我的论点的习惯。
我很想在变量和参数上看到一个“nullable”或“nonull”修饰符,以便编译器可以为您检查。
是的,最好在方法开头验证所有参数并引发适当的异常,例如 ArgumentException、ArgumentNullException 或 ArgumentOutOfRangeException。
如果该方法是私有的,因此只有您(程序员)可以传递无效的参数,则可以选择断言每个参数都有效 (Debug.Assert) 而不是 throw。
在C++中,我最喜欢的技术之一是DEBUG_ASSERT NULL 指针。这是高级程序员向我灌输的(以及一致性正确性),也是我在代码审查期间最严格的事情之一。我们从来不会在不首先断言指针不为 null 的情况下取消引用指针。
调试断言仅对调试目标有效(它在发布中被剥离),因此在生产中没有额外的开销来测试数千个 if。通常,它会引发异常或触发硬件断点。我们甚至有系统会抛出一个调试控制台,其中包含文件/行信息以及忽略断言的选项(一次或无限期地用于会话)。这是一个非常棒的调试和 QA 工具(我们会在测试人员屏幕上获得带有断言的屏幕截图,以及有关程序在忽略时是否继续的信息)。
我建议断言代码中的所有不变量,包括意外的 null。如果 if 的性能成为一个问题,请找到一种方法来有条件地编译并使它们在调试目标中保持活动状态。就像源代码控制一样,这种技术挽救了我的屁股,而不是让我感到悲伤(这是任何开发技术中最重要的试金石)。
如果您正在编写公共 API,请帮助调用者帮助他们快速找到 bug,并检查有效的输入。
如果您正在编写调用方可能不受信任的 API(或调用方的调用方),请检查有效输入,因为它具有良好的安全性。
如果你的 API 只能由受信任的调用方访问,比如 C# 中的“internal”,那么你就不必编写所有这些额外的代码。它对任何人都没有用。
您应该根据您在该函数中对参数值所做的一组假设来检查所有参数。
就像你的例子一样,如果你的函数的 null 参数没有任何意义,并且你假设任何使用你的函数的人都会知道这一点,那么被传递一个 null 参数会显示某种错误和采取的某种操作(例如,抛出异常)。如果你使用断言(正如詹姆斯·法塞特(James Fassett)在我之前所说的那样;-)),它们在发布版本中不会花费你任何费用。(在调试版本中,它们几乎不花钱)
同样的事情也适用于任何其他假设。
如果你生成错误,跟踪错误会比把它留给一些标准库例程来抛出异常更容易。您将能够提供更多有用的上下文信息。
它超出了这个问题的范围,但你确实需要公开你的函数所做的假设 - 例如,通过你的函数的注释头。
根据 Andrew Hunt 和 David Thomas 的 The Pragmatic Programmer 的说法,调用者有责任确保它提供有效的输入。因此,您现在必须选择是否将 null 输入视为有效。除非将 null 视为有效输入具有特定意义(例如,如果您正在测试相等性,将 null 视为合法输入可能是个好主意),否则我会认为它是无效的。这样,当您的程序遇到错误的输入时,它将更快地失败。如果您的程序将遇到错误情况,您希望它尽快发生。如果你的函数无意中被传递了一个 null,你应该认为这是一个错误,并做出相应的反应(即,你应该考虑使用一个终止程序的断言,而不是抛出异常,直到你释放程序)。
经典的合同设计:如果输入正确,输出将正确。如果输入错误,则存在错误。(如果输入正确但输出错误,则存在错误。这是一个噱头。
评论
我将对 Brian 之前提供的合同建议的出色设计添加一些详细说明(粗体)......
“按合同设计”的原则要求您定义调用方可以接受传入的内容(输入值的有效域),然后,对于任何有效的输入,方法/提供程序将执行的操作。
对于内部方法,可以将 NULL 定义为有效输入参数的域之外。在这种情况下,您将立即断言输入参数值不是 NULL。此协定规范中的关键见解是,任何传入 NULL 值的调用都是调用方的错误,并且 assert 语句抛出的错误是正确的行为。
现在,虽然定义非常明确且简洁,但如果您将该方法暴露给外部/公共调用者,您应该问问自己,这是我/我们真正想要的契约吗? 可能不是。在公共接口中,您可能会接受 NULL(从技术上讲,在方法接受的输入域中),但随后拒绝使用返回消息正常处理。(更多的工作来满足自然更复杂的面向客户的要求。
无论哪种情况,您所追求的都是从调用方和提供者的角度处理所有情况的协议,而不是大量的散射测试,这些测试可能使评估合同条件覆盖范围的完整性或缺乏完整性变得困难。
上一个:Rails 中的多页表单
评论