前言 实际工作中,我们经常面临版本迭代节奏快、需求多、测试时间短、代码量大等现象,当我们决定深入理解代码实现的时候,经常会遇到以下两个问题:如何进行代码分析?优先分析哪些代码? 代码分析的关键词:5步法+风险控制+类关系 5步法操作如下: 1、选取分析对象 2、明确模块名 3、理清模块间的关系 4、确定模块间的接口 5、从接口着手去梳理代码结构关系 测试周期短、代码量大,从哪里着手开始分析、或者优先分析哪些呢? 代码分析顺序的关键词:风险控制。所谓风险控制,从问题严重度和模块特点两个方面来说: 1、问题严重度:模块出现问题后,被用户感知的严重程度。按照问题严重度顺序做代码分析。比如,驱动层代码出问题会“蓝屏”,应用层出问题会“crash”、'无反应',脚本出问题,脚本挂掉,用户侧基本无感知。即蓝屏>crash>无反应>无感知,所以最优先分析的是驱动层代码,其次是应用层代码,最后才是脚本; 2、模块特点:问题严重度在同一层级,代码的分析顺序。首先,同一层级的代码,应该优先分析重要的模块,重要模块是核心功能处理、调用频繁度高的模块;其次,出现问题的模块会隐藏更多的问题,分析那些已经发现过问题的模块,可能会发现更多的问题。 通过查看工程文件或工程属性,明确源代码将会被编译成xxx名称的.xxx扩展名的文件。代码最终都会被编成.xxx后缀的目标文件,在开始梳理具体实现前,要明确目标文件的名称和运行方式。 确定了模块名后,紧接着要确定多个相互作用的模块的调用关系。从当前代码所在的模块出发梳理调用关系,首先需要明确调用关系是两个以上模块之间的关系,也就是在调用过程中,存在调用方和被调用方;其次,当前代码所在模块在实际调用中,可能会调用其它模块,也可能被其它模块调用。 1、哪些模块调用了当前模块:在整个业务代码范围内搜索当前模块的模块全名,包含.xxx后缀,就能找出该模块被哪些模块调用。 2、当前模块调用了哪些模块:在该模块的工程文件中,搜索和分析会和其他模块发生调用关系的关键词,这里的关键词一般是加载函数的名字、文件后缀名等,即可找到该模块调用了哪些模块。 在当前模块被调用和调用其他模块的语句处,即步骤三中发生调用关系的语句处,找到模块间起连接作用的接口。具体接口的形式、分类和使用方法依赖于平台和代码开发语言,确定接口的准则会有所不同,这里不赘述,大家可根据自己实际分析的代码确定接口。 从接口入手梳理代码结构关系首先要做两件事: ①查看该接口类的定义和各个方法名称,明确该接口具备什么方法。接口类一般都是抽象类,抽象类里的方法一般没有具体实现; ②找接口类的实现类,即搜索public 接口类名,找到该接口的实现类,从实现类的各个方法定义中开始分析各个方法的具体实现逻辑,这些实现过程可能包含对其他模块的调用、对其他接口/类的调用、对本接口其他方法的调用。 从接口入手开始分析代码的依据是类关系的强弱性,先简单说明UML给出的六种类关系的强弱顺序:泛化=实现>组合>聚合>关联>依赖。泛化和实现关系像是代码结构的纵向关系,我们理清实现和泛化关系,就等同于找到房子的承重墙;组合、聚合和关联更能代表代码结构的横向关系,是具体实现过程;依赖关系也是一种代码结构的横向关系,但是这种关系比较弱,即对代码的整个框架影响较小,所以分析时不建议花费太多时间。 在逐步分析确定具体实现逻辑的过程中,有以下几个关注点: ①从需求的功能出发,确定功能是否如期实现、代码中是否有分支遗漏、各种异常处理是否完备; ②从CodeReview角度出发,查找代码中的基本缺陷,如变量没有初始化、资源使用完后未释放、函数返回值出错等; ③从代码实现结构出发,确定设计的接口是否合理、多线程流程是否恰当、架构是否清晰等。 以下将按照上文阐述的”五步法“,以Windows系统Visual Studio下C++代码为例,进行实战演练。 例如分析的项目包含C++代码和Lua代码,从风险控制的角度出发,优先选择C++代码进行分析。 这时将会遇到另一个问题:模块关系是怎么样的?具体实现逻辑是怎么样的? 打开.vcproj工程文件,查看ConfigurationType值(2是dll文件,1是exe文件),或者VS里面查看工程属性Properties-ConfigurationType就可以看出该文件的生成文件名(xxx.xxx),dllTest.vcproj最后编译出来的二进制文件是dllTest.dll。 1、哪些模块调用了当前模块:在整个解决方案文件(.sln)中搜索模块名,可看出哪些模块调用了该模块,dllCall\dllCall.cpp所在模块dllCall.exe(步骤二的方法获取模块名)会调用dllTest.dll。 2、当前模块调用了哪些模块:在.vcproj工程文件中搜.lib、LoadLibrary、CoCreateInstance,可以看出dllCall.exe包含shell32.lib等静态库。
dllCall.exe会通过CoCreateInstance调用dllCom.dll等动态库。 除了通过搜索关键字的方式获取模块关系之外,还可以通过调试和depends.exe等工具来理清楚模块间的依赖关系。 C++接口一般分为COM接口和LoadLibrary导出接口两类,其中LoadLibrary导出接口的使用方法又细分为两种: ①COM接口,CoCreateInstance的第四个参数就是接口的IID,IID_后直接跟的就是接口类的名称; ②LoadLibrary导出接口,GetProcAddress的第二个参数就是接口函数:a.该接口函数直接被使用;查找该接口函数的定义,如下add是接口函数。
上例接口ICacheClient的实现类是CCacheClient。 CCacheClient的方法和属性如下: 组合、聚合和关联关系的代码在整个工程中占比非常大,是代码分析比较耗时的过程,我们可以借助工具辅助分析,推荐代码阅读分析工具Understand。 UnderStand集成了代码编辑器、代码跟踪器和代码分析器,支持C/C++/C#, Ada, Java, FORTRAN, Delphi和Jovial等语言,并且具有强大的界面,能将分析结果以图表、图形等形式呈现给大家,如下实例图依次为Understand生成的类关系图、函数调用关系图和数据流图。 最后,那么对于我们来说,要在有限时间内完成高质量的代码分析,才能更好的适应“迭代节奏快、需求多、测试时间短、代码量大”等现状、更好的打击和消灭bug。希望以上代码分析的方法能为大家提供一些思路,能帮助大家提升一些代码分析效率。 读者互动环节 大家日常工作中是如何做代码分析的?上述5步法,在你的项目中是否也有用武之地? 精彩的回答除了可以移入精选留言,还有机会获取TMQ提供的精美礼品一份~ 期待您的回答哦 寻人启示 获奖名单 获奖者微信ID:Friday |
|