代码评审三步走
作者:网络转载 发布时间:[ 2012/7/23 10:39:20 ] 推荐标签:
如果了解函数所在类的调用上下文,可以看出run函数是以线程方式运行的,cancel_的取值在该函数运行过程中是可以改变的——相比于函数启动固定的输入值而言,在过程中可变的输入值,更值得注意。
关注被检查对象运行过程中输入值的变化,是多线程模式下进行代码检视和单元测试的重点内容。
在这段代码中,cancel_的取值尽管只有true和false两种,但是结合运行中的动态变化,可以看出能够影响程序走向和处理的有四种状态:初始值为false,且全程不变;初始值为true,且全程不变;初始值为false,且中段变为true;初始值为true,且中段变为false。
后一种状态对程序走向没有影响,而且结合程序其他代码段来看,也没有提供从true->false的转换,因此归为无效状态,不用测试/检查。
前两种状态属于静态输入,与“常规输入”进行同样检查即可。
而第三种状态属于“动态输入”,需要检查在输入发生变化时,程序是否能够按照预期进行处理。例如在cancel后是否能够正常销毁对象,是否能够正常处理对象队列而不出现越界现象..在这段简单的示例代码中没有出现这些问题,但稍复杂一些的代码很有可能出现以上疏漏。
另外,在“层次一”中提到的questIDs_.length()其实与cancel_是同一性质的输入。从理论上说,它在循环中也是可以变化的——譬如在某次进入循环体后,由于外部动作导致questIDs_中的部分内容被删除,此时的questIDs_及其length()都会发生变化。因此questIDs_[processPos]这个访问很可能会引发一个越界异常。
当然,以上情况是基于questIDs_成员变量可以改变而言的,结合整个类的完整代码看,该变量是对象初始化时生成的,且在对象生命周期内没有提供改变的接口,因此不会发生以上异常情况。
第三个层次,是识别所谓的“共享资源”,这点在以上示例代码中没有直接体现。
所谓“共享资源”,指的是部分或全部代码共同分享的资源,包括但不限于全局变量(对象)、数据库连接、数据表内容、文件、内存块、线程锁或信号量..等等。当代码中出现数据库读写、文件读写等内容时,实际上都是对外部的“共享资源”发生了依赖。这些“共享资源”的取值范围或状态,可能是代码中需要考虑处理的。同样的情况也发生在并发程序获取锁或信号量时,程序代码如果没有充分考虑到会影响锁或信号量的其他代码,很可能会导致死锁的发生。
在检视涉及“共享资源”的代码时,除了检查代码对这些“共享资源”的取值是否有合适处理外,还要进一步检查当前检视代码与其他相关代码是否对“共享资源”的竞争进行正确处理,它们对“共享资源”的依赖,是否构成“依赖环”。
要将所有的共享依赖都梳理出来不是件简单的事,篇幅所限,这里不展开讨论了。
第四个层次,是对程序中所有异常也作为“非常规输入”处理。
异常,即Exception,是程序在遭遇非预期或不能处理的状况时,用于通知外层程序或调用者出现状况的一种机制。
从简单的a=b/c可能发生的除零异常,到复杂的函数调用可以抛出多种类型的异常,这些都是程序中常见的情形。
在调用函数或进行数据处理时发生的异常,都是由外部传入/返回给当前函数(被检视函数)的,因此对于当前函数而言,异常Exception自然也应当作为一种“非常规输入”进行处理。
由于异常在代码段中通常是以“隐身”的方式存在着,除了函数声明时可能有的throw指示(有时候也没有)之外,难以寻觅它的踪影,因而它也是容易被遗漏的“非常规输入”,应当作为一个重点的代码检视对象。
在示例代码段二中,对cvtQuestById、appendQuestState、getQuest等方法的调用,都有可能遭遇它们抛出的异常。而从代码中也可以看到,该段程序对可能遭遇的异常确实进行了处理。
再看示例代码段一,它的逻辑简单,看起来信心满满,没有任何异常保护。
但是它真的不会遭遇任何异常吗?仔细检查可以看到其中有这样一条语句:
NPCManagement::MonsterLoader::getMonsterType(hrdId, -1, monsterIds, mnsTypes);
循着这条语句顺藤摸瓜,会看到getMonsterType方法是通过数据库查询实现的,且此处的数据库查询是通过新建数据库连接进行的。
在其中建立数据库连接的部分(以一个DBConnection构造函数的面目出现),明明白白地写出了有可能抛出各种类型的异常。换言之,当无法获取数据库连接时,getMonsterType会抛出异常,而示例代码段一中没有捕获该异常。
这个未捕获的异常是否会引发未定义的问题,完全要看调用该方法的外层调用者的处理了——生存还是死亡,这是一个问题。
即便现存的所有调用者都注意到这点并进行了异常保护,也无法保证今后新增加的调用者也都会遵循这个“传统”。好的措施还是自我保护。
在进行代码检视时,对调用的外部函数、系统API等,都要仔细检查其是否可能抛出异常,并检查程序代码中对可能发生的异常,是否按照规范进行了适当处理。
对异常的处理分为两个阶段:捕获与处置。按捕获情况可分为三类:捕获特定异常,捕获所有异常,不捕获异常。 捕获异常后的处置方法可以分为两类:1、记录而不处理(吞噬异常),程序继续流转;2、记录并中断程序运行,重新抛出异常。无论是哪一种捕获和处置方法,是否“正确”都要看是否符合业务逻辑,是否符合设计规范,不能一概而论。
一般而言,对可能存在的异常不捕获是比较危险的。因为除非明确知道外围程序有异常保护机制,否则本程序对异常不处理会导致异常扩散,很有可能导致产品崩溃、错乱,发生无法预知的后果。
以上是我所提倡的“代码检视三步走”的全部内容。受个人能力和当前工作重点所限,以上给出的内容既不全面也可能存在许多错漏之处。希望能够起到抛砖引玉的作用,引出更多专家的宝贵经验。
相关推荐
更新发布
功能测试和接口测试的区别
2023/3/23 14:23:39如何写好测试用例文档
2023/3/22 16:17:39常用的选择回归测试的方式有哪些?
2022/6/14 16:14:27测试流程中需要重点把关几个过程?
2021/10/18 15:37:44性能测试的七种方法
2021/9/17 15:19:29全链路压测优化思路
2021/9/14 15:42:25性能测试流程浅谈
2021/5/28 17:25:47常见的APP性能测试指标
2021/5/8 17:01:11