DC娱乐网

谈谈防御性编程

防御性编程对于程序员来说是一种良好的代码习惯,是为了保护自己的程序在不可未知的异常下,避免带来更大的破坏性崩溃,使得程序

防御性编程对于程序员来说是一种良好的代码习惯,是为了保护自己的程序在不可未知的异常下,避免带来更大的破坏性崩溃,使得程序在错误发生时,依然能够云淡风轻的处理,但很多程序员入行很多年,写出的代码依然都是我行我素,在CodeReview时经常性的会被人反问,这种情况出现的概率极低,有必要去做控制吗?,有必要去做处理吗?

举个例子

一、一个很简单的工具类:实现的功能很简单,就是列出在开始时间与结束时间之间的所有的年月,看这段代码有什么问题?

public static List<String> getDayList(Date start, Date end){ if(end ==null){ end = new Date(); } if(start ==null){ start = end; } if(start.after(end)){ return null; } // 开始时间 Calendar calBegin=Calendar.getInstance(); calBegin.setTime(start); calBegin.set(calBegin.get(Calendar.YEAR),calBegin.get(Calendar.MONTH), 1); // 结束时间 Calendar calEnd =Calendar.getInstance(); calEnd.setTime(end); calEnd.set(calEnd.get(Calendar.YEAR),calEnd.get(Calendar.MONTH), 2); List<String> dateList = new ArrayList<>(); while (calBegin.before(calEnd)){ dateList.add(format(calBegin.getTime(),"yyyyMM")); calBegin.add(Calendar.MONTH,1); } return dateList; }

初看一下,感觉没什么问题,该判空的也判空了,但真的没有问题吗?假设开始时间为2020-01-01,而结束时间为20202020-01-01,这将出现什么情况?看到这里大致大家已经明白了,开始时间与结束时间的间隔没有被限制,这就可能会导致dateList爆满,从而导致内存暴涨,会直接导致CPU暴涨,导致服务卡死。

二、再来看一个例子:一个简单的往队列里面放入一个元素,然后在从队列里面取出元素进行业务逻辑处理,典型的生产者-消费者的这么一个使用场景,这里有什么问题?

class Test{ private static BlockingQueue<String> blockingQueue=new LinkedBlockingQueue(); public static void add(String element){ blockingQueue.add(element); } public static void execute(){ String element=blockingQueue.poll(); if(element!=null){ //处理业务逻辑 } }}

这个例子,比前面一个例子稍微简单些,大致大家一看可能就明白了,blockingQueue没有设置大小,是一个无界队列,如果因为消费的业务逻辑处理慢的情况,可能也会导致Queue的数据大小不可控,从而导致内存暴涨。

通过上述两个简单的例子,不难发现,均是遗漏了对于代码的防御性编程,从而带来了程序执行的不可控因素,最终均会导致服务卡死,业务中断,那么防御性编程在编程的时候主要要考虑哪些方面呢?

常见的防御性编程方法

一、严把参数质量关

在防御性编程的眼里,任何上下游的参数均是不可置信的,均为做出假设,如果上游调用方给出的参数不准确,将作何反应,因此参数的检测是必不可少的。

参数的检查主要在以下几个方面考虑

1、参数的格式是否存在问题

2、参数的取值范围是否在预期范围内,尤其是边界问题

3、参数的类型是否符合要求

4、多个参数的传值是否符合业务逻辑,比如有些参数之间是存在互斥的,比如有一些计算金额的参数,是要满足一定的计算公式.......

二、异常的处理机制是否优雅?

很多程序员为了图省事,直接在代码里面一个大的try....catch,企图通过一个try....catch 去解决所有异常问题,这是典型懒的行为。

一般来说不同类型的异常处理的方式均有可能存在着差异,比如有些情况下的异常,如网络异常,通过一定的重试机制就能解决。而有一些幂等的异常,是可以直接进行忽略,因此如何进行优雅的异常处理,对于防御性编程尤为重要。

需要针对于不同的异常进行针对性的处理,首先要对于可能产生的异常进行分类,然后提出以下问题

1、该异常是否可以通过重试恢复?

2、该异常是否可以通过兜底措施恢复?

3、该异常是否属于正常的异常,例如幂等异常?

4、该异常是否需要人工干预?

........

另外良好的错误码设计,对于调用方来说,也较为重要,调用方能通过错误码能清晰的知道,该错误是属于一个什么场景下的异常,是业务不符逻辑还是系统层面的?是一个严重的还是一个一般的异常等等。

三、在程序内部常使用断言进行内部检查

在程序内部要经常性的插入检查点,用于验证程序在某些特定的场景下,程序的执行依然符合预期,比如对于一个集合进行排序,那么我们是否可以采用断言来检查这个集合是否是真正有序的,从而保证程序的执行和实际的结果是符合预期的。

最后

防御性编程是稳定性建设中很重要的一个思维,是能有效提升系统稳定性,减少了线上故障发生,提高用户体验的很重要的手段,很多防御性编程是可以通过CR、代码编程规范以及辅以漏洞扫描工具去发现和解决,让问题尽可能的在上线前发现和解决。