我们有一个约定来验证构造函数和公共函数/方法的所有参数。对于引用类型的强制参数,我们主要检查非null,这是构造函数中的主要验证,我们在其中设置了类型的强制依赖。
我们这样做的首要原因是尽早捕获该错误,并且在几小时内没有得到空引用异常,而不知道引入错误参数的位置和时间。随着我们开始转向越来越多的TDD,一些团队成员认为验证是多余的。
作为TDD声音倡导者的Bob叔叔强烈建议不要进行参数验证。他的主要论点似乎是“我有一套单元测试,确保一切正常”。
但是我可以在它的生命周期中看不到单元测试会以什么方式阻止我们的开发人员在生产代码中使用错误的参数来调用这些方法。
请在那里安排测试人员,如果你能用合理的例子向我解释这个问题,我会非常乐意抓住这个参数验证!
答案 0 :(得分:7)
我的回答是"它不能。"基本上我觉得我不同意鲍勃叔叔(除此之外)。
您可以很容易地想象出您已经对您的库代码进行非空参数测试的情况,并且您已经对您的调用代码进行了单元测试,以获得恰好提供的路径库的null参数,如果你没有意识到它,但也的情况不会导致该特定路径出现任何问题。您可以获得100%的覆盖率,实际上是一组非常好的测试,但仍然没有注意到问题。
一切都好吗?不,当然不是 - 因为你在没有意识到的情况下违反了图书馆合同(不给我一个非空值)。你能否认为你提供空参数的唯一情况是不重要的?我不这么认为 - 特别是如果你甚至意识到该论点都是空的。
在我看来,无论调用代码和API本身是否经过单元测试,公共API都应验证其参数。调用代码中的问题应该暴露出来,并尽早公开。
答案 1 :(得分:3)
这是我多年来一直在问自己的问题,但仍然没有得到令人满意的答案。
但我相信在论证验证方面,你需要区分两种情况:
您是否正在验证参数以捕获逻辑编程错误?
if (foo == null) throw new ArgumentNullException("foo");
很可能就是一个例子。
您是否正在验证参数,因为它是一些外部输入(由用户提供,或从配置文件或数据库中读取),这可能无效并且必须被拒绝?
if (customerDateOfBirth == new DateTime(1900, 1, 1)) throw …;
可能是这种类型的参数检查。
(如果您公开了团队以外的人使用的API,则第2点也会大致适用。)
我怀疑单元测试,合同设计以及某种程度上“早期失败”等方法主要集中在第一种类型的参数验证上。也就是说,他们试图检测逻辑编程错误,而不是无效输入。
如果是这种情况,那么我敢说你遵循哪种错误检测方法并不重要;每个都有自己的优点和缺点。 † 在极端情况下(例如,当你完全信任你编写无错误代码的能力时),你甚至可以完全放弃这些检查。
但是,无论您选择哪种方法来检测代码中的逻辑错误,您仍然需要验证用户输入等,因此需要区分这两种参数检查。
†)业余爱好者未能完全比较设计合同,单元测试和“早期失败”的相对优势和劣势:
(虽然你没有要求......我只想提一些关键的区别。)
早期失败(例如,在方法开始时进行显式参数验证):
- 编写基本检查,例如对
null
的防范很容易写- 可能会混淆防范逻辑错误和使用相同语法验证外部输入
- 不允许您测试方法的交互
- 不鼓励您严格定义(并因此考虑)您的方法合同
单元测试:
- 允许您在不运行实际应用程序的情况下单独测试代码,因此可以更快地检测错误
- 如果发生逻辑错误,则不必跟踪堆栈以查找原因,因为每个单元测试代表代码的特定“用例”。
- 允许您测试的不仅仅是单个方法,例如甚至是几个物体之间的相互作用(想想存根和嘲笑)
- 编写简单的测试(例如防范
null
)比使用“早期失败”方法(如果你严格遵守Arrange-Act-Assert模式)更多的工作按合同设计:
- 强制您明确说明您的课程的合同(虽然这也可以通过单元测试 - 只是以不同的方式)
- 允许您轻松地声明类不变量(必须始终保持为真的内部条件)
- 不像许多编程语言/框架那样得到其他方法的支持
答案 2 :(得分:2)
这完全取决于您正在开发的应用程序类型。
我花了大部分时间编写不公开公共API的应用程序,在这种情况下,应用程序必须是确定性的,因为所有参数必须且将不同于null。简而言之,您应该在系统边界执行输入验证,而不是让这些无效输入潜入您的应用程序,这可能最终导致空引用等。在这种应用程序中,您可以完全控制在获取应用程序的位置检查应用程序的输入。
如果您正在编写公共API,则不建议不检查空引用。只需查看可以抛出异常的所有MSDN类方法,所有这些都在API中作为前置条件检查发生,您可以阅读C# Framework design guidelines以获取更多信息。
在我看来,无论是暴露的(或不是)API应用程序,为您的方法提供先决条件总是一件好事(这些合同是您将在未来处理代码的同行的文档)
答案 3 :(得分:2)
我几乎同意鲍勃叔叔,但这不是这个。我投票支持“快速失败并且努力失败” - 政策。
答案 4 :(得分:0)
这与TDD无关。
对于公共API,是的,我们应该尽快进行参数检查。
对我来说,所有构造函数参数检查似乎都是不必要的,因为它不会被团队外的任何人使用。为什么我们有空检查?我们不相信调用这些方法的代码。
那么什么是公共API?所有公共方法?如果是这样,那么我猜没有这样的内部API。那么为什么要使用单词public呢?为什么只说所有公共方法应该进行空/边界检查。
我认为问题的根本原因是我们自己的代码和团队成员缺乏信任,显然我们是以错误的方式解决问题。