有人在博文中写出程序流程:
main( ) { init(); //初始化
while(1) {
step1: 从系统消息队列中取一个消息
step2: 将消息发给指定任务处理
} }
每个任务的处理流程如下:
xx_task_process( *msg )
{
step1: 分析消息类型
step2: 调用对应的处理流程
step3: 向系统消息队列发送消息
return;
}
他是想说,每个任务的处理是互相独立的,如果有判空返回的防御,可让软件不崩溃,仅仅忽略一个任务的执行而已,将影响降到最低。
但是实际的电信设备类软件(嵌入式软件)真的这么简单吗?真的可以做到完美的防御所有入参为空指针的情况吗?我觉得大部分软件,包括电信设备类软件(嵌入式软件),不大可能做到完美防御;
同时,这类防御的成本太高,就算做到了,那成本也太大了,大到让我们不如不做这种防御。
作者写出的流程只是最上层的处理消息的流程,看上去好像很单纯,各个消息的处理很容易做到互相独立。但是我们要理解的是,要想真正的做到在任何判空的地方返回,完美地忽略掉任务,是非常难得。
因为有个叫“副作用”的东西。如果这个任务在返回之前没有产生副作用,那只要注意返回时不产生副作用就行;如果在返回前已经产生副作用,我们为了完美的忽略掉这个任务,还需要消除之前产生的副作用。
这非常难!要想做到任意一个判断返回都完美地忽略掉任务,基本不可能,就算做到了,那成本也太大了。
有人可能会说,既然在任意函数中判空返回,完美地忽略掉任务很难,那我们就看着办吧,碰运气,能做到完美最好,做不到,留下副作用也无所谓。这里就要说到留下副作用的危害了:“带病运行”。
最终导致问题的爆发点跟案发第一现场相差十万八千里。“带病运行”危害太大,应该尽量避免。
有人可能会说,我不滥用还不行吗,我只在某些函数中使用判空返回。针对这个,我想说两点:1.你必须保证完美防御 2.你需要考虑完美防御的成本是否太高。事实上,我认为这类防御手段不应该成为一种常用的防御手段。
我倾向于把这类防御手段看做是缓兵之计:暂时规避问题,保证不崩溃,减少损失;同时,赶紧找出问题根源,解决之。
我强烈反对滥用甚至强制在每个函数里面使用这类防御。很遗憾,我司有的项目组好像有这样的规定。用屁股想想就知道,他们虽然在每个函数里面防御了,但是肯定没有做到完美防御,就是他们的代码可能会“带病运行”。
同时,额外增加了很多成本: 1.每个函数开始处都要写这种恶心的判断代码 2.每个函数的返回值都被错误码占用了 3.每个调用函数的地方必须判断返回值,然后可能会做一些处理,但是我可以肯定,他们不可能做到完美防御。
4.不完美的防御导致“带病运行”,最终导致问题难以定位。
PS1: 有人列出了目前三大烂规定:1.函数针对入参判空并返回 2.代码里面写修改记录 3.判断语句常量放左边
PS2: 文中提到“ 事实上,我认为这类防御手段不应该成为一种常用的防御手段。我倾向于把这类防御手段看做是缓兵之计:暂时规避问题,保证不崩溃,减少损失;同时,赶紧找出问题根源,解决之。”
简单说,我的观点是谨慎酌情使用,不能滥用,我倾向于主要作为“缓兵之计”使用。
如果能做到完美防御(完美的忽略这次执行,不产生副作用。具体效果来说可以是无坏的影响或者将坏的影响降低。并且执行效果必须是确定的,不能是随机的),并且防御成本相对较低,那完全是可以做的呀,我没有反对。
同时要注意到,这篇文章的讨论仅仅针对“函数针对入参判空并返回”这个具体问题。这种问题往往是不能做到完美防御,或者很难做到完美防御的(成本很大)。
当然,如果你在某些点确实做到了完美防御并且相对成本较低,性价比很高,那当然是OK的。但是我相信,这样的点不会很多。如果你很多地方采用了这种防御,我有理由相信:1.有些点你没做到完美防御 2.你花的成本很大 如果防御是不完美的,
然后在那祈祷运气好,期望最终执行是无坏的影响或者坏的影响降低,那只能祝你好运了。鉴于不完美的防御导致带病运行,导致问题难定位,我觉得尽量不要使用不完美的防御。
PS3: 这次讨论的问题非常具体,就是“函数针对入参判空并返回”,不要泛化、上升,不然没法讨论了。针对这个问题我相信这篇博文表述的已经相当全面,请仔细全部阅读完再参与讨论。