C++ 代码评审最终指南——第 1 部分

2023-10-27

在这里插入图片描述

C++ 语言功能强大——但也极其复杂,复杂性使其极易引发误解和过度复杂化。相比简单语言,C++ 中的程序错误难以发现——相比其他语言,生产环境中的 C++ 程序错误更难定位。

简而言之,需要谨慎处理 C++——甚至是用鹰眼那样锐利的目光进行评审。

本篇分为两部分。第一部分,我们将讨论更多代码评审的普遍情况。第二部分,我们将深入探讨具体的 C++ 代码评审话题,并为 C++ 代码评审创建检查表。
*

C++ 代码评审的重要性

无论使用何种语言进行编码,总是难以发现代码中的程序错误和普通错误,而我们可能从头到尾都没意识到自己的错误。他人评审不失为一种解决问题的好方法。

此外,两个人或更多人之间的代码讨论便足以提出新问题,更有助于明确重要问题。

上述内容适用于任何所使用的编程语言,但 C++ 的复杂性使其尤为重要。必须额外增加至少一个人来评审代码。

代码评审前的注意事项

提交代码以供评审之前,需要注意两件事。

其一是通过静态代码分析,并能解释任一所发现的警告,这些警告可能是误报,或是出于某种原因不应处理。静态代码分析工具种类繁多——包括开源工具与商业工具——有什么理由不使用呢?当然,假定不存在任何编译器警告,但在极少数情况下,如果确实有理由忽略编译器警告,也应提前备好解释(比如,编译指示声明上的注释,用以指示编译器忽略该警告)。

其二是测试代码。运行单元测试时,最好透彻检查全部代码,包括错误和边缘案例。测试能保证代码运行,缺少测试,代码评审则毫无意义。此外,代码评审中待评审项目包含测试。如果需要在代码评审后增加测试,确保测试失败后对代码进行的变更不会避开代码评审。

静态代码分析和执行良好的单元测试均可降低问题影响代码评审的机率,并能事先修复问题,但也不能保证它们就是万无一失的。

例如,如果从一开始就弄错需求,再好的测试或者静态分析都无法发现其中的问题。另一方面,代码评审考虑了需求,有助于在代码和测试场景中直观了解。这样就能发现需求与实际代码间的不一致。

SmartBear 最近一项调查显示,24% 的受访者表示代码评审是公司提高代码质量的首要途径。单元测试位列第二,静态代码分析稍显落后。这并不是说单元测试和静态代码分析不重要,可能是受访者认为代码评审有时会受到忽视,或者没有很好地执行。但显而易见的,单元测试和静态代码分析无法取代代码评审。

代码评审中的主要注意事项

代码评审的最终目标是找出代码错误或可改进之处。这包括:

  • 代码是否符合需求
  • 代码是否符合编码惯例与准则
  • 逻辑错误、容易出错的代码、潜在程序错误与实际程序错误,包括并发问题
  • 设计缺陷、效率低下与未来准备情况
  • 可简化代码之处
  • 是否良好执行单元测试,这也有可能发现程序错误

详述上述清单之前,先来看看代码评审存在的技术问题和执行代码评审的方法。

代码评审的类型

执行代码评审的方法有以下几种:

传统代码评审会议

主要利益相关者与编写待商议代码的开发人员进行会面。群组规模取决于所评审代码的重要性与复杂性。评审可能包括其他团队的开发人员、系统工程师、产品经理与测试工程师。有人认为非开发人员参加代码评审是浪费时间,但如果会议重点是复杂需求的实现方式,他们可能会有宝贵见解。也可能只需要开发人员和一个评审人员。

“速战速决”可以立即修复代码评审中提出的问题(例如,重命名变量和函数或添加简短注释)。其他问题也应记录在案,便于日后处理。代码评审末尾,应该决定是否有必要合并代码(如果有必要,需要修正次要注释),或者有必要再次提交代码以供二次评审。

需要注意的是,所述会议可通过线下或是线上方式进行。

常规做法是让团队中未编写代码的人介绍代码,仅在不清楚的情况下才由编码人员进行介绍。这有助于确保代码的可读性。另一方面,让编码人员自行介绍代码可能更有效率。两种方式都值得尝试,要看哪一种更适合团队情况。

优点:

  • 讨论有助于提出潜在问题。
  • 编码人员可对注释作出回应,当场解决其中问题,并助力针对其他注释进行的讨论。

缺点:

  • 难以深入有待详细调查的问题(有可能线下进行,且无法在同一次会议中解决)。
  • 耗费时间、效率低下,尤其是在大型群组中进行的时候——并非每一次讨论都与全体与会者有关。
  • 需要选择一个全体利益相关者均能参与会议的时间段。

离线代码评审

开发人员完成对代码的变更,并通过源代码控制、拉取请求或其他代码评审工具,要求进行代码评审。评审人员提出的问题会在源代码控制或代码评审工具中作为公开问题。开发人员可修复问题或对其进行注释。全部修复和注释受批后,代码评审周期结束。

优点:

  • 适用于远程工作者或分散在更广地区和不同时区的团队。
  • 评审人员可根据自身情况随时进行评审。
  • 每个评审人员都能关注到代码的不同之处。

缺点:

  • 团队成员之间没有讨论和反馈。
  • 即便开发人员对于公开问题有着简单答复,可以关闭该问题。那么,如果开发人员只是关闭了这个问题(因为对此已有良好答复),我们还是不知道公开该问题的人对此有何意见。
  • 这可能是评审未能充分覆盖代码。

近距离评审

一旦开发人员完成代码,另一个团队成员就会在原开发人员的注视下——没错——对其进行评审,遍历任何错误并提出建议。这种方法中,代码评审就不那么正式了。

优点:

  • 适用于小型团队,可非正式地当面完成评审工作。
  • 尽可能在代码定稿前进行,这时编码人员对代码的印象尚且深刻,容易修复并调整。

缺点:

  • 可能会忽视部分项目或代码,遗漏其评审工作。
  • 许多情况下,这种评审会在单元测试前执行,因此不会评审代码的最终版本。

结对编程是否有代码评审的必要?

结对编程时,两名开发人员将在同一台机器上工作。一人编码,另一人提供建议和反馈,偶尔互换角色。这种方法是极限编程 (XP) 方法的一部分。适用于处于职业生涯早期的开发人员,但对全部开发任务来说效率不高。此外,在处理复杂问题时,结对编程大有裨益,可结合两名开发人员的精华思想,但往往发生在设计阶段。随后,实际编码工作可由单个开发人员处理。

问题是,结对编程是否有代码评审的必要?当然有!

结对编程的两名开发人员从同一个角度编写代码。他们可能会解释需求、思考设计,并以类似的方式分析问题。有必要引入外部观点——有人提出问题、指出问题,并考虑需求。这就是正式代码评审至关重要的原因。

总结:不要把结对编程当作是“已经把代码评审作为编程阶段本身的一部分”。后续可能还有麻烦。

代码评审工具

代码评审工具对于管理评审周期以及可能出现的注释和问题都很有用。一些团队认为它们有用;另一些团队则偏向使用简单文件或直接在源代码控制中公开问题。如果有工具辅助的代码评审适用于你的团队,那就选择一个可以实现轻松协作、跟踪变更和分享注释的工具——用于代码评审会议和离线代码评审。

以下是常用工具清单:

带着清晰简洁和明确的任务,以积极的语气,进行友好发问

确保以积极的语气,进行友好发问(而不是攻击、侮辱性问题和语气)。即使自认为知道答案,也要像不确定一样询问(“这个操作锁定了吗?需要锁定吗?”而不要:“我没发现锁定,是你的程序错误”!)。给予良好反馈(“好方法,干得漂亮!”)也是可以的(最好附带鼓励!)。

但当涉及到代码评审所产生的任务时,这些任务应该是清晰、简洁而明确的(“需要为更新缓存操作添加锁定机制”)。

代码评审期间的注释类型

代码评审期间有几种类型的注释,每种注释都应区别对待:

  1. 重命名——可在代码评审中讨论命名。毫无疑问命名是编码中最难的工作之一(不管有没有好的理由)。然而,命名对代码的可读性和可维护性至关重要。不要羞于在代码评审中要求重命名,即使这占用了代码评审的大部分时间,甚至需要增加额外评审(在第二部分我们将讨论命名注释与最佳惯例)。许多情况下,有了IDE 的帮助,重命名可以立即完成。这种情况,建议由开发人员处理。如果重命名需要更多时间(决定合适的名字或重构 IDE无法达到的其他部分代码),可将其记录为注释,让程序员稍后处理。
  2. 文档化——像命名一样,在意图不明之处添加注释,或改进现有注释,这对可读性和可维护性至关重要。务必将其纳入代码评审范围。同样,对于简短注释,允许开发人员在代码评审期间临时添加,否则就将其作为一项任务来记录。请注意,可能存在遗忘留待日后处理注释的可能,因此添加此时尚且适当的注释会是将来代码合并工作的障碍。
  3. 不符合编码准则和惯例——编码准则和惯例可让我们维护大型代码库,并理解他人编写的代码。代码评审可防止非标准或不合规的代码进入自己的 repo。
  4. 缺陷、程序错误与潜在程序错误——不符合要求、缺少错误处理或未处理特定边缘案例、逻辑错误、错误设计、效率低下和错误假设——这些是代码评审的基础(在本文的第二部分,我们将对其进行深入探讨,包括具体C++ 项目)。
  5. 需求调查——代码评审期间,有些问题不会得到明确答案(“该操作是否由两个线程同时调用?” “如果我们到达这个阶段,规范是否允许取消该操作?”)。由此产生的任务将是调查本身,在许多情况下,对每个可能的答案所采取的行动也会记录于任务中。
  6. 值得一试——在某些情况下,可能会有不希望在本轮迭代程序中实施的建议,但又希望将其作为优化建议进行记录。这可能包括改进效率和简化代码的建议,这些建议不做强制要求,或者可能极其耗费时间。还要记住提出建议的两个原因:(a)如果以后有时间或需要,可能会想实施这些建议;(b) 试图避免在未来评审中再次提出这些建议(所以有必要先行记录)。这样的注释可作为待办(TODO) 注释编入代码,并附有到期日(到期后如不执行该待办,则予以删除)。
  7. 下次或许要换个方式——这类注释属于教育层面的评审。在某些情况下,有一段代码可用不同的方式编写,但目前不值得变更(可以运行,那就好,不要动它)。但是,可能还是要提醒程序员,这段代码可以用更优雅或更简单的方式来编写。即使在这种情况下并非针对某条待办注释,如果有时间,也可在评审中提出。

无意义注释——避免类似注释:当替代方法毫无优势——“方法不错,但可以用不同的方法”。确保避免不必要注释,这些注释在评审期间将会浪费时间,甚至转移对真正问题的关注。有些评审人员喜欢借用代码评审来标榜过去处理类似问题的英雄事迹。别这样。直接谈论评审中的代码,如果没有什么实质性贡献,也没必要非得分享自己的精彩事迹。

从何处着手评审?

评审代码变更

当评审小型新特性或程序错误修复情况时,评审重点通常是变更的代码。这种情况最好进行并行代码评审,比较新老代码。从检出文件清单开始评审,并从高层次解释每个文件需要变更的需求。随后按照适当的逻辑顺序,深入研究每个文件中的变更细节。如果从高层次解释开始,可从任何方向进行评审,自上而下或自下而上。

评审新代码

首先解释所要解决的需求和高层次设计,然后列出要添加的新类和文件。确保深入研究更复杂的部分(新算法、并发问题等)。以适当的逻辑顺序评审代码(如果与会者没有相应背景知识理解该话题,不要直接跳到新算法)。留出足够的时间来讨论代码中比较复杂的部分(如果只剩下 5 分钟来评审复杂算法,就再安排一次评审会议,不要试图在本不充裕的评审时间里讨论复杂事物)。再次强调,从高层次的解释开始,应该允许以任何方向执行评审,自上而下或自下而上。即便如此,对于新代码,大多数情况下,在得到高层次解释后,按照自上而下的方法会更为容易和有效。

当然,评审可能混含新代码和代码变更,在这种情况下,最好选择具有普适性的方法。在这种情况下,有关着手之处的逻辑,即是从新代码开始还是从代码变更开始,这取决于哪种顺序更易遵循和理解。

评审测试

通过单元测试——无论是新的单元测试还是对现有单元测试的更新——都应该是代码评审的一部分。确保新代码得到良好测试、测试覆盖真实场景,而非与需求无关的虚构场景,这由你自行把握。可以考虑从测试开始评审,因为在某些情况下,这是了解正在实现的特性或修复情况的最佳切入点。

总结

本部分涵盖了有效代码评审的基础知识。最后,代码评审并非另外的步骤。最好用它来保障代码质量、减少程序错误。执行有意义的代码评审有助于在过程早期突出问题,这时编码人员对于代码的印象尚且深刻,比起过程后期更容易修复问题,也能避免后续浪费他人的时间。

点击了解 Incredibuild 加速 C++ 构建的解决方案,并获取试用 License!Incredibuild试用

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

C++ 代码评审最终指南——第 1 部分 的相关文章

  • 如何获取正在访问 ASP.NET 应用程序的当前用户?

    为了获取系统中当前登录的用户 我使用以下代码 string opl System Security Principal WindowsIdentity GetCurrent Name ToString 我正在开发一个 ASP NET 应用程
  • EF Core Group By 翻译支持条件总和

    听说 EF Core 2 1 将支持翻译小组 我感到非常兴奋 我下载了预览版并开始测试它 但发现我在很多地方仍然没有得到翻译分组 在下面的代码片段中 对 TotalFlagCases 的查询将阻止翻译分组工作 无论如何 我可以重写这个以便我
  • 我如何才能等待多个事情

    我正在使用 C 11 和 stl 线程编写一个线程安全队列 WaitAndPop 方法当前如下所示 我希望能够将一些内容传递给 WaitAndPop 来指示调用线程是否已被要求停止 如果 WaitAndPop 等待并返回队列的元素 则应返回
  • ASP.NET MVC:这个业务逻辑应该放在哪里?

    我正在开发我的第一个真正的 MVC 应用程序 并尝试遵循一般的 OOP 最佳实践 我正在将控制器中的一些简单业务逻辑重构到我的域模型中 我最近一直在阅读一些内容 很明显我应该将逻辑放在域模型实体类中的某个位置 以避免出现 贫血域模型 反模式
  • 查找c中结构元素的偏移量

    struct a struct b int i float j x struct c int k float l y z 谁能解释一下如何找到偏移量int k这样我们就可以找到地址int i Use offsetof 找到从开始处的偏移量z
  • Asp.NET WebApi 中类似文件名称的路由

    是否可以在 ASP NET Web API 路由配置中添加一条路由 以允许处理看起来有点像文件名的 URL 我尝试添加以下条目WebApiConfig Register 但这不起作用 使用 URIapi foo 0de7ebfa 3a55
  • 使用实体框架模型输入安全密钥

    这是我今天的完美想法 Entity Framework 中的强类型 ID 动机 比较 ModelTypeA ID 和 ModelTypeB ID 总是 至少几乎 错误 为什么编译时不处理它 如果您使用每个请求示例 DbContext 那么很
  • 关于 C++ 转换:参数 1 从“[some_class]”到“[some_class]&”没有已知的转换

    我正在研究 C 并且遇到了一个错误 我不知道确切的原因 我已经找到了解决方案 但仍然想知道原因 class Base public void something Base b int main Base b b something Base
  • 在 ASP.NET 5 中使用 DI 调用构造函数时解决依赖关系

    Web 上似乎充斥着如何在 ASP NET 5 中使用 DI 的示例 但没有一个示例显示如何调用构造函数并解决依赖关系 以下只是众多案例之一 http social technet microsoft com wiki contents a
  • while 循环中的 scanf

    在这段代码中 scanf只工作一次 我究竟做错了什么 include
  • 转发声明和包含

    在使用库时 无论是我自己的还是外部的 都有很多带有前向声明的类 根据情况 相同的类也包含在内 当我使用某个类时 我需要知道该类使用的某些对象是前向声明的还是 include d 原因是我想知道是否应该包含两个标题还是只包含一个标题 现在我知
  • 如何在 C 中调用采用匿名结构的函数?

    如何在 C 中调用采用匿名结构的函数 比如这个函数 void func struct int x p printf i n p x 当提供原型的函数声明在范围内时 调用该函数的参数必须具有与原型中声明的类型兼容的类型 其中 兼容 具有标准定
  • 什么时候虚拟继承是一个好的设计? [复制]

    这个问题在这里已经有答案了 EDIT3 请务必在回答之前清楚地了解我要问的内容 有 EDIT2 和很多评论 有 或曾经 有很多答案清楚地表明了对问题的误解 我知道这也是我的错 对此感到抱歉 嗨 我查看了有关虚拟继承的问题 class B p
  • 使用 x509 证书签署 json 文档或字符串

    如何使用 x509 证书签署 json 文档或字符串 public static void fund string filePath C Users VIKAS Desktop Data xml Read the file XmlDocum
  • Windows 窗体:如果文本太长,请添加新行到标签

    我正在使用 C 有时 从网络服务返回的文本 我在标签中显示 太长 并且会在表单边缘被截断 如果标签不适合表单 是否有一种简单的方法可以在标签中添加换行符 Thanks 如果您将标签设置为autosize 它会随着您输入的任何文本自动增长 为
  • 如何使用 C# / .Net 将文件列表从 AWS S3 下载到我的设备?

    我希望下载存储在 S3 中的多个图像 但目前如果我只能下载一个就足够了 我有对象路径的信息 当我运行以下代码时 出现此错误 遇到错误 消息 读取对象时 访问被拒绝 我首先做一个亚马逊S3客户端基于我的密钥和访问配置的对象连接到服务器 然后创
  • C# 成员变量继承

    我对 C 有点陌生 但我在编程方面有相当广泛的背景 我想做的事情 为游戏定义不同的 MapTiles 我已经像这样定义了 MapTile 基类 public class MapTile public Texture2D texture pu
  • 基于 OpenCV 边缘的物体检测 C++

    我有一个应用程序 我必须检测场景中某些项目的存在 这些项目可以旋转并稍微缩放 更大或更小 我尝试过使用关键点检测器 但它们不够快且不够准确 因此 我决定首先使用 Canny 或更快的边缘检测算法 检测模板和搜索区域中的边缘 然后匹配边缘以查
  • 是否可以在 .NET Core 中将 gRPC 与 HTTP/1.1 结合使用?

    我有两个网络服务 gRPC 客户端和 gRPC 服务器 服务器是用 NET Core编写的 然而 客户端是托管在 IIS 8 5 上的 NET Framework 4 7 2 Web 应用程序 所以它只支持HTTP 1 1 https le
  • C++ 标准是否指定了编译器的 STL 实现细节?

    在写答案时this https stackoverflow com questions 30909296 can you put a pimpl class inside a vector我遇到了一个有趣的情况 这个问题演示了这样一种情况

随机推荐

  • 本地部署 langchain-ChatGLM

    简介 什么是 langchain ChatGLM 一种利用 ChatGLM 6B langchain 实现的基于本地知识的 ChatGLM 应用 增加 clue ai ChatYuan 项目的模型 ClueAI ChatYuan large
  • 成为机器人工程师需要学习那些技术

    机器人工程师是未来比较吃香的工作岗位 要成为机器人工程师 ChatGPT的回答是 建议你需要学习以下技术 1 机械工程 了解机械结构 运动学和动力学 以及机械设计和制造方面的知识 2 电子工程 学习电路设计 电子元件选择和电子系统集成 以及
  • 初识Python装饰器

    Python装饰器 听过Python的人 肯定也听过装饰器的名头 但是好多人不明白装饰器是什么 是如何工作的 原理又是什么 先看看装饰器的定义 如果想要修改某个函数的功能 但是又不想修改这个函数的定义 这种在函数运行期间动态增加功能的方式成
  • ABP-使用Dapper框架

    ABP使用Dapper框架已经有很成熟的第三方包 简单的几句代码就能完成 一 首先准备好一个数据库建一个表 二 建一个实体表 Table BasBloodLevel public class BasBloodLevel Entity
  • 《设计模式》-代码质量评价标准和设计原则

    系列文章目录 设计模式 代码质量评价标准和设计原则 设计模式 创建型 单例模式 工厂模式 建造者模式 原型模式 设计模式 结构型 代理模式 装饰者模式 适配器模式 桥接模式 门面模式 组合模式 亨元模式 文章目录 系列文章目录 前言 一 代
  • 第10章 近似推断

    10 近似推断 在概率模型的应用中 一个中心任务是在给定观测 可见 数据变量X的条件下 计算潜在变量Z的后验概率分布 p Z X p Z X p Z X 以及计算
  • element表格翻页后回到顶部

  • Linux mode命令,linux命令

    一 inode节点号 在linux中 每一个文件都有唯一的inode号 inode号也是系统识别的唯一编码 而文件名仅仅是为了使用者区分辨认 inode index node 表中包含文件系统所有文件列表 一个节点 索引节点 是在一个表项
  • 数字IC设计——跨时钟域篇2(亚稳态)

    数字IC设计 跨时钟域篇2 亚稳态 一 建立时间与保持时间 前提条件 对任何一种触发器 在时钟触发沿前 后的一个小时间窗口内 输入信号必须稳定 输入信号应提前时钟上升沿 假设上升沿有效 T时间到达芯片 这个T就是建立时间Setup time
  • 海思3518E V200中RTSP实验自己构建RTP发送的函数代码及VLC播放器的一个播放注意事项

    在VLC播放器中 工具 首选项 设置里面需要注意必须使用下图中红色框圈出来的设置 如果选择的是后面的 RTP over RTSP TCP 的话会播放不出画面 具体原因未去深入探究 另可以参考他人的代码完成其他功能 网址如下 https bl
  • msi afterburner怎么设置最好?推荐设置

    msi afterburner是一款为显卡超频和监控提供的软件 广泛应用于游戏玩家和电脑爱好者之间 通过适当的设置 可以显著提升显卡的性能 下面就给大家介绍一下msi afterburner推荐设置 纯净之家 win7纯净版系统 win7
  • 使用Prometheus实现大规模的应用程序监视

    Prometheus是一个越来越受欢迎的开源工具 这有充分的理由 它可以为应用程序和服务器提供监视和警报 Prometheus的强大优势在于监视服务器端指标 并将其存储为时间序列数据 尽管Prometheus不适合应用程序性能管理 主动控制
  • 【转】一个FAE(AE)的体会和大家交流

    原文网址 http www 52rd com bbs dispbbs asp boardID 63 ID 228682 本人在国内某芯片设计公司工作近5年时间岗位是AE和FAE 两个工作量各一半吧 今日闲来无事写一些自己的体会与大家分享 不
  • 【一、搭建通用Arm平台的QT交叉编译环境】

    搭建通用Arm平台的QT交叉编译环境 前言 准备 一 下载arm平台交叉编译工具链 1 下载工具链 2 解压到交叉编译平台 Ubuntu 3 配置环境变量 二 下载QT源码 版本与交叉编译工具链版本保持一致 三 下载并编译tslib库 1
  • 微信小程序实现下载功能(以下载视频为例)

    首先 采用 wx downloadFile 方法 访问视频对应的Url 回调函数返回一个该视频文件的临时路径 wx downloadFile url app serverUrl me data videoInfo videoPath suc
  • getDerivedStateFromProps和componentDidUpdate的使用

    react 17版本 使用getDerivedStateFromProps接收外部数据同步到本地state componentDidUpdate里面发送异步请求 Foo js import useState from react impor
  • iMX6ULL-UBoot移植

    U Boot移植 文章目录 U Boot移植 1 获取源码 1 1 从u boot官网获取 1 2 从芯片厂商获取 1 3 从开发板厂商获取 2 移植 2 1 生成自己的配置文件 2 1 1 拷贝参考板的配置文件 2 1 2 生成修改配置文
  • pcb设计50经典实例_数字IC设计职位经典笔试面试100题(41~50)

    41 用与非门等设计全加法器 数字电子技术基础 192页 通过摩根定律化成用与非门实现 42 A B C D E进行投票 多数服从少数 输出是F 也就是如果A B C D E中1的个数比0 多 那么F输出为1 否则F为0 用与非门实现 输入
  • Unity导入模型UnityPacket

    asset gt import package gt import custom package
  • C++ 代码评审最终指南——第 1 部分

    C 语言功能强大 但也极其复杂 复杂性使其极易引发误解和过度复杂化 相比简单语言 C 中的程序错误难以发现 相比其他语言 生产环境中的 C 程序错误更难定位 简而言之 需要谨慎处理 C 甚至是用鹰眼那样锐利的目光进行评审 本篇分为两部分 第