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



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

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

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并抛出异常,因为你的对象的数据可能处于中间状态,可能是无效状态。

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

我在C++中最喜欢的技术之一是在NULL指针上调试SSERT。这是资深程序员向我灌输的(以及const正确性),也是我在代码审查期间最严格的要求之一。我们从未在不首先断言指针不是null的情况下取消引用指针。

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

我建议断言代码中的所有不变量,包括意外的null。若If的性能成为一个问题,请找到一种方法来有条件地编译它们,并使它们在调试目标中保持活动状态。就像源代码管理一样,这是一种比让我悲伤更能拯救我的技术(任何开发技术中最重要的试金石)。

是的,在方法开始时验证所有参数并抛出适当的异常(如ArgumentException、ArgumentNullException或ArgumentOutOfRangeException)是一种很好的做法。

如果该方法是私有的,因此只有程序员才能传递无效的参数,那么您可以选择断言每个参数都是有效的(Debug.assert),而不是抛出。

如果NULL是不可接受的输入,则引发异常。像您在示例中所做的那样,由您自己处理,这样消息会有所帮助。

处理NULL输入的另一种方法就是依次使用NULL进行响应。这取决于函数的类型——在上面的例子中,我会保留异常。

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

然而,如果它只在内部使用,那么输入应该是可信的,你可以为自己保存一堆不会给软件增加价值的代码。

您应该对照您在该函数中对其值所做的一组假设来检查所有参数。

在您的示例中,如果函数的null参数没有任何意义,并且您假设使用函数的任何人都知道这一点,那么传递null参数会显示出某种错误和所采取的某种操作(例如抛出异常)。如果你使用断言(正如James Fassett在我面前说的那样;-),它们在发布版本中不会花费你任何费用。(它们在调试版本中几乎不需要花费任何费用)

这同样适用于任何其他假设。

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

这超出了这个问题的范围,但您确实需要公开函数所做的假设——例如,通过函数的注释头。

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

经典合同设计:如果输入正确,输出就会正确。如果输入错误,则存在错误。(如果输入是正确的,但输出是错误的,那么就有一个错误。这是一个gimme。)

我将在Brian早些时候提供的优秀的合同设计建议中添加一些详细说明(粗体)。。。

"契约设计"的原则要求您定义调用方可以接受的传入内容(输入值的有效域),然后,对于任何有效输入,方法/提供者将做什么。

对于内部方法,可以将NULL定义为在有效输入参数的域之外。在这种情况下,您将立即断言输入参数值不是NULL 该约定规范中的关键见解是,任何传递NULL值的调用都是CALLER的BUG,断言语句引发的错误是正确的行为

现在,虽然定义得很好,也很吝啬,但如果您将该方法暴露给外部/公共调用程序,您应该问问自己,这是我/我们真正想要的合同吗?可能不会。在公共接口中,您可能会接受NULL(从技术上讲,在方法接受的输入域中),但随后拒绝优雅地处理返回消息。(更多的工作来满足自然更复杂的面向客户的需求。)

在任何一种情况下,您所追求的是一种从呼叫方和提供者的角度处理所有情况的协议,而不是大量的零散测试,这些测试可能会使评估合同条件覆盖的完整性或不完整性变得困难

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

然而,如果你能添加一些东西,用一个更准确的异常来包装并重新抛出它也没什么坏处。解码"NullPointerException"将比"IllegalArgumentException("必须提供FilePath")"(或其他什么)花费更长的时间。

最近,我一直在一个平台上工作,在测试之前,你必须运行一个模糊处理程序。每个堆栈跟踪看起来都像猴子在随意键入垃圾,所以我养成了一直检查参数的习惯。

我希望在变量和参数上看到一个"可为null"或"非all"修饰符,这样编译器就可以为您检查了。

如果您正在编写公共API,请帮助调用方快速发现错误,并检查有效输入。

如果您正在编写API,其中调用方可能不受信任(或调用方的调用方),请检查有效输入,因为这是很好的安全性。

如果您的API只能由受信任的调用方访问,比如C#中的"内部",那么就不必编写所有额外的代码。它对任何人都没有用。

相关内容

最新更新