5 分钟搞定防御性编程:打造稳健的软件
本文介绍了防御性编程的理念和实践,分析了防御性编程中应该重点关注的点,帮助开发者从容应对不可预知的错误。原文:Building Robust Software: Mastering Defensive Programming

🛡️简介
软件行为不可预测 -- 错误、崩溃和意外输入在所难免。防御性编程是一门艺术,它能预见挑战,并编写出即使在最糟糕的情况下也能确保可靠、安全和可维护性的代码。这不是妄想症,而是一种应变能力。通过采用这些技术,开发人员可以确保应用程序从容应对错误,而不是在压力下崩溃。
🏗️ 坚实的基础
每一个伟大的架构都始于坚实的基础,而在编程中,这始于细致的输入验证和正确的初始化。忽视这些就好比无本之木 -- 一个意外输入就会让一切轰然倒塌。
🔹合理的默认值:每个变量都应有一个合理的默认值,未定义的变量会导致不可预测的行为和难以调试的微妙错误。
🔹输入验证:切勿盲目信任输入数据。无论是来自用户、API 还是数据库,都必须对输入进行检查,以确保符合预期格式和约束条件。应及早拒绝无效数据,以防止损坏和故障。
⚠️ 错误和异常管理
即使是结构最合理的代码也会遇到意想不到的情况。防御性编程意味着为不可避免的情况做好准备,从而避免错误演变成全面失败。
🛠️ 错误处理:不要假设事情总是按计划进行。预测可能出现的故障,并实施强大的错误处理机制来管理这些故障。
🌀 异常管理:有效利用 try-catch
代码块,与其让意外情况导致程序崩溃,不如捕获并从容应对。
📢 日志:良好的日志记录可帮助深入了解应用程序的行为。在排除故障时,详细的日志非常宝贵,可确保开发人员无需猜测就能找到问题所在。
✅ 断言:使用断言在代码中执行假设。如果不满足关键条件,最好快速失败并尽早发现问题。
🎯 代码质量
代码被阅读的次数要多于被编写的次数。防御性编程不仅要防止错误,还要写出简洁、易懂、易于维护的代码。
🔍 代码审查:第二双眼睛可以发现细微的错误,提高整体代码质量。同行评审有助于确保潜在缺陷变成实际问题之前得到解决。
🧪 单元测试:每个模块都应独立测试。单元测试有助于验证每个组件的功能是否正确,并随着代码库的演进继续保持组件质量。
📌 静态类型检查:在运行前捕获与类型相关的错误,防止意外崩溃并简化调试。
🧩 避免过度优化:过度优化的代码会变得难以理解和维护,应该优先考虑可读性和可维护性,除非确实遇到了性能瓶颈。
🔄 简化复杂性:逻辑越简单,出错的机会就越少。将复杂操作分解成更小、更易于管理的函数。
📦 使用成熟的库:重新发明轮子会带来不必要的风险。成熟的库通常都经过实战检验,因此更加安全可靠。
🔐 安全与稳定
在充满网络威胁,并且软件环境不断变化的时代,稳定性和安全性永远都应该是优先考虑的问题。
🚫 避免空指针:空引用可能是灾难性的。始终正确初始化指针,优雅处理潜在的空值。
🔄 限制循环迭代:无限循环会冻结应用程序。建立明确的终止条件,避免资源耗尽。
🔑 保护关键资源:多线程应用程序必须谨慎管理共享资源,以避免出现竞争条件和死锁。锁等同步机制可确保数据完整性。
⚖️ 优雅降级:弹性系统不会在某个组件发生故障时完全崩溃--它会进行调整。通过设计可处理部分故障的应用程序,关键功能即使在不利条件下也能继续运行。
📜 全面的文档:未来的开发人员,包括未来的自己,都会感谢现在的你提供的清晰而全面的文档。没有文档的代码就像一本缺页的书。
🔗 依赖关系管理
现代软件严重依赖外部库和服务,但依赖性会带来风险,明智的管理依赖可确保长期稳定性。
📉 限制依赖性:每一个额外的依赖都是潜在故障点。软件依赖的外部组件越少,自给自足和稳定性就越高。
📌 版本控制:对依赖关系进行版本控制,可确保更新不会引入破坏性更改。必要时锁定版本,并在升级前进行全面测试。
🛠️ 谨慎更新依赖库:更新并不总意味着更好。更新依赖库应该是一个深思熟虑的决定,并通过全面测试来防止意外问题。
🚀 资源和并发管理
资源管理不善会导致性能迟缓、崩溃,甚至出现安全漏洞。防御性编程可确保系统保持反应灵敏和高效。
🔄 限制并发:过多的并发操作会使系统不堪重负。管理并发可确保性能流畅,而不会造成资源超载。
💾 限制资源使用:资源泄漏(无论是内存、文件句柄还是数据库连接)会随着时间的推移而降低性能。当不再需要资源时,一定要及时清理。
🎨 优化代码结构
结构良好的代码更易于调试、修改和扩展。防御性编程包括保持代码整洁和可持续的实践。
🛑 避免使用全局变量:全局状态会带来意想不到的副作用,使代码难以理解。请尽可能封装状态。
📏 保持函数简洁:函数应该只做一件事,而且要做得好。冗长、复杂的函数更难理解和调试。
🏗️ 限制类的责任:遵循单一责任原则(SRP,Single Responsibility Principle)可使类更易于维护和重用。
🎭 利用设计模式:工厂(Factory)、单例(Singleton)和观察者(Observer)等既定模式可提高模块化程度并减少冗余代码。
🌍 管理外部交互
应用程序与数据库、API 和外部服务交互,所有这些都可能发生故障。防御性编程可确保这些交互保持稳健。
📡 正确处理 API 调用:实施重试机制和超时,从容应对网络故障。
🛡️ 验证外部数据:切勿假定第三方数据是安全的。始终对输入进行检查,以防止注入攻击或损坏。
🛠️ 管理配置:不正确的配置可能导致故障。验证和保护配置数据。
🕵️ 对服务中断制定计划:做好应对服务中断的准备。缓存和回退机制可确保在外部依赖出现故障时的服务可用性。
📉 控制请求频率:对请求进行流控,防止系统超载并保持公平使用。
🔐 加密敏感数据:安全漏洞代价高昂。对传输中和静态数据进行加密可降低风险。
📜 实施数据审计:保存变更日志可提高可审计性和安全性。
真实世界的例子:亚马逊 S3 故障(2017 年)
2017 年 2 月,亚马逊的 S3 存储服务因例行维护期间的人为失误而发生重大故障。这次故障影响了 S3 服务的很大一部分,中断了数千个应用程序。如果有适当的防御程序,如冗余系统、错误处理和重试机制,级联故障本可以减轻或避免。
实践案例
🚀 最终思考
防御性编程不仅仅是为了避免错误,更是为了编写能够在不可预知的条件下具备适应性、可用性并茁壮成长的代码。通过积极主动处理输入、管理错误、优化结构并保护安全,开发人员可以创建在未来数年内仍然可靠和可维护的软件。
让我们今天就接受防御性编程,构建不仅能正常运行,而且经久耐用的软件。🔥
你好,我是俞凡,在 Motorola 做过研发,现在在 Mavenir 做技术工作,对通信、网络、后端架构、云原生、DevOps、CICD、区块链、AI 等技术始终保持着浓厚的兴趣,平时喜欢阅读、思考,相信持续学习、终身成长,欢迎一起交流学习。为了方便大家以后能第一时间看到文章,请朋友们关注公众号"DeepNoMind",并设个星标吧,如果能一键三连(转发、点赞、在看),则能给我带来更多的支持和动力,激励我持续写下去,和大家共同成长进步!
版权声明: 本文为 InfoQ 作者【俞凡】的原创文章。
原文链接:【http://xie.infoq.cn/article/414b1ef39ccc0d5879d7fac65】。
本文遵守【CC-BY 4.0】协议,转载请保留原文出处及本版权声明。
评论