Go 项目里命令一多,就会出现一个很常见的问题:每个人本地跑的命令不一样,CI 里跑的又是另一套。
最后就会变成“我本地是好的”。
所以我更倾向于把常用命令收口到一个地方:要么 Makefile,要么 Taskfile。工具选哪个不是重点,重点是本地和 CI 复用同一批入口。
这里我吃过的亏是:README 写了一套命令,CI 写了一套命令,开发同学本地又有自己的脚本。最后问题不是工具不够,而是没有一个“项目认可的入口”。命令入口不统一时,任何排查都会先浪费在“你到底跑的是哪条命令”上。
我一般会保留这些命令
一个普通 Go 服务,至少可以有这些命令:
或者 Taskfile:
命令不需要多,但名字要稳定。新人进项目后,不应该先去 README 里翻十几条不同命令。
Makefile 的写法
Makefile 最大的优点是常见,很多 CI 环境也天然支持。一个简单版本可以这样:
这里我会显式写 .PHONY,避免目标名和文件名冲突。构建产物统一放到 bin,临时文件不要散在项目根目录。
如果项目要兼容 Windows,本地 Make 的体验可能会有差异,这时可以考虑 Task。
Taskfile 的写法
Task 的配置更像 YAML,对不熟悉 Makefile 的人更友好:
Taskfile 的另一个好处是可以比较自然地组织依赖关系,比如 build 前先跑 test:
但我不会为了用 Task 而删掉已有 Makefile。如果团队已经熟悉 Makefile,继续用就行。
CI 不要另写一套逻辑
CI 里最容易犯的错,是把命令重新写一遍:
短期没问题,长期容易和本地命令分叉。更好的方式是让 CI 调本地同一个入口:
或者:
这样本地修问题和 CI 失败看到的是同一套命令,排查成本会低很多。
我一般会把命令分成几类:本地开发、代码质量、测试、构建发布、清理。不要把一个命令做得太重。比如 make test 就只跑测试,不要顺手构建镜像、推送镜像。
我会单独留一个重一点的 ci 入口,但它只是组合已有命令:
这样本地想完整模拟 CI 可以跑 make ci,但日常调试不会被迫跑一大串流程。
Makefile 和 Taskfile 本质上都是命令入口。项目里真正需要统一的是:本地和 CI 跑同样的命令,常用命令名字稳定,构建产物位置固定,复杂流程拆开。