Please enable Javascript to view the contents

【真】防御式编程指南

 ·  ☕ 5 分钟

去年开始,劳资关系极度紧张,矛盾激化。导致有部分程序员为了保证工作,提出要进行“防御式编程”。这里的防御性编程,是指写屎山代码,以保证自己的工作不会被别人替代。我已经写过一篇文章,来反对和批判这种行为。

2023年流行词汇大盘点:为什么我们不应该摆烂?

但其实,防御式编程是一个非常下面的编程思想。防御性编程(Defensive programming)是防御式设计的一种具体体现,它是为了保证,对程序的不可预见的使用,不会造成程序功能上的损坏。它可以被看作是为了减少或消除墨菲定律效力的想法。防御式编程主要用于可能被滥用,恶作剧或无意地造成灾难性影响的程序上。

《程序员的README》

我最近在读《程序员的README》,原作名: 《The Missing README: A Guide for the New Software Engineer》,意思是给萌新软件工程师的指南。

《程序员的README》

个人觉得这本书非常好,不仅适用于刚参加工作的程序员,也适用于老程序员查漏补缺。我会在后面的文章中,详细介绍这本书。这本书中,有一章节,专门讲解防御式编程。

防御式编程指南

编写拥有良好防御性的代码是一种对那些运行你的代码的人(包括你自己!)富有同情心的表现。切记让你的代码安全而有弹性。

防御式编程指南

避免空值

在许多语言中,没有值的变量默认为null(或nil、None或其他一些变体)。空指针异常是一种常见的情况。

切记在方法的开头进行空值检查。在条件允许的情况下,可以使用NotNull注解和编程语言中类似的特性。在前面校验变量是否为空意味着后面的代码可以安全地假定它是在处理真实的值,这将使你的代码更干净、更易读。

保持变量不可变

编译器或运行环境知道变量不会改变时就可以运转得更有效率。所以,在JavaScript 当中,优先使用const,let次之,var最后。

const 声明可以让那个浏览器运行时强制保持变量不变,也可以静态代码分析工具提前发现不合法的赋值操作,只在提前知道未来会有修改时,再使用 let。

使用类型提示和静态类型检查器

动态语言如Python(从Python 3.5开始)、通过Sorbet校验的Ruby(计划成为Ruby 3的一部分)和JavaScript(通过TypeScript)现在都对类型提示(类型声明和类型注解)和静态类型检查器有越来越强大的支持。类型提示会在动态定义的类型中让你明确指定一种变量的类型。

简单说,就是要用强类型语言或者类强类型语言。

验证输入

使用前置条件和后置条件的方式来校验方法中输入的变量。尽可能地提早拒绝不良输入。

如果一个参数应该大于0,那就要确保它大于0;如果一个参数是IP地址,那就要检查它是否是一个有效的IP地址。

文章举了一个不接受 Word 文档的例子:有一个系统不支持上传Word文档,但是用户总是上传Word文档。这个系统的开发者,他添加了一个画着一条红线的微软Word的大图标和一个说明超链接。大大的减少了用户的误操作。

计算机硬件并不总值得信赖,网络和磁盘可能会损坏数据。如果你需要强大的耐久性保证,使用校验和的方式来检查数据没有意外的变化。熟悉开放式Web应用程序安全项目(open Web application security project,OWASP)的十大安全报告以快速建立你的安全知识体系。

善用异常

不要使用特殊的返回值来标识错误类型(如null、0、−1等)。所有的现代编程语言都支持异常或有标准的异常处理模式。

在写代码时,可以考虑异常优先。Node.js的回调函数模式是一个很好的例子,它将错误作为第一个参数传递给回调函数。

异常要有精确含义

大多数语言都有内置的异常类型(如FileNotFoundException、AssertionError、NullPointerException等)。如果一个内置的异常可以描述问题,就不要创建自定义的异常。开发人员有经验去处理现有的异常类型,他们会知道这些异常具体是什么意思。

通用的异常很难处理,因为开发人员并不知道他们正面临什么样的具体问题。

不要在应用程序的运行逻辑中使用异常。你应该希望你的代码是不出人意料的,而不是聪明的。

早抛晚捕

“早抛”意味着在尽可能接近错误的地方引发异常,这样开发人员就能迅速地定位相关的代码。等待抛出异常会使我们更难找到错误实际发生的位置。当一个错误发生之后,却在抛出异常之前执行了其他代码,你就有可能触发第二个错误。如果第二个错误抛出了异常,你就不知道第一个错误其实已经发生了。跟踪这类错误是令人“抓狂”的——你修复了一个bug,却发现真正的问题出在上游。

“晚捕”意味着在调用的堆栈上传播这个异常,直到你到达能够处理异常的程序的层级。

当调用可能抛出异常的代码时,要么完全地处理它们,要么将它们在堆栈中进行传播。

智能重试

谨慎的做法是使用一种叫作“退避”(backoff)的策略。退避会非线性地增加休眠时间(通常使用指数退避,如(retry number)^2)。

如果一个网络服务器发生了一起突发事件,而且所有的客户端也都同时经历了这起突发事件,那么就用同样的方法进行退避。

让应用程序在遇到其在设计时没有预想到的错误时崩溃,这被称为“快速失败”。

构建幂等系统

处理重试的最好方法是构建幂等系统。一个幂等的操作是可以被进行多次并且仍然产生相同结果的操作。

及时释放资源

当故障发生后,要确保清理所有的资源,释放你不再需要的内存、数据结构、网络套接字和文件句柄。

1
2
3
f = open('file.txt', 'r')
# ...
f.close()

在f.close()之前发生的任何故障将阻止关闭文件指针。with语句会在调用路径离开代码块时自动关闭句柄。

1
2
with open('file.txt', 'r') as f:
    # ...

参考资料

分享

码中人
作者
码中人
Web Developer