我的代码应该如何“防御性”?

时间:2009-05-15 16:14:35

标签: defensive-programming

我和我的一位同事正在讨论你的代码应该是多么具有防御性。我都是职业防守编程,但你必须知道在哪里停止。我们正在开发一个由其他人维护的项目,但这并不意味着我们必须检查开发人员可以做的所有疯狂事情。当然,你可以这样做,但这会给你的代码增加很大的开销。

你怎么知道在哪里划线?

15 个答案:

答案 0 :(得分:14)

用户直接或间接输入的内容,您应该始终进行健全性检查。除此之外,一些assert在这里和那里都不会受到伤害,但你无论如何都无法对疯狂程序员编辑和破坏你的代码做多少工作! -

答案 1 :(得分:11)

我倾向于根据语言改变我在代码中放置的防御量。今天我主要是用C ++工作,所以我的想法正朝这个方向漂移。

在C ++中工作时,防御性编程就不够了。我把我的代码看作是在守护核机密,而其他程序员都是为了得到它们。断言,抛出,编译器时间错误模板黑客,参数验证,消除指针,深度代码审查和一般偏执都是公平的游戏。 C ++是一种邪恶的语言,我既爱又严重不信任。

答案 2 :(得分:9)

我不喜欢“防御性编程”这个词。对我来说它建议这样的代码:

void MakePayment( Account * a, const Payment * p ) {
    if ( a == 0 || p == 0 ) {
       return;
    }
    // payment logic here
}

这是错误的,错误的,错误的,但我必须看过它数百次。永远不应该使用空指针调用该函数,并且静静地接受它们是完全错误的。

这里的正确方法是有争议的,但最小的解决方案是通过使用断言或抛出异常来吵闹。

编辑:我不同意其他一些答案和评论 - 我不认为所有功能都应该检查他们的参数(对于许多功能,这根本不可能)。相反,我认为所有函数都应记录可接受的值,并声明其他值将导致未定义的行为。这是最成功和最广泛使用的库所采用的方法 - C和C ++标准库。

现在让downvotes开始......

答案 3 :(得分:4)

我不知道有什么办法可以回答这个问题。这只是你从经验中学到的东西。你只需要问问自己潜在问题可能有多常见,并做出判断。另外请注意,您不一定 始终以防御性方式进行编码。有时,只需记下代码文档中的任何潜在问题即可。

但最终,我认为这只是一个人必须遵循他们的直觉。没有正确或错误的方法。

答案 4 :(得分:2)

如果您正在处理组件的公共API,则值得进行大量参数验证。这让我养成了到处进行验证的习惯。这是一个错误。所有验证代码都没有经过测试,可能会使系统变得比它需要的更复杂。

现在我更喜欢通过单元测试来验证。验证肯定发生在来自外部源的数据上,而不是来自非外部开发人员的调用。

答案 5 :(得分:1)

我总是在Debug.Assert我的假设。

答案 6 :(得分:1)

我的个人意识形态:程序的防御性应该与潜在用户群的最大天真/无知成正比。

答案 7 :(得分:1)

对开发人员使用您的API代码采取防御措施与防范常规用户并没有什么不同。

  • 检查参数以确保它们在适当的范围内和预期的类型
  • 验证可以进行的API调用的数量是否在您的服务条款范围内。通常称为限制,它通常仅适用于Web服务和密码检查功能。

除此之外别无他法,除非确保您的应用在发生问题时能够很好地恢复,并且始终向开发人员提供充足的信息,以便他们了解正在发生的事情。

答案 8 :(得分:0)

防御性编程只是以合同设计方式编写合同的一种方式。

另外两个是

  • 总编程
  • 名义编程。

当然,你不应该为开发人员可以做的每一件疯狂的事情做好准备,但是你应该在上下文中说明它会做出预期使用前置条件的预期。

//precondition : par is so and so and so 
function doSth(par) 
{
debug.assert(par is so and so and so )
//dostuf with par 
return result
}

答案 9 :(得分:0)

我认为你必须提出你是否也在创建测试的问题。你的编码应该是防御性的,但正如JaredPar所指出的那样 - 我也相信这取决于你所使用的语言。如果它是非托管代码,那么你应该非常防守。如果它被管理,我相信你有一点摆动。

如果您有测试,而其他一些开发人员试图摧毁您的代码,则测试将失败。但话又说回来,它取决于代码的测试覆盖率(如果有的话)。

答案 10 :(得分:0)

我尝试编写的代码不仅仅是防御性的,而是非常恶劣的。如果出现问题我可以解决它,我会的。如果没有,抛出或传递异常并使其成为别人的问题。任何与物理设备交互的东西 - 文件系统,数据库连接,网络连接都应该被认为是不可靠的并且容易出现故障。预测这些失败并将其捕获是至关重要的

一旦你有了这种心态,关键是要在你的方法中保持一致。你希望交回状态代码来解决调用链中的问题或者你喜欢异常吗?混合型号会杀了你或至少让你喝酒。巨资。如果你正在使用elses api,那么将这些东西隔离成以你使用的术语陷阱/报告的机制。使用这些包装界面。

答案 11 :(得分:0)

如果这里的讨论是如何针对未来(可能是恶意或无能的)维护者进行防御性编码,那么你可以做的就是限制。通过测试覆盖和自由使用主张你的假设来实施合同可能是你能做到的最好的,并且它应该以理想的方式完成,不会使代码混乱,并使未来的非邪恶维护者的工作变得更加困难。码。断言易于阅读和理解,并清楚地表明给定代码片段的假设是什么,因此它们通常是一个好主意。

完全针对用户操作进行防御性编码是另一个问题,我使用的方法是认为用户出去接我。我会尽可能仔细地检查每个输入,并尽一切努力让我的代码安全失败 - 尽量不要坚持任何未经严格审查的状态,尽可能纠正,如果不能,则优雅退出等等。你只需要考虑外部代理可以在你的代码上执行的所有bozo事情,它就会让你处于正确的心态。

针对其他代码(例如您的平台或其他模块)进行防御性编码与用户完全相同:他们会帮助您。操作系统总是会在不合时宜的时候换掉你的线程,网络总是会在错误的时间消失,而且一般来说,每个角落都会出现邪恶现象。您不需要针对每个潜在问题进行编码 - 维护成本可能不值得增加安全性 - 但考虑到这一点肯定没有坏处。如果你想到一个场景但是由于某种原因认为不重要,那么在代码中明确注释通常没有坏处。

答案 12 :(得分:0)

系统应该具有良好设计的边界,其中发生防御性检查。应该确定用户输入的验证位置(在什么边界)以及其他潜在的防御性问题需要检查的位置(例如,第三方集成点,公共API,规则引擎交互或由不同程序员团队编码的不同单元) )。在许多情况下,更多的防御性检查比违反DRY的更多,并且只是为非常小的利益增加了维护成本。

话虽如此,有些地方你不能太偏执。缓冲区溢出,数据损坏和类似问题的可能性应该得到非常严格的防范。

答案 13 :(得分:0)

我最近有这样的场景,其中用户输入数据通过远程外观接口传播,然后是本地外观接口,然后是其他类,最终到达实际使用它的方法。我问自己一个问题:什么时候应该验证值?我只将验证码添加到最终类中,其中实际使用了值。在传播路径上的类中添加其他验证代码片段对我来说太过防御性编程。一个例外可能是远程外观,但我也跳过它。

答案 14 :(得分:0)

好问题,我在进行健全性检查而不做这些检查之间徘徊。它是50/50

情况,我可能会采取中间立场,我只会“Bullet Proof”任何例程:

(a)从项目中的多个地方召集

(b)具有可以改变的逻辑

(c)您不能使用默认值

(d)例程不能正常“失败”

Darknight