ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

Keil自动格式化实战:用AStyle统一代码风格

Keil自动格式化实战:用AStyle统一代码风格 翻到三年前自己写的STM32工程那缩进简直是一场灾难——有的函数用Tab、有的用四个空格if和else的括号一会儿上一会儿下一会儿挤在行尾。最讽刺的是所有代码逻辑都是对的但人一多、维护一长阅读成本高得惊人。后来我开始研究Keil自动格式化这件事试了一圈才明白Keil自己并不提供一键整理代码的功能你得把外部格式化工具塞进它的工具菜单里让它变成像VS Code里ShiftAltF那样顺手的东西。这篇文章就围绕Keil自动格式化怎么落地来写适合每天跟Keil MDK、C51打交道又不想为了格式化去换编辑器的人。我也会顺便聊几个日常会被问爆的点astyle这个常说的Keil代码自动对齐工具到底怎么配置、格式化后中文注释乱码怎么处理、怎么在项目里统一所有人的代码风格。内容不烧脑但都是实际操作过的经验。1. 格式化这件小事Keil为什么一直不做1.1 Keil自带的编辑功能到底弱在哪Keil这么多年底层编辑器确实很古老。uVision5里你可以在Edit菜单下找到Indent Selection、Convert Tabs to Spaces这类操作高级一点的还有Advanced里面的Auto Indent但它的Auto Indent只做一件事回车换行时把新行的缩进对齐到上一行不会去处理已有代码的乱缩进。对写了大半年没人碰的老文件来说等于没有。你可能会说那用Edit - Advanced - Format Code这个选项其实在很多版本里并不存在或者只是把Tab转换成空格而已。Keil真正强的是编译调试不是代码编辑。它的编辑器定位就是一个轻量输入工具什么括号自动补全、代码折叠、自动对齐这些“现代功能”它要么做得很基础要么干脆不做。所以实际情况就是一个STM32工程如果几个工程师轮流改过代码风格基本就是混搭风。有人习惯Allman风格把左括号单独起一行有人喜欢OTBS风格括号跟在行尾还有人写if语句不加大括号全靠缩进暗示逻辑范围。这种代码在编译器眼里完全没问题但在人眼里的维护成本很高。你要改一个bug光是想看清楚哪个if对应哪个else就可能花掉好几分钟。1.2 为什么最后选了AStyle这条路线先确认一下目标我需要一个工具能打开文件识别C/C语法然后按照我给定的规则重新排版最后把文件保存回去。这个工具最好还要体积小、不依赖运行时、支持命令行调用这样才能让Keil在“工具菜单”里直接启动它。我当时的备选方案有三个VS Code自带格式化、clang-format、AStyle。VS Code的格式化能力虽然强但它是编辑器的功能不能单独拿出来给Keil用总不能为了格式代码还得另外开个VS Code窗口来回切换太蠢了。clang-format确实很专业但装起来要带一堆LLVM的东西配置是YAML格式默认规则对嵌入式老工程杀伤力太大动不动就把你的宏定义、结构体指针改成完全陌生的样式调参成本高。AStyle则是专门为C/C/C#/Java设计的老牌命令行工具一个exe文件搞定参数全部是命令行式天然适合塞进Keil这类IDE的外部工具菜单。社区里常说的“astyle (keil代码自动对齐工具)下载”其实就是它。还有个现实原因AStyle用的人多你遇到问题随手搜一下就能找到答案。格式化这种工具不怕功能少就怕出问题没人能帮你看。所以我最终选了AStyle并且这套配置在后来几个不同项目里都跑得很稳定。2. 工具准备AStyle下载、安装与命令行初体验2.1 AStyle怎么下载装到哪里AStyle的官方发布渠道主要是SourceForge和GitHub Releases直接搜“Artistic Style下载”也能找到镜像。下载Windows版本解压后会看到bin目录里面有个AStyle.exe这就是全部核心工具了。装的时候不需要什么安装向导就是把exe放到一个固定目录例如D:\Tools\AStyle\bin\AStyle.exe。我建议不要放在桌面或者下载文件夹这种容易被清理的地方因为后面Keil要记住这个路径路径一变就得重新配置。然后可以把这个目录加到系统PATH环境变量里这样后面想在命令行里直接跑AStyle也很方便。版本选择上建议用3.4或更高版本。老版本尤其是3.1之前的对UTF-8编码文件处理不是很好格式化带中文注释的源码时容易出乱码这点后面会详细讲。关于x86还是x64版本其实都行因为AStyle.exe是独立进程跟Keil自己是32位还是64位没关系但我个人习惯用x86版兼容性最好。2.2 命令行先跑一次心里有底刚下载完先别急着配置Keil在命令行里跑一次确认工具本身没问题。假设你有个测试文件main.c内容故意写乱一点if(a1) { ba*2; cfoo(a,b); }打开命令行先加最基础的参数看看效果AStyle.exe --styleallman --indentspaces4 main.c这一步执行完目录里会出现一个main.c.orig文件这是AStyle默认生成的备份文件main.c本身已经被改写。打开main.c你会看到if (a 1) { b a * 2; c foo(a, b); }缩进统一了if和括号之间也加了空格效果很明显。但那个.orig备份文件很碍事如果你不想保留备份可以把参数改成AStyle.exe --styleallman --indentspaces4 --suffixnone main.c--suffixnone的意思是处理完不生成任何备份文件直接覆盖原文件。Keil工具菜单里配置时这个参数一定要带上不然格式化整个工程后目录里会多出一堆.orig垃圾文件到时候还得手动清理。3. 把AStyle塞进Keil工具菜单的全过程3.1 Tools Menu配置的具体步骤Keil提供了自定义工具菜单的功能只不过很多人从来没用过。在Keil里点菜单栏的Tools - Customize Tools Menu会弹出一个配置窗口。左侧的Menu Content是一串空行你选一个位置然后在右边填命令信息就完成一个自定义工具的注册。具体配置如下Menu Content里输入显示名字AStyle FormatCommand选择D:\Tools\AStyle\bin\AStyle.exeArguments填入参数和!EInitial Folder可以留空也可以用!E效果不大Arguments那栏很重要完整写法是--styleallman --indentspaces4 --indent-switches --indent-cases --pad-oper --pad-header --unpad-paren --align-pointername --align-referencename --convert-tabs --break-blocks --suffixnone !E这里的!E是Keil的特殊宏代表当前正在编辑窗口中打开的文件完整路径。如果当前激活的是main.c那!E就等于D:\Project\User\main.c。之所以要用引号包起来是因为很多工程路径里有空格AStyle会把路径拆成两个参数然后报“File Not Found”。这个坑我一开始踩过不加引号Keil明明传来的是正确路径AStyle就是找不到文件。配置完成后点OK再回到Tools菜单就会看到刚才加的AStyle Format。打开一个C文件点击这个菜单项AStyle会在后台运行格式化完成后Keil会弹出提示说文件已被外部程序修改问你要不要重新加载。选Yes编辑器就会刷新成格式化后的内容。3.2 我第一次配置时踩的坑写出来帮你避开第一次配置的时候我遇到了几个特别无语的问题列出来给各位避雷。第一个就是路径问题。如果AStyle.exe所在目录带中文或者特殊字符某些Keil版本读取Command路径会失败。我后来把AStyle放在了纯英文路径下问题就消失了。还有一个是Arguments里的参数顺序AStyle对参数顺序不敏感但你要是把!E前面的引号丢了或者不小心写成了全角引号那就会一直报错。别笑全角引号这种事我真的见过。第二个坑是格式化只读文件会失败。Keil里有时代码是从VSS或者Git上以只读方式checkout出来的AStyle去写文件时会直接拒绝。这个不是配置问题是文件权限问题解决办法就是先取消只读属性再格式化或者在Keil里把文件先设置为可写。第三个坑是关于大文件的。如果你打开的是一个特别大的文件比如几千行的驱动库AStyle执行时可能会卡几秒钟。这不代表程序死掉了就是格式化计算需要时间你耐心等它跑完就行。我还遇到过工程里某些文件被其它程序占用导致格式化失败通常关掉其它编译器窗口就能解决。4. AStyle参数详解从默认到一套够用的配置4.1 括号风格与缩进方式怎么选AStyle里的核心参数就是风格和缩进。括号风格最常用的是Allman和OTBS。Allman对应--styleallman特征是左括号单独占一行if (x 0) { doSomething(); }OTBS对应--styleotbs特征是左括号跟在语句行尾if (x 0) { doSomething(); }我建议嵌入式工程用Allman。原因很简单Keil自带的启动文件、标准外设库、HAL库代码基本都是Allman风格你新写的代码用同一种风格整个工程看起来很协调。除非你们团队明确规定用OTBS否则Allman是最稳的选择。缩进方面嵌入式C代码用--indentspaces4也就是4个空格缩进。有人喜欢用Tab觉得按键次数少但问题是不同编辑器里Tab显示宽度不一样有的显示4格有的显示8格代码换个人打开就乱了。用--convert-tabs把现有Tab全转成空格配合4空格缩进跨机器打开文件的显示效果完全一致。4.2 空格、指针与换行符细节决定代码好不好看格式化这件事括号缩进只是第一步真正让代码有“高级感”的是空格处理。AStyle提供了三个关键参数--pad-oper在运算符两边加空格。比如abc;会变成a b c;这个参数强烈建议开启因为运算符没空格读起来真的难受。--pad-header在if、for、while这些关键字后面加一个空格。效果是if(x 0)变成if (x 0)这个也建议开是主流代码风格。--unpad-paren把括号内部多余的空格去掉。比如if ( x 0 )会变回if (x 0)防止文件中被人手滑敲了很多空格进去。指针和引用的星号位置很多人容易忽略。AStyle通过--align-pointername把星号靠向变量名也就是uint8_t *pData如果习惯星号靠类型可以用--align-pointertype变成uint8_t* pData。这个纯属个人口味但在团队项目里必须统一。我给的建议是靠name因为当函数参数里出现多个指针时星号紧贴变量名不容易产生歧义比如void func(uint8_t *a, uint16_t *b)看起来就比void func(uint8_t* a, uint16_t* b)舒服一点。换行符也是容易被坑的地方。Windows工程用--lineendwindows保证是CRLF因为Keil在Windows下对LF换行的兼容性虽然还行但有些老版本工程如果混入LF汇编器可能不认。如果你后续用Git管理代码建议.gitattributes里统一指定换行符避免格式化工具和Git互相打架。4.3 一个足够通用的参数组合抄就完了根据我跑了几个项目的经验下面这套参数组合比较通用可以直接拿去用--styleallman --indentspaces4 --indent-switches --indent-cases --indent-preproc-block --pad-oper --pad-header --unpad-paren --align-pointername --align-referencename --convert-tabs --break-blocks --suffixnone其中--indent-switches是让switch里的case再缩进一层--indent-cases是让case后面的代码再缩进一层--indent-preproc-block是让多行预处理块内部的代码缩进对齐。--break-blocks则会在两个逻辑块之间插入一个空行比如两个if语句块之间代码结构更清晰。如果觉得--break-blocks插入空行太多可以去掉。如果希望接收参数的文件队列中也包含h文件在命令行里直接把通配符写上去就行。Keil的工具菜单配置我建议就只针对当前文件格式化批量格式化走命令行脚本这样不容易误操作。5. 实测总结格式化过程中遇到的坑与团队落地经验5.1 中文注释乱码怎么排查这个应该是很多人在AStyle上遇到的第一道坎。现象是格式化之后代码里的中文注释全部变成了乱码。网上一搜各种玄学说法都有但真正原因其实就是编码不匹配。排查方法很简单。先看Keil里Edit - Configuration - Editor - Encoding确认当前文件是什么编码。如果文件是GB2312/GBK编码AStyle默认按本地ANSI代码页处理一般没问题但如果你把文件转成了UTF-8就一定要在AStyle参数里加--utf8否则AStyle会按ANSI读取UTF-8文件中文自然就崩了。反过来也一样如果你文件是ANSI但加了--utf8同样会乱码。所以规则就一条文件是UTF-8就加--utf8文件是ANSI就不加。老工程最稳妥的做法是先把所有源码统一成某一种编码。如果你想统一成UTF-8记得Windows下最好带BOM头因为Keil的旧版本对无BOM的UTF-8识别不太好。统一编码虽然有点麻烦但从长远看解决了所有工具链之间的编码问题Git提交记录也不会频繁出现乱码diff。5.2 格式化后工程里多出几百个.orig文件这个问题的根源就是我前面说的没有加--suffixnone。AStyle默认在格式化前会把原始文件复制一份后缀追加.orig。你手动格式化一个文件无所谓但如果你对整个目录执行递归格式化一下能生成几百个.orig文件版本管理软件里全是红色看着就头大。如果已经发生了清理命令也很简单在Windows命令行进入工程目录del /s *.orig然后赶紧把AStyle配置里的--suffixnone加上。如果你确实需要备份建议用--suffix.bak至少名字明确不会跟其它工具有冲突。5.3 复杂宏定义被格式化破坏怎么办AStyle对宏的处理能力比一般编辑器强但仍然不是万能的。遇到那种用反斜杠续行的多行宏AStyle有可能会格式化得七零八落导致编译过不去。解决办法是用AStyle的“保护注释”把这些区段包起来。在你的多行宏上下分别加上这两行注释// *INDENT-OFF* #define MAX_TRY_TIMES \ do { \ x x 1; \ } while (0) // *INDENT-ON*AStyle遇到// *INDENT-OFF*和// *INDENT-ON*之间的内容会自动跳过不去动它。这个方法对驱动库里那种复杂的寄存器配置宏尤其好使我建议每个团队都把这个技巧写进规范文档里。5.4 批量格式化整个工程提高维护效率手动一个文件一个文件点菜单格式化适合日常写代码时的随手整理。但如果是从老同事手里接手一个祖传工程所有文件都需要统一风格那就用批量方式。在命令行下执行递归格式化AStyle.exe --styleallman --indentspaces4 --pad-oper --pad-header --unpad-paren --align-pointername --convert-tabs --suffixnone --recursive .\User\*.c .\User\*.h这条命令会把User目录下所有c和h文件递归处理一遍。注意通配符一定要加双引号否则命令行在某些环境下不会展开。如果还有其它源码目录比如Middlewares、Drivers就再追加类似的组。启动文件是.asm或.s后缀不在通配符范围内不会被误伤。更进一步你可以在Keil的Options for Target - User选项卡里把这条批处理命令加到After Build/Rebuild里这样每次编译完成后整个工程自动格式化所有工程师都省心。5.5 团队协作里如何统一代码风格很多人以为自动格式化是个人的效率工具其实它更大的价值在团队协作。代码审查的时候最烦的就是看到一份改动里夹杂着一堆空格调整和换行变动重要逻辑被淹没在格式噪音里。如果团队大家各用各的格式那每次merge都等于噩梦。解决办法是写一份统一的AStyle配置文件例如astyle.cfg然后提交到Git仓库。文件内容长这样styleallman indentspaces4 indent-switches indent-cases pad-oper pad-header unpad-paren align-pointername align-referencename convert-tabs break-blocks suffixnone然后Keil里的Arguments就可以简化成--optionsD:\YourProject\astyle.cfg !E这样不管谁去格式化规则都是同一份。新同事入职只要把AStyle装上、Keil菜单配好生成的代码格式跟大家一模一样。代码review的时候再也不会有“你这里该用Tab还是空格”这种无意义的讨论了。5.6 做这件事过程中最值钱的三个心得干这行久了你会发现自动格式化这事不是“能不能用”的问题而是“怎么用才不痛”。第一个心得是格式化工具一定要跟代码提交流程挂钩而不是只靠个人自觉。最好的状态是提交前所有人都能一键格式化如果做不到至少要在CI或者提交脚本里加一道格式检查把格式不合格的代码拦住而不是事后在review里人肉检查。第二个心得是格式化规则不是越多越好。有些人喜欢把AStyle参数调得很满什么空行都要插、什么括号都要动结果整个文件diff看起来面目全非。格式化最关键的是保持稳定和一致而不是追求审美上的极致。我见过有人开着--break-blocks跑了一遍全工程结果Git历史里每个文件都多了上百行变更后续查bug全靠Git blame的时候那叫一个痛苦。第三个心得跟工具本身无关但特别重要格式化永远不会替代“清晰的逻辑”。代码格式再漂亮设计烂还是烂。AStyle能做到的是把确定性的格式问题消灭掉让你把精力留给真正需要判断的问题。这一点想明白了你就不会指望靠一个工具来拯救一团糟的工程架构。我是从给老工程统一缩进开始接触AStyle的一开始觉得这不过是个偷懒工具用久了才发现工程里的格式问题解决了很多无谓的沟通争吵也跟着消失了。现在每次写新的STM32或者GD32工程我第一件事就是把Keil的自定义工具菜单配好省得以后再回头收拾残局。如果你也被五花八门的代码风格折磨过真的可以试试这套方案。
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进