1. 概述
1.1. 目标和原则
- 提高代码质量,及早发现潜在缺陷,降低修改/弥补缺陷的成本。
- 促进团队内部知识共享,提高团队整体水平。
- 评审过程对于评审人员来说,也是一种思路重构的过程,帮助更多的人理解系统。
- 作为一个传递知识的手段,让其它并不熟悉代码的人知道作者的意图和想法,从而可以在以后轻松维护代码。
- 可以被用来确认自己的设计和实现是一个清楚和简单的。
- 鼓励相互学习对方的长处和优点。
-
1.2. 实施方法
工程静态分析中进行人工测试的主要方法有桌前检查、代码审查(会审)和走查。经验表明,使用这种方法能够有效地发现30%到70%的逻辑设计和编码错误。
桌前检查(**Desk Checking**)
由程序员自己检查自己编写的程序。程序员在程序通过编译之后,进行单元测试设计之前,对源程序代码进行分析、检验,并补充相关的文档,目的是发现程序中的错误。检查项目有:
- 检查变量的交叉引用表:重点是检查未说明的变量和违反了类型规定的变量;还要对照源程序,逐个检查变量的引用、变量的使用序列;临时变量在某条路径上的重写情况;局部变量、全局变量与特权变量的使用;
- 检查标号的交叉引用表:验证所有标号的正确性:检查所有标号的命名是否正确;检查转向指定位置的标号是否正确。
- 等值性检查:检查全部等价变量的类型的一致性,解释所包含的类型差异。
- 常量检查:确认每个常量的取值和数制、数据类型;检查常量每次引用同它的取值、数制和类型的一致性。
- 标准检查:用标准检查程序或手工检查程序中违反标准的问题。
- 风格检查:检查在程序设计风格方面发现的问题。
- 比较控制流:比较由程序员设计的控制流图和由实际程序生成的控制流图,寻找和解释每个差异,修改文档和校正错误。
- 选择、激活路径:在程序员设计的控制流图上选择路径,再到实际的控制流图上激活这条路径。如果选择的路径在实际控制流图上不能激活,则源程序可能有错。用这种方法激活的路径集合应保证源程序模块的每行代码都被检查,即桌前检查应至少是语句覆盖。
- 对照程序的需规,详细阅读源代码:程序员对照程序的规格说明书、规定的算法和程序设计语言的语法规则,仔细地阅读源代码,逐字逐句进行分析和思考,比较实际的代码和期望的代码,从它们的差异中发现程序的问题和错误。
补充文档:桌前检查的文档是一种过渡性的文档,不是公开的正式文档,通过编写文档,也是对程序的一种下意识的检查和测试,可以帮助程序员发现和抓住更多的错误。
这种桌前检查,由于程序员熟悉自己的程序和自身的程序设计风格,可以节省很多的检查时间,但应避免主观片面性。
- 代码会审(**Code I**nspections / Review**)**
代码会审是由若干高级管理人员、程序员和测试员组成一个会审小组,通过阅读、讨论和争议,对程序进行静态分析的过程。高级管理人员领导整个会审小组。
代码会审分两步。第一步,小组负责人提前把设计规格说明书、控制流程图、程序文本及有关要求、规范等分发给小组成员,作为评审的依据。小组成员在充分阅读这些材料之后,进入审查的第二步:召开程序审查会。在会上,首先由程序员逐句讲解程序的逻辑。在此过程中,程序员或其他小组成员可以提出问题,展开讨论,审查错误是否存在。实践表明,程序员在讲解过程中能发现许多原来自己没有发现的错误,而讨论和争议则促进了问题的暴露。
在会前,应当给会审小组每个成员准备一份常见错误的清单,把以往所有可能发生的常见错误罗列出来,供与会者对照检查,以提高会审的实效。这个常见错误清单也叫做检查表,它把程序中可能发生的各种错误进行分类,对每一类列举出尽可能多的典型错误,然后把它们制成表格,供会审时使用。这种检查表类似于本章单元测试中给出的检查表。
- 走查(**Walkthroughs**)
走查与代码会审基本相同,一般由编写代码的程序员来组织讨论,其过程分为两步。第一步也把材料先发给走查小组每个成员,让他们认真研究程序,然后再开会。开会的程序与代码会审不同,不是简单地读程序和对照错误检查表进行检查,而是让与会者“充当”计算机。即首先由测试组成员为被测程序准备一批有代表性的测试用例,提交给走查小组。走查小组开会,集体扮演计算机角色,让测试用例沿程序的逻辑运行一遍,随时记录程序的踪迹,供分析和讨论用。
代码审查是一种正式的评审活动,而代码走查的讨论过程是非正式的。人们借助于测试用例的媒介作用,对程序的逻辑和功能提出各种疑问,结合问题开展热烈的讨论和争议,能够发现更多的问题。
2. 常用代码管理工具
工具 | 用途 | 备注 |
---|---|---|
Git | 版本控制(Version Control System) | 版本控制系统,是一种工具,用来记录一个或若干文件内容变化,以便将来查阅特定版本修订情况。 |
GitHub | 代码托管仓库 | 一个基于Git实现的在线代码托管仓库。 |
GitLab | 代码托管仓库 | 一个基于Git实现的在线代码仓库托管软件,你可以用gitlab自己搭建一个类似于Github一样的系统,一般用于在企业、学校等内部网络搭建git私服。 |
Gitee | 代码托管仓库 | 国内代码托管仓库。 |
Gerrit | 代码评审 | 代码评审也称代码复查,对软件源代码的系统性检查,查找软件源代码质量,结构,漏洞等问题。若要使用Gerrit的审批功能,必须使用git-v2.x以上版本,否则有些命令不支持。 |
GerritHub | 代码托管仓库 && 代码评审 | GitHub在线仓库和Gerrit的功能合体。 |
Jenkins | 持续集成 | Jenkins是一款开源CI&CD软件,一个可扩展的持续集成引擎。提供了数百个插件来支持构建,测试,部署和自动化任何项目。支持各种运行方式,可已通过系统包,Docker或者通过一个独立的Java程序。 |
JaCoCo | 代码覆盖率 | 一个开源的覆盖率工具,自0.7.1版起,完全支持Java 8。Eclipse中支持JaCoCo插件EclEmma,IDEA也支持相应的JaCoCo插件。 |
sonarQube、Sonar Scanner、SonarLint | 代码质量管理 | Sonar是一个用于代码质量管理的开放平台。通过插件机制, 可以集成不同的测试工具,代码分析工具,以及持续集成工具,通过插件Findbugs、Checkstyle等工具检测代码存在的缺陷。支持的语言包括: Java、 C#、 C/C++、JS/TS、Python、Go、Swift、COBOL、Apex、PHP、Kotlin、Ruby、Scala、HTML/CSS、ABAP、Flex、Objective-C、PL/I、PL/SQL、RPG、T-SQL、VB、VB6、XML等27种编程语言(截止2021年3月)。 |
3. 代码管理架构
3.1. 持续集成流程
一般Git、Gerrit和Jenkins集成后的使用流程如下:
- 开发者提交代码到Gerrit。
- 触发对应的Jenkins任务(如:代码质量检查),通过以后Verified加1。
- 人工审核,审核通过后Code Review加2,触发对应的Jenkins任务。
- 通过以后确认本次提交,Gerrit执行与Git仓库的代码同步操作。
- 代码进入Git仓库。
- 持续交付、测试、部署、回退等。
3.2. 持续集成方案
1. 架构设计
2. 权限配置
- Gerrit权限管理
为了利用Gerrit的优势,我们需要为开发团队成员分配一些额外的角色。理解角色、它们的相关职责和访问权限,可以在代码审查工作流中正确地插入和分配职责。
角色 | 描述 |
---|---|
贡献者(Contributor) | - 在Gerrit中,每个能够以读模式访问存储库至少一个分支并能够上传新更改的用户都被称为贡献者。代码评审背后的理念是“从团队中获得早期反馈”,而且通常没有更好的反馈,只是针对相同问题的替代解决方案:这就是为什么通常每个具有读访问权限的用户都被授予上传代码更改能力的原因。 - 这个角色的存在使得代码审查从根本上不同于以前的代码分析/检查工具,因为任何人都可以成为贡献者并提供输入和价值,不管他们的项目历史、访问权限或关于编码规则和最佳实践的权威性如何。使用外部贡献者的能力使代码审查成为创新和项目增长的燃料,特别是在开源领域。 |
评论者(Reviewer) | - 每个团队成员通常都被允许表达一个给定变化的分数,用标签+数值(正、负或中性)表示。此角色称为评论者。除了对代码或整个更改的简单注释外,团队成员和贡献者通常还被授予表达积极(+1)或消极(-1)评审分数的能力。在一个变更上提供一个分数不应该被理解为对其他人的技能的判断:代码评审总是关于对代码的交换意见,并且不应该被用来评估团队成员的表现。 |
提交者(Committer) | - 当团队成员在代码和底层设计方面变得更有经验时,他们开始成为项目的“技术权威”。然后,他们有权对代码提供更全面的反馈,他们还将能够对提议的更改拥有最终决定权(无论是积极的还是消极的。这些成员被称为提交者,他们有能力为更改提供+2(-2)的评分。这些审查的结果是绝对的: +2使代码能够提交到主分支,而-2表示不接受更改。 - 通常,提交者的数量是相当有限的,在Gerrit代码审查项目中提交者的数量不超过6个。这个角色可以根据项目历史中每个成员成功地执行了多少更改和审查来获得。 |
维护者(Maintainer) | - 有时指派一个提交者管理和监控项目的职责是有用的,但是他在代码评审过程中没有积极的角色。这个角色称为维护者。它们的主要活动是管理用户到角色的映射,并将不同角色的访问控制权限分配给项目分支。 |
审查标签和角色自定义 | - Gerrit是一个高度可定制的代码审查系统,到目前为止提到的所有标签和概念都可以使用强大的规则引擎根据项目的需要进行定制。每一个评论输入通常被认为是带有正/负分数的标记。评分背后的逻辑也可以定制,并在项目规则中用于批准/拒绝更改。Gerrit本身使用一组默认的规则来实现到目前为止所描述的逻辑,但是项目维护者可以创建额外的规则,并定义新的模板,以便在其他项目中使用。 |
Gerrit中的访问控制是基于组的。每个用户帐户都是一个或多个组的成员,访问和特权被授予这些组。不能将访问权限授予单个用户。
类型 | 描述 |
---|---|
系统组 | 系统组被分配特殊的访问权限和成员管理权限,Gerrit提供了以下系统组。 - Anonymous Users:所有用户都自动成为该组的成员。没有登录的用户只属于这个组,而不属于其他组。分配给这个组的任何访问权限都由所有用户继承。管理员和项目所有者可以将访问权限授予此组,以便允许匿名用户查看项目更改,而不需要首先登录。 - Registered Users:分配给这个组的访问权限总是在应用该访问权限的项目上下文中进行评估。因此,这些权利适用于作为本项目所有者的所有用户。Gerrit管理员可以为项目所有者定义一组默认的访问权限。子项目继承这些访问权限,并将它们解析为拥有子项目的用户。在父项目上为项目所有者分配默认访问权限可以避免最初为新创建的子项目配置访问权限。 - Project Owners:分配给这个组的访问权限总是在应用访问权限的更改的上下文中进行评估。因此,这些权利适用于作为此更改所有者的用户。通常将一个标签分配给这个组,允许变更所有者对他的变更进行投票,但实际上不会导致它被批准或拒绝。 - Change Owner:所有登录的用户都自动成为这个组的成员。任何分配给这个组的访问权限都会在所有用户登录到Gerrit时被继承。在为该组分配任何权限时,应谨慎行事。它是典型的分配代码审查-1..+1到此组,允许已登录用户对更改进行投票,但实际上不会导致更改被批准或拒绝。注册用户总是被允许对他们所读访问的任何项目中的任何更改发表评论。 |
预定义组 | 预定义的组与系统组的不同之处在于它们存在于ACCOUNT_GROUPS表中(与普通组一样),但是预定义的组是在Gerrit站点初始化时创建的,并且为这些组分配了惟一的uuid。这些uuid在不同的Gerrit站点上是不同的。Gerrit有两个预定义的组: - Administrators:Gerrit中登录的第一个帐户自动加入该组,并授予整个Gerrit实例的管理权限。administrators组的成员可以将其角色委托给其他用户。 |
Non-Interactive Users:在创建Gerrit DB时自动生成,但通常是空的。
| | 用户组 | 帐户组包含零个或多个用户帐户成员的列表,这些列表由组所有者单独添加。列为组成员的任何用户帐户都将获得授予该组的任何访问权限。每个组都指定一个其他组作为其所有者。所有者组成员的用户拥有以下权限:
- 将用户和其他组添加到该组。从该组中删除用户和其他组。
- 更改此群组的名称。
- 更改此群组的描述。
- 更改此组的所有者组。
新创建的组,将自动把自己(新创建的组)设置为所有者组,因此,组中的每个成员都具有该组的最大权限。 | | 外部组 | 比如我们常用的ldap外部组,它们将不会出现在“groups”列表中。我们可以直接使用它,使用的方式是:添加前缀 ldap/,比如 ldap/develop |GitLab权限管理
GitLab用户权限:GitLab用户在组中有五种权限:Guest、Reporter、Developer、Master、Owner。 | 权限 | 描述 | | —- | —- | | Guest | 可以创建issue、发表评论、不能读写版本库。 | | Reporter | 可以克隆代码,不能提交,QA、PM可以赋予这个权限。 | | Developer | 可以克隆代码、开发、提交、push、RD可以赋予这个权限。 | | Master | 可以创建项目、添加 tag 、保护分支、添加项目成员、编辑项目、核心RD负责人可以赋予这个权限。 | | Owner | 可以设置项目的访问权限-Visibility Level、删除项目、迁移项目、管理组成员、开发组leader可以赋予这个权限。 |
GitLab访问权限:GitLab中的组和项目有三种访问权限:Private、Internal、Public。 | 权限 | 描述 | | —- | —- | | private | 只有组成员可以看到。 | | internal | 只要登录的用户就能看到。 | | public | 开源的所有的人都可以看到。 |
Jenkins权限管理
Jenkins本身自带安全管理的功能,但是一般情况下很少去使用,更多是使用插件的方式进行更加灵活的处理。Jenkins的权限管理需要依赖Jenkins的权限管理插件。通过配置插件(Role-based Authorization Strategy),可以很方便给不同用户不同job的管理和执行权限。
3. 代码评审流程
4. 代码同步
Gerrit和GitLab集成后,在Gerrit上的项目仓库有变化时,会自动同步到GitLab上对应的项目仓库中。但Gerrit和GitLab的同步只能是单向同步(Gerrit → GitLab),也就是说直接在GitLab上项目仓库的变动不会自动同步到Gerrit上。因此建议在Gerrit和GitLab集成后,所有的操作都在Gerrit上完成。
如果想要将Gerrit上的改动自动同步到GitLab上,就需要用到Gerrit的Replication插件。Replication插件可以同时对接已有的Git仓库系统,通常用于提供changes的镜像或者热备份,自动地将Gerrit Code Review创建的任何改动push到另外一个系统里。
4. 功能介绍
4.1. 版本控制
在项目开发过程中使用Git的方式主要有以下几种:Centralized Workflow(集中式工作流)、Feature Branch Workflow、Gitflow workflow、Forking Workflow。
1. Centralized Workflow
像SVN 一样,集中式工作流以中央仓库作为项目所有修改的单点实体。所有修改都提交到Master 这个分支上。这种方式与SVN 的主要区别就是开发人员有本地库。Git 很多特性并没有用到。
2. Feature Branch Workflow
如果团队已经非常顽固依赖于中央库的工作模式(SVN技术控),但是又希望简化这种模型的合作代价,那么非常适合此种工作模式。 通过为每一个feature都设计一条独立的分支,那么就可以在完全合入到官方项目分支前做充分的讨论,测试和验证。
Feature branch workflow的核心想法或原则是:所有的feature开发都必须在特定的branch下而不是直接在master分之下开发。这种模式将使得几个开发人员同时开发同一个feature的开发活动相对于主分支完全隔离开来。这同时也意味着master分支永远不会存在未经测试的代码,这对于持续集成的开发模式带来巨大的好处。
封装隔离一个feature的开发过程也使得充分利用pull request变得方便,而pull request本身就是一个对分支上的代码改动启动讨论的最佳实践。这也给其他开发人员在feature代码落地到主分支之前review确认代码的机会。或者,在feature开发的中间阶段,也可以发起pull request来获取其他同事的建议。
3. Gitflow workflow(推荐)
Feature Branch Workflow是一种非常灵活地开发项目的方式。问题是,有时这种方式显得太灵活。对于大型团队来说,通常如果给一些特定分支更加明确的角色要好很多。那么Gitflow workflow就是一个通用的用于管理feature development、release preparation以及维护的工作模式。Gitflow workflow通过为功能开发、发布准备和维护设立了独立的分支,让发布迭代过程更流畅。严格的分支模型也为大型项目提供了一些非常必要的结构。
GitFlow基于特性分支进行开发,发布分支进行测试环境版本打包构建,主分支(master)进行生产版本打包构建。以下是GitFlow在境内账单综合组的应用模式。
- 特性分支:以feture开头,后跟CQ单号的形式,如:feture-BoComxxxx。配置管理员需基于当前master分支在远端创建好特性分支,开发人员拉取特性分支后,基于该分支进行开发工作,开发完成且单元测试通过后提交代码。后续缺陷代码继续使用该分支提交。
- 发布分支:以release开头,后跟需求计划上线时间的方式命名,如release-20201113。配置管理员在release分支对应的CQ单进入SIT/UAT时,将该cq单对应的特性分支代码合并至release分支,基于合并后分支代码进行打包构建并发布测试。CQ单进入测试阶段后,特性分支有代码更新,release分支需同步进行合并。
- 热修复分支:生产缺陷紧急修复分支。以hotfix开头,后跟所基于的tag内容,如hotfix-v20201103
- 主分支(master):生产投产发布分支。在封板日将发布分支代码合并至master分支,基于合并后代码进行生产版本打包构建。
4. Forking Workflow
Forking Workflow是在GitFlow基础上,充分利用了Git的“Fork”和“pull request”的功能以达到代码审核的目的。更适合安全可靠地管理大团队的开发者,而且能接受不信任贡献者的提交。Forking Workflow这种工作流主要好处就是每个开发者都拥有自己的远程仓库,可以将提交的commits推送到自己的远程仓库,但只有工程维护者才有权限push提交的commits到官方的仓库,其他开发者在没有授权的情况下不能push。Github很多开源项目都是采用Forking Workflow工作流。
4.2. 代码质量管理
Sonar(sonarQube)是一个开源平台,用于管理源代码的质量。Sonar 不只是一个质量数据报告工具,更是代码质量管理平台。支持的语言包括:Java、PHP、C#、C、Cobol、PL/SQL、Flex 等。
sonarQube是一种自动代码审查工具,用于检测代码中的错误,漏洞和代码味道。它可以与您现有的工作流程集成,以实现跨项目分支和提取请求的连续代码检查。其目的是对代码库的质量进行360°透视。 为此,它会定期分析项目的所有源代码行。
另外,SonarLint是一个Sonarl IDE插件,可以接收和连接sonrarQube对代码库扫描的结果从而通知Developer, SonarLint本身也可以基于一些规则对代码IDE中的代码进行即时的检测。
Sonar可以从以下7个维度检测代码质量,而作为开发人员至少需要处理前5种代码质量问题。
序号 | 维度 | 描述 |
---|---|---|
1 | 不遵循代码标准 | Sonar可以通过PMD、CheckStyle、Findbugs等等代码规则检测工具规范代码编写。 |
2 | 潜在的缺陷 | Sonar可以通过PMD、CheckStyle、Findbugs等等代码规则检测工具检 测出潜在的缺陷。 |
3 | 糟糕的复杂度分布 | 文件、类、方法等,如果复杂度过高将难以改变,这会使得开发人员 难以理解它们, 且如果没有自动化的单元测试,对于程序中的任何组件的改变都将可能导致需要全面的回归测试。 |
4 | 重复 | 显然程序中包含大量复制粘贴的代码是质量低下的,Sonar可以展示 源码中重复严重的地方。 |
5 | 注释不足或者过多 | 没有注释将使代码可读性变差,特别是当不可避免地出现人员变动 时,程序的可读性将大幅下降 而过多的注释又会使得开发人员将精力过多地花费在阅读注释上,亦违背初衷。 |
6 | 缺乏单元测试 | Sonar可以很方便地统计并展示单元测试覆盖率。 |
7 | 糟糕的设计 | 通过Sonar可以找出循环,展示包与包、类与类之间的相互依赖关系,可以检测自定义的架构规则 通过sonar可以管理第三方的jar包,可以利用LCOM4检测单个任务规则的应用情况, 检测耦合。 |
4.3. 代码评审
在之前,开发人员认为,通过系统化,可以很容易地实验 ”编码规则“,并通过静态或动态的代码分析来实现自动化。比如Sonar工具等。然而,现代的代码评审除了传统的代码检查之外,还鼓励人员之间的积极交互,并促进协作,而这是不能被自动化的。代码评审周期最积极的结果是:除了更高的代码质量标准之外,还包括让不同的人查看解决方案的各个版本,并围绕解决方案进行建设性的讨论。
优点 | 描述 |
---|---|
稳定构建 | 代码评审有助于减少零碎的构建,这得益于将评审状态链接到验证步骤的能力,从而减少开发团队所浪费的时间。这样做的好处有两方面:首先,正常的分支稳定性没有受到影响;其次,代码评审分支允许对代码进行预验证和清理,从而在不丢失对评审和持续集成反馈的跟踪的情况下,允许一致的历史记录。 |
知识共享 | 每当有不止一个人在查看代码时,解决方案的知识就开始在团队中传播。之前,这是通过应用结对编程实现的,结对编程是极限编程的规则之一。“pair”之间的交互将导致从不同的角度对解决方案进行更早的讨论,从而对代码库有更一致的理解。然而,这种方法受到时间和空间的限制:两人必须同时坐在同一张桌子前,这种情况在分布式团队中通常很难实现,这是开源项目的常见设置。代码评审是“团队编程”的一种实践,其中代码可能与开发团队的任何成员共享,每个人都可以在代码首次编辑时间线之外的不同时间查看代码。 |
外部的早期反馈 | 让任何人检查您的代码的可能性允许来自不同心态和技能水平的人的外部贡献来共享他们对解决方案的看法。所有的反馈都是有价值的,例如,一个初级开发人员的评论可能是“我不理解这个片段”——这是宝贵的输入,可以促使您打破复杂性,并引入更简单的语句。稳定的代码必须是易于维护的:一段对大多数开发人员来说是晦涩难懂的代码将有更多的机会以错误的方式进行修改,从而在未来成为项目的关键和不稳定的一部分! |
共享代码风格 | 并不是所有的程序员都有相同的代码风格,学习阅读别人的代码也意味着习惯一种不同的编写代码的方式,从而有可能维护它。当开发人员花费大量时间阅读其他人的代码时,他们不仅会了解项目的其他部分,而且还会自觉地或不自觉地遵循其他人的编码风格。 |
团队互动 | 代码评审能够在开发人员中触发一组完整的新行为和动态。一些成员通过评论和提出新的创造性的解决方案,提出更简单的框架或算法来实现相同的目标,从而自然而然地出现并起带头作用。评审活动通过评论和提交批准来显示团队成员的价值和欣赏,从而为更积极主动的成员提供了自然选择。这样做的一个好处是,级别较低的开发人员可能会强迫自己改进,并试图跟上团队中最强大的成员。 |
没有人能保证他产出的代码一定是完美的。每个专业的软件开发者都知道,代码审查是任何正式开发过程中的必要环节。但大多数开发者不知道的是,代码审查分为很多种类型。根据项目和团队架构的不同,每一种代码审查类型都有它特有的优缺点。代码审查分为不同类型,主流的方式如下:
类型 | 描述 |
---|---|
瞬时的代码审查 | 也称为结对编程(pair programming),当一个开发者在敲键盘写代码的同时,另一个开发者盯着代码,注意着代码中潜在的问题,并在此过程中给出提升代码质量的建议。当需要解决一个复杂问题时,这种代码审查的方法很适用。在仔细寻找解决方案的过程中,把两个人的脑力聚集起来,会增加成功的几率。让两个头脑思考同一个问题,并相互讨论可行的方案,这样你会更可能覆盖到问题的一些边界情况。适用于两个有相似经验水平的开发者处理复杂的业务问题的情况。 |
同步的代码审查 | 也称为即时代码审查(over-the-shoulder),一个开发者独自编写代码,写完代码后,立即找代码审查者进行审查。审查者来到开发者的桌前,看着同一块屏幕,一起审查、讨论和改进代码。如果一个经验丰富的高级开发者将要对一个很初级的程序员写出的一段代码进行审查,那么,当初级程序员写完代码后就和高级开发者一起改进这段代码,效率是远远高于初级程序员自己一个人看的。 |
异步的代码审查 | 也称为有工具支持的代码审查(tool-assisted),这一类型的审查不是在同一时间、同一块屏幕上完成的,而是异步的。开发者在写完代码后,让这些代码对审查者可见,然后开始她的下一个任务。当审查者有时间了,可以按自己的时间表进行代码审查。不需要当面和开发者沟通,而是用工具写一些评论。在完成审查后,那些工具会把评论和需要的改动通知给开发者。开发者根据评论改进代码,同样的,是以自己的时间表来做这些事情。 |
偶尔的代码审查 | 也称为基于会议的的代码审查(meeting-based),根据实际需要临时安排会议审查代码,当整个团队都没有代码审查的经验时,让把每个人聚起来,一起做代码审查,这样弄几次之后,可能会帮助每个人理解代码审查的目标和意义。 |
专业的团队应该把异步的代码审查作为默认的选择,因为它避免了同步代码审查的缺陷。当审查者不能理解开发者做出一项代码修改的原因时,可以使用同步的代码审查。但在那种情况下,审查者将会去询问开发者,以获得额外的信息和说明。如果开发人员都在一个团队中工作,这样的情况应该很少发生。
如果开发人员不在同一个团队中,而是和另一群人一起工作,那么同步的代码审查就有意义了。如果审查者对开发人员过去几天的工作内容毫不知情,那么在开始一起做代码审查之前,向审查者给出一个合适的说明是很合理的。如果两个开发者,他们具备相似的技能组合,并且在攻克一个复杂的业务问题,那么也有理由切换到结对编程的模式。但是,一个团队往往由许多经验水平不同的成员组成,并且不会一直都在处理复杂的业务问题。大多数时间,手上是复杂度在平均水平的常规任务。因此,专业团队的最佳选择是:使用异步的代码审查作为默认选择,然后当需要时切换到同步的代码审查或者结对编程。
4.4. CI/CD
1. 持续集成(CI)
持续集成(Continuous Integration)强调开发人员提交了新代码之后,立刻进行构建、(单元)测试。根据测试结果,我们可以确定新代码和原有代码能否正确地集成在一起。
持续集成是一种软件开发实践,即团队开发成员经常集成他们的工作,通常每个成员每天至少集成一次,也就意味着每天可能会发生多次集成。每次集成都通过自动化的构建(包括编译、代码质量检查、发布、自动化测试)来验证,从而尽快地发现集成错误。许多团队发现这个过程可以大大减少集成的问题,让团队能够更快的开发内聚的软件。
2. 持续交付(CD)
持续交付(Continuous Delivery)在持续集成的基础上,将集成后的代码部署到更贴近真实运行环境的“类生产环境”(生产验证环境)中。比如,我们完成单元测试后,可以把代码部署到连接数据库的“Staging环境”中更多的测试。如果代码没有问题,可以继续手动部署到生产环境中。
持续交付指的是频繁地将软件的新版本,交付给质量团队或者用户,以供评审。如果评审通过,代码就进入生产阶段。 持续交付可以看作持续集成的下一步。它强调的是,不管怎么更新,软件是随时随地可以交付的。注意,持续交付在自动化测试和集成结束后,不一定会自动部署。如果有自动部署,则是持续部署的概念了。
3. 持续部署(CD)
持续部署(Continuous Deployment)则是在持续交付的基础上,代码通过评审,把部署到生产环境的过程自动化。其目的是可以随时部署,迅速投入生产阶段。持续部署这一步,意味着产品和观众见面,但是要通过重重考验,测试、构建、部署等步骤,而且每一步都是自动的。
5. 管理要求
5.1. 制定开发规范
没有规则,就没有执行。规则中首当其冲的就是开发规范。 规范中建议包含:
- 工程规范(工程结构,分层方式及命名等等)。
- 命名规范(接口、类、方法名、变量名等)。
- 代码格式(括号、空格、换行、缩进等)。
- 注释规范(规定必要的注释)。
- 日志规范(合理的记录必要的日志)。
-
5.2. 制定评审规范
确定Code Review实施环节
代码审查建议是放在代码提交测试前,也就是开发人员完成代码开发及自测后将代码提交到测试分支时进行代码审查。毕竟,如果测试通过后再进行代码审查,如果需要代码变更,势必会增加测试的工作量,甚至影响项目进度。以通用的GitFlow workflow来说,那就是把代码审查放在Feature分支合并到Develop分支时了。
- 制定角色行为规范
| 角色 | 规则 |
| —- | —- |
| Developer |
1. 一次提交的功能必须是完整的。
1. 默认细粒度提交(以独立的方法/功能/模块为单位)。如需粗粒度提交,需提前跟审查员沟通确认。
1. 提交信息中要清晰描述变更的主题 必要时,可以以链接或者文件的形式附上需求文档/设计文档。
| | Reviewer |
1. 不允许自我审查并合并代码。
1. 审查不通过打回前需跟开发人员说明原因并达成一致。
1. 审查不通过需明确填写打回的原因。
1. 单次审查时长需控制在2分钟~2小时内完成(特殊情况请说明原因)。
| | Approver |
1. 审批不通过需注明原因。
1. 审批时长需要控制在1小时以内。
1. 对于放行的非质量问题,需持续跟进。
|
其目的是:
- 控制提交代码审查的代码的粒度。
- 控制单次**代码审查**的时间。
- 提升提交/合并请求描述的质量,减少沟通成本。
这样,我们就可以通过细粒度高频次的方式尽可能利用工程师碎片化的时间进行代码审查,一定程度上保证代码审查的效率。
- 代码审查清单(**Checklist**)
在代码审查中,检查清单是一个非常好的工具:它们保证了审查可以在团队中始终如一的进行。它们也是一种保证常见问题能够被发现并被解决的便利方式。软件工程学院的研究表明,程序员们会犯15-20种常见的错误。所以,通过把这些错误加入到检查清单当中,可以确保不论什么时候,只要这些错误发生了,就能发现它们,并且可以帮助你杜绝这些错误。
序号 | 类型 | 审查描述 |
---|---|---|
1 | 常规项 | 代码能够工作么?它有没有实现预期的功能,逻辑是否正确等。 |
2 | 常规项 | 所有的代码是否简单易懂? |
3 | 常规项 | 代码符合你所遵循的编程规范么?这通常包括大括号的位置,变量名和函数名,行的长度,缩进,格式和注释。 |
4 | 常规项 | 是否存在多余的或是重复的代码? |
5 | 常规项 | 代码是否尽可能的模块化了? |
6 | 常规项 | 是否有可以被替换的全局变量? |
7 | 常规项 | 是否有被注释掉的代码? |
8 | 常规项 | 循环是否设置了长度和正确的终止条件? |
9 | 常规项 | 是否有可以被库函数替代的代码? |
10 | 常规项 | 是否有可以删除的日志或调试代码? |
1 | 安全 | 所有的数据输入是否都进行了检查(检测正确的类型,长度,格式和范围)并且进行了编码? |
2 | 安全 | 在哪里使用了第三方工具,返回的错误是否被捕获? |
3 | 安全 | 输出的值是否进行了检查并且编码? |
4 | 安全 | 无效的参数值是否能够处理? |
1 | 文档 | 是否有注释,并且描述了代码的意图? |
2 | 文档 | 所有的函数都有注释吗? |
3 | 文档 | 对非常规行为和边界情况处理是否有描述? |
4 | 文档 | 第三方库的使用和函数是否有文档? |
5 | 文档 | 数据结构和计量单位是否进行了解释? |
6 | 文档 | 是否有未完成的代码?如果是的话,是不是应该移除,或者用合适的标记进行标记比如‘TODO’? |
1 | 测试 | 代码是否可以测试?比如,不要添加太多的或是隐藏的依赖关系,不能够初始化对象,测试框架可以使用方法等。 |
2 | 测试 | 是否存在测试,它们是否可以被理解?比如,至少达到你满意的代码覆盖(code coverage)。 |
3 | 测试 | 单元测试是否真正的测试了代码是否可以完成预期的功能? |
4 | 测试 | 是否检查了数组的“越界“错误? |
5 | 测试 | 是否有可以被已经存在的API所替代的测试代码?你同样需要把特定语言中有可能引起错误的问题添加到清单中。 |
5.3. 分享与统计
- 定期分享
代码审查的目的是为了互相学习,那么在这个过程中学到的知识,定期的分享出来,既可以加强知识的流动,又可以检查大家究竟有没有在代码审查过程中学习到知识,或者有没有认真的进行代码审查。
至于分享的内容,可以是开发规范中的范例代码,也可以是规范中的正例代码,也可以是针对某个功能实现的最佳算法/最佳实践,也可以是代码审查过程中的争议代码,也可以是自己踩过的坑。
总之,代码审查之后的代码分享,不但可以加强知识的流动,还可以检验代码审查的效果。
- 数据统计
- 每人每周代码审查所消耗的时间。
- 每人每周被代码审查所消耗的平均时间。
- 超过规定时间的代码审查情况。
- 代码提交描述字数过少的情况。
- 其他指标统计(根据自己的需要来)。
通过以上的数据,可以让代码审查的情况直观的展示出来。来发现大家执行过程中需要优化的事项,不断帮助大家完善规则,做好执行。
5.4. 代码安全审计
- 确定审计策略:审计策略主要考虑的是代码开发语言、架构、安全审计质量准则或出口准则、检测效率等;安全审计质量准则/出口准则需要考虑检测工具是否能够满足对检测质量要求,也就是对于检测项的覆盖,同时工具应该具有弹性扩展。
- 部署环境:主要考虑代码审计工具能够适合公司所有的物理环境,包括网络、服务器、工具兼容性等。
- 工具扫描:主要考虑代码工具的技术指标,例如工具支持的开发语言、检测精度、效率,是否支持迭检测、CI模式下检测等。
- 人工复核:主要考虑人工复核的工作量,检测结果是否容易复核。这就要求检测工具精度高、结果容易检查、误报少。
- 审计结果和报告:主要考虑的是根据需要是否能够自动产生审计结果、审计报告,报告能够根据需要求孽缘定制;检测结果和报告,中文显示,便于阅读。检测过程能够跟踪,回溯。
- 出口准则:能够根据代码审计需要制定审计范围、选择审计安全漏洞的严重程度级别。检测完成,能够判断是否满足选择的出口准则,便于快速决策。
开发修复:主要考虑检测结果是否便于开发人员修复、安全漏洞能够快速定位、提示信息准确、甚至能够自动进行修复或给出修复检查代码。检测精度高,误报少,漏报少,这就要求检测工具比较成熟,且得到国际公认组织的认证、认可,符合国际、国内主流标准。同时,对于开发团队确认和修复代码可以提供培训和支持。
6. 环境搭建
6.1. 安装准备
OS:RHEL/CentOS 8.x
- JDK:jdk-8
- Maven:maven-3.x
6.2. 介质清单
| 介质 | 版本 | 备注 | | —- | —- | —- | | Git | git-2.19.2.tar.gz
Git-2.19.2-64-bit.exe | https://mirrors.edge.kernel.org/pub/software/scm/git/
http://git-scm.com/download/win
http://npm.taobao.org/mirrors/git-for-windows/v2.19.2.windows.1/Git-2.19.2-64-bit.exe | | GitLab | gitlab-ce-12.8.6-ce.0.el8.x86_64.rpm | https://mirrors.tuna.tsinghua.edu.cn/gitlab-ce/yum/el8/ | | Gerrit | gerrit-2.12.war | https://gerrit-releases.storage.googleapis.com/index.html | | Jenkins | jenkins-2.222.1.war | jenkins-2.222-1.1.noarch.rpm | http://updates.jenkins-ci.org/download/war/
http://mirrors.jenkins-ci.org/redhat/
http://ftp-chi.osuosl.org/pub/jenkins/war-stable
http://mirrors.jenkins.io/war-stable | | MySQL | mysql-5.7.30-linux-glibc2.12-x86_64.tar.gz | https://cdn.mysql.com/Downloads/MySQL-5.7/mysql-5.7.30-linux-glibc2.12-x86_64.tar.gz
https://dev.mysql.com/get/Downloads/MySQL-5.7/mysql-5.7.30-linux-glibc2.12-x86_64.tar.gz
http://www.mysql.com/Downloads/MySQL-5.7/mysql-5.7.30-linux-glibc2.12-x86_64.tar.gz | | sonarQube | sonarqube-7.8.zip | https://binaries.sonarsource.com/Distribution/sonarqube/
注意:兼容性选择时,如果SonarQube >= 4.5,那么maven-sonar-plugin >= 2.7;如果SonarQube < 4.5,那么maven-sonar-plugin = 2.6;如果Maven >= 3.0,那么maven-sonar-plugin >= 3.1,如果Maven < 3.0,那么maven-sonar-plugin = 3.0.2。MySQL >=5.6 && <8.0,且sonarqube-8.x+已经不支持MySQL,仅支持:H2、PostgreSQL、Microsoft SQL Server、Oracle。 | | SonarScanner | sonar-scanner-cli-4.2.0.1873-linux.zip、sonar-scanner-cli-4.2.0.1873-windows.zip | https://binaries.sonarsource.com/Distribution/sonar-scanner-cli/ |
6.3. 介质安装
- Gerrit
详见:《RedHat8_Gerrit-2.12-安装手册.docx》。
- GitLab
详见:《RedHat8_GitLab-ce-12.8.6-安装手册.docx》。
- Jenkins
详见:《RedHat8_Jenkins-2.222.1-安装手册.docx》。
- sonarQube
详见:《RedHat8_sonarQube-7.8-安装手册.docx》。
7. 运维管理
7.1. 升级方案
根据具体升级需求制定,如:GitLab版本升级、Gerrit版本升级、Jenkins版本升级、sonarQube版本升级等。
7.2. 备份方案
7.3. 灾备方案
7.4. 扩容方案
8. 参考
- 《Gerrit用户手册(英文版).pdf》
- 《Gerrit工作流程及使用手册.pdf》
- 《代码管理用户手册-v1.0.docx》