版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领
文档简介
调测之前我们能做些什么?——代码走查实践体会与总结手机事业部软件一部蒋荣摘要:今年事业部正在大力推广软件的代码走查活动,因为代码走查是成本相对比较低的一种发现缺陷的手段。但是即使是这种简单的活动,似乎在项目中推广起来还是举步维艰。这里面包括项目压力、观念、方法等诸多原因。本人在做代码走查方面方法改进和推广的工作,几个月下来,收集了一些值得总结的问题和方法,特总结于此,希望能给同行一些指引。代码走查的重要性很多人可能耳朵都听出老茧了,本文不想涉及过多的理论化的口号,也不想引用一些大家都不知道是否真实的业界的统计数据,主要是本人的一些亲历体会和认为可以推荐的一些方法共享给大家。期望据此展开这方面的方法和经验的讨论,而不是仍然停留在做与不做的阶段进行无休止的徘徊和争论。关键词:代码走查调测调试测试缺陷写程序“三分编,七分调”?我刚毕业开始做软件开发的时候,所在的不正规的软件部门没有正规的测试手段。对故障以及故障会意味着什么也没有什么深刻的体会,反正只要能写出来完成差不多的功能就行。开发人员代码写完了大概跑一跑,崔得急的话可能马上就拿到现场去试着用,有时候还得在现场直接改代码。后来在修改过程中逐渐发现,很多时候花很少时间就已经完成编码的模块,但从开始调试到最终基本稳定要花几倍于编码的时间,而大部分情况都是在处理一些不大不小的小故障,这其中定位故障几乎花了80%的时间。在纳闷中我问我的师傅,师傅告诉我写程序就是这样,所谓“三分编七分调”,以后你就习惯了。于是我如获至宝似的获得了一个真理,在调试解决故障上花时间似乎是必然付出的代价,也没想去找什么捷径。这种懵懂的过程一直持续了很久,到后来逐渐有所改变。因为经过一段时间的磨练,自己也积累了一些所谓的经验的小窍门,我发现有时候当有些故障出现的时候,凭一些经验即使不用调试代码,直接阅读代码居然能大概定位到一些问题,这样给自己节约了不少的时间,因为毕竟调试和跟踪总是很耽搁时间的。再后来我也开始当师傅了,新来的兄弟似乎在重复着我原来的过程,有时候他们遇到故障的时候,我便提出来让他们尝试别先急着调试跟踪,先分析一下现象后大概阅读一下有关的代码,就算是猜吧!运气好的话也许不用调试就找到问题了呢?虽然不保证肯定能行,但是花一点时间试一试,万一搞定了不是很节约时间吗?有很多次居然可以奏效。到那个时候我对我师傅的话开始有点犯疑,因为我发现在调试之前,似乎还是有其他办法可以试一试的,而他居然从来没有给我提起过。我一直有个习惯,假如我在自己肩膀上发现了一条虫,除了飞快的拍掉它后我还会把自己全身都来个大搜索,因为我担心身上其他地方还有,我母亲说这个习惯是因为我从小就胆小,但是我辩解说既然肩膀上有,那其他地方肯定不排除啊,呵呵。我不知道自己这个习惯的来源,有趣的是这个习惯居然体现到了我的平日的编码习惯中。只要发现了一种典型的错误做法,我会不自觉的检查整个模块是否有同样的错误。这个习惯我一直沿用至今,他至少提醒我错误一旦被发现,同样的错误不要在一个模块中重犯。直到后来我才明白,原来我所体会的这种习惯有一个更正规的名字,叫做代码的“专项走查”。后来有缘进入了ZTE,对代码安全性和正确性的要求更高了,才知道代码走查是软件开发过程中非常重要的环节。但是很遗憾,我当时所在的ZTE部门在这个方面仍然不是做的很好,大家面对强大的项目压力和现场故障,所能做的都是不断的测试和调试,阅读代码只是根据个人定位故障的习惯随意采用而已,并没有提升到一个很严格的研发过程中来。但是有几次故障定位的经历使我印象特别深刻。其中一个是我在培训时候给大家讲过,当时我们的一个语音服务器在辽宁的一个地方出现了故障,现象非常严重。由于是在线系统不能停,我和几个开发人员每天都是半夜开始加班编写调试模块跟踪现象,累了几天晚上,仍然毫无头绪。忙乱过后我们静下心来做了仔细分析:(1)这个平台在国内外几十个点都在稳定运行;(2)辽宁这个平台话务量特别大;(3)辽宁这个平台刚开始一直没有问题因此有一个累计过程;(4)重新启动后故障依旧证明故障出现在具有记忆存储体的处理逻辑中。经过分析我们发现,由于我们这个平台唯一的存储体就是采用一种hash算法的内存数据库,这个数据库纪录了语音的索引并定期保存到磁盘印象文件。我们把重点放在这个内存数据库模块上,由于这个模块比较独立,于是我发动所有开发人员对这部分代码进行地毯式阅读,尤其关注引起语音索引混乱的数据问题。最后,一位开发人员在代码中找到了原因,是一个纪录语音复用个数的数据定义太小,当转发次数超过64K后发生归0溢出,造成语音定位混乱。问题好像就这样解决了,但其实还并没有完。发现这个现象后,我们立即怀疑是否还有其他地方涉及这个参数的地方定义是否也有类似的问题?于是我们继续阅读了相关几个模块的代码,发现在程序的启动模块中由于需要对数据库进行数据整理,与此相关变量同样定义小了,也就是如果我们只修改前面提到那个地方暂时解决了问题,但当系统由于某种原因发生双机倒换而重新启动的时候,那个问题又会冒出来。当时我们冒了一身冷汗,每个人都觉得很庆幸,至此修改完后问题彻底得到了解决。经过一些教训后我总结出几个结论:只要程序是人写的,肯定就有缺陷。调试代码环境要求高,有些时候根本不允许调试(比如现场)。阅读代码没有任何环境要求,只要有源代码,随时可以阅读。调试代码每次只能定位到一个缺陷。阅读代码可能找到一批类似的缺陷。带着问题有针对性的阅读代码可以提高成功率。基于上面的结论我们可以总结出这样一个原则:当故障出现后,定位故障除了采用跟踪调试的手段,有时候直接阅读代码也许是不错的选择,并且一旦发现一个故障,通过阅读代码有针对性的扫描整个模块相类似的错误通常可以找到更多的错误。当然,谁也没有否定调试跟踪的重要性,我只是在想,假如有10个故障依次需要调试跟踪,其中能有5个故障可以尝试通过阅读代码而定位,是否总体上可以节约一些时间?为何都钟情于调试和测试?前面啰嗦了那么多,无非就是想说明有时候通过阅读代码定位故障在成本上有时候很划算。但是我还是觉得有点不满意,因为定位故障虽然多了这样一种选择,但是毕竟是故障已经发生了,我们是根据故障现象有针对性的去阅读代码。为什么我们不在故障发生前来阅读代码呢?大部分程序员(包括我自己在内)编码完成后第一件想做的事情就是先“编译”,编译如果出了一些错误和告警便一个个找原因并修改;编译通过后就尽量能整个程序连接在一起“运行”,运行出了错呢?开始仔仔细细的逐个跟踪找原因,最后一个个原因找到后基本运行通过了。上面是正常开发的一般做法。那么当发现故障后我们是怎么做的呢?我们首先想到的是复现故障现象,然后分析大概位置,最后通过增加一些跟踪信息等手段去逐步顺藤摸瓜,只要现象容易浮现,一般还是能定位到原因。我们多数情况下都是这样做的。写完代码后我们很少主动的去阅读和检查代码,即使有时候阅读代码,也是当故障出现后为了解决故障被动的针对性的阅读代码,就像前面我举的例子一样。我一直在考虑,我们宁愿花大量的时间一个一个的调试排除故障,却不愿牺牲哪怕一次故障调测的时间来做一次广普性的代码检查。同样是排除故障,一个是通过发现故障后“调测”跟踪,一个是通过“阅读”代码广谱查找。我常常把这两种方式做一个比喻:前者正如某个人生病了上医院治病;后者正如某个人主动的到医院进行健康检查。大家能得出什么结论吗?至少我觉得以下几点可以达成共识:生病就医的言下之意是一种被动就医,已经生病已经付出了代价。健康检查的言下之意是一种主动行为,可提前预防。生病就医太有针对性,花很大的成本最后就治愈了一种病,综合成本高。健康检查是广谱性的体检,花一定成本一次可能查出多种病状,综合成本低。生病就医发现的病症大部分已经是晚期,一般都很严重了,医治成本和代价都很高。健康检查发现的病症大部分都是早期,一般有治愈希望大,医治成本和代价都较低。生病就医的最佳结果只能是治愈。健康体检的最佳结果是健康,因为可主动指导和改善生活习惯,减少病状发生。很遗憾我们大部分人对健康体检的常用托辞是“我工作很忙”。有趣的是这种说法和软件开发中“我项目压力大、时间紧”的说法多么的相似,只不过所有项目到最后都在忙着找故障,而再也没有谁去关注如何预防故障的发生。因为当故障已经出现的时候,解决故障变为第一要素,却没有人考虑过这是一个周而复始的循环,是否有人考虑过能否减少这种事情发生的次数。当然,谁也不会否认,当一个人生了重病后是肯定要医治的。问题的关键是我们不能总是经常性的等生病后往医院跑。耽搁一点正常的工作时间,或者牺牲或延长一次生病就医的时间做一次健康检查,用于指导改善你的生活习惯,不是更好吗?我相信大部分开发人员如今对“代码走查”这种预防性活动还是有很正确的认识,问题是为什么在强大的项目压力下在“故障调试”、“新功能开发”和“代码走查”之间总是把前者放在最重要的位置,而把“代码走查”放在可有可无的次要位置呢?我总结了几个原因:没有可供检验的结果:''新功能开发”和“故障调试”都是不可逃避的,因为二者都有明确的最终结果作为目标检验标准;“代码走查”没有明确的最终结果,做了没有?做得怎么样?没有标准就没有监督和审核的标尺,项目很紧的情况下,也就变得可有可无。没有可控制的中间过程:“新功能开发”总是要编写代码,“故障解决”总是要跟踪调试;但“代码走查”的过程没有固定的模式,读程序快慢与否、认真与否全部没法掌控,既然过程不受控,自然就容易马马虎虎。没有固定统一的方法:在没有故障提示情况下,盲目的流水帐似的阅读代码,最后变成不同人按自己的方法随意的走马观花似的大概略读一下代码,时间长了觉得没有效果,也就疲了,最后不了了之。效果影响信心:在代码走查中某些模块中没有发现什么缺陷,由此对自己的阅读代码查找缺陷的能力产生潜意识的怀疑,从而认为在其他模块中发现问题的可能性为零,最终放弃这种低成本的查找缺陷的机会。完美主义:用完美主义的眼光来挑剔代码走查,认为既然强调“代码走查”的重要性,那么应该可以发现绝大多数缺陷并解决之,否则我宁愿不做。没有目标:在问题没有暴露前找问题,不知道该如何找。什么问题都想找,东一榔头西一棒,最后晕头转向,多次失败的情况下,最终放弃。•枯燥乏味:“新功能开发”是一个创造性的活动,“故障解决”带有强烈的目的性,唯独阅读代码是被动的在一大堆符号中寻找缺陷,在没有明确目标的情况下,确实很乏味,甚至最后都忘记了自己的最终目的。2小时做一个尝试我时常在想,在我们编码之后调试之前,牺牲一次故障调试的时间,就算是尝试,给自己的代码做一次代码走查,哪怕一个缺陷没有找到,这个应该不算是多大的冒险吧?做这样一个尝试并不难。首先,做一个深呼吸(可省,呵呵),然后给自己做一次心理暗示,你只需要先保持这样一种简单的想法就OK了:代码走查不可能解决所有的问题,我的目的是减少遗留到后面的缺陷数量。我只花一次普通故障调试的时间作为代价(比如2个小时)。如果我代码走查找到1个这样普通的缺陷,我的代价就收回来了。如果我找到2个这样的普通缺陷,我就赚了。如果我找到2个以上这样的普通缺陷,我将是非常幸运的,证明这是一次非常划算的尝试。如果连一个像样的缺陷都没有发现,没关系,我损失不大,不就2个小时吗?也许我加加班就可以补回来,或许证明我的代码故障不多,不是好事吗?下面怎么做呢?同样的,抛弃完美主义,你不需要怀疑自己的方法和能力,更不需要怀疑自己漏掉某些缺陷,你只需要简单的反问自己:我代码中所有的数组是否都保证绝对不会越界?我代码中所有的内存操作函数都保证不会出现越界的可能?我绝对不使用和创建无边界控制的内存函数?我代码中所有的指针指向的内存区都是安全的。不必求多求全,做了上面的检查后,你是否可以拍着胸脯说,我这部分代码至少不会出现内存异常之类的故障。那么,最头疼的一类故障也许你已经可以很放心了,就算是没有找到这类故障,花这点代价给自己一个放心,不也是值吗?你已经杜绝了最严重的一类故障,如果你还有时间,你还可以再进一步作一些尝试,看看自己的代码还有没有下面的问题,检查一下并不难:我的代码中有循环吗?是否绝对不可能出现死循环?我的代码中所有的资源的申请和释放是否都成对得到保护?我的代码是否所有的不可能条件都有断言保护,所有非法输入都有异常返回的保护?我的代码中是否不会出现遗漏分支没有处理?我的代码中重要函数调用的异常返回是否都做了必要的处理?如果以上问题都得到扫描,那么你又缩小了很多出现故障的范围。你又可以很有信心的对自己说,我的代码至少逻辑分支和异常处理是完善的,就算以后在测试过程中出现问题,定位问题的范围至少可以缩小很多。这样的尝试还可以有很多,代码走查并不是一个非常复杂的过程,不需要繁琐的准备工作,只要有代码,有一个好的心态,马上就可以做。你可能一无所获,也很可能收获很多,但你的损失都将是有限的,那么,何不先做起来呢?寻找可控的方法前面我们讨论的是把代码走查仅仅作为一种个人行为而言,大家可以看到,其实也是一件很简单的事情,甚至可以说是举手之劳。但是如果把代码走查当成一个组织行为又如何呢?回到前面提到的把代码走查同健康体检进行的类比,我们知道,医院已经很普及的健康体检运作得都很成熟,准确率还是很高,我们可以简单的来做一个体检流程的分析:做健康检查之前都有一个需要进行体检的项目清单表格。每个人都会在这个清单中根据自己的实际情况选择一部分项目做检查。每个人拿着这个清单一项一项依次到不同的科做体检。不同的体检项目的检测仪器都不一样,但都有一个表明是否合格的标准范围。体检的结果最后都被记录下来,还有体检医师的签字。看起来上面列出的几项好像也没有什么神秘的,但是现在医院大多数也就这么运作的,事实证明比较合理。反过来如果不这么做会出现什么情况呢?试一试这样做看行不行:体检前不需要什么体检项目的表格。体检人也不选择体检项目,就一个笼统的需求医生,帮我看看我身体如何?体检是否合格没有一个客观的标准。体检的时候不分项目也不按科分别体检,就一个医生给你全包完。由于没有合格标准,体检完了医生告诉你的情况你也不懂,只好给你开一些药。大家一定马上能想到,第一种体检方法是西医的做法,客观科学;第二种体检方法和现在中医的做法惊人的相似,就一个医生给你号号脉,然后给你描述一大堆阴阳之说,最后肯定你也不懂,就只好给你开一些药了。也许你会说,高水平的中医确实可以给你的身体一个全面的健康建议啊!完全正确,第二种方法的成功与否恰恰就是取决于医生个人的水平,而第一种方法无论是检测设备还是检测指标都是客观的,所以放之四海而兼宜,就算是一个刚从医科大学毕业的学生来体检,对结果的正确性影响也不大。现在我们回到代码走查上来,如果把代码走查放到一个软件工程中,作为一个过程控制的节点,我们一定希望这个关键点提供一个可控的、客观的、标准的检测缺陷的方法,我们不希望代码走查的质量过分的取决于走查者的技术水平。因此,我们希望的控制过程一定是上面介绍的第一种健康体检的过程而不是第二种,因此我们可以得出下面的代码走查的控制方法:走查前需要拟定一个计划。在走查计划中约定这次要走查的代码的模块、走查人、走查时间。在走查计划中明确这次要走查的缺陷类别的范围。根据走查范围明确具体走查项目即走查清单。有了上面的走查计划,其实相当于已经给你明确了走查的目标和范围了,并且也明确了每种检测项目的合格的标准(检查单)。你已经知道你这次要在哪几个科室做健康检查也知道需要检查些什么项目,现在的问题仅仅是怎样做的问题。我想谁也不会傻到想在其中一个科让一个医生给你全包完,医院之所以那么做是因为不同检测项目的手段、仪器和方法是不同的,所以需要按项目分多次进行。同样的,代码走查也可以这么做,因为不同缺陷类型的走读方法也不同,我们可以这样做:缩小范围,按不同的走查项目多次扫描检查规范要求,刻意忽略代码逻辑检查代码缺陷,刻意忽略总体逻辑检查框架缺陷,刻意忽略代码细节上面仅仅是其中一些总结,根据不同的缺陷类别还可以总结更多,但其中心思想就是推荐阅读代码的时候一定要带有目标性,明白当前在代码中需要查询的是哪一类型的缺陷。因为不同的缺陷类别需要阅读代码的方法不一样,另外每个人的精力有限,不可能在一次代码阅读中能发现所有的问题,正如一个医生利用一种设备不可能完成所有的体检项目一样。在下面一个小节中我们将详细给大家讨论这一点。实践中总结前面提到的都是一些总体上的方法,具体到不同的代码走查的检查项目的方法,需要在实践中进行不断的总结,这里面没有正确与错误之分,最重要的是能排除干扰专著与每一项走查项目的代码阅读,在实践中总结你认为最适合自己的方法。也许每个人阅读代码的习惯和能力都不一样,但是在某些大的层面上,仍然有一些可以统一总结的方法。软件所有的缺陷中,我们大概归纳为下面几个大类:规范层面:命名、注释、布局、表达式书写是否规范。内存层面:是否出现内存越界、内存泄露。编码层面:逻辑分支、循环、数据类型、异常保护的正确性。需求层面:是否实现了所有设计要求实现的功能。设计层面:函数和数据结构布局、设计的合理性。应用层面:和应用相关的算法和实现逻辑。上面的分类层面有一个特点,我大概是按代码走查寻找缺陷的容易程度和适合程度进行排列的。任何手段都有它擅长的一面也有不擅长的一面。代码走查的长处是发现一些比较直观的东西,包括细节层面的和宏观层面的:细节层面:比如规范层面、内存层面、编码层面等,因为这些层面大部分不考虑应用逻辑和上下文关系,都是语句级别的,只要仔细认真走查,即使经验不多的走查人员也能发现一些问题。宏观层面:主要是设计层面和需求层面,这两个方面需要站在设计者的角度审核代码是否完成了设计需要的功能和函数设计等是否合理,往往是后面的测试无法办到的,但是走查时候难度也相对大一些。比如结构定义是否合理、全局变量定义是否合适、函数之间是否耦合太强、函数内部是否内聚充分、函数是否太复杂可分拆等。中间层面:也就是应用层面,一方面这部分牵涉到复杂的上下文逻辑需要仔细分析,另一方面不同的应用处理逻辑根本没法统一,这部分的验证应该在单元测试中解决最好。所以代码走查这一部分其实是有点赶鸭子上架,不是它的强项,是最困难的,但因为我们单元测试做的不是太好,所以我们希望能通过走查减少这方面的故障。对缺陷类型进行细分以后,可用于指导走查活动的开展,下面是在实践过程中总结的一些零星的方法供大家参考:如何规划走査层面的顺序规范层面的走杳可在编码中途安排一次命名规范性的检杳,命名如果规范且完善可以减少很多不必要的注释,而且走查也不难。命名还有个特点就是有一定惯性,变量和函数命名如果不规范,到最后这种不规范的名字会分布在整个代码区,并且后面的人也跟着不规范的写法,到最后就懒得更改了,所以早点纠正早做榜样。规范层面剩下的比如布局、注释和表达式的可读性等可在代码编写完毕后再做一次,因为这一部分做早了没用,中间不断的在增加和修改代码,最后做一次检查并规范之,在注释和布局上优化调整,为后来者阅读代码带来便利。另外最后这一次检查规范性还可以从后来这的角度来审视这个代码的可读性,比如有些表达式过于复杂,可以用中间变量代替或者用宏代替等等。内存层面和编码层面的走杳也建议安排在代码编写完成后,因为这是一种排错性质的检查,一般不牵涉上下文关系,修改影响面不大,等代码编写完毕后进行一次彻底清理效率比较高。设计和需求层面的走查建议安排在编码中途,这就需要开发组长在安排编码任务的时候要求大家养成一个先搭框架后做逻辑细节的习惯。一个模块的框架完成后基本的数据结构定义和函数调用关系就出来了,这时候检查有没有遗漏的需求和功能没有考虑到,是否有不合理的全局变量设计,有哪些函数过大或者调用关系不合理,有哪些函数不满足高内聚低耦合的要求,有哪些函数的原型定义不合理等等。在框架阶段就把这种需求和设计类的缺陷提前找到,可以降低后面对代码修修补补带来的工作量,说得简单点这一部分也是有惯性的,前面做好了,可减少后面的编码工作量。应用层面的走查可安排在最后,因为难度最大,也不好控制。应用层面的走查也就是检查代码逻辑算法的处理是否正确,主要困难在需要阅读代码的上下文,需要把整个代码完全读懂才行,所以放在最后,能走查到什么程度,和走查人员对业务的熟悉程度有关。应用层面的走查重点是考虑那种极端条件和异常条件下代码的正确性。如何走査内存层面:内存层面的故障可以说是软件中最严重的一类故障,它的严重性主要体现在一旦发生,产生故障的地方和故障的制造者之间没有逻辑上的必然联系。比如内存覆盖,当一个地方出现内存覆盖后,它影响到的地方甚至可能是另外一个线程的某个函数调用。所以这个类别的故障采用跟踪和调试的办法没有头绪,代价也很大,采用代码走查方式清除这一类别的故障是最合适的。首先我们可以分析一下,在C语言中能够进行内存操作的有哪些途径,不外乎下面几种:通过C或OS的库函数;通过数组操作;通过指针操作;通过自编的其他模块的函数。然后我们再来看对内存可能有哪些非法的结果,不外乎下面几种:内存越界;内存泄漏;非法内存使用(栈溢出、使用NULL指针等)。这样,我们在走查内存层面时候就可以按那三种异常结果来分别进行走查,而走查关注的代码则根据不同的非法操作类型有所不同。内存越界可首先走查,引起内存越界的上面4种操作都有可能,我们可以采用先易后难的顺序,比如首先是关注和内存操作有关的C库函数:是否使用了strcpy、strcat等无边界控制函数,然后关注所有的memcpy、memmove等的长度控制是否可靠,对这些函数的走查完全可以采用直接查找的形式,没有必要一行一行的阅读;这部分完了后再看看数组越界的可能,你甚至同样可以采用直接查找数组符号的方式,关注每个数组的索引是否有越界的可能。这两个最直观的走查做完后,再关注那些我们自己编写的有内存操作的函数的边界控制是否到位。最后把比较头疼的指针问题放到最后来做,指针问题主要关注两点,一个是关注指针指向内存区的可靠性,也就是关注指针的来源而不要关注指针指向元素的赋值等操作,另外一个就是关注指针的+/-运算的地方。如果上面列举的几个地方都仔细检查了,则内存覆盖问题基本上没有问题。内存泄漏相对单纯一点,因为内存泄漏意味着都是动态分配的内存,内存动态分配都要依赖特定的库函数,比如malloc(),我们只需要关注类似于malloc()和对应的free()这样的库函数是否配合得当就行了(当然,不同的OS可能有自己的内存管理函数命名)。对于内存的分配和释放,原则上是谁申请谁释放,也就是如果申请和释放尽量在一个函数中最好,这样直接关注函数分支的完整性能否正确保证分配和释放就行了。但很多情况下我们代码中的内存分配和释放是一种异步操作,也就是分配和释放不在一起,而是在某个流程控制下先分配内存,然后把内存指针交给某个全局的托管指针,最后在另外的流程下把它释放。这样内存是否合理释放完全依赖于应用流程的正确性,问题其实已经上升到业务应用层面了,所以我建议这一部分的走查把它归类到应用层面的走查,我们在后面讨论。如何走查编码层面:编码层面的缺陷主要体现在编码过程中表达式前后的呼应和保护,包括分支和循环等。我们可以把这一类型的缺陷进行以下的分类:数据和数据类型;分支的完整性(if、case等);>循环的正确性;>异常保护;资源保护。其中,前面两个(数据类型和分支的完整性)很多都可以用pc-lint工具进行检查,比如变量初始化、变量和宏命名重复或冲突、数据类型匹配、精度丢失、部分if和case的分支处理缺失等,所以建议在做这部分走查之前,先用pc-lint检查并处理,这样可以减少不必要的重复劳动。但是也有些pc-lint没法发现的深层次问题。比如数据类型定义的边界值,当应用层面上某些数据达到设计最大值后代码中定义的数据类型是否能够满足,这个层面的走查我建议放到应用层面来做,以便从业务的边界值作为入口来考虑。循环的走杳可单独出来,直接用查找的方式针对每个循环进行检查,因为C中的循环不外乎就是for、while等几种,特征很明显。这个层面中循环主要关注是否死循环,循环内部的指针和数组是否越界那是在内存层面解决,循环本身的业务逻辑是否正确那是在业务层面解决,走查一定要分层面,思路一定不要到处乱飞。异常保护主要是两方面,一个是函数入口的保护,另一个是函数内部的函数调用返回的异常保护。函数的返回处理pc-lint会告警,特别关注那些可能返回失败的函数的异常处理,不能想当然的认为所有函数的返回默认肯定是成功。对于指针,大部分来自函数参数入口的指针都应该用断言assert,而函数内部的函数调用返回的指针一般用异常判断,比如:VoidMainfun(void){char*pBuf;pBuf=malloc(sizeof(MAX_BUF_LEN));assert(pBuf); 〃这里断言不合适,因断言在release中无效使得反而失去保护fun(pBuf);}Voidfun(char*pBuf){Assert(pBuf); 〃这里一般用断言,因为如果断言失败证明调用者逻辑不严格…}需要提醒的是,一定要区分断言和异常保护的概念。断言是指不可能出现的事件,也就是如果断言条件不满足那么肯定是调用当前函数的地方逻辑上有问题或保护不严格,大家只要记住断言只是用来在调试阶段协助暴露逻辑故障的手段而在release中无效这个原则就行了。而异常判断是用来做异常保护的,它是用来在运行阶段排出可能出现的异常输入或返回的容错的措施,是可能出现的事件。资源保护主要是关注资源的申请和释放,那么哪些是资源呢?文件句柄、信号量、定时器、互斥变量、中断等等都是资源。站在系统的层面上看,资源都是从OS申请来的,一般申请和释放资源都是通过调用C或OS的库函数来实现,所以走查资源的申请和释放情况同走查动态内存的malloc和free方法一样,以那些库函数为线索。我们尽量在一个函数中完成资源的申请和释放过程,也就是谁申请谁释放原则。但是对于很多情况下在一个异步流程中进行申请和释放资源的操作,由于和业务流程的严密性相关,建议这部分也放到业务流程的走查层面上来做。如何走查需求和设计层面:需求和设计层面放在一起讨论,是因为他们走查的时间阶段和策略有相似性,他们之间也有一定联系,因为一个模块的设计终归和这个模块要实现的功能需求有一定关系。走查需求和设计层面的前提是我们一定要有一个模块封装的观念。模块是一个设计层面上的概念,设计者应该给这个模块一个明确完成某个功能的定义,包括这个模块提供给其他模块的接口界面和这个模块调用其他模块功能的途径,其他的任何东西都应该被严格封装在内部黑匣子中。需求戻面是设计概念上的功能需求,不是用户需求,用户需求是站在系统戻面来定义。从设计层面上而言,一个模块所应该具备的功能应该在总体设计阶段就应该明确,但我们一般是在详细设计的时候才进一步详细这些功能。无论如何,走查某个模块的需求依据的应该是设计文档对这个模块的功能的定义。在需求层面的走查只需要关注是否有实现某个功能需求的代码,阅读代码只需要到函数级别就行了,至于这个功能实现的如何,有的可以在业务层面来走查,有的应该在单元测试甚至系统测试来验证。也就是只需要关注它做了没有、有没有遗漏的需求,而暂时不必关注它做得怎么样和怎样做的。设计层面的关注点很多,其中最重要的一个是数据,一个是函数,走查需要一定的经验,可按如下层次走查:数据设计的合理性:数据类型定义合理(结构、联合、枚举),避免简单的堆砌和重复;尽量避免全局变量,至少优先考虑用static;避免局部变量中定义static;避免局部变量中定义大数据类型(大的数组、结构等);避免在代码中出现常数(整数、字符串等),不利于维护。函数设计的合理性:函数整体调用关系尽量是分层结构,避免网状结构;避免无确定功能的函数,做到高内聚(这种函数一般都太长);避免函数之间关联太多,做到低耦合;只开放需要公布给外部调用的函数接口,其余作为内部函数用static保护;用类似于Get和Set函数访问内部数据,避免外部直接访问本模块的内部数据。可先关注数据的声明和数据类型的定义。对于一个模块而言原则上数据尽量封装,理论上是可以完全杜绝全局变量的使用的,本模块需要共享的数据可以用static使得其他模块不可访问,即使其他模块需要访问本模块的数据,也可以通过类似于Get和set的函数进行间接操作。数据结构的定义要有层次,不可扎堆似的把一大堆数据堆砌在一起。当然,数据结构同样需要封装,不该暴露在外面的结构类型就不必开放出去,这往往取决于接口函数定义的合理性。对于一个模块而言,从外部访问权限来看可分为内部函数和外部(接口)函数两种:从内部结构上来看可分为框架类函数和实现类函数。函数之间一定要有层次,避免杂乱的网状结构。比如,下面的两种函数调用层次看看哪种比较好:可以看出来,图1的函数调用关系呈现网状结构,函数之间没有层次杂乱无章,其根本原因是因为每个函数的功能定义不清晰,上层本应该属于框架类函数同时又有一些实现的代码混杂在一起;图B的函数调用层次清晰,根本原因是框架和实现类函数界面清晰各司其职,要做到这样的函数层次必须做到下面提到的函数“高内聚低耦合”的原则。关注函数是否满足高内聚低耦合的原则。高内聚的意思就是每个函数所要完成的功能非常单一和清晰,低内聚的函数总是把一大堆功能放到一个函数中实现,这种函数一般都非常大,你会发现这种函数没法命名或者名字意义很模糊,建议分拆。耦合性不好的函数之间总是有这样那样的牵连,最好的耦合就是函数调用之间只存在参数传递。我们容易犯的错就是其中一个函数的正确运行往往依赖于调用者的正确与否,这也是不合理耦合的表现。函数的内聚和耦合走查后,接下来需要关注的封装,内部函数应该用static保护起来,只开放需要提供给外部模块调用的函数。比如我们TTP代码中的handler函数,这个函数是一个信号的入口和信号分拣处理函数,但是我们目前的做法是在这个函数中的Begin和End信号中又嵌入很多实现方面的代码,这样就使得这个函数的内聚性不高。因为它不完全是一个负责信号分发的框架类函数,使得这个函数的功能不单一,你会发现很难给这个函数一个明确的功能定义,因为它好像出了信号分拣还要处理其他事情,这不是一个好的程序设计风格。如何走査应用层面:现在我们来讨论应用层面的走查,这也是代码走查最不擅长的方面,因为这需要走查人员完完全全的读懂代码,分析代码的每一个流程
温馨提示
- 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
- 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
- 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
- 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
- 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
- 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
- 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。
最新文档
- 桥梁工程总承包合同协议
- 城市绿化带广告牌安装施工合同
- 盐城市设计创意中心租赁合同
- 购物中心休息区地砖铺装协议
- 乡村旅游鱼塘施工合同范本
- 酒店租赁合同协议:电竞比赛专用
- 环境监测系统施工合同
- 物流配送招投标合同承诺书
- 城市商业街箱涵施工协议
- 建筑电气工程皮卡租赁合同
- 专门学校情况报告
- 工业互联网平台构建
- 数学思想与方法-国家开放大学电大机考网考题目答案
- 杭州奥泰生物技术股份有限公司IVD研发中心建设项目环境影响报告表
- 公共卫生事业管理专业职业生涯规划书
- GB/T 43232-2023紧固件轴向应力超声测量方法
- 低压配电室的安全操作规程
- 新目标汉语口语课本2课件-第2单元
- 二手车买卖合同(标准版范本)
- 国有企业合规制度培训
- 血液透析的医疗质量管理与持续改进
评论
0/150
提交评论