嘿,让我们小心点。
埃斯特豪斯中士,每天向希尔街布鲁斯部队进行情况介绍
当开发人员遇到意外错误并无法修复时,他们将“添加一些防御性代码”以使代码更安全并更容易发现问题。 有时只是这样做将使问题消失。 他们将加强数据验证–确保检查输入和输出字段以及返回值。 复查并改善错误处理-可能在“不可能”的情况下增加一些检查。 添加一些有用的日志记录和诊断。 换句话说,应该首先放在那里的那种代码。
期待意外
防御性编程的重点是防止出现意外错误。
Steve McConnell,代码完成
防御性编程的一些基本规则在史蒂夫·麦康奈尔(Steve McConnell)关于编程的经典著作《 代码完成》 ( Code Complete)中的一小章中进行了解释:
- 无论您决定将“外部”放在哪里,都可以保护代码免受来自“外部”的无效数据的侵害。 来自外部系统或用户或文件的数据,或来自模块/组件外部的任何数据。 建立“路障”或“安全区”或“信任边界”-边界之外的所有事物都是危险的,边界内部的一切都是安全的。 在路障代码中, 验证所有输入数据 :检查所有输入参数的类型,长度和值的正确范围。 仔细检查极限和界限。
- 检查不良数据后,请决定如何处理它。 防御性编程与吞咽错误或隐藏错误无关。 这是关于确定健壮性(如果可以解决问题,请继续运行)与正确性(永远不会返回不准确的结果)之间的权衡 。 选择一种处理不良数据的策略:返回错误并立即停止(快速失败),返回中性值,替换数据值,…确保策略清晰且一致。
- 不要以为代码外的函数调用或方法调用可以像宣传的那样工作。 确保您了解并测试围绕外部API和库的错误处理。
- 使用断言来记录假设并强调“不可能”的条件,至少在开发和测试中要如此。 在由不同人员随时间维护的大型系统中或在高可靠性代码中,这尤其重要。
- 智能添加诊断代码,日志记录和跟踪,以帮助解释运行时的状况,尤其是遇到问题时。
- 标准化错误处理。 确定如何处理“正常错误”或“预期错误”和警告,并始终如一地进行所有操作。
- 仅在需要时使用异常处理,并确保从内到外理解语言的异常处理程序。
使用异常作为其正常处理的一部分的程序会遭受经典意大利面条代码的所有可读性和可维护性问题。
实用程序员
我还要添加其他一些规则。 摘自Michael Nygard的发行版! 永远不会永远等待外部呼叫,尤其是远程呼叫。 出现问题时,永远可能会很长一段时间。 使用超时/重试逻辑及其电路断路器稳定性模式来处理远程故障。
对于C和C ++之类的语言,防御性编程还包括使用安全的函数调用来避免缓冲区溢出和常见的编码错误。
不同种类的偏执狂
实用程序员将防御性编程描述为“实用偏执狂”。 保护您的代码不受他人的错误和您自己的错误的影响。 如有疑问,请验证。 检查数据一致性和完整性。 您无法测试所有错误,因此对于“不可能发生”的事情使用断言和异常处理程序。 从测试和生产中的失败中学习-如果失败,请寻找其他可能失败的原因。 专注于代码的关键部分-核心,即运行业务的代码。
健康的偏执编程是正确的编程方式。 但是偏执狂可能被认为太过分了。 在“ 清理代码”的“错误处理”一章中,Michael Feathers警告说,
“许多代码库都以错误处理为主”
太多的错误处理代码不仅会掩盖代码的主要路径(代码实际上是在尝试执行的操作),而且还会掩盖错误处理逻辑本身,因此很难正确处理,更难以检查和测试。 ,并且更容易更改且不会出错。 与其使代码更具弹性和更安全,不如使代码更易于出错和变脆。
有一个健康的偏执狂,然后是对错误的过度检查,然后是疯狂的残酷残酷的偏执狂-防御性程序接管并重新启动。
我研究的第一个现实系统是遍布美国和加拿大的服务器(当时称为微型计算机)的“存储和转发”网络控制系统。 它在分布式系统,计划的作业和网络上的协调报告之间共享数据。 它旨在抵抗网络问题并自动从操作故障中恢复并重启。 当时这是开创性的工作,是一项技术挑战。
该系统上的原始程序员不信任网络,不信任O / S,不信任Operations,不信任其他人的代码以及不信任自己的代码-这是有充分理由的。 他是一名化学工程师,是一名自学成才的系统程序员,在深夜编码时喝了很多酒,并在影响下编写了数千行非结构化的FORTRAN和Assembler。 该代码充满了错误检查,自我诊断和错误纠正代码,文件和数据包具有自己的校验和,文件级密码和隐藏的控制标签,并且有许多代码可以处理序列记帐异常和与时序相关的代码问题-大部分时间里大部分有效的代码。 如果出现错误而无法恢复,则程序将崩溃并报告“退出标签”并转储变量的内容,例如今天的堆栈跟踪。 从理论上讲,您可以使用此信息回顾代码以弄清楚到底发生了什么。 这些都不像我在学校学到的东西。 阅读和使用此代码就像从Arkham Asylum中编程出路一样。
如果程序员遇到错误并且无法修复它们,那不会阻止他。 他将找到一种方法来解决这些错误并使系统保持运行。 然后,在他离开公司后,我会发现并修复错误,并向自己表示祝贺,直到它破坏了网络中其他地方依赖于该错误的“纠错”代码。 因此,在我终于弄清楚发生了什么之后,我尽可能安全地删除了这种“保护”,并清理了错误处理,以便我实际上可以维护该系统而不会遗忘我的脑海。 我为代码设置了信任边界-尽管当时我不知道这就是所谓的含义-决定哪些数据不能被信任,哪些数据可以被信任。 完成此操作后,我便可以简化防御性代码,以便在不使系统崩溃的情况下进行更改,并且仍然可以保护核心代码免受不良数据,其余代码的错误以及操作问题的影响。
使代码更安全很简单
防御性编码的目的是使代码更安全,并帮助打算维护和支持代码的人–而不会使他们的工作变得更辛苦。 防御性代码就是代码–所有代码都有错误,并且,由于防御性代码正在处理异常,因此特别难于测试并确保其在必要时能够正常工作。 了解要检查的条件以及需要多少防御性编码需要经验,可以在生产中使用代码并查看在现实世界中可能出什么问题。
设计和构建安全,有弹性的系统涉及的许多工作在技术上是困难的或昂贵的。 防御性编程既不像防御性驾驶一样,每个人都可以理解和做到。 它需要纪律,意识和对细节的关注,但是如果我们想使世界变得安全,这是我们所有人都需要做的事情。
参考: 防御性编程:与我们的JCG合作伙伴完全矛盾 Building Real Software博客上的Jim Bird。
翻译自: https://www.javacodegeeks.com/2012/03/defensive-programming-being-just-enough.html