在开始之前,我必须感谢 SigureMo
说来惭愧,我写了这么久 Python,都不知道什么是 Formatter 和 Linter,也从来没有对代码进行格式化过。
https://github.com/SigureMo/nyakku.moe/pull/217
第一次格式化是用 Prettier =-=,因为 CI 没通过,SigureMo 提醒我用 Prettier 格式化代码。
学习优雅的工作流,让代码变得更可爱
超纲,太超纲了!!我之前连一个格式化工具都没有用过,结果一上来,black,isort,Poetry,pytest,git hook.
但是,为了能够比较快地融入大型开源仓库的开发,我有必要把这些学下去,哪怕只是囫囵吞枣。
在项目中配置和应用工作流
流程:
分成 IDE 配置和 git hook 配置,以及 CI 配置。
IDE 配置让我们可以在保存代码的时候自动格式化代码,git hook 配置让我们在提交代码的时候自动格式化代码和检查代码规范性,CI 一般是 PR 的工作流里检查代码,我们能做的通常是让 git hook 能和 CI 保持一致,这样会省掉很多反复 CI 的功夫。
我们专注于前两个,CI 一般是仓库维护者配置的,我们只会稍微学习做一个简单的 CI 工作流。
而且一般仓库也会提供.pre-commit-config.yaml
,这是 git hook 的配置文件,一般直接套用即可,就能保证代码风格的一致性。
代码规范性弄完了,还有环境统一性的问题,这个我们应该会用 docker 解决。
IDE 配置:
这一次是 CPP 和 Python 以及 Markdown 的工作流。对于 CPP 格式化和编写,我都使用 vscode。但 vscode 似乎不支持类跳转,所以可能还会用到 Clion。
Formatter:
Python 我们日常使用 isort,black,pylint
CPP 我们使用 clang-format,clang-tidy
Markdown 我们使用 prettier
这里测试了一下,发现 vscode 对 isort 和 black 的 formatter 似乎无法正常识别
“There is no formatter for ‘python’ files installed.”
暂时无解,所以现在 Python 和 CPP 在 Git Hook 中格式化,写脚本批量格式化所有代码可能动到自己没有修改的文件,而 Git Hook 的格式化添加到 git add 或者 git commit 之前,不会有这个问题,而且很适配我们的问题。【当然可以的话尽量在我们的 IDE 里面也都配置一份,不然 commit 的时候会有很多很多待 solve 的问题。】
那么 IDE 就只需要配置 Markdown 的格式化了。
项目根目录创建:./.vscode/settings.json
:
{
"editor.formatOnSave": true,
"editor.defaultFormatter": "esbenp.prettier-vscode"
}
并且再 vscode 的插件中安装 Prettier - Code formatter 插件。
Linter:
这里我用 Pyright 作为 Python 的 linters,clang-tidy 作为 CPP 的 linters。
./.vscode/settings.json
:
{
"C_Cpp.clang_format_style": "file",
"C_Cpp.codeAnalysis.clangTidy.enabled": true
}
Pyright 在 vscode 插件中安装后即可,似乎不需要额外配置。
clang-tidy 一般还需要.clang-tidy
文件来作为配置文件,这个不用自己写,白嫖别人的就好,参考后面 git hook 中我从 paddle 那里白嫖了一个 codestyle。
在终端中安装 clang-fortmat,clang-tidy:
sudo apt install clang-format clang-tidy
clang-format --version
clang-tidy --version
Git Hook 配置:
git hook 的配置主要是编写.pre-commit-config.yaml
文件,这个文件是 pre-commit 的配置文件,里面会说明我们要使用哪些 formatter 和 linters,顺序怎么样。一般开源仓库会提供这个文件来作为代码风格约束。
这里我们日常使用的就是 isort ,black,clang-format,clang-tidy,prettier。
我们需要把它们引入工作流中,当然也得先学会阅读仓库提供的.pre-commit-config.yaml
文件。
诶嘿,这个现在不用学了,丢给 gpt 嘎嘎两下就出来了。
因为我 markdown 平时写完及时保存,保存时会自动格式化,所以我不需要在 git hook 里配置 Prettier。而且无伤大雅。
小练习 1:为 python 小仓库配置 git hook
找一个合适的仓库来做,不宜太大,那搞得累死,也不宜太小,不然整个撸下来都没几个问题。
这里我打算用我之前写的一个 chromedriver 爬虫 template 来做,说实话我垃圾代码巨多,你要是有兴趣随便拉一点。
你需要先创建.pre-commit-config.yaml
文件:
# 这是根级配置项
exclude: |
(?x)^(
patches/.+|
third_party/.+
)$
repos:
# 配置仓库及钩子
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v4.4.0
hooks:
- id: check-added-large-files
- id: check-merge-conflict
- repo: https://github.com/psf/black
rev: 24.8.0
hooks:
- id: black
我们都是依赖于 pip:
pip install pre-commit
pip install isort black
cd . # 进入项目根目录,.git文件夹所在的目录
pre-commit install
pre-commit run --all-files # 对所有文件进行检查
这里即使不写 install isort black 也可以,因为已经指定了镜像仓库,会自己拉下来。
但是我们有时候会需要自己本地运行一些格式化,所以还是建议本地也有一个格式化的工具,你大概也不希望一个一个给注释前面加上一个空格吧。
这么搞完之后,你的.git/
文件夹下会多一个hooks/
文件夹,里面会有一些文件,这些文件是 pre-commit 的脚本。
这意味着之后每次提交代码,即使没有使用先前 pre-commit 的虚拟环境,也会自动运行 pre-commit 的脚本。
如果仓库提供的.pre-commit-config.yaml 文件里要求使用不同的格式化工具,那么你要做的也类似。
小练习 2:为 CPP 小仓库配置 git hook:
不同于 Python,CPP 的格式化工具是 clang-format,clang-tidy。
这俩并不能似乎通过 pip 安装,而是通过 apt 安装,而且不小。
这里我也有一个 CPP 仓库,你可以试一试。【我放弃了,真的太烂了,在我把所有的格式化问题解决后才发现规范性问题才要命】
https://github.com/MrXnneHang/CPP-Primer-Note
clang-format,clang-tidy:
sudo apt install clang-format clang-tidy
clang-format --version
clang-tidy --version
值得注意的是,clang-format 和 clang-tidy 现在并不能通过 pre-commit 安装(如果 repo 写的是远程镜像仓库),因为它的镜像仓库有个奇怪的现象:如果通过 https 来访问,会出现鉴权的情况(输入用户名和密码,但是现在 github 不支持直接用密码登陆,2FA 了。),而通过 ssh 连接,似乎要对仓库具有写权限,这样也不行。
所以,那个仓库就那么挂着,我也只能干巴巴看着,但是总有办法,这是 PaddlePaddle 的.pre-commit-config.yaml
它里面写的是 Local 方法,写了几个 bash 脚本,可能稍微绕了点路,但是总归让我两三行代码就能把东西都配置好,版本兼容也不用考虑。
https://github.com/MrXnneHang/daily-code-style-template/tree/cpp-python-paddle-rule
这是我提取出来的 PaddlePaddle 的代码规范。
每次 Python+CPP 空仓库可以直接套用,非常方便。
主要碰到一个问题,对于先前完全没有进行格式化过的大仓库,不宜引入 pre-commit。
因为虽然格式化大量代码似乎会有一种看别人挤黑头的快感,但是实测,很难通过 pre-commit 的检查,而且需要修改大量的文件,可能毁了仓库。
学会 pre-commit,只能说有一个好的开始,下次好好来个从零开始的可爱仓库。
不过这也说明,code-style 这东西应该最初就定好,而且不会太经常改变,不然对维护代码而言是一种灾难。
CI 配置:
CI 是一个脚本,准确来说是一个 github 的 workflow,它会检查代码的规范性,如果代码不符合规范,那么 CI 就会报错,不允许合并 PR。
Sigmore 的仓库就有 CI,在发起 PR 后会先检查我代码是不是符合 Prettier 的规范,一下子就很有 b 格。它其实和本地的 git hook 的工作方式是一样的,只不过是在 github 上自动运行的。
在 Paddle 里,它的 CI 就用来做很多事情了,包括各种平台化测试,显卡兼容性测试,以及代码规范性检查。
我记得上次我提交了一个改了三个单词的 PR,然后 CI 跑了一个早上,我突然就觉得挺对不起 github 的服务器的。
我们可以通过配置.github/workflows/
文件夹下的 yaml 文件来配置 CI。
以 prettier-check 为例:
name: Prettier Check
on:
pull_request:
branches:
- main # 或你希望监控的其他分支
jobs:
prettier:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v2
- name: Set up Node.js
uses: actions/setup-node@v2
with:
node-version: "16" # 选择你需要的 Node.js 版本
- name: Install dependencies
run: npm install
- name: Run Prettier
run: npm run prettier -- --check .
如果你在提交过程中碰到 git hook 的阻截,比如每行末尾多余空格之类的,你可以考虑先用 Prettier 对 yaml 文件进行格式化,然后再提交就没问题了。
名词解释:
git hook
:
它是 Git 的一个重要特性,它让你可以在 Git 仓库中定义一些自动化的脚本,这些脚本可以在特定的 Git 事件(如提交代码、接收代码等)发生时被触发执行。
pre-commit
:
它是一个 python 的库,通过配置.pre-commit-config.yml
(通常开源仓库会提供作为它的代码规范)可以配置 git hook.它通常说明了在提交代码时要进行那些规范性检查和格式化。
Formatter
(代码风格格式化):
python 常用的 isort 和 black,isort 用于格式化 import 语句,black 用于格式化代码。
CPP 常用的有 clang-format.
Linter
(代码规范性检查):
python 常用的有 flake8,pylint,其中 pylint 是 vscode 内置的。
CPP 常用的有 clang-tidy.
CI
:
在 github PR 的工作流里面,一般会定义一个 CI 的工作流,用于检查代码的规范性,通过 CI 的检查才能合并 PR。