理论基础
软件质量是软件与明确地叙述的功能和性能需求、文档中明确描述的开发标准以及任何专业开发的软件产品都应该具有的隐含特性相一致的程度。
作为软件开发人员怎样保证所研发的软件产品是一个高性能、无缺陷的的产品,从开发者的编码角度考虑,编码前需要有清晰的架构设计质量和详细功能流程保证、编码的时候需要保证所编码的功能质量,编码后需要保证测试质量能够功能能得到验收。整体架构设计易于扩展、模块之间耦合性低、易于复用,代码简洁易懂,易于维护 是软件开发者对高质量软件产品的一个代名词。
影响软件质量的整体的因素有:软件需求缺陷、软件架构设计缺陷、编码缺陷、文档缺陷、错误地修复功能问题。
一个高质量的软件产品,离不开整个团队多个角色的配合,那么真正关注软件质量的角色主要有那些呢?下图罗列我哦认为在软件质量问题上最为关注的三个角色。
软件质量基础
软件质量从软件开发者的视角,它主要细分为:过程质量、结构质量、功能质量。
过程质量这个主要涉及到:及时的交付软件、不超出软件开发成本预算、可信地交付软件。
结构质量这个主要涉及到:代码的可维护性、代码的可理解性、代码性能、代码安全性、代码可测性。
功能质量这个主要涉及到:满足架构设计的需求、软件没有功能缺陷、软件有足够好的性能、软件易于学习和使用。
结构质量和功能质量的提升主要依赖于做合理的架构设计审查、代码审查。
解决或提升软件质量的方案:对架构设计审查、代码审查、单元测试、新功能验证测试、压力测试、系统测试、客户现场功能验证。
高质量的代码设计
1、合理的代码模块层级;
2、代码需综合考虑、约束:各模块的大小、模块的复杂度、接口的大小、模块之间的共享资源;
3、高内聚低耦合:消除重复功能代码、分离代码变化方向、缩小代码依赖范围、向稳定的方向依赖;
4、代码统一的代码风格:代码样式的统一、命名风格统一、词汇的统一;
5、整洁代码:代码可读性高、没有坏味道、代码保存简单和直接原则、功能类或函数尽可能小;
6、熟悉开发语言和标准库函;
7、可信安全编码:代码内存安全、资源安全、线程安全;
8、避免代码中出现未定义行为;
9、防止代码缓存溢出:检查缓冲区长度大小、探测内存、调用安全的系统函数(例如strcpy、strcat是不安全的,strncpy、strncat是安全的:它们通过限制缓冲区的数据大小 来保证缓存安全);
10、防止堆栈溢出:检查空指针、检查返回值、检查字符串类型转换、输入数据的检查、内存分配的检查;
11、整数安全:有符号和无符号混用检查;
12、避免代码逻辑漏洞。
如何做代码审查
1、结对编程:通过相互技术分享、技术传播能够有效的防止编码错误,但是这种结对编程的人力成本相对较高。
2、组织正式的代码审核会议:为试图寻找代码的缺陷提供一种非常结构化的流程,通过会议形式,它还可以用于发现软件需求缺陷和软件设计缺陷,这种方式代码审查效果较好。
3、代码走查:通常由经验丰富的工程师对需要审查的代码进行检查,这种方式人力成本相对较低。
4、基于静态代码分析:基于文本和模式匹配(通过检查对代码规范的遵守),基于源代码抽象语法树的分析(检查代码中数据类型问题、未初始化问题;检测进行控制流和数据流分析)。
5、基于动态代码分析:根据代码功能需求构建测试用例、使用覆盖率检查工具检查用例对代码的覆盖率、使用动态代码检查工具检查代码;
6、静态代码和动态代码分析工具:结合静态分析工具和动态分析工具,有效利用各种工具长处,更早发现代码问题;
7、代码审查三要素:代码度量分析、静态代码检查、动态代码检查
代码审查重点
1、代码规范的检查:检查软件产品中的代码风格是否统一、代码中是否存在不符合规范的代码。
2、代码度量检查:检测软件中代码的复杂度、代码嵌套的深度、代码扇入扇出度量(面向对象中扇入指的是派生类的梳理,面向对象中扇出指由一个类衍生出类的数量)、代码继承树的深度(DIT继承深度)、类的加权方法、对象间的耦合度
3、代码设计缺陷检查:代码中是否存在空指针、是否存在内存溢出的代码、代码类型转换是否存在错误、函数的返回局部变量的引用、代码中未定义的行为、代码中是否存在申请的内存空间未释放、线程死锁、线程竞争。
4、代码坏味道检查:代码中的函数功能太长、代码中函数中过长参数列表、类功能和数据过于庞大、哑数据类和对象(哑数据就是不含有行为,只有数据的对象)、代码中switch表达式过于太长太惊悚、代码中平行的继承体系、代码中存在重复代码。
5、代码和设计需求是否存在背离情况
6、代码是否符合架构的设计
代码度量分析
1、代码中函数的复杂度过大:可能代码中函数本身实现过于复杂、可能因为架构设计过于复杂,导致代码中函数功能过于复杂;
2、函数嵌套过多或过深的情况:函数很有可能出错、需要仔细进行人工评审代码;函数需要进行重构(使用卫戍句进行优化代码逻辑;优化条件逻辑;提取函数;架构设计出现坏味道,需要重构架构);
3、注释比例过低(注释和语句比例或注释和圈复杂度的比例):在代码中有些复杂的逻辑需要添加一定的注释说明,否则影响代码的可理解性和可阅读性。
4、模块的扇入过大:代码中的模块是公共模块,需要进行人工评审模块接口是否是稳定和安全;模块承担过多职责,应该考虑遵循单一职责,分解模块的职责;
5、模块的扇出过大:检查代码中是否有多个模块都依赖于本模块所依赖的几个模块,应该把被依赖的多个模块合并为一个模块,进行重构依赖的接口;
6、类的继承树过深:应该考虑在代码的继承树的深度上是否有新的变化方向;考虑设计新的策略类,或设计其他模块实现优化继承树过深问题;
7、子类过多:检查子类的实现中是否有共同的地方,先考虑提出公共的中间子类;检查是否可以通过Bridge模块、装饰模式、组合模式等结构型模式重构代码。
使用提权函数降低函数复杂度,用表驱动的方式降低圈复杂度、利用多态减少圈复杂度
认知代码复杂度:嵌套越深、复杂度越高;判断条件越复杂、复杂度越高;递归增加复杂度、break增加复杂度。
静态代码审查
静态代码检测一般需要借助静态检测工具例如:SourceMonitor、 CCCC(代码复杂度检查工具)、Embold、SonarQube、cppcheck等等。
通过这些工具可以静态检测代码中的:代码行数、语句数目、函数数目、函数深度、类的梳理、注释语句比例、继承树深度等等。
静态代码检测的主要作用:代码规范检查、代码bug检查、代码性能检测、代码坏味道检测(过长的参数列表,过长的函数)。
静态检查虽然能够帮助检测出一些代码的缺陷(代码bug),但是对于检查规则覆盖的代码、能够工作更好;对于检查规则没有覆盖的代码,那么就无能为力,并且会存在一定概率误报的情况。
动态代码审查
动态的代码检测同样也是需要借鉴工具进行对代码动态分析,动态代码检测工具例如:Valgrind、GCC、Clang等等,通过运行软件进行动态检测代码,可以让内存问题、线程死锁、线程竞争、未定义的行为更容易的暴露出来,动态检测和单元测试、功能测试、系统测试相结合,可以提高检测的覆盖率,可以挖掘出更多代码中的bug。
动态检测代码作用
1、能够准确定位问题,误报率相对比较低;
2、动态检测和测试用例绑定在一起,查找缺陷比例与测试用例的覆盖率有关;
3、对于没有覆盖到的错误,动态分析也无能为力;
4、嵌入式系统会存在内存资源的限制;
5、应该要动静结合,多种检测手段结合,能够更有效提升代码质量。
总结
提高软件质量是作为开发人员的一个很重要的指标,通过多维度的论述软件质量问题,并且从软件开发者视角看讨论如何写出高质量的软件。最后再从代码审计的代码度量分析、静态代码检查和动态代码检查相结合,去分析如何挖掘出软件的的种种问题,从而提高软件质量。