ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

clang-format 与 VSCode:C/C++ 团队代码格式统一

clang-format 与 VSCode:C/C++ 团队代码格式统一 你肯定遇到过这种场面一次功能改动只动了十几行代码Review 页面却刷出来八百多行 diff一半是大括号换了位置一半是空格变成了 Tab。提 PR 的人说我就顺手按了一下格式化Review 的人只能一行行往下翻最后放弃抵抗点了 Approve。这个问题跟个人习惯无关纯粹是因为团队里没有一把统一的尺子。clang-format 就是这把尺子——它把代码长什么样这件事从人的审美争论里彻底剥离出来交给一个确定性的程序去裁决。而 VSCode 在其中扮演的角色是让这把尺子在保存文件的那一瞬间自动落下去你甚至感觉不到它的存在。下面这些内容是我在几个 C/C 项目里把 clang-format 从零推到全组落地的完整过程包括配置怎么写、VSCode 怎么接、以及那些文档里不会告诉你的坑。1. 为什么团队里总有人为了大括号换行吵起来1.1 格式化工具解决的是哪一类问题代码风格这件事分成两层。第一层是排版比如缩进几个空格、大括号换不换行、指针的星号贴左边还是右边、行宽限制多少、include 怎么排序。第二层是命名与结构比如变量叫userName还是user_name、函数要不要拆、类的成员怎么排列。clang-format 只管第一层而且只管得极其彻底给定一份配置文件它对同一段代码的输出结果是逐字节确定的不存在看心情。这一点非常关键因为团队争论之所以耗时间往往不是因为谁对谁错而是因为双方给出的都是主观偏好没有裁判。clang-format 提供的就是这个裁判。它跟编辑器里那种自动缩进完全是两个量级的东西。自动缩进只在你按回车的时候算一下当前应该缩进多少属于局部启发式clang-format 是把整个文件重新解析成 AST理解for循环体在哪结束、namespace嵌套了几层、函数参数是不是超宽需要折行然后按配置重新打印一遍。所以你经常看到这样的现象一段手写得很整齐的代码跑完 clang-format 反而变丑了——那是因为它在按配置办事而配置跟你脑子里的默认约定不一致。我在项目里推这件事的最大心得是不要先讨论用哪套风格先讨论要不要统一。只要全组接受统一由工具决定那么 Google、LLVM 还是自定义风格实质上只是改几行 YAML 的事争论成本会瞬间从几小时降到几分钟。1.2 clang-format 覆盖哪些语言哪些场合它不该上虽然名字里有 clang但它并不只能格式化 C/C。同一份可执行文件支持的语言相当多常见的包括语言在配置里对应的Language说明C / CCpp最成熟选项最全C#CSharp支持但项目里用得少JavaJava能用但通常有更专业的工具JavaScript / TypeScriptJavaScript能用前端团队一般用 PrettierObjective-C / Objective-CObjCApple 生态下可用ProtobufProto处理.proto文件挺方便TableGenTableGen编译器等 LLVM 相关项目才用这里有个务实的判断如果一个语言生态里已经存在被广泛接受的官方格式化工具就不要用 clang-format 硬上。前端有 PrettierPython 有 BlackGo 有 gofmtRust 有 rustfmt这些都是各自社区的原生方案插件生态和团队认知度都更好。clang-format 真正的主场是 C/C以及在同一个仓库里混着.h、.c、.cpp、.proto、.cu这类文件时能用一套工具和一份配置把它们全兜住——这个价值很大比如 CUDA 的.cu文件用 clang-format 处理起来基本没有违和感。另外要明确一件事clang-format不做语义检查不做重构不修 bug。它只保证排版一致。别指望它帮你把int * a改成int* a的同时还顺带解决点什么内存问题它没这个能力。2. 三个平台把 clang-format 装到命令行上2.1 WindowsLLVM 官方包、VS 自带、pip 三条路怎么选Windows 上其实有三条几乎并行的路径各有取舍。第一条是装 LLVM 官方发行包。从 LLVM 的 GitHub Releases 页面下载LLVM-x.y.z-win64.exe安装时记得勾上Add LLVM to the system PATH装完C:\Program Files\LLVM\bin\clang-format.exe就位。这条路的好处是版本明确、二进制完整、跟clang、clangd、clang-tidy同一个版本号一起装省心。第二条是用 Visual Studio 自带的。装了 VS 2022 之后路径通常在C:\Program Files\Microsoft Visual Studio\2022\Community\VC\Tools\Llvm\x64\bin\clang-format.exe注意这里的Community要换成你实际装的版本Professional、Enterprise而且 VS 更新时这个版本会跟着变。它的优点是零额外安装缺点是版本号往往比官方最新版落后一两个大版本这就为后面配置漂移埋了伏笔。第三条是用包管理器装我个人最推荐给纯 VSCode 用户# pip 方式会下载对应平台的预编译二进制 pip install clang-format18.1.8 # 或者 npm npm install -g clang-formatpip 这条路的优势在于版本可以精确锁定——你可以在requirements-dev.txt之类的文件里写死版本号让全组人的 clang-format 完全一致。这在跨平台团队里价值极高因为 macOS 和 Linux 的包管理器给出的版本号经常差好几个。提示不管走哪条路都不要把 clang-format 的路径写到.clang-format里配置文件里只放排版规则。路径信息属于个人环境应该留在 VSCode 的settings.json或项目外的本地配置里。2.2 Linux版本号才是真正要盯的东西Linux 上包管理器最方便但坑也最集中——你在不同发行版上装到的版本可能差得很远。# Debian / Ubuntu 系 sudo apt update sudo apt install clang-format # 想指定版本 sudo apt install clang-format-18 # Fedora / RHEL 系clang-format 打在 clang-tools-extra 里 sudo dnf install clang-tools-extraDebian/Ubuntu 系的clang-format包通常会同时保留多个带版本号的包clang-format-16、clang-format-17、clang-format-18可以共存不带后缀的那个默认指向发行版选定的主版本。这就带来一个非常常见的问题CI 流水线里的 Ubuntu 镜像装的是 clang-format-14而开发机上是 18同配置跑出来的结果就不一样了。我的做法是在项目 README 或者docs/dev-setup.md里明确写一行本项目使用 clang-format 18.1.8然后在 CI 里也用同样的方式安装而不是依赖发行版默认。如果公司内网有镜像源可以做个内部包固化版本这比每次靠人肉对齐靠谱得多。2.3 macOSbrew 与 Xcode 命令行工具里的那一份macOS 上最常见的是 Homebrewbrew install clang-format which clang-format # /opt/homebrew/bin/clang-format Apple Silicon # /usr/local/bin/clang-format Intel另外装了 Xcode 命令行工具之后工具链里也有一份/Library/Developer/CommandLineTools/usr/bin/clang-format # 或者完整 Xcode 里 # /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/clang-format问题来了这两份是不同版本而PATH里谁在前谁生效。如果你的 shell 配置里把/usr/bin放在/opt/homebrew/bin前面那实际跑的可能就是 Xcode 那份老版本。排查时一定要用which -a clang-format把所有候选列出来而不是只看which的第一个结果。which -a clang-format clang-format --version2.4 装完必须做的一件事确认版本并处理多版本共存装完第一件事不是写配置是确认版本clang-format --version # clang-format version 18.1.8为什么这么强调版本因为 clang-format 的行为在大版本之间是会变的。举个实际例子BreakBeforeBraces: Custom下的BraceWrapping.AfterControlStatement这个字段早期版本接受布尔值true/false后来改成了接受Never / MultiLine / Always三种枚举值。你用新版配置喂给老版二进制它会直接报错退出或者更糟——静默忽略这一项然后按默认值排版于是你看到一段格式化了但没完全格式化的代码。如果机器上确实有多个版本共存正确做法是用带版本号的可执行文件名并在 VSCode 里显式指定路径clang-format-18 -i main.cpp{ C_Cpp.clang_format_path: /usr/bin/clang-format-18 }我在一个嵌入式项目里就吃过这个亏同事用 Ubuntu 自带的 14我用 brew 装的 17同一份.clang-format提交上去CI 每次都报格式不一致查了两天才发现是版本差异导致AlignConsecutiveMacros的表现不同。从那次之后我在每个项目的.vscode/settings.json里都写死clang_format_path虽然会牺牲一点可移植性但换来的是确定性。3. .clang-format 从 BasedOnStyle 开始一层层盖3.1 先用 dump-config 看清一个预设的真实取值绝大多数人第一次写.clang-format是这样的网上抄一份几十行的 YAML粘进项目跑一下发现跟自己想的不一样然后开始盲改。这个流程效率极低因为你不知道那些没写进配置的选项现在是什么值。正确姿势是先让 clang-format 自己把一份预设展开给你看# 把 LLVM 预设展开成完整配置重定向到文件 clang-format -styleLLVM -dump-config .clang-format # 或者展开 Google 预设 clang-format -styleGoogle -dump-config .clang-format输出会包含所有可用选项及注释一般在几百行。你不需要全懂但你会立刻明白一件事BasedOnStyle: Google背后其实是一百多个具体取值你后面写的每一行都是在覆盖这些默认值。推荐的配置文件骨架长这样--- Language: Cpp BasedOnStyle: Google Standard: c17 ColumnLimit: 100 IndentWidth: 4 TabWidth: 4 UseTab: Never DerivePointerAlignment: false PointerAlignment: Left SortIncludes: CaseSensitive IncludeBlocks: Regroup顶部的---是 YAML 文档分隔符作用是让你可以在一个文件里写多段配置按语言分别生效--- Language: Cpp BasedOnStyle: Google ColumnLimit: 100 --- Language: Proto BasedOnStyle: Google ColumnLimit: 80这个特性在混合语言仓库里特别有用.clang-format放在仓库根目录一份文件管住所有.cc、.h、.proto不需要搞多个配置文件。注意BasedOnStyle必须放在你自定义项的前面。顺序反了不会报错但会以一种很隐蔽的方式出错——实际上 YAML 映射在 clang-format 里的处理是后出现的覆盖先出现的所以把BasedOnStyle写在最后会把前面所有自定义项全部冲掉。我见过不止一个人在这上面浪费半天。3.2 值得逐项推敲的高频配置几百个选项里日常真正需要动的也就二三十个。下面这张表是我在每个项目里都会过一遍的清单选项常见取值影响与取舍ColumnLimit80/100/120行宽上限。太小折行频繁太大在分屏时看不过来IndentWidth2/4缩进宽度。嵌入式团队常用 4Google 系用 2UseTabNever/ForIndentation建议一律Never混用 Tab 和空格是灾难源头AccessModifierOffset-4/-2public:相对class的缩进配IndentWidth调BreakBeforeBracesAttach/Allman/Custom大括号位置队内争议最大的一项PointerAlignmentLeft/Rightint* p还是int *pDerivePointerAlignmentfalse必须显式关掉否则它会根据文件现状自己猜AllowShortFunctionsOnASingleLineNone/Inline一行短函数是否允许AllowShortIfStatementsOnASingleLineNever建议关掉一行if是断点调试的噩梦SortIncludesCaseSensitive/Never自动排序 includeIncludeBlocksPreserve/Regroup是否在 include 分组之间插空行NamespaceIndentationNone/All命名空间内容是否缩进AlignConsecutiveAssignmentstrue/false连续赋值是否对齐等号其中有两个我特别想展开说。DerivePointerAlignment的默认值是true。这意味着如果你只写了PointerAlignment: Left却没关掉它clang-format 会先统计文件里现有的星号位置多数决之后再用那个结果。后果就是同一个仓库里 A 文件格式化成左贴B 文件格式化成右贴因为两个文件的历史代码不一样。这个坑极其隐蔽因为单独看每个文件都合理。只要你在配置里写了PointerAlignment前面就必须加一行DerivePointerAlignment: false。BreakBeforeBraces: Custom是我在多数项目里选的方案因为它允许细粒度控制各种块的大括号BreakBeforeBraces: Custom BraceWrapping: AfterClass: true AfterControlStatement: Never AfterEnum: true AfterFunction: false AfterNamespace: false AfterStruct: true BeforeCatch: true BeforeElse: true SplitEmptyFunction: false这段配置表达的风格是类型定义class/struct/enum的大括号另起一行控制语句if/for/while和函数的大括号跟在行尾。很多团队的第一版偏好就是这种混合风格靠预设很难一步到位必须用Custom。3.3 用 dry-run 和 off 注释定位局部争议配置文件写完之后不要急着-i覆盖全仓库。先拿一两个代表性文件做无副作用试跑# 输出到终端不改原文件 clang-format main.cpp | less # 显示将要改什么但不写回 clang-format --dry-run main.cpp # 在 CI 里把警告升级为错误任何不达标的文件都会让流程失败 clang-format --dry-run --Werror main.cpp--dry-run是 clang-format 10 之后加入的选项对 CI 场景价值最大因为它不产生副作用却能让流水线红灯。另外一种情况是某段代码天生就不适合自动排版比如一张手写的常量表、一段刻意对齐的位运算、或者一个宏展开的表格。这时候不要为了迁就工具去改代码结构直接用注释把这块圈起来// clang-format off static const int kLookupTable[16] { 0, 1, 4, 9, 16, 25, 36, 49, 64, 81, 100, 121, }; // clang-format onclang-format off和on之间的一切原样保留。我的经验是尽量少用——每用一个就多一块工具管不到的地方长期看会变成风格孤岛。如果某个文件里这个标记出现了十几处通常说明配置本身有问题而不是代码有问题。如果某个文件整体就不该被格式化比如第三方生成的代码、generated/目录下的产物可以在配置里对路径级别处理或者干脆在文件头部写DisableFormat相关的注释。近几个版本还支持了.clang-format-ignore这类忽略文件机制写法类似.gitignore但支持程度跟版本强相关用之前先用--version确认一下别配了不生效还以为路径写错了。4. 让 VSCode 在保存那一刻自动动手4.1 两条技术路线C/C 扩展内置的还是 clangd在 VSCode 里接 clang-format本质上有两条路选错了后面会一直别扭。路线一微软的 C/C 扩展ms-vscode.cpptools。这个扩展自己打包了一份 clang-format同时提供 IntelliSense、调试、代码导航等一整套功能。它的格式化能力通过C_Cpp.formatting: clangFormat之类的设置控制好处是装了就能用不需要另外配置语言服务器。路线二clangd 扩展llvm-vs-code-extensions.vscode-clangd。clangd 是 LLVM 官方的语言服务器格式化是它内置的 LSP 能力之一走的是textDocument/formatting协议。它的代码理解通常比 cpptools 更贴近真实编译器代价是需要生成compile_commands.json才能发挥全部能力。我的一般建议是新项目直接上 clangd因为格式化行为跟命令行clang-format完全一致本来就是同一份代码且不受扩展内置版本影响老项目尤其是 Windows MSVC 工具链的继续用 cpptools 更省事。注意这两条路线不能同时开。两个扩展都启用格式化时保存文件会出现格式化两次的现象表现为缩进翻倍、空行突然变多。要么在 cpptools 里把 IntelliSense 引擎关掉给 clangd 让路要么干脆禁用其中一个扩展。4.2 settings.json 里真正起作用的几个字段不管走哪条路有几组设置是绕不开的。先看 cpptools 路线{ C_Cpp.clang_format_path: /usr/bin/clang-format-18, C_Cpp.clang_format_style: file, C_Cpp.clang_format_fallbackStyle: Google, C_Cpp.clang_format_sortIncludes: true, [cpp]: { editor.defaultFormatter: ms-vscode.cpptools, editor.formatOnSave: true, editor.rulers: [100] }, [c]: { editor.defaultFormatter: ms-vscode.cpptools, editor.formatOnSave: true, editor.rulers: [100] } }几个字段的含义需要说清楚clang_format_style: file表示去文件所在目录往上找.clang-format。这是唯一正确的团队协作姿势绝对不要在这里写Google或者内联一个 JSON 风格字符串那样每个人的 VSCode 各排各的仓库里的配置文件就形同虚设。clang_format_fallbackStyle是找不到配置文件时用什么默认是Visual Studio。这个默认值跟多数团队的选择不一致建议显式改成Google或LLVM避免在没有.clang-format的临时目录里排出一堆意外格式。clang_format_path就是前面强调的版本锁定指向具体二进制。editor.rulers只是画一条竖线不影响格式化但把它的位置跟ColumnLimit对齐能让你在写代码时就直观看到会不会折行。这个细节对减少保存后大幅挪动的体感帮助很大。clangd 路线的话配置重心转到扩展参数和默认格式化器{ clangd.arguments: [ --background-index, --clang-tidy, --fallback-styleGoogle, --header-insertionnever ], [cpp]: { editor.defaultFormatter: llvm-vs-code-extensions.vscode-clangd, editor.formatOnSave: true } }这里--fallback-style同样只在找不到.clang-format时生效项目里只要放了配置文件clangd 就会优先用它。4.3 保存自动格式化与其他插件的互相干扰editor.formatOnSave打开之后你会开始碰到一连串关掉就好了的问题。第一类和其他格式化器抢活。如果一个.cpp文件同时被 Prettier、EditorConfig、cpptools、clangd 视为管辖范围保存时按注册顺序依次执行最终结果取决于谁最后运行。排查方法是看 VSCode 右下角状态栏或者打开输出面板选对应的语言服务器看日志。根治方法是给每种语言显式指定editor.defaultFormatter只留一个。第二类跟代码片段和宏展开打架。有些团队的代码里存在大量宏比如日志宏、断言宏、测试框架宏。clang-format 不知道这些宏的语义会把它们当普通函数调用排版结果可能是宏调用被拆成多行或者宏里的参数被强制对齐。解决办法是在配置里声明这些宏的性质StatementMacros: - Q_UNUSED - Q_UNUSED_RESULT - LOG_INFO - ASSERT_TRUE NamespaceMacros: - TEST - TEST_F TypenameMacros: - QList - QVector AttributeMacros: - __attribute__ - __declspecStatementMacros告诉 clang-format这个宏后面不带分号是个完整语句防止它把后面的代码错误地并对齐进来。写 Qt 项目的同学对这个一定不陌生不加这两行Q_UNUSED后面的代码经常莫名其妙地缩进错位。第三类自动保存与格式化的顺序。files.autoSave: afterDelay配合formatOnSave时偶尔会在你输入到一半时触发格式化光标位置跳走。我自己的设置是把自动保存延迟调大或者干脆用onFocusChange避免在敲代码过程中被打断。5. 多人协作下配置怎么落地才不打架5.1 配置文件进仓库与路径发现规则.clang-format必须进版本控制放在仓库根目录。这是整件事的地基。clang-format 的查找规则是从目标文件所在目录开始逐级向上找.clang-format或_clang-format找到第一个就用。这个规则带来两个很实用的后果一是子目录可以覆盖根目录的配置。比如src/legacy/下是历史代码你可以在那里单独放一份更宽松的配置比如关掉 include 排序而不影响新代码目录。二是查找是向上走的所以把配置文件放在根目录全仓库都能命中。但要注意如果你的工作区是 monorepo 里的一个子目录而.clang-format在更上层VSCode 打开的工作区根目录看不到它可能会误判为没有配置文件。这时候要么把配置文件也放一份在工作区根目录要么在 settings 里用绝对路径指过去。另外强烈建议在仓库里同时加两个东西# 编辑器层面的基础约定缩进、换行符、编码 .editorconfig # 提交信息模板、忽略规则等 .gitattributes.gitattributes里写一行*.cpp text eollf能挡住跨平台换行符差异导致的整个文件都变了这种假 diff。这个跟 clang-format 没有直接关系但两者经常一起出现在同一个问题现场。5.2 只格式化改动行git clang-format全量格式化一个有几万行历史代码的仓库是最容易引发团队内战的操作。正确做法是只格式化这次改动涉及的行。LLVM 提供了一个现成的脚本# 比较工作区与 HEAD 的差异只格式化改动过的行 git clang-format # 指定比较基准 git clang-format HEAD~1 # 直接应用到工作区 git clang-format --force它做的事情是算出 diff 里被修改的行号区间把这些区间映射到格式化后的文件上只把区间内的改动写回去。所以你会看到这个文件被格式化了但只有十几行变了而不是整文件重排。这个脚本的可用性跟版本有关有些发行版把它单独放在clang-format-diff.py里有些直接提供git-clang-format命令。如果git clang-format提示找不到命令可以检查一下 LLVM 包有没有装全或者手动把脚本下载到PATH里的某个目录并加执行权限。我推全组落地时的顺序是这样的先在根目录放好.clang-format并提交这一版不改任何源码。在.vscode/settings.json里配好自动格式化让新写的代码自然合规。老代码不做全量格式化谁改谁负责。提交前跑一次git clang-format。观察一两个月等大部分活跃文件已经自然合规再考虑是否全量跑一次。这种渐进方式的成本最低。直接上来就find . -name *.cpp | xargs clang-format -i你会面临三个后果巨型 diff 卡死 Review、git blame全部指向那次格式化提交导致追溯失效、以及潜在的功能回归风险虽然罕见但格式化确实可能改坏某些依赖行内汇编或特定宏布局的代码。5.3 提交前钩子与流水线校验靠自觉永远不够得有两道自动门。第一道提交前钩子。在.git/hooks/pre-commit里放一段脚本提交时自动跑一遍增量格式化#!/bin/sh # .git/hooks/pre-commit # 把改动过的行格式化后加入暂存区 git clang-format --staged注意钩子文件默认不会跟着仓库走.git/hooks/不在版本控制里所以团队要落这个得靠工具把钩子装到每个人本地或者干脆换成在 CI 里挡。第二道CI 校验。这里是硬性闸门# 检查指定文件是否合规不合规直接失败 clang-format --dry-run --Werror src/main.cpp src/util.cpp更省事的写法是遍历出所有待检查文件# 只检查本次改动涉及的文件避免全仓库跑太慢 CHANGED$(git diff --name-only origin/main...HEAD -- *.cpp *.h *.cc *.hpp) if [ -n $CHANGED ]; then clang-format --dry-run --Werror $CHANGED fi这套组合下来效果是本地保存时自动格式化提交时增量兜底CI 上最终把关。三道网里任何一道漏掉的行都会被后面一道抓住。我实际推下来的体感是只要 CI 那道闸门立住了团队接受度会自然提高——因为大家发现与其被 CI 打回来重跑不如一开始就让 VSCode 自动排好。6. 实际项目里最容易翻车的几个点6.1 版本不一致导致同一份配置跑出两种结果这是出现频率最高、排查成本也最高的一类问题。现象是本地git clang-format跑完一切正常提交到 CI 后报格式不一致或者反过来同事的机器上格式化出来的结果跟你不一样两人对着同一份配置文件谁也看不出问题。根因就是二进制版本不同。前面提到过BraceWrapping.AfterControlStatement从布尔改成枚举这个例子实际影响更大的还有一些AlignConsecutiveAssignments系列在新版本里拆成了多个子选项AcrossEmptyLines、AcrossComments、PadOperators等老版本只认单一布尔值。SortIncludes从布尔升级成枚举Never/CaseSensitive/CaseInsensitive用true会直接报错。IncludeBlocks和配套的IncludeCategories在分组行为上有细节差异。一些新加入的选项在老版本里完全是未知字段会被静默忽略。排查步骤这个顺序很重要不要跳在报错机器上执行clang-format --version记下完整版本号。在执行正常的机器上也跑一遍对比。用which -a clang-format确认实际生效的是哪个二进制特别注意 macOS 上 Xcode 那份和 brew 那份的先后关系。用同一份测试文件在两台机器上分别clang-format --dry-run看输出差异确认是版本问题而不是配置问题。锁定版本CI 里显式安装指定版本VSCode 里写死clang_format_path。我现在的标准做法是在项目根目录放一个tools/install-clang-format.sh里面写清版本号和安装方式新人入职照着跑一遍就对齐了。这个脚本十几行省下的沟通时间远不止十几行。6.2 宏、模板与条件编译里的排版意外clang-format 要正确排版前提是它能正确解析。C 里有一大类东西天生让解析器为难第一类是条件编译。一段被#if 0 ... #endif包起来的代码里面的语法可能根本不合法比如是半截代码、伪代码clang-format 解析失败后可能整块原样保留也可能做出奇怪的重排。如果发现某个文件的一部分格式化了但缩进乱了先看看是不是在条件编译块里。第二类是模板和嵌套尖括号。std::vectorstd::pairint, std::mapstd::string, std::vectordouble这种东西在没有 C11 之前的解析规则下会被拆开。现代 clang-format 处理没问题但如果Standard设成了c03或者配置文件里Standard缺失导致默认值偏老就可能出现意外的空格插入。第三类是宏参数里的逗号。像EXPECT_EQ(a, b)这种宏如果a内部还有模板逗号clang-format 可能会把参数拆行拆错位置。解决办法是在配置里把BinPackArguments和BinPackParameters设为false让每个参数独占一行减少歧义或者用前面提到的StatementMacros声明。第四类是原始字符串和行连接符。R(...)里的内容理论上是原样保留的但里面如果混了//或者奇怪的缩进某些版本处理起来结果不理想。带反斜杠续行的宏更是重灾区——#define换行时反斜杠的对齐方式clang-format 会尝试重新规制如果配置里没声明这是个多行宏对齐可能被破坏。检查方法是格式化后编译一遍编译器对反斜杠后面的空格极其敏感。6.3 全量格式化引发的巨型 diff 与追溯失效最后说一个流程层面而不是技术层面的坑但它的破坏力最大。假设你在一个跑了五年的仓库里执行了全量格式化产生了一个改动八万个文件的提交。后果是这个提交在 Review 时无法有效检查等于把一次大规模变更直接放进主干。之后所有人执行git blame任意一行看到的都是这次格式化提交真正的作者信息被埋在历史里。需要用git blame --ignore-rev 格式化提交的哈希才能穿透而且这个参数必须每次都带或者写进.git-blame-ignore-revs文件。如果这个仓库同时在维护多个长期分支把格式化的 commit cherry-pick 到旧分支会引发巨大冲突。如果确实需要做全量格式化比如为了赶上某个规范要求我的建议是单独开一个纯格式化分支里面不含任何功能改动。提交信息写清楚这是格式化提交并记录使用的 clang-format 版本号。提交后立刻在仓库根目录创建.git-blame-ignore-revs把这个哈希写进去。通知所有人更新本地分支并配置git config blame.ignoreRevsFile .git-blame-ignore-revs。在这个提交前后各打一个 tag方便需要时对照。另外提醒一句.git-blame-ignore-revs这个文件本身也要提交进仓库否则每个人的效果不一致。GitHub 等平台的 blame 页面会自动识别这个文件但本地 CLI 需要显式配置。我在项目里踩过最难受的一次是格式化提交和一次大的重构混在同一个 PR 里导致 Review 的人花了三个小时才发现有个逻辑改动藏在格式化噪音中间。格式化提交必须是独立的这条规则我现在会在团队规范第一条里写清楚。
返回列表