防御性编程

目录

(一)前言        

1. 墨菲定律

2. 防御式编程

(二)防御性编程技巧

1. 防御性编程的关键原则:

1.1.  边界防御:检查所有的外部输入

1.2. 异常处理:在正确性和健壮性之间做好取舍

1.3. 应检尽检:没有完全可靠的外部环境

1.4. 显示约束:简单直接的代码风格

1.5, 减少依赖:write once, run anywhere

1.6. 傻瓜式注释

1.7. 契约式编程

2. 避免过度设计

3. 防御性编程技巧

3.1 好的编码风格 和 合理的设计

3.2 编码注意的一些细则

3.3 代码复盘,不用bug驱动工作

3.4 思考问题的方式:


(一)前言        

        可以实现产品需求的代码并不一定就是好的代码。好的代码,在效率、质量、可维护性、可扩展性、可读性等方面都有较高的水准。

        软件的健壮性是衡量一名工程师水平的重要标准。合理的顶层设计、完备的测试的根本都是提升代码质量。

1. 墨菲定律

        任何一个事件,只要具有大于零的几率,就不能够假设它不发生,风险能够由可能性变为突发性的事实。墨菲定律1. 任何事都没有表面看起来那么简单2. 所有的事都会比你预计的时间长3. 会出错的事总会出错4. 如果你担心某种情况发生,那么它就更有可能发生),如果有两种或两种以上的方式去做某件事情,而其中一种选择方式将导致灾难,则必定有人会做出这种选择;这是一种偏悲观的思想,认为所有可能出问题的坏情况都会发生。

        抱有该想法去做设计时,需要对最坏情况做出预测并采取相对应的措施;让使用人员不需要进行复杂的思考,通过直觉即可使用某个系统,即所谓的防呆设计

【注】:程序员奉为圭臬的著作《Code Complete》(代码大全)里,详细介绍了防御式编程。

2. 防御式编程

        防御式编程,其核心思想是子程序应该不因传入错误数据而被破坏,哪怕是由其他子程序产生的错误数据。简单来说,就是怀疑一切,认为自身代码之外的环境都是不可信的,在这种情况下,考虑代码该怎么写。防呆设计

         防御性编程,使我们可以:① 尽早的发现较小的问题,而不是等到它们发展成大的灾难的时候才发现;② 系统中的每个组件,使其尽可能的保护自己。

(二)防御性编程技巧

1. 防御性编程的关键原则:

1.1.  边界防御:检查所有的外部输入

        所有的外部输入都是不可信的,需要校验是否在可允许的范围内。

        需要检查的项包括:空指针数组越界不合法入参等。(尤其是在写公共方法时不确定该方法会在未来的某个时刻以什么样的形式被外部系统调用,做好输入检查才能提升自身程序的健壮性

1.2. 异常处理:在正确性和健壮性之间做好取舍

        正确性:程序永不返回不准确的结果,即使这样做会不返回结果或是直接退出程序。

        健壮性:系统在不正常的输入或不正常的外部环境下仍能正常运行,哪怕输出结果是错误的或者不完整的。

        取舍的结果取决于其使用场景。异常低容忍度的场景:正确性 > 健壮性,eg:火箭发射系统、医疗系统。电商等消费场景中:健壮性 > 正确性,选择商品不成功,重新刷新一下即可。

        但比较好的做法是重试或者返回错误码,而不是让程序直接退出。

1.3. 应检尽检:没有完全可靠的外部环境

        对所有的外部调用保持警惕,没有完全可靠的外部环境。一种好的思路是尽可能地按照自身逻辑,对外部调用做检查和异常处理

        这些API或者三方类库也是人写的,人写的就意味着可能有bug。eg:① 调用数据库是否成功;② 调用完数据库是否手动释放资源(有可能造成内存泄漏)。

1.4. 显示约束:简单直接的代码风格

例子:

① "a++ = b++"(不提倡)

     写成"a = b; a++; b++",(代码多写两行,意义更清晰,更容易理解)

② 使用显示约束。多用const final static,避免使用select *,字段名前加上表名。

1.5, 减少依赖:write once, run anywhere

        在代码中减少对环境的依赖,确保外界环境改变了,程序依然可以正常运行。

例如:"i++ != j++" 在 不同的编译器中执行结果是不同的,这种也是我们需要避免的。

1.6. 傻瓜式注释

        clean code提倡代码即注释,这是一种理想化的情境。

        好的注释应该出现在:复杂的业务逻辑非常规的写法可能有坑的地方临时解决方案项目当中核心的类或者方法

1.7. 契约式编程

        在设计阶段就已经确定好每个方法的边界,包括每个方法的参数、返回值,以及它们的类型和所有可能的值。

2. 避免过度设计

① 预防不可能发生的事情;

② 过多防御式代码,导致整体程序显得臃肿、难以维护,影响程序性能(充斥大量的判断代码、非业务代码);

当代码中有非常多的异常捕获和处理时,可能会导致异常被吞掉,没有正常的报出来。

3. 防御性编程技巧

3.1 好的编码风格 和 合理的设计

1、const、volatile、static

 ① const关键字:

  • const对象被创建后,其值不能修改,存放在常量区;const对象必须初始化
  • 传达非常有用的信息。eg:函数的形参前添加const,在函数体内不会被修改,属于输入参数。
  • 合理地使用关键字const,使编译器很自然的保护那些不希望被修改的参数,防止其被无意的代码修改,减少bug的出现。

 ② volatile关键字:

  • 修饰的变量是易变的,每次读操作都从内存中读取,每次写操作都直接写到内存中

        两个线程同时访问一个变量时,为避免编译器过度优化,使用该关键字解决共享变量的内存可见性问题

  • 在一些并行设备的硬件寄存器(如状态寄存器),中断服务子程序中会访问到的全局变量以及多线程应用中被几个任务共享的变量前使用volatile关键字来防止编译优化。

 ③ static关键字:

  • 限定作用域和存储方式内存只被分配一次;模块内的static使用范围被限制在声明它的模块内。
  • 函数体内static变量的作用范围为该函数体,不同于auto变量,该变量的内存只被分配一次,因此其值在下次调用时仍维持上次的值。
  • 模块内的static全局变量可以被模块内的所有函数访问,但不能被模块外其它函数访问。
  • 模块内的static函数只可能被这一模块内的其它函数调用,这个函数的使用范围被限制在声明它的模块内

 2、位操作运算中,尽可能使用<<、 >>、 &、|等运算符,尽可能少使用/、%、*运算符。

 3、变量和函数的命名要有意义,尽可能:一个函数只做一件事情,一个模块只做一类事情。

4、多采用面向对象的思想来编写代码

  • 继承、封装、多态
  • 抽象、模板

5、在投入到编码工作之前,先考虑大体的设计方案,这也非常关键。 

  • 确定需求、如何扩展、模块划分,着眼于未来
  • 核心技术的采用,公共库的准备和逐步完善,关键步骤的设计

6、不要仓促的编写代码

  • 写每一行的时候都三思而后行,考虑所有可能出现的逻辑分支;充分设计、减少缺陷的好办法
  • 可能出现的问题:① 将 “==” 错误的写为 “=” ,编译器不会报错;② 不关闭文件;③ 不释放指针;④ 不解锁;.....
  • 执行期间所获取的任何资源,必须释放

7、不要相信任何人:首先检查是否遵守契约

8、检查所有的返回值:

  • 一个函数返回一个值;如果返回值是一个错误代码,必须辨别这个代码并处理所有的错误
  • 定义的方法采用bool类型返回值,错误信息由调用者确定;
  • 大多数难以察觉的错误都是因为程序员没有检查返回值而出现的;
  • 迭代开发错误码、错误日志(eg:打开文件错误码);

9、编码目的要清晰,而不是简洁

  • 多写两行代码,意义更清晰、更容易调试。
  • 例如:“a = ( b>1 ? c : d);”写成“e = b>1 ? c : d; a = e;”。

10、变量随时用随时定义

  • 变量在使用的十行之内能找到其定义最好。在声明的位置初始化所有变量。 

11、编译时打开所有警告开关;

12、系统以2的整数倍分配空间,在申请内存空间大小时,以2的整数倍来申请,避免出现系统分配了但由于未申请而造成的浪费。

3.2 编码注意的一些细则

1、编写不带else子句的if语句是否该处理这个逻辑上的默认情况;

2、switch语句中,将 default case 的执行明示出来;

3、确保每次运算数值变量都不会溢出;

4、数据类型的使用要谨慎;

5、 变量的作用域:

        ① 尽量缩小变量的作用域,防止干扰其他代码;

        ② 不被外部调用的变量,不定义为公共成员变量;

        ③ 参数数目较多,定义参数类,数据和操作分开;

        ④ 能用较少参数传递解决的问题,不要定义私有成员变量;

        ⑤ 提高代码可读性。

6、注意强制转换是否合理;

7、正确设置常量;

3.3 代码复盘,不用bug驱动工作

        每天下班之前,复盘自己的代码;争取一次写到最终状态;

        过些天再检查一下,但不能想着靠复盘来解决问题。

3.4 思考问题的方式:

【例子】:

英文字符串 转换为 中文字符串:

① 初级、刚毕业程序员:直接写到程序段里,用 if - else 交差,扩展的时候增加 else;

② 稍微有些经验的程序员:在程序中写一个map对照表,用的时候去这个表里面查,增加的时候改这个map  --------  但还是程序员修改;

③ 高级程序员:写一个配置文件,并实现一套配套的文件解析代码,程序启动的时候解析到程序里,用的时候去查,扩展的时候修改这个文件  --------  不需要修改代码;(写程序解析代码,找可以将解析位置放到的位置)

④ 项目经理、系统架构师:配置文件、及配套的解析程序开发一个单独的库,做成多语言扩展版本,不一定仅仅局限于中英文。提供简洁的接口,使调用超级便捷。除了此次应用,还可以供其他开发的时候调用。

【参考】:

      如何写出好代码 - 防御式编程_云智慧AIOps社区的博客-CSDN博客_代码防御编程

【未完善内容 可参见】:

      《代码大全》学习之--防御式编程 - Core Hua - 博客园

  • 2
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值