Skip to content

格式化与质量门:用 .clang-format 守住代码外观 ​

引言:为什么要在意“格式”这种小事 ​

您可能会觉得“代码能跑就行,少个空格、大括号换不换行有什么关系”。关系在团队协作上:如果十个人各按各的习惯敲,code review 就会塞满“这里 if( 没空格”、“那里大括号该换行”这种纯风格争论,既浪费时间,又掩盖了真正该被 review 的逻辑问题。而且风格不一致会让 git diff 在“没动逻辑只是换了个人写”的地方刷一大片,合并冲突凭空变多。

解法是让机器统一格式:定一份格式规则,所有代码过一遍工具自动对齐,人不用吵。C/C++ 这边的事实标准是 clang-format,规则写在 .clang-format 文件里,这个文件就是“格式的真相源”,所有人和工具都认它。这一章咱们看本项目的 .clang-format 定了什么、clang-format 怎么用、怎么挂进日常和 CI,最后把开发环境这一程攒下的整套质量门收拢。

.clang-format:格式的真相源 ​

本项目的 .clang-format 放在仓库根目录,clang-format 会自动找到它。咱们挑几个最影响外观的关键项解读:

yaml
BasedOnStyle: LLVM          # 以 LLVM 风格为基础,下面逐项覆盖
IndentWidth: 4              # 缩进 4 个空格(不用 Tab)
ColumnLimit: 100            # 一行最多 100 字符,超了自动折行
BreakBeforeBraces: Attach   # 大括号「贴」在行尾(if/函数的 { 不另起一行)
PointerAlignment: Left      # 指针贴类型:'int*' 而不是 'int *'
SortIncludes: Never         # 不自动重排 #include 顺序(保教学顺序)
AllowShortIfStatementsOnASingleLine: Never   # 短 if 也强制展开成多行

每一项都直接决定代码长什么样:IndentWidth: 4 就是 4 空格缩进、ColumnLimit: 100 行宽 100(第 12 章的 Makefile、第 5 章的代码都遵守这个);Attach 决定大括号跟在行尾(if (...) { 不换行)。SortIncludes: Never 特别值得说:它不自动按字母重排咱们的 #include,因为第 1 章讲过 include 顺序有讲究(先对应头、再标准库、最后项目头),机器重排会破坏教学意图,所以这里关掉。AllowShortIfStatementsOnASingleLine: Never 是“教学代码要可读”的体现,哪怕 if (x) return; 能写一行,也强制展开成带大括号的多行。

这里有个诚实的点要交代(和第 10 章发现并上报的一致):.clang-format 定的 PointerAlignment: Left 是“指针贴类型”(int* p),但本项目部分文档的内嵌代码目前用的是 C 传统的“贴变量名”写法(int *p)。两者不完全一致,真相源是 .clang-format,文档侧在逐步统一,咱们遇到对不上时以 .clang-format 和 examples 目录的实际代码为准。这种“配置和存量代码偶尔错位、以配置为准逐步收敛”在真实工程里很常见。

clang-format 怎么用:检查、格式化、原地改 ​

咱们写一个故意乱来的 mesy.c 当靶子(缩进忽深忽浅、if(x==42) 没空格、大括号贴得乱七八糟):

C
#include <stdio.h>
int main(void) {
    int x = 42;
    if (x == 42) {
        printf("%d\n", x);
    }
    return 0;
}

clang-format 有三种用法,咱们挨个试。头一种是检查,只看、不改,报告哪些行不合规,这正是 CI 的 format-check job 用的:

终端
$ clang-format --dry-run --Werror mesy.c
mesy.c:2:15: error: code should be clang-formatted [-Wclang-format-violations]
int main(void){
              ^
mesy.c:3:6: error: code should be clang-formatted [-Wclang-format-violations]
int x=42;
     ^
...
$ echo $?
1                                   ← 退出码 1,有格式问题

--dry-run 是“只模拟不真改”,--Werror 把“格式不合规”当成 error(于是退出码非 0)。第 17 章那个 format-check job 靠的就是这一行:CI 里它退出码非 0,提交就红。咱们看另一种用法,看格式化结果(输出到屏幕、不改文件):

终端
$ clang-format mesy.c
#include <stdio.h>
int main(void) {
    int x = 42;
    if (x == 42) {
        printf("%d\n", x);
    }
    return 0;
}

咱们对比一下原文件:缩进统一成 4 空格、if(x==42) 变成 if (x == 42)(if 后和运算符两边补空格)、大括号按 Attach 摆好,这就是 .clang-format 那几项规则落地的样子。最后一种是原地改:加 -i(clang-format -i mesy.c),它直接把文件改整齐,平时咱们整理代码用这个。

把格式化挂进日常和 CI ​

光有工具不够,得让它“自动”跑、不用人记。三个挂法。头一层是编辑器:VS Code、Vim、CLion 都能配成“保存时自动 clang-format”,您一按 Ctrl+S,文件就自动按 .clang-format 整齐,这是最省心的,写的时候就是合规的。再一层是git pre-commit hook:在 .git/hooks/pre-commit 里挂一段,git commit 前自动对本次改动的 .c/.h 跑 clang-format,格式不对就拦下提交。最后一层是CI 的 format-check job(第 17 章讲过):每次 push/PR 自动跑 clang-format --dry-run --Werror,任何格式不合规直接 CI 红、挡合并,这是最后一道、也是最硬的一道。三层从近到远(从编辑器到本地提交再到远端 CI)把格式守住,咱们几乎不需要手动操心格式。

开发环境这一程的质量门全景 ​

到这,咱们把开发环境这一程攒下的所有质量门收拢成一张全景。本地自检时,咱们跑这几条就能复现 CI 的核心检查:

终端
$ python3 scripts/build_examples.py          # 1. 每个示例都能编译(硬门)
✅ examples 全部构建通过。
$ python3 scripts/validate_frontmatter.py    # 2. 文档 frontmatter 合法
✅ frontmatter 全部通过。
$ git ls-files 'examples/*.c' 'examples/*.h' | xargs clang-format --dry-run --Werror
$ echo $?                                     # 3. examples 代码格式合规
0

再配上第 11 章的 sanitizer 构建(-fsanitize=address,undefined)和第 9 章的 -Werror(藏在每个示例的编译旗标里),就构成了本项目的完整防线。这套门不只是“写完检查一下”,它在第 17 章的 CI 里每次提交都自动强制执行,任何一道没过,代码就合不进 main。文档这侧也有对应的“质量门”:AGENTS.md 和 .claude/writing-style.md 定了代码风格、C 专属的硬规则、写作声音,validate_frontmatter.py 校验 frontmatter 字段,markdownlint 查 Markdown 写法。咱们看代码和文档两侧,都有自己的真相源和检查工具。

小结:开发环境这一程收尾 ​

clang-format 用一份 .clang-format 当格式的真相源,机器统一所有代码外观,把“风格之争”从人手里拿走交给机器。本项目 .clang-format 的关键项:4 空格缩进、行宽 100、Attach 大括号、SortIncludes: Never(不破坏 include 教学顺序)、短 if 强制展开;指针 PointerAlignment: Left(贴类型 int*),注意存量文档代码与此略有出入、以 .clang-format 为准逐步统一。clang-format 三种用法:--dry-run --Werror 只检查不改动(CI format-check 用它,退出码非 0 即红)、不带参数输出格式化结果、-i 原地改文件。把它挂进“编辑器保存时格式化、pre-commit hook、CI”三层,格式就基本不用人操心。开发环境这一程到此收尾,整套质量门是:build_examples.py(每个示例能编)+ validate_frontmatter.py(frontmatter 合法)+ clang-format --dry-run --Werror(格式合规)+ sanitizer(-fsanitize,第 11 章)+ -Werror(第 9 章,藏在编译旗标里),全由第 17 章的 CI 在每次提交自动强制。从第 1 章的工具链体检到这一章,开发环境与编译这条线就完整了。下一程咱们要钻进 C 语言本身,从程序结构、整型家族、指针内存一路往下,那时您会反复用到这一程立起来的工具(gcc/clang 的旗标、make/CMake、GDB、sanitizer、CI)。

参考资源 ​

  • clang-format 文档与 clang-format --dump-config(所有配置项;本项目 .clang-format)
  • clang-format --help:--dry-run/-i/--Werror/--style=file
  • 第 17 章:GitHub Actions(CI 的 format-check job)、第 9 章:警告旗标(-Werror)、第 11 章:Sanitizer 门禁
  • 本项目 AGENTS.md(代码风格与 C 专属硬规则)、.claude/writing-style.md(文档写作规范,文档侧的“质量门”)

87bb5f2 · 87bb5f2 · 2026-09-21