您是在每个构造函数中检查数据有效性,还是只是假设数据正确并在参数有问题的特定函数中抛出异常?
9 回答
构造函数也是一个函数——为什么要区分?
创建一个对象意味着所有的完整性检查都已完成。在构造函数中检查参数并在检测到非法值后抛出异常是完全合理的。
其中包括简化调试。当您的程序在构造函数中抛出异常时,您可以观察堆栈跟踪并经常立即看到原因。如果您延迟检查,则必须进行更多调查以检测导致当前错误的早期事件。
从一开始就拥有一个所有不变量都“满足”的完全构造的对象总是更好的。但是,请注意在非托管语言中从构造函数中抛出异常,因为这可能会导致内存泄漏。
我总是在构造函数中强制值。如果用户懒得遵守规则,我只是默默地执行它们而不告诉他们。
所以,如果他们传递了 107% 的值,我会将其设置为 100%。我只是在文档中明确说明会发生这种情况。
只有在没有明显的逻辑强制的情况下,我才会向他们抛出异常。我喜欢称其为“那些懒得或愚蠢而无法阅读文档的人最惊讶的原则”。
如果你在构造函数中抛出,堆栈跟踪更有可能显示错误值的来源。
在一般情况下,我同意尖牙,但有时对象可能具有某些功能有效而某些功能无效的状态。在这些情况下,最好将检查推迟到具有这些特定依赖项的函数。
通常你会想要在构造函数中进行验证,只是因为这意味着你的对象将始终处于有效状态。但是某些类型的对象需要特定于功能的检查,这也可以。
这是一个困难的问题。在过去的几年里,我已经多次改变与这个问题相关的习惯,所以我想到了一些想法:
- 检查构造函数中的参数就像检查传统方法的参数......所以我不会在这里区分......
- 检查方法的参数当然有助于确保传递的参数是正确的,但也会引入更多你必须编写的代码,在我看来这并不总是有回报的......尤其是当你编写委托的短方法时经常使用其他方法,我猜你最终会得到 50% 的代码只是进行参数检查......
- 此外考虑到,当您为您的类编写单元测试时,您通常不希望为所有方法传递有效参数......通常只传递对当前测试用例重要的参数确实有意义,因此进行检查会减慢您编写单元测试的速度...
我认为这真的取决于情况......我最终只是在我知道参数确实来自外部系统(如用户输入、数据库、Web 服务或类似的东西)时才编写参数检查...... )。如果数据来自我自己的系统,我大部分时间都不编写测试。
我总是试图尽早失败。所以我明确地检查了构造函数中的参数。
理论上,调用代码在调用函数之前应始终确保满足先决条件。构造函数也是如此。
在实践中,程序员很懒惰,最好还是检查前置条件。断言在那里派上用场。
例子。请原谅我的非花括号语法:
// precondition b<>0
function divide(a,b:double):double;
begin
assert(b<>0); // in case of a programming error.
result := a / b;
end;
// calling code should be:
if foo<>0 then
bar := divide(1,0)
else
// do whatever you need to do when foo equals 0
或者,您可以随时更改前提条件。在构造函数的情况下,这不是很方便。
// no preconditions.. still need to check the result
function divide(a,b:double; out hasResult:boolean):double;
begin
hasResult := b<>0;
if not hasResult then
Exit;
result := a / b;
end;
// calling code:
bar := divide(1,0,result);
if not result then
// do whatever you need to do when the division failed
总是尽快失败。运行时.. * 展示了这种做法的一个很好的例子,如果您尝试对数组使用无效索引,则会出现异常。* 如果不是在稍后的某个阶段尝试,铸造会立即失败。
想象一下,如果引用中保留了错误的演员表,并且每次您尝试使用时都会遇到异常,那么灾难代码会是怎样的。唯一明智的方法是尽快失败并尝试尽快避免不良/无效状态。