【问题标题】:Should I make sure arguments aren't null before using them in a function?在函数中使用参数之前,我应该确保参数不为空吗?
【发布时间】:2010-09-16 23:44:09
【问题描述】:

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

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

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

我想知道这是一种好的做法,还是应该让它到达实际的哈希器,然后抛出异常,因为它是空的。?

谢谢。

【问题讨论】:

    标签: validation function exception arguments


    【解决方案1】:

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

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

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

    【讨论】:

      【解决方案2】:

      我最喜欢的 C++ 技术之一是对 NULL 指针进行 DEBUG_ASSERT。这是高级程序员(以及 const 正确性)向我灌输的,这是我在代码审查期间最严格的事情之一。我们从来没有在没有先声明它不为空的情况下取消引用指针。

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

      我建议在代码中声明所有不变量,包括意外的空值。如果 if 的性能成为问题,请找到一种方法来有条件地编译并使它们在调试目标中保持活动状态。就像源代码控制一样,这种技术救了我的命多于给我带来的痛苦(任何开发技术中最重要的试金石)。

      【讨论】:

        【解决方案3】:

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

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

        【讨论】:

          【解决方案4】:

          如果 NULL 是不可接受的输入,则抛出异常。就像您在示例中所做的那样,您自己来做,以便该消息很有帮助。

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

          【讨论】:

            【解决方案5】:

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

            但是,如果它只是在内部使用,那么输入应该是可信的,并且您可以为自己节省一堆不会为软件增加价值的代码。

            【讨论】:

              【解决方案6】:

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

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

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

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

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

              【讨论】:

                【解决方案7】:

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

                契约式经典设计:输入正确,输出正确。如果输入错误,则存在错误。 (如果输入正确但输出错误,则存在错误。这是给我的。)

                【讨论】:

                • 如果输入错误而输出正确...... hrm..那也不太好。
                【解决方案8】:

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

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

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

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

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

                【讨论】:

                  【解决方案9】:

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

                  但是,如果您可以向其中添加一些内容,那么将异常包装为更准确的异常并重新抛出它并没有什么坏处。解码“NullPointerException”将比“IllegalArgumentException(“必须提供文件路径”)”(或其他)花费更长的时间。

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

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

                  【讨论】:

                    【解决方案10】:

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

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

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

                    【讨论】:

                      猜你喜欢
                      • 1970-01-01
                      • 2020-04-13
                      • 2012-02-06
                      • 2012-05-20
                      • 2020-07-04
                      • 1970-01-01
                      • 2023-02-22
                      • 1970-01-01
                      • 2013-02-17
                      相关资源
                      最近更新 更多