转自:http://www.codeceo.com/article/defensive-programming-vs-crazy-programming.html
译者: 码农网 – 小峰
啊,这里要小心! ——Sergeant Esterhaus,《每日简报》
当程序员遇到意想不到又不能修复的bug时,,他们会“添加一些防御性的代码”,这不但可以使得代码更安全,还更容易发现问题。有时候这样的行为甚至可以直接消灭问题。开发人员还会进行数据验证——确保检查输入和输出域和返回值;审查和改进错误处理——可能会围绕一些“不可能”的条件做一些检查;添 加一些有用的日志记录和诊断。换句话说,问题代码优先。
防御性编程的整体要点就是防范你不想要出现的错误。——Steve McConnell,《Code Complete》
Steve McConnell的经典编程之书——《Code Complete》,用一个短篇解释了防御性编程的一些基本规则:
如果一个程序将异常作为正常进程的一部分,那就会饱受所有经典的可读性和可维护性问题导致的代码混乱不堪的困扰。
–《The Pragmatic Programmer》(程序员修炼之道)
此外,我还想补充几点,来自于Michael Nygard的《Release It》:
在《The Pragmatic Programmer》一书中将防御性编程形容为“务实的偏执”。保护你的代码避免受到别人和自己错误的侵袭。有疑问,就验证。检查数据的一致性和完整 性。由于我们不能测试每一个错误,所以使用断言和异常处理程序来应对“不应该发生”的事情。从测试和产品失败中学习 ——出现失败,就找找看还有哪里也会失败。关注代码的关键部分——核心,执行目的的那部分代码。
健康的偏执型编程是正确的编程形式。但偏执程度却可大可小。在《Clean Code》的错误处理章节,Michael Feathers告诫说,
“在很多代码库中,错误处理占据了主导地位。” –Michael Feathers,《Clean Code》
如果代码中有太多的错误处理,那么不仅会掩盖代码的主路径(代码的实际目标),也会遮蔽错误处理本身的逻辑——以至于很难纠正、很难审查和测试,也很难不犯错误地更改,最后只能束手无策。这非但不会让代码更有弹性和更安全,实际上还会导致代码更容易出错和更脆弱。
有健康的偏执,有错误检查过度的偏执,还有疯狂而有害的偏执——以及防御性编程这四种。
我搞的第一个真正意义上的全球性系统是为服务器(当时还被叫做小型机)跨越美国和加拿大研发的“Store and Forward”网络控制系统。它在分布式系统、调度作业以及协调整个网络报告之间分享数据。它的设计目的为可适应网络问题,并且面对操作失误可以自动恢 复和重启。这在那时可谓是史无前例的,但却是技术人员的噩梦和地狱。
此系统的原有程序员不信任网络,不信任O/S,不信任操作运算,不信任别人的代码,甚至也不信任他自己的代码——理由振振有词。他曾是一名化学工程 师,自学成为系统程序员——熬夜写代码的时候会喝很多酒,然后在酒精的影响下写下成千上万行非结构化的FORTRAN和Assembler代码。代码中充 满了错误检查、自我诊断和纠错码,文件和数据包有各自的校验和、文件级密码和隐藏的控件标签,并有大量的代码来处理序列记录异常和关于时序的问题。如果出 现问题,它就无法恢复,程序也会崩溃,同时报告“label of exit”并清空变量内容——有点像今天的堆栈跟踪。理论上你可以使用这些信息追溯代码来弄清楚到底发生了什么。但是所有这一切和我在学校里学到的完全不 同。阅读和使用这些代码,感觉能让人彻底疯掉。
这个顽固的系统程序员,即使是没法修复的bug,也不会阻碍他前进的脚步,因为他会找到一种方法来解决这些bug,保持系统的运行。然后,在他离开 公司以后,我接手了这个系统。我又发现了bug,特别开心自己修复了它,然而却不小心在其他地方毁坏了一些“纠错”代码,事实上,这些“纠错”代码其实依 赖于网络中的bug而生存。所以,当我终于理清各种关系之后,我会先尽可能安全地将这些“保护伞”移除,并清理错误处理,这样我就可以放心大胆地去维护系 统了。我为代码设置了信任边界——当然那个时候我还不知道应该这样叫——用来决定什么样的数据不能被信任,而什么样的数据是可以信任的。这样做了之后,我 发现我简化了防御代码,这不但有助于在做出修改的同时不引起系统混乱,同时又能保护核心代码免受不良数据、剩余代码错误以及操作问题的干扰。
防御性编码的要点就是让代码更安全,并帮助其他人维护和支持代码——而不是使得程序员的工作更为困难。不过,防御性代码也是代码——只要是代码就会 有bug,但是,由于防御性代码用于处理异常,所以想要给它做测试并且确保它能够有效工作就会显得非常非常难。理解检查条件和明确需要怎么样的防御代码是 需要经验积累的,处理产品中的代码并预见现实世界中会出现的问题,同样如此。
想要设计出一种长效的系统不但是个技术老大难而且成本非常高。防御性编程却两者皆非——因为每个人都能理解并办到。只是,它需要磨练和警觉,需要我们能够做到注重细节。但是如果我们想要让世界变得更安全,那么这是我们必须要走的独木桥。