第35章:增强工具链:Bonus Skills 实战
"核心技能把需求变成能跑的代码。增强技能管后面的事:代码能活多久、别人看得懂吗、文档跟得上吗。"
第 8 章讲过 goal-workflow 的七步流水线:/prd、/prd-to-spec、/to-issues、/goal、/review-it、/note-it、/ship-it。七个核心技能串成一条线,把需求一路推到上线。
这一章讲七个技能。/refactor 管重构,/modern-go 管 Go 代码现代化,/insight-diagram 管画图,/code-to-spec 管逆向文档,/humanize-it 管去 AI 味,这五个是流水线外的增强技能。/note-it 和 /ship-it 在流水线里,第 8 章各提了一句,这里展开讲。
这一章拿 goscapy 当例子。goscapy 是我写的一个 Go 网络库,把 Python Scapy 搬到 Go 生态,不依赖 libpcap、不用 CGO。给它加 IPv6 扩展头支持的过程中,这七个技能轮番上场。一个技能一节,说清楚它干什么、怎么干、什么时候用。
35.1 /refactor:专家级重构
/refactor 基于 Martin Fowler 的《重构》第二版。这本书把重构做成了一门手艺:先认出坏味道,再挑一个手法,小步改,改完跑测试。/refactor 把这套东西装进了 skill 里。
35.1.1 五条铁律
skill 开头列了五条 Golden Rules,说这是不能碰的红线。碰了任何一条,重构就变成了乱改。
一,行为不变。变的是代码怎么写,不是它做什么。重构前有测试,重构后测试照样过。引入行为变更不叫重构,叫重写。
二,小步走。每一步都是能编译、能过测试的最小改动。一步坏了,你知道是哪个改动引起的。
三,版本控制是朋友。开始前 commit,每成功一步 commit。随时能回退,重构做不下去可以整个放弃,不留后患。
四,测试是必需品。skill 里有句话:"没有测试,你不是在重构,你只是在改代码。" 目标代码没测试,先写特征测试,把当前行为钉住,防止回退。
五,一次只做一件事。重构不跟功能改动混在一起,也不一次重构两件不相干的事。一个 commit 只装一个重构操作。
35.1.2 什么时候不重构
skill 还给了一张反向清单。代码能跑、以后不会再改,别动。关键路径没测试,先补特征测试。赶工期的,把坏味道记下来,以后再改。没有明确收益的,不为了重构而重构。代码方向根本错的,那是重写,不是重构。
35.1.3 实战:重构 IPv6 扩展头
goscapy 加 IPv6 支持时,我让它先重构一段已有的序列化逻辑。
IPv6 头是 40 字节定长。第 4 到 8 字节是 PayloadLength,记录头后面跟了多少字节。这段长度计算散在 ipv6.go 和 ipv6_builder.go 两个文件里,写法还不完全一样。
/refactor 先报坏味道:
- 重复代码。PayloadLength 的计算逻辑出现在两处,改一处容易漏另一处。
- 特性依恋。
IPv6Builder反复钻进IPv6的内部字段去算长度,它该管的是组装,不是去翻别人家的字段。
然后给重构手法:
- 提取方法。把长度计算抽成
CalculatePayloadLength(),名字说清楚它算什么。 - 搬移方法。把序列化逻辑从 Builder 挪回 Layer,让数据归数据,组装归组装。
最后按五阶段安全协议走:补测试钉住行为,执行重构,验证测试,审查 diff,确认没有行为变更。整个过程不碰功能,只动结构。
35.2 /modern-go:Go 代码现代化
/modern-go 干的是 go fix 的活,再加一批社区公认的现代化规则。它读 go.mod 里的版本,把低于等于这个版本的所有转换规则跑一遍。
35.2.1 按版本门控的规则
skill 里的规则从 Go 1.0 排到 Go 1.27,每一条都标了最低版本。只有项目声明的版本够得着,才套这条规则。比如:
interface{}换any,Go 1.18 起。strings.Index找分隔符再切片,换strings.Cut,Go 1.18 起。- 循环里
+=拼字符串,换strings.Builder,Go 1.10 起。 - 手写取最小值的 if,换
min,Go 1.21 起。 - 清空 map 的循环,换
clear,Go 1.21 起。 for i := 0; i < n; i++换for i := range n,Go 1.22 起。
规则总数超过 35 条。版本越高,能套的越多。
35.2.2 语义迁移只报不改
有些规则会改行为,skill 只标成建议,不自动套。math/rand/v2 会换随机数流,固定种子复现结果的项目不能用。iter.Seq 迭代器会重塑 API 形态。os.Root 会改错误路径。这些它指出来,让你自己拍板。
35.2.3 实战:goscapy 升级到 1.26
goscapy 的 go.mod 写的是 1.22。我把 go 指令改成 1.26,再跑 /modern-go。它扫一遍,报出匹配的转换:
interface{}换any,找到 6 处。strings.Cut替换 Index 加切片的写法,找到 3 处。slices.Contains替换手写查找循环,找到 2 处。min/max替换手写比较,找到 1 处。new(expr)替换&v临时变量,找到 2 处。
跑完它给一张汇总表:文件、套了哪些规则、共改了多少处、哪些规则因为版本不够被跳过。改完每个文件再跑一遍 goimports,把多出来的 import 清掉。
35.3 /insight-diagram:架构可视化
/insight-diagram 读代码库,生成 UML 图、架构图、流程图。它一共支持 17 种图,分两大类:结构性图形管静态结构,行为性图形管交互过程。最后用 /architecture-diagram 渲染成 HTML 加 SVG,存到 docs/ 目录。
35.3.1 十七种图
结构性图形 8 种:系统架构图、类图、对象图、组件图、部署图、包图、复合结构图、剖面图。
行为性图形 9 种:流程图、用例图、活动图、状态机图、序列图、通信图、定时图、交互概览图、泳道图。
最常用的是两个非 UML 的:系统架构图看全局组件关系,流程图看主流程和分支。
35.3.2 先分析,再选择
skill 不直接画。它先读项目:入口文件、子目录的 CLAUDE.md、源码结构、接口定义、依赖注入点。提炼出组件清单、关系图、核心类型、主流程,然后问你画哪几张。
它给的默认推荐是架构图、序列图、流程图三件套。架构图看整体,序列图看关键交互,流程图看流程分支。
35.3.3 实战:goscapy 的架构
我让它分析 goscapy。它扫出 6 个子包、12 个协议层,推荐了四张图:
- 包依赖关系图。哪个包依赖哪个,有没有环。
- Layer 继承层次图。以太网、IP、ICMP 这些层的继承关系。
- Builder API 交互序列图。
NewEthernet().SrcMAC(...)一路链式叠加的过程。 - 数据包处理流水线。收包、解析、构造、发送的流程。
每张图生成后它跑一个几何校验脚本,查三件事:箭头落点对不对、框有没有重叠、框间距够不够。查出 ERROR 必须修,修完再跑,直到归零。这一步保证了图能看,不会叠成一团。
35.4 /code-to-spec:逆向生成文档
/code-to-spec 反向走一遍。它读一个已经写好的项目,产出一份 SPEC,说清楚这个项目干什么、怎么搭的、对外暴露什么契约。输出是一份活的文档,新成员靠它能上手,重写项目靠它能重建。
35.4.1 十个方面
它扫十个方面:项目身份(README、go.mod、git 历史)、架构、技术栈、功能、数据模型、API 面、配置、外部依赖、测试质量、部署运维。
35.4.2 十二章 SPEC
扫完它合成一份 12 章的 SPEC:项目概述、技术栈、项目结构、数据模型、API 面、配置、外部依赖、业务规则、非功能特性、测试策略、已知缺口、附录。
不是每章都要。项目没有数据库,数据模型那章就省掉。没有部署配置,部署那章也不写。
35.4.3 几条反模式
skill 特别划了几条线,防的是 AI 逆向文档时的老毛病:
- 不编动机。搞不清某个东西为什么存在,就说搞不清,不瞎编理由。
- 不贴代码。描述行为和契约,不复制实现。
- 不写运行时状态。SPEC 记的是设计,不是当下内存里的值。
- 不假设 README 准确。README 经常落后于代码,每一条都要拿真实实现核对。
35.4.4 实战:给贡献者一份地图
goscapy 想吸引贡献者,但没有一份像样的项目文档。我让 /code-to-spec 扫整个仓库,产出一份 12 章的 SPEC。
最有用的是 API 面那章。它把 Builder API 和 Shortcut 函数逐个列出来:每个函数签名、参数、返回值。新贡献者照着这张表就能看懂两套构造路径的区别,不用自己翻源码猜。
它还在已知缺口里诚实写了一条:IPv6 分片重组还没实现,测试只覆盖了 NS 和 NA 两种 NDP 消息。这句话比任何宣传都实在。
35.5 /note-it:实现笔记
/note-it 在第 8 章出现过,是流水线的倒数第二步:/review-it 之后、/ship-it 之前跑一次。它把实现过程中那些不会写进代码的东西记下来,生成 docs/issue#XXXX.html。
35.5.1 四类记录
笔记分四类:
- 设计决策。spec 没写清的地方,你选了哪条路、为什么。
- 偏离。你故意不按 spec 做的地方,spec 怎么说、你做了什么、为什么更对。
- 取舍。你考虑过的备选方案、各自的利弊、为什么选了这个。
- 待确认。你拿不准的假设,希望用户验证或后续跟进。
每一类就算没内容,也要写一句 "None" 说明为什么没有。
35.5.2 实战:IPv6 的决策
goscapy 的 IPv6 实现做完,我让 /note-it 记了一页:
决策一,为什么用自定义 IPv6Addr,不用 net.IP。原因是 net.IP 是 4 或 16 字节的可变长度,序列化时还得判断一次。IPv6 地址固定 16 字节,自定义类型能直接按定长读写,省掉判断。
偏离一,原计划支持全部 NDP 消息类型,实际只实现 NS 和 NA。RS、RA、Redirect 在当前阶段的收益太低,先放下。
待确认一,IPv6 Fragment Header 怎么处理。这件事影响分片重组的设计,得先定下来。
这些内容代码里不会有,spec 里也没有。它记的是"当时为什么这么干",隔半年回来看,价值比注释还高。
35.6 /ship-it:提交交付
/review-it 审完,/note-it 记完,代码还在功能分支上。最后一步是 /ship-it:提交、推送、建 PR、合入、关 Issue,五步跑完。
35.6.1 五步交付
skill 把交付拆成五步,每步有硬性规则。
第一步 commit。只暂存本 Issue 相关的文件,别的变更一律不碰。commit message 必须带 Issue 编号,比如 Add IPv6 extension headers (#42)。以后从 Issue 反查代码,靠的就是这个编号。
第二步推分支。分支名有约定:feat/issue-42-ipv6-ext 这样的格式,Issue 编号直接写在名字里。
第三步建 PR。PR 描述里写 Closes #42。合入后 GitHub 自动关掉这个 Issue,不用人操心。
第四步合入。先跑 gh pr checks,CI 全绿再合。默认 squash merge,整个 Issue 压成一个 commit,历史干净。--delete-branch 顺手把远程分支也删掉。
第五步补实现总结。PR 合入后,在 Issue 上加一条评论:核心变更有哪些、PR 编号、commit hash。以后翻 Issue,一眼就能跳到代码。
35.6.2 出错怎么办
skill 连错误处理都列好了。CI 挂了,修完追加 commit 再推。有冲突,rebase 到最新的 main,解决完 force push。合入被分支保护规则挡住,说明还差 required review,找人 approve。Issue 没被自动关掉,多半是 PR 描述里漏了 Closes #N,手动补一句 gh issue close。
35.6.3 实战:交付 IPv6 支持
goscapy 的 IPv6 扩展头做完,/ship-it 一趟跑完:commit 带上 Issue 编号,PR 描述写清楚实现了哪些扩展头,checks 全绿后 squash 合入,Issue 上留下实现总结。从提交到合入,中间没让人盯过任何一步。
交付这件事最容易被草率带过。commit message 随手写个 update,PR 描述空着,Issue 忘了关,分支留了一堆。/ship-it 把规矩写死在流程里:每一步都是它替你做,每一步都做得规规矩矩。
35.7 /humanize-it:去 AI 味
/humanize-it 管文档的最后一道工序:把 AI 生成的文档改得有人味。它下面挂了三个子 skill,按文档类型挑一个起手,效果不好就换下一个,最多迭代 42 轮。
35.7.1 三个子 skill
humanizer-zh,管通用文本。识别 24 种 AI 写作模式,注入节奏和观点。humanize-chinese,管通用、学术、长文本。20 多条规则加统计特征,量化打分。technical-writing,管技术文档。去黑话、去主持腔、证据先行。
技术文档默认先走 technical-writing,通用文章先走 humanizer-zh。
35.7.2 迭代到达标
每改写一轮就打个分,百分制,五档权重:AI 痕迹去除占 30%、自然度 25%、信息完整 20%、风格一致 15%、可读性 10%。过了 80 分就停。没过就换下一个 skill,在上一版基础上继续改。同一个 skill 连续三轮分数不涨,也换。
35.7.3 实战:润色 TECH_SPEC.md
/code-to-spec 生成的 TECH_SPEC.md 我交给 /humanize-it 处理。它先指出问题:additionally、moreover、in conclusion 这类连接词太多,段落结构过于工整,读起来像模板。
然后迭代:第一轮去掉连接词和套话,第二轮把长句拆短,第三轮把评价性的形容词换成具体事实。三轮下来,同一份文档,读起来像是人随手写的,而不是机器吐出来的。
35.8 本章小结
- 七个技能覆盖了交付前后的辅助工作:重构、现代化、画图、逆向文档、实现笔记、提交交付、去 AI 味。
/refactor和/modern-go保代码库健康,一个管结构,一个管语法习惯。/insight-diagram和/code-to-spec解决文档滞后问题,一个出图,一个出文。/note-it留住实现过程中的隐性知识,尤其是"当时为什么这么干"。/ship-it把交付的规矩写死:commit 关联 Issue,PR 自动关单,合入后留一条实现总结。/humanize-it给 AI 生成的内容收个尾,去掉那股机器的味道。
核心技能让代码跑起来,增强技能让代码活得久。两套一起用,工程才算完整。