代码复审
<tr><th rowspan = "5">设计规范</th><td>设计是否遵从已知的设计模式或项目中常用的模式?</td><td>没有使用常用的设计模式。</td></tr>
<tr><td>有没有硬编码或字符串/数字等存在?</td><td>存在硬编码,循环长度固定为常数</td>
<tr><td>代码有没有依赖于某一平台,是否会影响将来的移植(如Win32到Win64)?</td><td>不依赖于平台</td></tr>
<tr><td>开发者新写的代码能否用已有的Library/SDK/Framework中的功能实现?在本项目中是否存在类似的功能可以调用而不用全部重新实现?</td><td>没有可以直接调用的功能</td></tr>
<tr><td>有没有无用的代码可以清除?(很多人想保留尽可能多的代码,因为以后可能会用上,这样导致程序文件中有很多注释掉的代码,这些代码都可以删除,因为源代码控制已经保存了原来的老代码。)</td><td>存在少量注释掉的代码,例:
<pre><Code>/* if (buf_top < 5)<br>
{<br>
print_sudoku();<br>
}*/<br>
//print_sudoku();<br>
</Code></pre></td></tr>
<tr><th rowspan = "1">代码规范</th><td>修改的部分符合代码标准和风格么(详细条文略)?</td><td>存在少量无意义的空行和缩进不当的注释;类和部分函数过长,建议按照单一功能划分成不同函数和类</td></tr>
<tr><th rowspan = "6">具体代码</th><td>有没有对错误进行处理?对于调用的外部函数,是否检查了返回值或处理了异常?</td><td>有针对无解情况、参数不正确情况的处理。大部分函数检查了返回值,find_another_fit_array(9);有不同返回值,但没有针对不同返回值的不同处理,main函数的不同返回值可能意义不大</td></tr>
<tr><td>参数传递有无错误,字符串的长度是字节的长度还是字符(可能是单/双字节)的长度,是以0开始计数还是以1开始计数?</td><td>参数传递没有错误;字符串的长度是字节的长度;从0开始计数<td></tr>
<tr><td>边界条件是如何处理的?Switch语句的Default是如何处理的?循环有没有可能出现死循环?</td><td>for循环中设置i < n的边界条件,while循环较为多样化;default处理了case中没有列出的情况;所有循环都存在可到达的推出循环的条件,不存在死循环</td></tr>
<tr><td>有没有使用断言(Assert)来保证我们认为不变的条件真的满足?</td><td>没有使用断言</td></tr>
<tr><td>对资源的利用,是在哪里申请,在哪里释放的?有没有可能导致资源泄露(内存、文件、各种GUI资源、数据库访问的连接,等等)?有没有可能优化?</td><td>打开的文件都被关闭,申请的空间均为固定长度的数组,大多数在类中申请,没有析构函数</td></tr>
<tr><td>数据结构中是否有无用的元素?</td><td>数据结构中的元素都被充分地利用</td></tr>
<tr><th rowspan = "3">效能</th><td>代码的效能(Performance)如何?最坏的情况是怎样的?</td><td>程序性能很好,生成100w个数独需要1.527s,求解100w个数独需要
57.368s,如果只进行一次输出而不是每生成一个数独进行一次输出可能IO效率会更高
<tr><th rowspan = "1">可读性</th><td>代码可读性如何?有没有足够的注释?</td><td>可以在create_sudoku中能够清晰的看到生成数独的逻辑,但是类和函数过长,函数功能不单一,定义了较多变量,且没有注释,函数的细节不容易理解。</td></tr>
<tr><th rowspan = "2">可测试性</th><td>代码是否需要更新或创建新的单元测试?</td><td>存在对于整体的测试,但是缺乏针对各个模块的单元测试,需要创建新的单元测试以保证各个步骤处理各种情况时的正确性</td></tr>
<tr><td>还可以有针对特定领域开发(如数据库、网页、多线程等)的核查表。</td><td>没有涉及多线程等特定领域</td></tr>
<tr><th>评论链接</th><td colspan = "2"><a href = "https://github.com/514DNA/sudoku/commit/0432474737cb181a6c9f34c7fee2173f4b606cfd">commit 1</a><br><a href = "https://github.com/514DNA/sudoku/commit/8bd6a35399d8bac796efcd8f6892501c322c9aff">commit 4</a></td></tr>
</tbody>
主要部分 | 检查条目 | 检查结果 |
---|---|---|
概要 | 代码能符合需求和规格说明么? | 代码能够有效的完成生成和求解数独的需求,能够对错误的命令行参数、不存在的文件和无解的数独进行处理并输出相应的错误信息 |
代码设计是否有周全的考虑? | 代码全面的覆盖了生成和求解数独需求,以及对各种可能出现的错误情况的处理,考虑相对周全 | |
代码可读性如何? | 类和函数过长,数据成员比较多,且没有注释,不容易理解代码 | |
代码容易维护么? | 模块划分不太清晰且可读性不太好,可能会给维护带来一定困难 | |
代码的每一行都执行并检查过了吗? | 存在始终不被调用的函数:
| |
代码中,特别是循环中是否有明显可优化的部分(C++中反复创建类,C#中string的操作是否能用StringBuilder 来优化)? | 没有明显的可优化的部分,循环中有充分的剪枝,避免了许多不必要的遍历 | |
对于系统和网络调用是否会超时?如何处理? | 没有系统和网络调用 |
代码规范
问题
使用了cpplint3.0,有288处报错,粗略阅读了Google C++ Style Guidance,有了进一步的理解
- 工具提供的代码规范与个人 代码风格的不同
- 不使用tab而使用空格,在个人的代码中,通常为了省事而使用tab。《构建之法》中提到过,tab在不同情况下会显示不同的长度,会导致排版的混乱
- {, (, 前应该有空格
- 工具提供的代码规范中个人没有想到的部分
- 版权信息。由于一直都是个人编程,完成一些作业所以无需考虑版权问题,但是在实际的软件开发中,版权也是一个不可忽视的问题
- 行末不应该有空格
- 代码块的开头不应该有多余的空行,代码块的末尾不应该有多余的空行。空行的作用应该是将代码按照逻辑分块,时期看上去更加清晰,可读性更高,如果加一些不必要的空行,可能会降低代码的可读性。
- '//' 和注释内容之间应该有空格
- 这样的规范的意义
在使用cpplint之前已经看过《构建之法》中关于代码规范部分,也大致的浏览过Google中C++的代码规范,所以对于代码规范的意义也有了一定的认知。代码规范分为代码风格规范和代码设计规范,与代码有关的对象一个是机器一个是人,人又分为编程者自己和团队或其他人。代码对于机器来说比较重要的是正确性和性能,虽说是与机器相关的两点,但是主要还是涉及到对人的影响。
- 编程者自身:对于编程者自身来说清晰的逻辑非常重要,而规范的设计和较好的可读性对于具有清晰的逻辑是非常重要的。遵循代码设计规范能够有效的避免一些错误,例如对返回值的处理、错误处理、参数传递,减少了在这些方面花费的时间,更容易把精力放在程序本身的逻辑上,而不是一味地被一些没有什么技术含量的bug困扰。另外设计短而功能集中的函数能够使得函数功能单一化,更容易发现缺陷和进行测试,也更容易进行代码的复用以及提高程序的性能。另外遵循代码风格规范、可读性高的代码更容易发现和修改bug,在编码时也会较少的犯错。清晰的排版,有意义且分类明确的命名,必要的注视能够使程序的逻辑一目了然,前期的编写和后期的维护都会容易很多。
- 团队或他人:在实际应用中几乎不会单人完成项目,因此在团队的软件开发中,代码规范也具有相当大的意义。在团队中,每个人都有具体负责的部分,遵循共同的代码规范能够有效地减少冲突。此外,可能一段代码不会仅仅编程者一个人过目,可能会有测试、会与其他人的部分有交叉、会由其他人接手。此时较好的可读性是十分必要的,另外尽管每个人的逻辑会有一定得差异,但是统一的代码规范,能够很好的帮助团队成员之间理解彼此的代码,更容易进行测试、优化、维护以及后续的开发。
其中有些规则可能看上去并没有特别的意义,例如{前的空格,但是重要的不是留空格这条规则本身的含义,而是整体的规范和统一性。所以代码规范能够起到的作用是排除其他干扰,使得编程人员能够把精力集中到程序本身,更好的保证程序的正确性和性能,保证开发和测试的效率。
代码规范
代码风格规范
- 缩进:使用4个空格进行缩进
- 行宽:限定在100字符以内
- 括号:复杂的逻辑表达式内,用括号表示逻辑的优先级
- 断行与空白{}行:‘{’和‘}’都独占一行
- 分行:每条语句单独占一行
- 命名:
- 通用命名规则:名称应该是描述性的,避免缩写
- 文件名:小写,包括下划线(_)或连字符(-),跟随项目传统的使用方式,如果没有一致的模式,使用下划线(_)
- 类型名(类、结构、枚举):大写字母开头,每个新单词开头大写,例MyExcitingClass
- 变量名:变量名、数据名应该全部小写、单词之间使用下划线(_),类的数据成员尾部应有下划线,例a_class_data_(结构的数据成员不必如此)
- 静态变量名称:constexpr或const定义的值在一段时间或整个程序中保持不变,k开头加大小写,例kDaysInAWeek
- 函数名:常规函数大小写混合,访问器和变异器(?)按照变量的方式命名
- 命名空间命名:全部小写,最顶层的命名空间的命名基于项目名称,避免顶层和嵌套命名空间之间的冲突
- 枚举命名:使用常量或宏的命名规则
- 宏命名:全部大写,下划线连接
- 注释:复杂注释放在函数头,不做冗余注释,尽量只用ASCII字符
代码设计规范
- 函数:
- 写短函数:写短而功能集中的函数
- 函数重载:在保证可读性的情况下使用重载函数
- 返回值:对函数的返回值要有处理
- goto:为了使函数由单一的出口,使用goto
- 错误处理:所有传入的参数都要验证正确性;对某事十分肯定时,使用断言,否则进行相应的错误处理
- 类
- 类的使用:只在必要时使用类,有显示构造和析构函数的类,不应该建立全局的实体,数据封装使用struct即可
- 按照共有、保护、私有的顺序说明类中的成员
- 数据成员:用_name命名,不使用公共的数据成员
- 虚函数:用虚函数实现多态,仅在必要时使用虚函数,实现多态的类型基类中的析构函数应该是虚函数
- 构造函数:仅在构造函数中初始化数据成员;构造函数不应该返回错误
- 析构函数:把所有清理工作放在析构函数中,析构函数不应该出错
- new和delete:实现自己的new和delete;检查new的返回值
- 运算符:使用成员函数,仅在必要时自定义操作符;运算符不做标注语义之外的任何动作;运算符的实现必须有效
- 异常:不要将异常处理作为逻辑控制来处理程序的主要流程;了解异常和异常处理的花销;使用异常时注意数据清理的位置;一场不能跨国DLL或进程边界传递信息
- 类型继承:仅在必要时使用类型继承;const标注只读的数据和不改变数据的函数