在我看来,代码质量就是程序员的职业底线。维护底线不能全靠自觉,因此本文将介绍几种自动化的工具,并展示如何将它们集成到日常的工作流中,省心省力的持续保障代码质量。
Linter 是一类用于标记程序错误、bug、风格错误和可疑结构的静态代码分析工具,比如 Python 中的 Pylint、 flake8,JavaScript 中的 EsLint,这类工具会找到你代码中的错误,并给出如何修复的提示。
人总是会粗心和犯错,借助 Linter 我们可以发现代码中不易察觉或遗漏的错误,并进行纠正。使用 Linter 有以下好处:
flake8 使用示例接下来我们以 Python 中的 flake8 为例,介绍其用法。
首先通过 pip 安装 flake8:
|
|
flake8 的使用非常简单,直接以需要检查的文件或整个目录的路径作为 flake8 命令的参数:
|
|
在运行结果中, flake8 不仅指出了具体的错误原因,还给出了出错的文件位置及代码所在行数。
更多其他的选项可通过以下命令获取帮助:
|
|
flake8 的可自定义程度很高,除了在运行时添加命令选项,我们还可以通过配置文件来自定义检查规则。方法是在运行 flake8 的项目根目录添加 .flake8 文件,并写入如下内容:
|
|
flake8 还可以添加插件实现更多更强大的功能,比如我们想强制要求编写函数 docstring,可以安装 flake8-docstrings :
|
|
重新运行检查后,会发现结果中多出了如下错误:
|
|
更多其他功能和插件可查看 flake8 的文档。
除了 flake8 以外,Python 还有以下 Linter 可供选择,它们的用法大同小异,但在检查范围、容忍度等方面有所区别:
Python 从 PEP 484 引入 Type Hints (类型提示)以来,配套的工具链生态也在逐渐成熟,出现了如 mypy、 pyright 等静态类型检查工具。
它们可以根据 Python 代码中的类型提示进行静态类型检查,不需要运行程序就可以找到程序中的错误。而且如果有遗留代码不好处理,还可以在程序中混合使用动态和静态类型。
我们以 mypy 做简单示例,假设我们有以下代码:
|
|
通过 pip 安装 mypy :
|
|
对该文件运行检查:
|
|
在检查结果中, mypy 提示我们在调用 greeting() 时传入的参数类型出错。
静态类型检查工具可以帮助我们像静态语言一样在运行代码之前就捕获到某些错误,但需要我们在程序中加入大量的类型提示才能完整发挥其作用。虽然稍微加大了工作量,但引入类型提示还会带来以下好处:
Autoformatter (自动格式化器)顾名思义是可以将代码按特定规则自动格式化的一类工具。Linter 可以帮助我们发现代码中的格式错误和风格问题,但并不会自动纠正,因此我们还需要借助自动格式化器,进一步将我们从重复琐碎的手动修改中解放出来。
Python 中有 black 、 yapf、 autopep8、 isort 等众多格式化工具,除了整体上遵循 PEP8 以外,各自都有不同的风格规范和适用范围,我们可以根据自己的喜好进行选择。
以 black 和下面这段代码为例:
|
|
首先通过 pip 安装 black:
|
|
然后对需要格式化的文件或整个目录的路径执行 black 命令:
|
|
black 会首先检查出不符合其规范的文件,然后编辑文件完成修改,并输出修改的文件路径及数量。格式化后的文件内容如下:
|
|
black 号称「不妥协的代码格式化器」,其不妥协体现在基本没有可供自定义的选项,要么全盘接受它的代码风格,要么就不用它。如果你或者你的团队能够接受它的风格,使用 black 可以节省很多花在代码风格上的精力和时间(再也不用争论某种风格孰优孰劣了)。
实践中我们可以对最大行长度以及引号格式化选项做一些调整,方法是在执行 black 的项目根目录添加 pyproject.tomal 文件,并写入以下内容:
|
|
现代的 IDE 和编辑器基本都会自带 Linter 和格式化等功能,比如 PyCharm 就可以通过 ⌥ ⌘ L 快捷键格式化当前文件的代码,这样的话我们还有必要手动集成这些第三方工具吗?答案当然是有必要,我们以第三方和 IDE 自带的代码工具作对比:
pyproject.toml、tox.ini等),且通常都可以很快地集成到不同 IDE 中。PyCharm 但你的同事用 VSCode 呢?在接下来的 Hooks 和 Pipeline 两节内容中,你将进一步认识到集成第三方工具带来的优势。
在上文中我们介绍了很多保障质量的工具,但在日常编码中,我们总不能每一次提交代码前,都把这些工具手动执行一遍吧?这看上去不是高效的做法,庆幸地是我们有现成途径解决这些问题。
Git 可以在仓库中发生特定事件时自动运行自定义的脚本,这些自定义脚本即称为 hooks。 它们让你可以在开发周期的关键点触发可定制的动作,比如每次执行提交前都执行一次 flake8 命令。大部分其他的版本控制系统也会提供类似的功能。
hooks 保存在 Git 仓库的 .git/hooks 目录中,它可以是普通的 shell 脚本,也可以是 Python、Ruby 等语言的可执行脚本,因此我们可以很方便地将常用工具集成到 hooks 中。 hooks 通常按客户端和服务端分为两大类,常用的客户端类有 pre-commit 、 prepare-commit-msg、 commit-msg、 post-commit、 pre-rebase 等 hooks,它们分别在名称所代表的事件触发时运行。
以 pre-commit 为例,该 hooks 在输入提交信息前运行。 它可以检查即将提交的快照,如果该 hooks 以非零值退出,Git 将放弃此次提交,我们可以利用它来检查提交的代码风格是否正确(运行例如 flake8、 black 等程序)、是否引入安全风险等等。整个流程示意如下:

接下来我们将展示,如何将上文介绍的众多工具快速地集成到 pre-commit hooks 中。
pre-commit 是一个用于管理和维护多种语言 pre-commit hooks 的框架,就像 Python 的包管理器 pip 一样,我们可以通过 pre-commit 将他人创建并分享的 pre-commit hooks 安装到自己的项目仓库中。 pre-commit 大大减少了我们使用 git hooks 的难度,你只需要在配置文件中指定想要的 hooks,它会替你安装任意语言编写的 hooks 并解决环境依赖问题,然后在每次提交前执行。
pre-commit 的简单使用方法如下:
通过 pip 安装 pre-commit:
|
|
pre-commit 是面向多语言的,因此它还支持通过 homebrew 等方式安装。
添加配置文件。
你需要创建一个名为 .pre-commit-config.yml 的文件,通常放在项目根目录下,在配置文件中我们按特定格式添加需要运行的 hooks 并指定参数,示例如下:
|
|
在该示例文件中,我们添加了 black 、 flake8 以及一个检查是否添加了大体积文件到 Git 的 hooks,还分别指定了语言版本、跳过检查路径等选项。
配置文件的选项含义参见 https://pre-commit.com/#plugins,所有支持安装的 hooks 列表参见https://pre-commit.com/hooks.html。
安装 git hook 脚本。在配置文件所在的根目录运行命令:
|
|
这会在 .git/hooks 文件夹中创建一个 pre-commit 脚本文件,并在接下来你每一次提交之前运行!现在我们可以尝试提交一次代码了。
(可选步骤)对所有文件运行 hooks。
通常 pre-commit 只会在触发 git hooks 时对发生更改的文件运行,但我们也可以手动对当前仓库的所有文件运行:
|
|
结果显示我们有文件未通过 black 检查,但 black 同时也帮我们自动进行了格式化,因此只需要暂存并重新提交这些文件即可。
pre-commit hooks 的限制使用 pre-commit ,我们可以很方便的将丰富多样的代码工具集成到 Git 的工作流中,这很大程度上提高了我们的效率,但 pre-commit hooks 本身存在以下限制:
hooks 并不会随代码库一起被复制,我们必须在本地仓库通过 pre-commit install 执行安装之后 hooks 才会生效。git commit --no-verify 即可绕过所有的 pre-commit hooks。pre-commit 解决了在本地集成代码工具的问题,但在团队合作的场景下,我们希望有一种机制可以在服务器端对所有成员推送的代码进行检查,施加特定的约束,这时候就需要用到 Pipelines 了。
首先我们需要了解 CI/CD 这一概念。
CI(Continuous Integration,持续集成)是一种软件实践,它要求我们频繁地向共享仓库提交代码。更频繁地提交代码可以更快地发现错误,减少我们在发现错误时需要调试的代码量,也使得团队不同成员间的变更更容易合并。CI 可以节省我们调试错误或解决合并冲突的时间,让我们有更多的时间来编写代码。
当提交代码到共享仓库时,我们需要对每一次提交的代码进行构建和测试,以确保提交不会引入错误。这些测试既包括上文提到的 Linter,也包括安全性检查、测试覆盖率、功能测试和其他自定义检查。我们将需要一个 CI 服务器作为 Runner 以运行对提交代码的构建和检查。
CD(Continuous Deployment,持续部署)是 CI 的下一步。我们不仅在每次推送代码到共享代码库时进行构建和测试,还会在不需要任何人工干预的情况下,自动部署代码到生产环境。此外还有Continuous Delivery (持续交付),它和持续部署的区别在于需要人工干预才会执行部署。
Pipeline 是持续集成、交付和部署的顶层具象化组件,它由顺序执行的阶段(stage)以及每个阶段中并行的任务(job)组成。一个简单的 pipeline 如下图所示:

首先并行地执行 build 阶段的所有任务(build_a 和 build_b),所有任务成功后继续执行下一阶段的任务,以此类推。pipeline 还能定义成按更复杂的逻辑规则运行。
下面我们以 GitLab CI 为例,展示如何配置一个 pipeline,并在测试阶段执行上文介绍的检查工具,解决不能对团队成员提交代码进行强制约束的问题。
首先我们需要配置一个运行任务的服务器作为 Runner,这需要我们有可用的服务器主机(也可以使用本地的开发机器或购买 GitLab 提供的 Runner)。配置 Runner 整体分两步:
register 命令使用 URL 和令牌完成注册,并选择合适的 Runner 类型,详细步骤参见文档。配置完成后,我们将可以在 Runners settings 页面查看可用的 Runner 和状态:

.gitlab.yml我们将在项目根目录创建一个配置 pipeline 的 .gitlab.yml 文件,GitLab 将在每一次我们推送代码到共享仓库时,读取该文件中的定义和指令,创建一条不同阶段及任务组成的 pipeline,并将阶段中的任务派发给可用的 Runner 执行。我们以如下 .gitlab.yml 文件为例:
|
|
在这个文件中,我们定义了:
上面的示例定义了如下 pipeline:

before_script关键字定义了在每个任务前执行的一组命令,我们通过这里设置的命令安装运行 pipeline 所需的依赖。stage 关键字定义了 Pre-commit Hooks 和 Static Analysis 两个阶段。pre-commit 、 git-lint、 mypy 和 flake8 等多个任务及所属阶段。script 关键字定义该任务所运行的脚本命令。pre-commit 任务中,我们成功地将上一节介绍的 pre-commit 放在服务端运行,这能确保对任何人提交的代码都会运行原先只在本地生效的 pre-commit hooks。我们还通过预定义变量和 cache 关键字缓存运行任务所需的文件以加快执行速度。mypy 任务中,我们通过 allow_failure 关键字更改了任务成功才会进入下一阶段这一默认行为,并通过 only 关键字限制其只对 master 分支的提交执行。关于 .gitlab.yml 的更多说明请参阅 GitLab CI/CD pipeline 配置参考手册 以及 GitLab CI/CD 示例。
现在当我们推送新的代码到共享仓库时,一个新的 pipeline 会被触发运行。我们可以通过以下途径在 GitLab 仓库查看 pipeline 的运行状态:
进入 CI/CD > Pipelines。将展示一个包含两个阶段的 pipeline(当前状态为已通过)。

点击 pipeline 的 ID 即可查看该 pipeline 的运行示意图:

点击一个任务名称,可查看该任务的详细运行信息:

以上即为一个完整的 GitLab CI pipeline 示例。在该 pipeline 中,我们会在每一次提交代码后:
hooks 的 pre-commit,确保代码已在本地通过了 pre-commit 检查。mypy 、git-lint 等检查工具。接下来我们将通过 GitHub Actions 为例,展示如何运行一个简单的 CD pipeline。
GitHub Actions 是 GitHub 在 2018年10月推出的持续集成服务,它和上文介绍的 GitLab CI 原理和使用方法基本相同,但存在以下区别:
.gitlab.yml 的 yaml 文件,但需要添加到项目根目录的 .github/workflows 路径中。接下来我将演示如何使用 GitHub Actions 自动发布一个 Hugo 静态站点到 GitHub Pages。
大致的工作流程如下:
GitHub 上有许多这类自动化部署任务的开源 Actions 项目,我们选择了其中一个简单易用的 GitHub Actions for Hugo。具体的操作步骤截图和详细配置项可以查看该项目的 README。下面简单介绍下配置过程:
在本地内容仓库中添加目录和文件:.github/workflows/gh-pages.yml,gh-pages.yml 文件内容如下:
|
|
在该配置文件中,我们定义了如下的 workflow:
github pages。main 分支时执行。deploy 的任务,并需要在ubuntu-18.04平台的 Runner 执行。actions/checkout@v2 的 action,以拉取提交的内容到 Runner。peaceiris/actions-hugo@v2 action 配置 Hugo,并指定版本。hugo --minify 命令,生成静态站点(默认输出文件到 public 目录)peaceiris/actions-gh-pages@v3 action 将生成的静态文件推送到指定的外部仓库。这个 workflow 基于内容仓库运行,但我们需要将运行过程生成的静态文件推送到发布仓库进行发布,因此还需要在两个仓库中分别设置密钥。
在本地生成 SSH 部署密钥:
|
|
在 GitHub 分别进入内容仓库和发布仓库的 Settings 页面:
gh-pages.pub 作为 Secret 添加到内容仓库,并设置 Name 为 ACTIONS_DEPLOY_KEY。gh-pages 作为 Deploy Key 添加到发布仓库,并设置为 Allow write access。接下来我们测试一下效果。
在本地内容仓库做一些更改,预览效果后提交并推送,然后在共享仓库的 GitHub Actions 页面检查相应 workflow 的运行状态与详细结果:

运行成功后,很快发布仓库将新增一个由该 workflow 创建的提交,相应的 GitHub Pages 也会更新相应的内容。
本文中,我们围绕保障代码质量这一目的:
pre-commit 集成到 git hooks 中,在代码开发工作流中自动执行。经过简单几步的一次性配置,你就可以拥有一套高度自动化的代码质量工具工作流程。但工具只是保障代码质量众多环节中最容易发力,见效快,但作用也很有限的一环。真正实现保证代码质量这一目标任重而道远,还需要我们在个人和团队这两个方面认识到代码质量的重要性,并不断践行各种代码质量设计与活动。
更新:
在我看来,代码质量就是程序员的职业底线。维护底线不能全靠自觉,因此本文将介绍几种自动化的工具,并展示如何将它们集成到日常的工作流中,省心省力的持续保障代码质量。
Linter 是一类用于标记程序错误、bug、风格错误和可疑结构的静态代码分析工具,比如 Python 中的 Pylint、 flake8,JavaScript 中的 EsLint,这类工具会找到你代码中的错误,并给出如何修复的提示。
人总是会粗心和犯错,借助 Linter 我们可以发现代码中不易察觉或遗漏的错误,并进行纠正。使用 Linter 有以下好处:
flake8 使用示例接下来我们以 Python 中的 flake8 为例,介绍其用法。
首先通过 pip 安装 flake8:
|
|
flake8 的使用非常简单,直接以需要检查的文件或整个目录的路径作为 flake8 命令的参数:
|
|
在运行结果中, flake8 不仅指出了具体的错误原因,还给出了出错的文件位置及代码所在行数。
更多其他的选项可通过以下命令获取帮助:
|
|
flake8 的可自定义程度很高,除了在运行时添加命令选项,我们还可以通过配置文件来自定义检查规则。方法是在运行 flake8 的项目根目录添加 .flake8 文件,并写入如下内容:
|
|
flake8 还可以添加插件实现更多更强大的功能,比如我们想强制要求编写函数 docstring,可以安装 flake8-docstrings :
|
|
重新运行检查后,会发现结果中多出了如下错误:
|
|
更多其他功能和插件可查看 flake8 的文档。
除了 flake8 以外,Python 还有以下 Linter 可供选择,它们的用法大同小异,但在检查范围、容忍度等方面有所区别:
Python 从 PEP 484 引入 Type Hints (类型提示)以来,配套的工具链生态也在逐渐成熟,出现了如 mypy、 pyright 等静态类型检查工具。
它们可以根据 Python 代码中的类型提示进行静态类型检查,不需要运行程序就可以找到程序中的错误。而且如果有遗留代码不好处理,还可以在程序中混合使用动态和静态类型。
我们以 mypy 做简单示例,假设我们有以下代码:
|
|
通过 pip 安装 mypy :
|
|
对该文件运行检查:
|
|
在检查结果中, mypy 提示我们在调用 greeting() 时传入的参数类型出错。
静态类型检查工具可以帮助我们像静态语言一样在运行代码之前就捕获到某些错误,但需要我们在程序中加入大量的类型提示才能完整发挥其作用。虽然稍微加大了工作量,但引入类型提示还会带来以下好处:
Autoformatter (自动格式化器)顾名思义是可以将代码按特定规则自动格式化的一类工具。Linter 可以帮助我们发现代码中的格式错误和风格问题,但并不会自动纠正,因此我们还需要借助自动格式化器,进一步将我们从重复琐碎的手动修改中解放出来。
Python 中有 black 、 yapf、 autopep8、 isort 等众多格式化工具,除了整体上遵循 PEP8 以外,各自都有不同的风格规范和适用范围,我们可以根据自己的喜好进行选择。
以 black 和下面这段代码为例:
|
|
首先通过 pip 安装 black:
|
|
然后对需要格式化的文件或整个目录的路径执行 black 命令:
|
|
black 会首先检查出不符合其规范的文件,然后编辑文件完成修改,并输出修改的文件路径及数量。格式化后的文件内容如下:
|
|
black 号称「不妥协的代码格式化器」,其不妥协体现在基本没有可供自定义的选项,要么全盘接受它的代码风格,要么就不用它。如果你或者你的团队能够接受它的风格,使用 black 可以节省很多花在代码风格上的精力和时间(再也不用争论某种风格孰优孰劣了)。
实践中我们可以对最大行长度以及引号格式化选项做一些调整,方法是在执行 black 的项目根目录添加 pyproject.tomal 文件,并写入以下内容:
|
|
现代的 IDE 和编辑器基本都会自带 Linter 和格式化等功能,比如 PyCharm 就可以通过 ⌥ ⌘ L 快捷键格式化当前文件的代码,这样的话我们还有必要手动集成这些第三方工具吗?答案当然是有必要,我们以第三方和 IDE 自带的代码工具作对比:
pyproject.toml、tox.ini等),且通常都可以很快地集成到不同 IDE 中。PyCharm 但你的同事用 VSCode 呢?在接下来的 Hooks 和 Pipeline 两节内容中,你将进一步认识到集成第三方工具带来的优势。
在上文中我们介绍了很多保障质量的工具,但在日常编码中,我们总不能每一次提交代码前,都把这些工具手动执行一遍吧?这看上去不是高效的做法,庆幸地是我们有现成途径解决这些问题。
Git 可以在仓库中发生特定事件时自动运行自定义的脚本,这些自定义脚本即称为 hooks。 它们让你可以在开发周期的关键点触发可定制的动作,比如每次执行提交前都执行一次 flake8 命令。大部分其他的版本控制系统也会提供类似的功能。
hooks 保存在 Git 仓库的 .git/hooks 目录中,它可以是普通的 shell 脚本,也可以是 Python、Ruby 等语言的可执行脚本,因此我们可以很方便地将常用工具集成到 hooks 中。 hooks 通常按客户端和服务端分为两大类,常用的客户端类有 pre-commit 、 prepare-commit-msg、 commit-msg、 post-commit、 pre-rebase 等 hooks,它们分别在名称所代表的事件触发时运行。
以 pre-commit 为例,该 hooks 在输入提交信息前运行。 它可以检查即将提交的快照,如果该 hooks 以非零值退出,Git 将放弃此次提交,我们可以利用它来检查提交的代码风格是否正确(运行例如 flake8、 black 等程序)、是否引入安全风险等等。整个流程示意如下:

接下来我们将展示,如何将上文介绍的众多工具快速地集成到 pre-commit hooks 中。
pre-commit 是一个用于管理和维护多种语言 pre-commit hooks 的框架,就像 Python 的包管理器 pip 一样,我们可以通过 pre-commit 将他人创建并分享的 pre-commit hooks 安装到自己的项目仓库中。 pre-commit 大大减少了我们使用 git hooks 的难度,你只需要在配置文件中指定想要的 hooks,它会替你安装任意语言编写的 hooks 并解决环境依赖问题,然后在每次提交前执行。
pre-commit 的简单使用方法如下:
通过 pip 安装 pre-commit:
|
|
pre-commit 是面向多语言的,因此它还支持通过 homebrew 等方式安装。
添加配置文件。
你需要创建一个名为 .pre-commit-config.yml 的文件,通常放在项目根目录下,在配置文件中我们按特定格式添加需要运行的 hooks 并指定参数,示例如下:
|
|
在该示例文件中,我们添加了 black 、 flake8 以及一个检查是否添加了大体积文件到 Git 的 hooks,还分别指定了语言版本、跳过检查路径等选项。
配置文件的选项含义参见 https://pre-commit.com/#plugins,所有支持安装的 hooks 列表参见https://pre-commit.com/hooks.html。
安装 git hook 脚本。在配置文件所在的根目录运行命令:
|
|
这会在 .git/hooks 文件夹中创建一个 pre-commit 脚本文件,并在接下来你每一次提交之前运行!现在我们可以尝试提交一次代码了。
(可选步骤)对所有文件运行 hooks。
通常 pre-commit 只会在触发 git hooks 时对发生更改的文件运行,但我们也可以手动对当前仓库的所有文件运行:
|
|
结果显示我们有文件未通过 black 检查,但 black 同时也帮我们自动进行了格式化,因此只需要暂存并重新提交这些文件即可。
pre-commit hooks 的限制使用 pre-commit ,我们可以很方便的将丰富多样的代码工具集成到 Git 的工作流中,这很大程度上提高了我们的效率,但 pre-commit hooks 本身存在以下限制:
hooks 并不会随代码库一起被复制,我们必须在本地仓库通过 pre-commit install 执行安装之后 hooks 才会生效。git commit --no-verify 即可绕过所有的 pre-commit hooks。pre-commit 解决了在本地集成代码工具的问题,但在团队合作的场景下,我们希望有一种机制可以在服务器端对所有成员推送的代码进行检查,施加特定的约束,这时候就需要用到 Pipelines 了。
首先我们需要了解 CI/CD 这一概念。
CI(Continuous Integration,持续集成)是一种软件实践,它要求我们频繁地向共享仓库提交代码。更频繁地提交代码可以更快地发现错误,减少我们在发现错误时需要调试的代码量,也使得团队不同成员间的变更更容易合并。CI 可以节省我们调试错误或解决合并冲突的时间,让我们有更多的时间来编写代码。
当提交代码到共享仓库时,我们需要对每一次提交的代码进行构建和测试,以确保提交不会引入错误。这些测试既包括上文提到的 Linter,也包括安全性检查、测试覆盖率、功能测试和其他自定义检查。我们将需要一个 CI 服务器作为 Runner 以运行对提交代码的构建和检查。
CD(Continuous Deployment,持续部署)是 CI 的下一步。我们不仅在每次推送代码到共享代码库时进行构建和测试,还会在不需要任何人工干预的情况下,自动部署代码到生产环境。此外还有Continuous Delivery (持续交付),它和持续部署的区别在于需要人工干预才会执行部署。
Pipeline 是持续集成、交付和部署的顶层具象化组件,它由顺序执行的阶段(stage)以及每个阶段中并行的任务(job)组成。一个简单的 pipeline 如下图所示:

首先并行地执行 build 阶段的所有任务(build_a 和 build_b),所有任务成功后继续执行下一阶段的任务,以此类推。pipeline 还能定义成按更复杂的逻辑规则运行。
下面我们以 GitLab CI 为例,展示如何配置一个 pipeline,并在测试阶段执行上文介绍的检查工具,解决不能对团队成员提交代码进行强制约束的问题。
首先我们需要配置一个运行任务的服务器作为 Runner,这需要我们有可用的服务器主机(也可以使用本地的开发机器或购买 GitLab 提供的 Runner)。配置 Runner 整体分两步:
register 命令使用 URL 和令牌完成注册,并选择合适的 Runner 类型,详细步骤参见文档。配置完成后,我们将可以在 Runners settings 页面查看可用的 Runner 和状态:

.gitlab.yml我们将在项目根目录创建一个配置 pipeline 的 .gitlab.yml 文件,GitLab 将在每一次我们推送代码到共享仓库时,读取该文件中的定义和指令,创建一条不同阶段及任务组成的 pipeline,并将阶段中的任务派发给可用的 Runner 执行。我们以如下 .gitlab.yml 文件为例:
|
|
在这个文件中,我们定义了:
上面的示例定义了如下 pipeline:

before_script关键字定义了在每个任务前执行的一组命令,我们通过这里设置的命令安装运行 pipeline 所需的依赖。stage 关键字定义了 Pre-commit Hooks 和 Static Analysis 两个阶段。pre-commit 、 git-lint、 mypy 和 flake8 等多个任务及所属阶段。script 关键字定义该任务所运行的脚本命令。pre-commit 任务中,我们成功地将上一节介绍的 pre-commit 放在服务端运行,这能确保对任何人提交的代码都会运行原先只在本地生效的 pre-commit hooks。我们还通过预定义变量和 cache 关键字缓存运行任务所需的文件以加快执行速度。mypy 任务中,我们通过 allow_failure 关键字更改了任务成功才会进入下一阶段这一默认行为,并通过 only 关键字限制其只对 master 分支的提交执行。关于 .gitlab.yml 的更多说明请参阅 GitLab CI/CD pipeline 配置参考手册 以及 GitLab CI/CD 示例。
现在当我们推送新的代码到共享仓库时,一个新的 pipeline 会被触发运行。我们可以通过以下途径在 GitLab 仓库查看 pipeline 的运行状态:
进入 CI/CD > Pipelines。将展示一个包含两个阶段的 pipeline(当前状态为已通过)。

点击 pipeline 的 ID 即可查看该 pipeline 的运行示意图:

点击一个任务名称,可查看该任务的详细运行信息:

以上即为一个完整的 GitLab CI pipeline 示例。在该 pipeline 中,我们会在每一次提交代码后:
hooks 的 pre-commit,确保代码已在本地通过了 pre-commit 检查。mypy 、git-lint 等检查工具。接下来我们将通过 GitHub Actions 为例,展示如何运行一个简单的 CD pipeline。
GitHub Actions 是 GitHub 在 2018年10月推出的持续集成服务,它和上文介绍的 GitLab CI 原理和使用方法基本相同,但存在以下区别:
.gitlab.yml 的 yaml 文件,但需要添加到项目根目录的 .github/workflows 路径中。接下来我将演示如何使用 GitHub Actions 自动发布一个 Hugo 静态站点到 GitHub Pages。
大致的工作流程如下:
GitHub 上有许多这类自动化部署任务的开源 Actions 项目,我们选择了其中一个简单易用的 GitHub Actions for Hugo。具体的操作步骤截图和详细配置项可以查看该项目的 README。下面简单介绍下配置过程:
在本地内容仓库中添加目录和文件:.github/workflows/gh-pages.yml,gh-pages.yml 文件内容如下:
|
|
在该配置文件中,我们定义了如下的 workflow:
github pages。main 分支时执行。deploy 的任务,并需要在ubuntu-18.04平台的 Runner 执行。actions/checkout@v2 的 action,以拉取提交的内容到 Runner。peaceiris/actions-hugo@v2 action 配置 Hugo,并指定版本。hugo --minify 命令,生成静态站点(默认输出文件到 public 目录)peaceiris/actions-gh-pages@v3 action 将生成的静态文件推送到指定的外部仓库。这个 workflow 基于内容仓库运行,但我们需要将运行过程生成的静态文件推送到发布仓库进行发布,因此还需要在两个仓库中分别设置密钥。
在本地生成 SSH 部署密钥:
|
|
在 GitHub 分别进入内容仓库和发布仓库的 Settings 页面:
gh-pages.pub 作为 Secret 添加到内容仓库,并设置 Name 为 ACTIONS_DEPLOY_KEY。gh-pages 作为 Deploy Key 添加到发布仓库,并设置为 Allow write access。接下来我们测试一下效果。
在本地内容仓库做一些更改,预览效果后提交并推送,然后在共享仓库的 GitHub Actions 页面检查相应 workflow 的运行状态与详细结果:

运行成功后,很快发布仓库将新增一个由该 workflow 创建的提交,相应的 GitHub Pages 也会更新相应的内容。
本文中,我们围绕保障代码质量这一目的:
pre-commit 集成到 git hooks 中,在代码开发工作流中自动执行。经过简单几步的一次性配置,你就可以拥有一套高度自动化的代码质量工具工作流程。但工具只是保障代码质量众多环节中最容易发力,见效快,但作用也很有限的一环。真正实现保证代码质量这一目标任重而道远,还需要我们在个人和团队这两个方面认识到代码质量的重要性,并不断践行各种代码质量设计与活动。
更新: