读程序员的README笔记04_防御式编程

发布时间 2023-12-08 06:49:20作者: 躺柒

1. 编写可维护的代码

1.1. 生产环境下的软件必须一直保持可用的状态

1.1.1. 用户行为不可预测,网络不可靠,事情总会出错

1.2. 编写可维护的代码有助于你应对不可预见的情况,可维护的代码有内置的保护、诊断和控制

1.2.1. 切记通过安全和有弹性的编码实践进行防御式编程来保护你的系统,安全的代码可以预防许多故障,而有弹性的代码可以在故障发生时进行恢复

1.2.1.1. 切记让你的代码安全而有弹性

1.2.1.2. 编写拥有良好防御性的代码是一种对那些运行你的代码的人(包括你自己!)富有同情心的表现

1.2.1.3. 防御性的代码较少发生故障,就算它发生故障,也更有可能恢复

1.2.1.4. 安全的代码利用编译时的校验来避免运行时的故障,使用不可变的变量、限制范围的访问修饰符和静态类型检查工具来防止bug

1.2.2. 一个可维护的系统具有可配置参数和系统工具

1.2.3. 你需要在不修改代码的情况下控制系统

1.2.4. 将日志、指标和跟踪的调用信息暴露出来可以方便诊断

2. 避免空值

2.1. 没有值的变量默认为null(或nil、None或其他一些变体)

2.2. 通过检查变量是否为空,通过使用空对象模式(null object pattern),或通过可选类型(option type)来避免空指针异常

2.2.1. 空对象模式会使用一个对象来代替空值

2.3. 切记在方法的开头进行空值检查

2.3.1. 可以使用NotNull注解和编程语言中类似的特性

2.3.2. 在前面校验变量是否为空意味着后面的代码可以安全地假定它是在处理真实的值,这将使你的代码更干净、更易读

3. 保持变量不可变

3.1. 不可变的变量一旦被赋值就不能被改变

3.1.1. 不可变的变量可以防止意外的修改

3.2. 使用不可变的变量可以使并发编程变得更简单,而且当编译器或运行环境知道变量不会改变时就可以运转得更有效率

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

4.1. 限制变量可以被赋的值

4.2. 静态类型检查器在代码执行之前会使用类型提示来发现潜在bug,所以配合静态类型检查器一起使用,你就可以防止运行时出现故障

5. 验证输入

5.1. 永远不要相信你的代码接收的输入

5.2. 校验输入的值可以避免出现意外

5.3. 开发人员、有问题的硬件和人为的错误都会破坏输入的数据

5.3.1. 计算机硬件并不总值得信赖,网络和磁盘可能会损坏数据

5.3.2. 不要忽视安全问题,外部输入是危险的

5.3.2.1. 恶意用户可能试图在输入中注入代码或SQL,或撑爆缓冲区以获得对你的应用程序的控制权限

5.4. 通过校验输入的正确性去保护你的代码

5.4.1. 用先决条件、校验和(checksum)以及校验数据合法性

5.4.1.1. 如果你需要强大的耐久性保证,使用校验和的方式来检查数据没有意外的变化

5.4.2. 套用安全领域中的最佳实践

5.4.2.1. 强制转义输入的字符来防止SQL注入攻击

5.4.2.2. 在使用strcpy(特别是strncpy)等命令操作内存时,明确地设置缓冲区的大小,以防止缓冲区溢出

5.4.2.3. 使用广泛采用的安全与密码类库或协议,而不是自己去编写这样的类库或协议

5.4.2.4. 熟悉开放式Web应用程序安全项目(open Web application security project,OWASP)的十大安全报告以快速建立你的安全知识体系

5.4.3. 使用工具来发现常见的错误

5.4.3.1. 使用成熟的类库和框架来防止跨站脚本攻击

5.4.4. 尽可能地提早拒绝不良输入

5.5. 使用前置条件和后置条件的方式来校验方法中输入的变量

5.5.1. 当你使用的数据类型不能完全地捕获有效的变量值时,可以使用校验前置条件的类库和框架

6. 善用异常

6.1. 有弹性的代码使用异常处理中的最佳实践来优雅地处理故障

6.2. 不要使用特殊的返回值来标识错误类型(如null、0、−1等)

6.3. 异常要有精确含义

6.3.1. 精确的异常使代码更容易使用

6.3.2. 尽可能地使用内置的异常,避免创建通用的异常

6.3.2.1. 如果一个内置的异常可以描述问题,就不要创建自定义的异常

6.3.2.2. 当你创建自己的异常时,不要把它们弄得太通用

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

6.3.3. 使用异常处理来应对故障,而不是控制应用程序的运行逻辑

6.3.3.1. 不要在应用程序的运行逻辑中使用异常

6.3.4. 使用异常来跳出方法常常令人困惑,并且使代码难以调试

6.3.4.1. 你的代码是不出人意料的,而不是聪明的

6.4. 早抛晚捕

6.4.1. 遵循“早抛晚捕”的原则来处理异常

6.4.2. “早抛”意味着在尽可能接近错误的地方引发异常,这样开发人员就能迅速地定位相关的代码

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

7. 智能重试

7.1. 在故障发生之后,系统处于什么状态并不总显而易见

7.1.1. 面对一个错误时,适当反应往往是简单地再试一次就好

7.1.2. 不要盲目地重试所有失败的调用,尤其是那些写入数据或可能触发一些业务流程的调用

7.2. 最单纯的重试方法是捕捉到一个异常马上就进行重试

7.3. 谨慎的做法是使用一种叫作“退避”(backoff)的策略

7.3.1. 退避会非线性地增加休眠时间(通常使用指数退避,如(retry number)^2)

7.4. 构建幂等系统

7.4.1. 处理重试的最好方法是构建幂等系统

7.4.2. 一个幂等的操作是可以被进行多次并且仍然产生相同结果的操作

8. 及时释放资源

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

8.2. 所谓网络套接字泄露,是指在使用后没有关闭它们

8.2.1. 网络套接字泄露会使无用的连接一直存在,从而填满连接池