项目实战中的防御性编程

项目实战中的防御性编程_SweetTool的专栏-CSDN博客

介绍

      说明,本博客中提到的工程时Android工程,其他开发者如果有疑惑的话可以在评论中指出,会继续补充。

      最近几个月已经把一个中等规模的项目开发完成,进入良性迭代开发阶段,相对比较有空闲总结项目经验。因为本公司项目从零开始开发,包括需求整理之类的,这一年写博客相对少,业余时间要么休息放松,要么思考项目的需求或者解决方案之类的,项目的事情就不多少了。

       经过这几年的摸爬滚打也负责过两三个完整项目,一步一步走过来,每一次项目完成进入迭代开发后都会趁着空闲时间复盘一下收获和经验教训。这一年在基础框架方面接触比较多,除了自己会根据实际业务写几个class全新 DIY轮子以外,更多的是站在巨人肩膀上使用成熟的开源库进行二次封装,或者熟悉开源库后按照开源库的某一套规则在团队中形成规范,保持代码风格统一。在定义项目基础框架时有一种思想——防御性编码,渐渐在脑海中呈现,就是基础框架要做容错处理,但是也不能肆无忌惮的包容,根据不同的错误情况进行处理;例如release版本的app启动时加入哨兵,检查如果初始化配置是debug测试参数(例如host、其他重要初始化参数)直接打印信息并throw error让程序闪退。

一、《代码大全》

     其实在六月初时候就打算写关于防御性编码的文章,但是当时不确定在编程中是否存在“防御性编程”的说法,有时候我还想着要把文章标题修改为“防御性设计”,但是想想还是先百度一下,避免乱造名词闹出笑话。百度一下,果不其然,有几篇文章介绍了防御性编程,并且在追踪之下发现一本比较权威的书籍《代码大全》(英文书名《code complete》)提出了相关概念,并且在第八章——防御式编程(Defensive Programming)专门介绍相关思想。

      于是乎就搜索资源下载免费pdf,专门调第八章“防御式编程”阅读,阅读后收益良多,感觉在开发项目中隐隐约约运行的一些编码规范,一些思想都和书中提及的方面不谋而合,并且更加系统全面。读者有兴趣可以自行阅读代码大全的防御式编程章节,可以给你的框架设计带来很多借鉴。

二、防御性编程的项目实战

       花了几天断断续续阅读了代码大全“防御式编程”后,觉得Steve McConnell写的实在太赞了,应该是个牛人来着。迟迟不敢下笔,因为跟他的内容比,我的构思漏洞百出。就这样悄悄的过去两个月才有勇气提笔编写。

       本人介绍的防御性编程主要来自项目实战,就是根据个人阅历和经历来写的一个实践过程,有勇气写的原因也在于此,算是给自己一个总结。说不定过个十年回头再阅读自己博客时可以判断当年的方向是否正确,细节是否掌握。

防御性编程之单元测试

       单元测试相信大家都非常熟悉,在大学时候软件工程就提到单元测试,真正使用它的时候还是工作两年后才使用。单元测试的最大好处就是在函数接口的开发阶段就可以模拟各种边界条件,重要接口最好达到判断覆盖或者条件覆盖,至于传说中的路径覆盖如果你是做飞机软件或者医疗手术机器人或者高精密仪器工控软件那么路径覆盖就有用武之地了。根据二八原则,实际集成后函数接口中的运行路径大部分时候都走正常逻辑,异常逻辑比较少,bug往往就产生在异常逻辑,例如参数边界、非法访问等等,所以要在函数实现时相应的进行单元测试用例开发,可以提高验证效率。

       每次说起单元测试,大家总能一套一套的,无论哪个团队都一样(限于本人阅历),甚至盲目崇拜单元测试,觉得如果单元测试做好了,软件就不会有 bug,岂知单元测试代码也是代码,只要是代码就会有bug。

       另外就是实施问题,用过单元测试也有好多年,Java一般是unit test,Android有AndroidUnitTest,c++有Google test。这些工具都很成熟,用起来也方便。可惜人就会犯懒,特别是项目赶进度时压根就没时间写单元测试,甚至连正常代码都写的马马虎虎,这也是客观事实,越是时间紧迫的项目,后期的需求变动越大。因此我渐渐从大部分代码写单元测试转变到针对核心功能写单元测试,并且核心功能做容错处理,即如果有非法参数无法处理的,直接返回错误状态并且打印详细日志,对于致命错误直接throw error闪退(debug阶段)

防御性编程之todo大法

        在开发中,大家通常会遇到这样的场景,我们写函数,会遇到对应的case,这个case处理的时间紧迫程度比较低,当时占用的工作时间比较大,可能是体力活纯debug调节参数,也可能是难度高又技术深度,这个时候我们可以在不影响业务的情况下先用替代方案实现或者忽略,并且在代码之上注释todo标注作者、时间以及todo信息(解决方案、替代方案、或者怕上wc怕忘了,嘻嘻),这样既可以绕过耗时功能继续完成项目功能点,尽快达到冒烟测试标准或者alpha测试标准。

        todo大法好处是应对项目时间赶,并且需求可能会变更的时候,这时候你防御的对象有两个:一个是自己——过两天来看时会骂这垃圾代码谁写的都不做异常处理,然后查git拍拍拍打脸;另一个是产品经理——产品功能点大体没问题,只是在细节方面因为软件还没出来,产品经理看不到实际的软件,无法勾勒出细节或者对某些操作方案没法拍板,todo大法就可以绕开细节,延迟细节部分交付,避免功能被砍后的尴尬。

最后强调两点:第一、todo千万不要写联系方式,todo千万不要写联系方式,todo千万不要写联系方式,重要的事情要说三遍,因为你不知道这代码会不会成为祖传代码,几年后你是否认识;如果你坚持要写联系方式,那么务必坚持原则二;

原则二、一定要定期消灭todo,每一个todo可以说是一个地雷,你不知道什么时候会踩坑,也不知道压到地雷的是步兵还是坦克。俗话说得好,“出来混的迟早要还,只不过这个债是谁来还的问题”(请联系思考原则一)

防御性编程之日志埋点

        开发测试阶段我们可能随时随地会遇到其他同事开发的功能模块异常闪退,或者测试部门报告异常闪退,有些现象是很难复现的,这时候apk日志就非常关键了,能否根据日志判断出问题的方向全靠前期的日志埋点。在实际项目中,日志埋点不可能每个分支都埋点,除了基础框架模块会在关键逻辑分支中增加日志埋点,其他地方的埋点基本靠二分法,就是在一个ui界面到核心基础框架间,一个功能要走多少个函数才会实现该业务逻辑,那么根据实际调用路径的深度在if else分支埋点log.w警告信息,一般每三次函数调用就要埋点一次,这样当闪退发生时就可以达到判断错误方向的情况。

      《代码大全》中也提到,太多的日志有一个风险,就是反编译者可以根据log信息推断应用的业务实现逻辑,所以安全性高的模块还要严格设置log等级。

防御性编程之函数参数合法性

       编写函数如果有输入参数,一般有两种处理参数合法性:可以在java用annotation申明是否nullable,以方便调用者知道是否需要检查null,另一种则在函数内检查参数的是否为 null,如果为null那么返回无效的处理;针对第二种,如果是比较核心模块或者公共频繁使用模块,可以增加一些日志以及埋点监督。

       null参数是比较典型的情况,如果参数不为 null,那么还需要检查参数内容是否在合理范围内,假如不是合法范围内,《代码大全》中提到两种处理:第一种、返回异常值,并打印日志信息;第二种、自动矫正将参数值设置为边界值,例如我在处理绘制图案时,如果手势touch超出某个可绘制区域了,那么超出绘制区域的地方就不绘制。核心点在与如果函数参数范围不符合预想,到底出来兜底处理的问题,如果函数用矫正边界值方式,那么调用者就可以做甩手展柜,置之不理。

防御性编程之release哨兵

       由于新开发的项目经常编译打包,小组中人员超过两人,这时候修改一些配置文件可能不容易知会其他项目成员,因此就会出现极端情况,某些测试版本的初始化参数或者初始化配置因为开发人员为了开发调试方便一时打开了,然后在git commit或者svn commit时一并提交,负责打包的是其他同事,不知道初始化参数被修改,编译后自测试功能无明显错误就提交测试部门测试,一般测试都会测出问题,并且反馈测试失败。有一种比较方便的方案就是设置release哨兵,在条件编译时如果release为true,那么application启动后做完必要的厨师话工作后,就开始校验某些配置是否是release版本,如果不是release版本的配置,那么直接throw error闪退,并打印详细日志,这样就做到自动化检查,保证提交测试的release版本配置没问题。

缺点:对于手机app应用release哨兵实现时打印的log容易让反编译者知道重要的初始化配置;如果时web后台服务器,那么哨兵实现比较麻烦,因为比如域名、安装路径这些配置信息变动的概率非常大,而哨兵写在硬代码中,必须重新编写相关模块,这对于以后老项目管理无疑是一个大坑。

防御性编程之公共垃圾桶

      针对异常case在一个utils类中归类处理,方便后期扩展使用

      我们在处理某种case时可能会遇到异常情况,例如if-else if的最后一个else,或者switch-case的default情况,通常可以将这类情况作为exception处理,不过简单就地printStackTrace或者throw exception感觉不合适,因为以后不知道多少个地方有这种异常处理,是否所有异常处理都是必须的,所以更好的方案就是定义一个专门的utils类集中处理这种逻辑。

防御性编程之选择性失明

       开发时我们会有debug版本和release版本,有些程序异常从软件实现逻辑的角度来讲可能是大问题,但是对于用户日常使用来讲影响不大,这时候我们针对相同的错误信息,在debug版本和release版本的可以采用两种不同的策略(strategy)。其实《代码大全》中有距离表格软件在处理异常数据时,如果时debug阶段就用明显的提示性交互,而release版本则忽略用户的非预期操作导致的异常逻辑。

       本条目在本人的项目实践中跟多的就是定义某种交互需求的重要程度,不是没中极端case都花非常大的精力去调试,但是在平常可以保留线索,方便后续如果问题影响超出预估时进行针对性修复。

防御性编程之删库跑路

       最后一点,其实只是比喻。平常写代码要养成习惯,不要憋大招,代码修改完该提交到源码仓库就提交到参数,别第二天开机硬盘坏了就抓狂;原则上可以是完整功能点完成,或者修复某个缺陷(即使代码量不大)后提交代码;如果功能过大,可以选择在分支开发,并把大功能按照几个细分阶段commit提交,等到功能开发完成再合并到主干。

      防止删库跑路在server开发,更多是服务端代码操作数据库时一定要留下操作记录,如果那天不小心sql命令把数据删除了还能通过日志恢复。至于容灾性的异地备份,定时备份这些,还真是大工程,本人也不太懂,就不介绍了。

总结,防御性编程在实践中主要与预判性相结合,在完成功能的同时为软件的扩展性,健壮性争取更大的操作空间。阅读《代码大全》的防御式编程,本人觉得作者写的太好了,有兴趣的朋友可以自行找书中相关章节阅读,博客仅仅是一种经验分享,还是不够够系统不够专业。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值