如何提高 C++ 代码质量

Blog
Author:
Amir KirshAmir Kirsh
Published On:
12月 13, 2021
Estimated reading time:
1 minute

所谓高质量的代码,是指符合格式规范、易于阅读、简单易懂、经过适当测试、没有错误并且备有文档记录的代码。拥有高质量的代码将使你受益匪浅,为之付出努力,必将有所回报。要编写并维护好的代码,有许多适用的技巧和诀窍,还可运用多种工具来协助开展质量改进工作,如代码重构。

代码质量是所有开发人员都关心的问题,无论他们属于哪个级别,使用哪种编程语言。但在这篇文章中,我们将重点讨论 C++。作为一名有经验的 C++ 开发人员,你已经知道这是一种成熟的语言,拥有庞大的代码库,在某些情况下甚至可以追溯到 30 多年前。也许你正在研究如何对遗留 C++ 代码进行现代化改造,或者希望最大限度地提高项目的灵活性和可扩展性,不断向前发展。无论是哪种情况,C++ 质量都值得重视。

C++ 代码质量的定义及其表现形式

代码质量是一个通用术语,指的是代码的清晰度、可靠性、可维护性和可扩展性。它适用于用任一种语言编写的代码,也值得所有程序员关注,但 C++ 的某些特定方面尤其值得一提。

在这篇文章中,通常情况下,当讨论代码质量时,我们假设代码的功能是正确的——也就是说,代码按预期工作,通过了所有功能测试,没有可见的错误(但可能有隐藏的错误,特别是在其质量不高的情况下),并且也没有任何外界意见指出存在问题或缺陷。

为什么 C++ 代码的质量很重要?

面临着任务截止日期以及发布周期缩短带来的源源不断的压力,程序员可能会问,为编写高质量的代码而采取额外步骤,到底是不是在合理地利用时间。

无论面对何种环境,使用哪一种软件编程语言(比如说 Java Python),保证代码质量都是必须具备的优秀品质,但对于 C++ 来说尤其如此。

C++ 是一门复杂的语言,而且不仅仅是我们这样认为。这就是为什么保证代码质量如此重要:要使代码更易于阅读和维护,并且更不易出错。

部分开发人员有着这样的经历:他们不得不接管项目、维护其他人的代码并为旧项目添加新的功能。他们非常清楚,代码质量高将为高效开发铺平道路,并降低将新的错误引入代码的可能性。事实上,我们可以有把握地说,质量差的代码更容易出错,并且生命周期非常有限。除非对其加以改进,否则将无法维护。不幸的是,尽管代码质量很重要,但它往往被忽视,或者在对需要考虑的因素进行优先级排序时,其受重视程度远低于应有的水平。

https://www.incredibuild.cn/blog/%e5%a6%82%e4%bd%95%e6%9b%b4%e6%96%b0-c-%e9%81%97%e7%95%99%e4%bb%a3%e7%a0%81%ef%bc%9f

促成团队合作

高质量的代码更容易被不同的开发人员阅读和理解。 例如,确保一致性并且合理地给变量命名,使新的开发人员更容易遵循执行路径并添加相关功能。以正确方式编写的代码注释和文档不仅有助于解释整体功能,还有助于解释为什么要作出某些选择。好的注释可以提示当事物发生变化时可能产生的副作用。代码质量高有助于程序员之间开展有效沟通,这是一点是毋庸置疑的。

提升用户体验

假定应用程序可以正常工作,那么高质量代码是如何提升用户体验的呢?答案是反馈。例如,许多编写良好的程序会检测出错误并作出相应的反应。但是,高质量代码将显示并记录详细的错误消息,无论这种情况发生的可能性有多小,因此可以正确识别。相反,意义不明的错误消息很容易让终端用户感到困惑,不确定自己到底做错了什么。

稳定性和可持续性

高质量的代码是稳健的,这意味着代码库甚至环境发生微小变动不会对其造成破坏,或大幅影响其应用。举例来说,设想一个处理固定大小数量的元素的系统,而这个数量在未来的版本中会发生变化。一个稳定的系统可能会使用一个指代大小的已命名变量,而脆弱的代码实现可能使用了一个未命名的魔数作为常数,在整个代码中多次出现。它会很好地发挥作用,直到后来对数字进行了更改,而新的开发人员忽略了要更新它的每个实例。尽管这种情况看起来微不足道,但这种性质的错误实际上是很常见的。

代码复用和可互换代码内容

更好的代码质量意味着更易读的代码,通常来说,这是因为它是模块化的、可移植的,并遵循单一职责原则 (SRP)。特别是对于 C++ 和面向对象的语言,遵循 SOLID 原则有助于确保高质量代码可复用,并且可以轻松地合并到其他系统中。

代码质量差与技术债务有何联系?

技术债务这个术语所描述的是,(尤其是)因为在早期阶段为节省时间选择了一个较为简单的方法,导致未来需要重新设计解决方案所需的成本。在最初的实现过程中,选择一个更好的解决方案需要花费更长时间,但也可以因此减少技术债务。

质量高、风格良好的代码不仅更易于阅读和维护,特别是在减少技术债务方面,高质量的代码可以提高稳定性,从而减少错误。此外,错误将更容易被追踪、识别并得到解决。

代码质量度量指标

代码质量度量指标是通过一系列检查生成的,将计算出一段代码在质量方面的客观得分。代码度量指标要么是定量的,要么是定性的,这两者都是在一般情况下评估质量时需要考虑的重要因素。

定量度量指标是那些你可以通过使用特定的公式进行衡量的指标,例如代码行数,或者更具体地说,可执行代码的行数。还可以确定更复杂的方面,例如 Halstead 复杂度量测和圈复杂度

定性度量指标衡量代码质量,超越了明确的数字。这使得它们更难以衡量,并且在许多情况下是基于主观判断的。这方面的例子包括可维护性和清晰度等衡量标准,这些衡量标准基于代码格式、是否使用了好的变量名等特性。一些度量指标(可能是那些与效率相关的度量指标)根据环境的不同会有很大的不同,而用于判断一个源代码块的文档化程度的度量指标,无论在哪个平台上都是相似的。

有几种工具可用于自动计算代码度量指标。例如,Visual Studio 具有内置于 IDE 中的特定功能,用于计算数个代码质量度量指标。谈及支持包括 C++ 在内的多种语言的独立应用程序,SourceMonitor 是一款著名的免费软件实用工具(尽管令人遗憾的是,在对该实用工具连续进行长达 20 年维护后,其作者已宣布退休)。

如何提高 C++ 代码质量

不应耗费过多的时间来提高代码质量,最好不要将其作为一项专门的任务来处理。相反,应该在整个开发过程中考虑代码的质量,一旦出现机会,便抓住机会加以改进。关键点在于,代码质量可以在事后得到提高,但最重要的是,在开发的每个阶段都应该考虑到最佳实践。

遵循编码标准

我们就提高代码质量给出的第一个建议是遵循编码标准。维护和遵循一套标准在任何行业都很重要,软件开发也不例外。遵循编码标准提高了一致性,最大限度地降低了复杂性,并且不用再去揣测他人的想法,从而提高了效率,使修复错误变得更简单,并使代码更易于维护。

对于 C++,存在许多有效的编码规范,比如 Google 所推广的编码规范,但在一个项目中混合使用这些规范会让开发人员感到困惑,并导致错误发生。

例如,如果针对同一个项目开展工作的两个不同团队使用了不同风格,例如 East const const West,可能会造成极大混乱。影响最小的情况下,这种做法会减缓开发速度,而最糟糕的情况下则将导致错误发生。确保每个人都按照相同的规范工作,将有助于开展协同工作,构建更好的团队基础,最终打造出卓越产品。

遵循最佳实践

一般来说,最佳实践是指个人和组织所遵循的被认为是正确的、针对具体情况最有效的程序。当放在 C++ 开发这一背景下来考虑,这包括遵循编码标准并确保开发人员采用一致的编码规范。最佳实践可以是全球性的,也可以是组织级别的。C++ 核心指南是一个很好的例证,它是以 C++ 社区为首并由 C++ 之父 Bjarne Stroustrup 积极领导共同努力所取得的成果。

代码注释

加入有意义、简洁且具有相关性的代码注释是编码工作的重要环节,并且确实是一个值得单独探讨的话题。进行注释的关键在于,添加的内容应使代码更具可读性,而不是更加令人困惑。在实践中,如果一个注释不能简洁地表达出来,则可能表明代码存在问题。同样,代码注释写得好并不意味着代码就很好。

注释应准确地描述当前的代码状态,而不是其旧版本,这一点很重要。修复错误或更改代码时,应相应更新注释。同样,注释需要准确而简洁地阐明代码中发生了什么,以及通过代码本身无法清楚传达的实现决策和细节。要牢记:目标受众是你的程序员同事。

要了解更多关于代码注释的信息,请参阅 Ellen Spertus 撰写的这篇文章,并查看来自 Stack Overflow Jeff Atwood 给出的另一些重要建议

重构

代码重构是在不改变代码基本功能的情况下,对其进行更改,以使其更高效、更易读、更易维护和可扩展的过程。有时,这一过程简单到只需将一组变量打包放到一个新的类中,以使该代码更加简洁并得到恰当的封装;而有些时候它可能是一个复杂的过程,涉及大部分代码库,尤其是当需要重构以增加新的功能时。

当放在 C++ 代码质量这一背景下来考虑,进行重构时应始终注意改进代码,使其更容易阅读和维护,这样也更难引入新的错误。应该更容易定位错误(例如,通过在曾经发现过错误的地方加入断言、进行日志记录和设置前置条件)并促进代码重用。

代码评审

在代码评审这一过程中,参与其中的开发人员通过系统化的方式检查彼此编写的代码。可由担任监督角色的人或由同一级别的同行来进行代码评审。

代码评审的目标包括验证程序设计、确保所有功能都已实现,以及实施包括编码风格和规范在内的最佳实践。例如,进行代码评审将有助于确定可以优化的领域,如使代码更加通用、使用模板、改进算法,或引入某种软件模式。

静态代码分析

静态代码分析是一种代码自动分析质量评估方法,无需实际运行。事实上,这一步通常在代码评审之前完成,可以准确地找出与安全性、漏洞和一般缺陷相关的错误。这些错误都是可以通过人工代码评审(如前所述)发现的,但是静态代码分析工具可以加速这个过程,并自动更深入地考虑问题。例如,进行人工代码评审时也许不会追踪所有具相关性的可能的执行路径,而静态代码分析工具可能会在追踪跨函数调用执行路径方面做得更好。

但这种方法也有着明显的局限性:例如,它无法确定功能是否符合预期,因为它无法验证意图。此外,如果某些东西不是静态可执行的,也许是因为规则在本质上是主观的,那么这种方法将不会带来任何帮助。要了解不同工具的部分优缺点,请查看 C++ 静态代码分析工具比较

使用静态代码分析工具将提高代码质量,因为它可以识别潜在的错误,即使实际上这些错误在当前测试的用例中并未发生。例如,像变量类型这样简单的东西,随着时间的推移发生了改变,可能会导致微妙的语义错误。让我们来看看以下代码:

静态代码分析示例

bool Ball::roll(int& direction) {
  // ...
  if(direction < 0)
    direction = -direction;
  // ...
} 

假设这段代码按预期工作并且没有副作用。然而,想象一下,在开发过程中,当被移植到另一个平台后,方向变量改为了 std::size_t 类型。更新后的代码如下所示:

//bool Ball::roll(int& direction) {
// ...
if(direction < 0)
direction = -direction;
// ...
}/

此处的问题在于,size_t无符号的,并且该值永远不会小于 0,这意味着分配代码不可达。可能出现的情况是,不再支持通过使用反方向进行指定来反向运行代码(如果有人希望针对这个非常简单的错误能够得到警告,当然,这肯定与所使用的警告级别有关。在 GCC 中使用 -Wextra 进行检查,确实得到了警告,然而,Clang 拒绝发出警告,即使使用-Wextra -Wsign-compare -Wabsolute-value -Wunreachable-code 也没用)。静态代码分析工具应该能够突出显示这种语义错误。,进行静态代码分析是有好处的。这是一个简单的错误,可能并非所有编译器都能将其捕获,而且可能超出了人工代码评

总结

C++ 代码质量是一个重要的特性,可以直接转化为更好的代码可读性、可维护性和更高的效率。拥有高质量的代码可以改善最终用户体验,促进开发人员之间的协作,并为将来高效地扩展代码库铺平道路。代码质量由多种要素构成,包括衡量代码质量的度量指标、采用流行的编码风格和编码规范,以及遵循 C++ 最佳实践。还应坚持使用自动静态代码分析工具,坚持对任何新的拉取请求进行系统化的人工代码评审,以达到所需的代码质量水平。