在函数中使用它们之前,我应该确保参数不为null吗?

时间:2008-10-13 22:30:58

标签: validation function exception arguments

标题可能无法解释我真正想要达到的目标,无法真正想出一种描述我的意思的方法。

我想知道在使用它们之前检查函数接受空值或空的参数是否是好的做法。我有这个函数,它只包含一些哈希创建。

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作为参数,在我检查的函数的开头,以确保它不是什么。

我想知道这是一个好习惯,还是我应该让它到达实际的hasher然后抛出异常,因为它是null。?

感谢。

10 个答案:

答案 0 :(得分:20)

一般来说,我建议在使用公共函数/方法之前验证公共函数/方法的所有参数是一种好的做法,并且在执行函数的一半之后而不是早期失败。在这种情况下,你是正确的抛出异常。

根据您的方法正在做什么,早期失败可能很重要。如果您的方法正在改变类的实例数据,则不希望它改变一半数据,然后遇到null并抛出异常,因为对象的数据可能处于中间且可能无效的状态。

如果您正在使用OO语言,那么我建议验证公共方法的参数是必不可少的,但对于私有和受保护的方法则不那么重要。我的理由是你不知道公共方法的输入是什么 - 任何其他代码都可以创建你的类的实例并调用它的公共方法,并传入意外/无效的数据。但是,私有方法是从类内部调用的,并且该类应该已经验证了内部传递的任何数据。

答案 1 :(得分:3)

我最喜欢的C ++技术之一是在NULL指针上使用DEBUG_ASSERT。这是由高级程序员(以及const正确性)钻进我的,并且是我在代码审查期间最严格的事情之一。我们从不取消引用指针而没有先声明它不是null。

调试断言仅对调试目标有效(它在发布时被剥离),因此您在生产中没有额外的开销来测试数千个if。通常,它会抛出异常或触发硬件断点。我们甚至有一些系统会抛出一个带有文件/行信息的调试控制台,以及一个忽略断言的选项(一次或无限期地用于会话)。这是一个非常好的调试和QA工具(我们可以在测试人员屏幕上获取断言的屏幕截图以及如果忽略该程序是否继续的信息)。

我建议在代码中声明所有不变量,包括意外的空值。如果if的性能成为一个问题,找到一种方法来有条件地编译并使它们在调试目标中保持活动状态。就像源代码控制一样,这种技术可以让我更频繁地拯救我的屁股,而不是让我感到悲伤(这是任何开发技术中最重要的试金石)。

答案 2 :(得分:2)

是的,最好在方法开头验证所有参数,并抛出适当的异常,如ArgumentException,ArgumentNullException或ArgumentOutOfRangeException。

如果方法是私有的,只有程序员可以传递无效参数,那么你可以选择断言每个参数都是有效的(Debug.Assert)而不是throw。

答案 3 :(得分:1)

如果NULL是不可接受的输入,则抛出异常。就像你在样本中所做的一样,你自己也可以使用这条信息。

处理NULL输入的另一种方法是依次响应NULL。取决于函数的类型 - 在上面的示例中,我将保留异常。

答案 4 :(得分:1)

如果它是面向外部的API,那么我会说你想检查每个参数,因为输入不可信。

但是,如果它只是在内部使用,那么输入应该可以信任,你可以节省一大堆没有为软件增加价值的代码。

答案 5 :(得分:1)

您应该根据您在该函数中对其值进行的假设集检查所有参数。

在你的例子中,如果你的函数的null参数没有任何意义,并且你假设任何使用你的函数的人都知道这个,那么传递一个null参数会显示某种错误和某种行为采取(例如,抛出例外)。如果你使用断言(正如James Fassett进来并在我之前说的那样;-))它们在发布版本中没有任何成本。 (它们在调试版本中几乎没有任何成本)

同样的事情适用于任何其他假设。

如果生成错误,那么跟踪错误会比将它留给某个标准库例程抛出异常更容易。您将能够提供更有用的上下文信息。

这超出了这个问题的范围,但你需要公开你的函数所做的假设 - 例如,通过你的函数的注释标题。

答案 6 :(得分:1)

根据Andrew Hunt和David Thomas的 The Pragmatic Programmer ,调用者有责任确保它提供有效的输入。因此,您现在必须选择是否认为空输入有效。除非将null视为有效输入具有特定意义(例如,如果您正在测试相等性,将null视为合法输入可能是个好主意),我认为它无效。这样你的程序在遇到不正确的输入时,会很快失败。如果您的程序将遇到错误情况,您希望它尽快发生。如果你的函数无意中被传递为null,你应该认为它是一个bug,并做出相应的反应(即不要抛出异常,你应该考虑使用一个杀死程序的断言,直到你释放程序)。

合同经典设计:如果输入正确,输出将是正确的。如果输入错误,则存在错误。 (如果输入正确但输出错误,则存在错误。这是一种给定的。)

答案 7 :(得分:1)

我将通过Brian先前提供的合同建议,为优秀设计添加一些详细说明(粗体)...

“按合同设计”的原则要求您定义调用者可以接受的内容(输入值的有效域),然后对于任何有效输入,定义方法/提供者将执行的操作。

对于内部方法,您可以将NULL定义为有效输入参数的域之外。在这种情况下,您将立即断言输入参数值为NOT NULL。 此合同规范中的关键见解是,任何传入NULL值的调用是一个CALLER'S BUG 并且assert语句抛出的错误是正确的行为。

现在,虽然定义得非常好,但是如果你将方法暴露给外部/公共呼叫者,你应该问问自己,这是我/我们真正想要的合同吗? 可能不是。在公共接口中,您可能接受NULL(在技术上在该方法接受的输入域中),但随后拒绝正常处理w /返回消息。 (更多工作是为了满足自然更复杂的面向客户的要求。)

在任何一种情况下,您所追求的是一个协议,它从调用者和提供者的角度处理所有案例,而不是大量的散射测试,这些测试可能难以评估完整性或合同条件覆盖范围不完整。

答案 8 :(得分:0)

大多数时候,只要你确定不会忽略异常,让它只是抛出异常是非常合理的。

但是,如果你可以添加一些内容,那么用更准确的方法包装异常并重新抛出异常并没有什么坏处。解码“NullPointerException”将比“IllegalArgumentException(”FilePath必须提供“)”(或其他任何东西)花费更长的时间。

最近我一直在开发一个平台,你必须在测试之前运行一个混淆器。每个堆栈跟踪看起来像猴子键入随机垃圾,所以我养成了一直检查我的参数的习惯。

我希望在变量和参数上看到“可空”或“无效”修饰符,以便编译器可以检查你。

答案 9 :(得分:0)

如果您正在编写公共API,请让您的来电者帮助他们快速查找错误,并检查有效输入。

如果您正在编写调用者可能不受信任的API(或调用者的调用者),请检查有效输入,因为它具有良好的安全性。

如果您的API只能由可信赖的呼叫者访问,例如C#中的“internal”,那么您不必编写所有额外的代码。它对任何人都没用。