一、代码复审check list
-
概要部分
- 代码符合需求和规格说明吗?
符合要求和规格说明,-s指令和-c指令都能实现需求。并且能够处理非法输入。 - 代码设计是否有周全的考虑?
程序的main函数中对各种输入情况都有考虑且做出了处理,例如对超过范围的输入数字和非法的参数都输出错误提示。同时,代码具有良好的可扩展性,因其设计为面向对象的,对象要实现的功能都提炼成函数。 - 代码可读性如何?
程序分为数独生成类,数独解决类和输出类,逻辑清晰,且包含很多注释。在数独生成类中,每个函数都有其规格,规格中包括Description, Input, Require, Concrete function部分,让人对程序的功能一目了然。 - 代码容易维护么?
代码的逻辑清晰,将需求切割得较为精细,也就保证了每个类和类中的每个函数功能单一。这也就使代码的可维护性更强。 - 代码的每一行都执行并检查过了吗?
程序中包含冗余的代码,例如数独输出类中有四个print方法,但仅使用了一个,因此冗余代码应当注释或删除。
- 代码符合需求和规格说明吗?
-
设计规范部分
- 设计是否遵从已知的设计模式或项目中常用的模式?
代码设计中没有用到设计模式,但使用了面向对象的模式,逻辑功能的划分清晰。 - 有没有硬编码或字符串/数字等存在?
在对数独矩阵做循环时出现了数字9,对矩阵做行列等价变换时出现了一些行号列号数字。但等价交换部分的数字在函数的规格中做出了解释。 - 代码有没有依赖于某一平台,是否会影响将来的移植(如Win32到Win64)?
代码中没有使用十分大的int数字,因此不会出现int溢出的现象,在x86环境和x64环境都能正常运行且都能满足需求。因此不存在依赖32位或64位平台的现象。 - 开发者新写的代码能否用已有的Library/SDK/Framework中的功能实现?在本项目中是否存在类似的功能可以调用而不用全部重新实现?
在生成数独时,数独第一行的全排列可以使用algorithm库中的next_permutation函数实现,但开发者使用自行实现的递归函数generateMatrix来生成第一行。 - 有没有无用的代码可以清除?(很多人想保留尽可能多的代码,因为以后可能会用上,这样导致程序文件中有很多注释掉的代码,这些代码都可以删除,因为源代码控制已经保存了原来的老代码。)
该程序包含比较多的代码注释,以及几个不使用的print函数,在最终版本中应该将这些代码删除。sudo类中的print函数放在头文件中是不合适的,应该在头文件中声明,在源代码文件中定义实现。
- 设计是否遵从已知的设计模式或项目中常用的模式?
-
代码规范部分
- 修改的部分符合代码标准和风格么(详细条文略)?
代码风格总体标准且一致,数独生成类的函数规格十分规范漂亮,注释清晰。但是存在左花括号换行与不换行同时存在的代码,存在运算符左右空格与不空格同时存在的代码。
- 修改的部分符合代码标准和风格么(详细条文略)?
-
具体代码部分
- 有没有对错误进行处理?对于调用的外部函数,是否检查了返回值或处理了异常?
对输入做了检查,对于不合法的输入会输出错误信息,生成超过一百万数独时也会提示错误信息。进行文件操作前,检查了打开的文件是否存在,写入文件前检查了文件是否可以写入。并且在最后将文件输入输出流关闭。
if ((f = fopen(filepath, "r")) == NULL || (fp = fopen("sudoku.txt", "w")) == NULL)
{
printf("cannot open file ");
exit(0);
}
......
fclose(fp);
fclose(f);- 参数传递有无错误,字符串的长度是字节的长度还是字符(可能是单/双字节)的长度,是以0开始计数还是以1开始计数?
参数传递时没有错误,且传递的参数数量较少,多为一到三个参数,参数意义明确。项目中的计数都是以0开始的。项目中没有涉及字符串长度的处理。 - 边界条件是如何处理的?Switch语句的Default是如何处理的?循环有没有可能出现死循环?
程序中对超过范围的请求输出错误信息,对于边界条件,可以和正常数据一样做处理并得到结果,因此除了输入检查外没有做其他的边界条件处理。switch语句最后都加上了default,且每条case语句的结尾都使用了break语句,防止程序发生错误。循环中都有明确的结束条件,可以保证不会发生死循环的现象。 - 有没有使用断言(Assert)来保证我们认为不变的条件真的满足?
在单元测试中使用了assert来对测试结果进行验证。在工程代码中没有使用assert来确定条件真正满足。并且我认为在最终完成的版本中不应该包含assert。 - 对资源的利用,是在哪里申请,在哪里释放的?有没有可能导致资源泄露(内存、文件、各种GUI资源、数据库访问的连接,等等)?有没有可能优化?
程序中有两处使用了动态申请内存。第一部分是在循环中创建sudoSolver对象,在其生命周期结束后使用delete清空了其占用的内存。只是sudoSolver类的析构函数为空,可能会无法释放类中动态申请的空间。在当前版本的sudoSovler类中没有使用动态申请,故不会存在问题。第二部分是在创建char数组保存写入到文件中的字符串,但没有释放这一部分空间。但由于仅需申请了一次,在程序结束后会自动释放内存,故不存在大的内存泄漏。 - 数据结构中是否有无用的元素?
代码中没有定义数据结构。
- 有没有对错误进行处理?对于调用的外部函数,是否检查了返回值或处理了异常?
-
效能
- 代码的效能(Performance)如何?最坏的情况是怎样的?
程序生成数独使用的方法是通过交换模板数字位置的方式生成母数独,然对母数独做等价变换生成数独。这种做法免去了大量的判断是否满足数独要求和回溯的消耗。在解数独时,使用递归回溯的方法。当我们构造一个神奇的数独时,程序将尝试几乎所有可能的解,造成解数独的效能变得很差。这是回溯法都会面临的问题。 - 代码中,特别是循环中是否有明显可优化的部分(C++中反复创建类,C#中string的操作是否能用StringBuilder 来优化)?
在解决数独的部分, 程序存在反复创建soduSolver类解数独的情况。可以将其修改为以此创建解数独的类,然后多次调用类中回溯解数独的方法,进而优化循环的性能。 - 对于系统和网络调用是否会超时?如何处理?
代码中不存在网络调用。系统调用仅适用文件操作,不存在超时的可能性。
- 代码的效能(Performance)如何?最坏的情况是怎样的?
-
可读性
- 代码可读性如何?有没有足够的注释?
代码的可读性很好,有足够且逻辑清晰的注释。在数独的生成类中,每个函数都有其规格,包含了Description, Input, Require, Concrete function部分,让人对程序的功能一目了然。
- 代码可读性如何?有没有足够的注释?
-
可测试性
- 代码是否需要更新或创建新的单元测试?
代码的单元测试对关键函数做了黑盒测试,在不修改关键函数的需求和输入输出的情况下对代码更新以后,不需要更新或创建新的单元测试。 - 还可以有针对特定领域开发(如数据库、网页、多线程等)的核查表。
该项目不涉及数据库,网页或多线程,故没有核查表。
- 代码是否需要更新或创建新的单元测试?
二、设计一个代码规范
- 使用c++编码,参考谷歌的代码规范。
- 工具提供的代码规范和你个人的代码风格有什么不同?
- 规范中提倡不使用宏定义。因为宏定义会使人看到的代码和编译器看到的代码不一致。且宏定义是全局的,与c++提倡的命名空间有冲突。一般对于常量的宏定义用const代替,函数用inline来代替。这样做也可以保证数据类型的吻合。
- 常量定义在google的规范中是以k开头的,之后是每个单词的第一个字母大写。
- 工具提供的代码规范里有哪些部分是你之前没有想到的?
- 变量的声明尽可能的置于最小作用域内,且要做初始化。
- 传递对象时用引用而不用copy。
- 所有按引用传递的参数在函数参数定义时必须加上const,可以保证在函数中不会对这个引用修改。
- 前置自增即++i比后置自增效率高。
- 使用的代码规范
- 代码风格规范整理
- 缩进以及不同单词之间的空隙完全由VS工具自动调整的结果为准
- 命名:(1)类型命名首字母大写,不含下划线(2)变量命名一律小写,单词之间用_连接,类的成员变量最后加_(3)常量以k开头,首字母大写不加下划线(4)常规函数使用大小写混合, 取值和设值函数则要求与变量名匹配(5)特殊的命名再做商议
- 注释:(1)尽量为每个类都写一份注释声明(2)为复杂函数前加上注释声明(3)注释要上下对齐
- 代码设计规范整理
- 不用goto语句
- 尽可能的把清理工作放在析构函数中
- 构造函数尽可能的简短
- 仅在必要时再使用类的继承
- 代码风格规范整理