ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

Golang商业级项目AI编程协作:提示词、开发日志与验收体系

2026/9/18 15:35:52 拓冰建站 浏览量
Golang商业级项目AI编程协作:提示词、开发日志与验收体系 两个月59 篇开发日志一个从零起步、最后能跑在真实业务里的 Golang 商业级项目。这事儿干完之后我最大的感受不是AI 真好用而是我以前用 AI 的方式太糙了。前面那篇聊的是工具选择、环境搭建、模型能力边界这些偏外功的东西这一篇我想把内功部分摊开讲——同样一个 AI为什么有的人用起来像给自己配了个能扛事的工程师有的人用起来像在跟一个记性差、还老爱自由发挥的实习生扯皮。差别不在模型在于你给它什么上下文、你让它按什么节奏干活、你用什么方式验收它的产出。这篇内容适合已经能写 Golang、准备接第一个商业项目的人也适合写了几年业务代码、想把手上的重复劳动真正压下去的同行如果你刚开始接触 AI 编程里面的日志体系和提示词骨架也能直接抄去用。1. 心法总纲AI 是需要带教的初级工程师不是许愿池1.1 从帮我写个功能到这是一份任务单我前二十篇日志里最大的浪费全花在许愿式提问上。什么叫许愿式就是丢一句帮我写个订单支付模块然后等它吐出一大坨代码。它确实会吐而且看起来还挺像回事结构体、接口、方法名都齐了甚至还有注释。但你一旦把它放进真实项目里问题立刻露出来——它不知道你的订单状态机是怎么定义的不知道你的金额用的是 int64 存分还是 decimal不知道你的日志组件封装在哪一层于是它自己发明了一套还发明得挺自信。后来我改了做法把每一次请求都当成给一个刚入职的初级工程师下任务单。任务单里必须有的东西这个功能在业务里扮演什么角色、输入输出的边界在哪、依赖哪些已有的包、错误该怎么往外抛、有没有并发要求、验收标准是什么。听起来麻烦但实际写下来也就七八行字省下来的是后面两小时的返工。举个真实对照。许愿式写一个用户余额扣减的接口。任务单式在 internal/service 下新增 BalanceService.Deduct输入 userID int64、amount int64单位分、bizNo string要求1用现有 repository 层的 TxManager 开事务2扣减前先查余额不足时返回 ErrInsufficientBalance已定义在 internal/errs3同一 bizNo 重复调用必须幂等靠唯一索引兜底4失败时用项目里已有的 logx.WithCtx 记录结构化日志字段包含 userID、amount、bizNo5不要引入新依赖。后者出来的代码一次就能编译过八成能直接用。心得你给 AI 的每一条约束都是在减少它自由发挥的空间。约束不是限制它的能力是限制它的想象力的跑偏方向。1.2 上下文预算把对话窗口当纸巾用我踩过最狠的坑是一个对话窗口从项目第一天用到第十五天。前期它确实越来越懂我到了后期它开始把三天前改掉的字段名当成现存的把已经废弃的接口继续调用甚至把两个不同模块的命名风格混在一起。这不是模型变笨了是上下文里塞了太多过期信息它分不清哪条是当前有效的。我的处理方式后来固定成两条规则。第一一个任务一个窗口任务做完就关不心疼。第二跨任务需要共享的信息落到文件里不靠对话记忆。落到文件里的东西就是我的那 59 篇开发日志以及项目根目录下的一个 ARCH.md。ARCH.md 我控制在 200 行以内只写三样目录结构树、每一层的职责一句话、全局约定命名、错误码分段、日志字段规范。每次开新窗口第一件事就是让它读这个文件再读相关的三五个源码文件。这样做的好处是上下文永远是当前快照而不是一锅陈年乱炖。实测下来同一个需求用干净窗口加 ARCH.md 的方式返工率大概能降一半以上。还有一个细节不要用接着上面的改这种指代。指代意味着你要依赖对话历史而对话历史一旦被截断或者被摘要压缩指代就断了。我在日志里吃过一次亏第七天说的上面的那个 handler第二十一天再让它改它改的是另一个文件。从那以后我要求自己每次引用都写全路径、全文件名、全函数名。1.3 让它先复述再动手这是我在第四十篇日志之后才养成的习惯但价值极高。发完任务单之后我不直接让它写代码而是先让它用五句话复述它认为要改哪些文件、每个文件做什么、有哪些不确定的地方。这一步基本上每次都能抓出问题——要么它理解偏了要么它发现我的任务单里漏了边界条件要么它指出某个依赖在当前代码里不存在。这个复述-确认-执行的三段式看起来多花了两分钟实际上把沟通成本从写完再发现方向错了降到了还没写就纠正了。尤其是涉及数据迁移、对外接口变更这类不好回滚的操作这两分钟能救你一整天。2. 提示词在 Golang 工程里的落地姿势2.1 一条能反复复用的提示词骨架网上那些万能提示词模板大部分在真实项目里不好使因为它们假设你面对的是一个空白项目。商业项目的特点是有历史包袱、有既定风格、有不能碰的红线。所以我的提示词骨架是围绕约束组织的不是围绕角色扮演组织的。我常用的骨架长这样顺序基本固定背景这是什么项目什么语言版本什么框架代码在哪个目录。任务一句话说清要做什么动词开头。边界允许改哪些文件不允许动哪些文件不允许引入什么依赖。规范命名风格、错误处理方式、日志方式、注释语言。验收怎么算做完了需要哪些测试需要跑哪些命令。输出格式只给完整文件内容不要给 diff 片段不要给省略号。最后一条特别重要。如果你不强调给完整文件模型很喜欢给你一个片段中间写个 // ... 省略你还得自己去拼接。在 Golang 里一个文件动辄几百行拼接一次就是一次出错机会。我一般在任务单末尾加一句输出必须是可以直接保存并编译的完整文件内容。另外关于语言版本要写清楚比如 go 1.22因为不同版本的写法差异很大。举个最直观的老版本循环变量捕获问题Go 1.22 之后每一轮迭代都是新变量但如果你不告诉它版本它可能给你加一堆多余的局部变量拷贝看起来像是老代码风格上很割裂。2.2 先让它写测试再让它写实现这条是我整个项目里收益最高的单个技巧。流程是我先手写接口签名和数据结构这部分绝不交给 AI因为它是设计的核心然后把签名丢给 AI让它先写表驱动测试用例覆盖正常路径、边界、错误分支。测试写完我先审一遍——审测试比审实现快得多因为测试揭露的是你到底想让这个函数干什么。type DeductReq struct { UserID int64 Amount int64 // 单位分 BizNo string } func TestBalanceService_Deduct(t *testing.T) { cases : []struct { name string balance int64 req DeductReq wantErr error }{ {正常扣减, 10000, DeductReq{1, 500, b1}, nil}, {余额不足, 100, DeductReq{1, 500, b2}, errs.ErrInsufficientBalance}, {金额为零, 10000, DeductReq{1, 0, b3}, errs.ErrInvalidAmount}, {重复单号, 10000, DeductReq{1, 500, b1}, nil}, } for _, c : range cases { t.Run(c.name, func(t *testing.T) { // 依赖用内存实现替身不连真实数据库 }) } }测试确认之后再让它基于测试写实现。这时候它的目标非常明确让这些用例全绿。你会发现它几乎不会跑偏也不太会自作主张加一堆你没要求的功能。这就是先立靶子再开枪的道理——没有靶子它就会自己画一个。注意AI 写的测试里有个常见毛病喜欢断言具体的错误字符串而不是错误值。这在 Golang 里是反模式因为字符串一变测试就碎。我在任务单里会明确要求用 errors.Is 做断言禁用字符串比较。2.3 让它自己找反例还有一个我很喜欢的用法实现写完之后让 AI 扮演挑刺的代码审查者专门找自己刚才写的代码的问题。提示词大概是你现在是资深 Golang 审查者请找出上面这段实现的至少五个潜在问题重点是并发安全、错误处理、资源释放、边界条件不要夸只列问题。这招之所以有用是因为生成和批判是两种不同的任务模式模型在批判模式下的注意力会集中在缺陷上。我实测它能揪出不少真问题比如忘记 defer cancel、比如在循环里起了 goroutine 但没做错误收集、比如把 map 当成并发安全的结构用。当然它也会报一些假警报这需要你自己判断。3. 59 篇开发日志这套体系是怎么滚出来的3.1 日志模板与颗粒度控制先说清楚这 59 篇日志不是流水账也不是日记。它是给未来的自己和未来的 AI看的工程档案。我的模板固定三段今天做了什么客观描述、为什么这么做决策理由、留下什么悬念未解决的问题、下次的入口。颗粒度上我摸索出一个平衡点一篇日志控制在 300 到 600 字。太短了没信息量第二天自己都看不懂太长了写起来有负担坚持不下来。我一般在每天收工前花十分钟写正好也是回顾当天决策的过程。写日志最重要的一点记录被否决的方案。这个是我以前完全忽略的。第一次写的时候我只记做了什么结果第五周遇到一个问题明明感觉以前考虑过类似方案但完全想不起来当时为什么放弃了只能重新踩一遍坑。后来我加了否决记录比如考虑过用缓存做幂等但因为要引入额外组件且当前 QPS 不需要暂时用唯一索引兜底。这类信息在两个月后回头看价值比代码本身还高。3.2 日志反向投喂给 AI 建一份长期项目记忆日志写完了还有第二步它得能喂给 AI。这就要求日志里的关键决策要能被检索。我的做法是每篇日志标题带上模块前缀和日期正文里的关键决策用固定句式写比如决策xxx。理由xxx。影响范围xxx。这样我只要关键词搜一下就能把某个模块的所有决策拉出来拼成一段上下文丢给 AI。这个习惯带来的直接好处是当我半个月后重新回到订单模块改东西时不需要重新读代码理解设计直接把那五六篇相关日志丢给 AI它立刻就知道当前的架构约定是什么、哪些方案已经被否了。相当于给它装了一份持续更新的项目记忆而不是每次都从零开始。还有个小技巧日志里我会顺手记下AI 在这块常犯的错。比如这个模块 AI 容易把分转成元、容易漏判负数、容易在事务里做网络调用。下次在同一个模块让它写代码时我把这条贴在任务单里命中率明显提升。这算是一种针对特定代码库的防错清单比通用提示词有效得多。3.3 三类必须记的东西和两类不必记的必须记的第一类是接口契约变更尤其是对外暴露的接口。这个不用解释漏记一次就是线上事故。第二类是数据结构变更特别是数据库字段的增删改连带迁移脚本的版本号。第三类是环境相关的坑比如某个依赖在特定内核版本下需要额外参数、某个测试必须串行跑否则会互相污染。不必记的有两类。一类是纯粹的语法细节比如某个函数怎么调用这个查文档比翻日志快。另一类是当时的情绪比如这个 bug 折腾了我三小时写一次无妨写多了日志就变成流水账检索价值下降。经验日志的价值不在于记录量在于可检索性。我宁可用统一句式写 300 字也不用自由发挥写 1000 字。4. 商业级项目里的 AI 协作细节4.1 目录分层与包边界先把规矩定死项目一开始我就定了一层不太标准的目录结构但我觉得对 AI 协作特别友好。大致是cmd 放入口internal/handler 只做协议转换internal/service 放业务逻辑internal/repo 放数据访问internal/errs 集中放错误定义internal/logx 放日志封装internal/config 放配置加载pkg 放确实需要对外暴露的通用件。为什么这套结构对 AI 友好因为边界清晰。handler 层不许碰数据库service 层不许直接读配置文件repo 层不许写业务判断。这些不许我在 ARCH.md 里写一次之后每次任务单里提一句遵循 ARCH.md 的分层约定它基本就不会越界。反过来如果项目结构是那种哪里方便写哪里的风格AI 就会随机选一个文件塞代码最后你得到一个几百行的巨型文件。命名风格也是同理早定早省事。我要求接口用动词开头加 -er 后缀Deductor、Notifier 这类错误变量统一 Err 前缀测试函数统一 Test 加被测对象加方法名。定好之后AI 生成的代码风格一致性比我预想的好很多几乎看不出来是人机混合的产物。4.2 自定义 errorAI 最容易写出看着对的地方这块必须单独拎出来讲因为我在日志里专门记了七次相关踩坑。AI 写错误处理有几大顽疾一是喜欢用 errors.New 到处建新错误导致上层没法用 errors.Is 判断二是喜欢用 fmt.Errorf 包了但不加 %w链条断掉三是喜欢定义一大堆平行的错误类型最后谁也不认识谁。我的做法是在 internal/errs 里集中定义一套结构然后把这个文件作为固定上下文每次都喂给 AI。结构大概是这样type AppError struct { Code string // 业务错误码如 user.balance.insufficient Message string // 面向用户的可读信息 Cause error // 底层原因可为 nil } func (e *AppError) Error() string { if e.Cause nil { return e.Code : e.Message } return e.Code : e.Message : e.Cause.Error() } func (e *AppError) Unwrap() error { return e.Cause } func New(code, msg string) *AppError { return AppError{Code: code, Message: msg} } func Wrap(err error, code, msg string) *AppError { if err nil { return nil } return AppError{Code: code, Message: msg, Cause: err} }关键的几个约定我在任务单里反复强调跨层传递错误必须用 Wrap 保留 Cause判断错误统一用 errors.Is 或 errors.As禁止比较字符串对外返回给客户端时只暴露 Code 和 MessageCause 只写日志不外泄。最后这条是安全考量Cause 里经常带着 SQL 语句或者内部路径直接返给前端是隐患。还有一点很实用我让 AI 帮我写了一个错误码检查的小工具编译期扫描所有 New 和 Wrap 的调用把 Code 参数和 errs 包里的常量做比对出现的字面量就直接报错。这样能防止有人包括 AI随手写个字符串当错误码导致错误码表失控。4.3 并发、context 与资源释放三个高频翻车点并发这块 AI 的表现很有意思它能写出语法正确的并发代码但经常漏掉生命周期管理。我总结下来三个高频问题。第一个是 context 传递断裂。比如 handler 里带了 ctxservice 层签名里也有 ctx但中间某个辅助函数偷懒没传导致超时和取消信号传不下去。我的对策是在任务单里硬性规定任何可能阻塞的函数第一个参数必须是 context.Context命名统一用 ctx。并且我让 AI 帮我加了一个静态检查规则凡是函数体里出现网络调用或者数据库调用但签名里没有 ctx 的直接标记。第二个是 goroutine 泄漏。典型场景是起了 goroutine 却没等它返回或者忘了在 ctx 取消时退出。我后来基本不用裸 go 关键字统一用 errgroupg, ctx : errgroup.WithContext(ctx) g.SetLimit(8) // 控制并发度防止下游被压垮 for _, id : range ids { id : id g.Go(func() error { return s.fetchOne(ctx, id) }) } if err : g.Wait(); err ! nil { return errs.Wrap(err, batch.fetch.failed, 批量获取失败) }SetLimit 这个细节值得说一下。不加限制地并发在本地测试时看不出问题一上量就把下游打挂。我踩过一次批量任务并发几百个请求把内部服务打出了限流告警。后来所有批量并发都强制带并发度上限这个值我一般取下游 QPS 配额的十分之一作为起点再压测调整。第三个是资源释放。defer 写是写了但写在错误的位置。比如在循环里 defer file.Close()看起来没问题但如果循环几千次文件句柄就爆了。或者先判断 err 再 defer结果 err 分支里直接 return资源没释放。这类问题 AI 生成的代码里出现频率不低我在审查时会把所有 defer 单独过一遍。提示凡是看到defer出现在for循环体内先停下来想三秒。多数情况下应该把循环体抽成函数让 defer 跟着函数作用域走。4.4 数据访问层生成代码要验收不要迷信我们这个项目的数据访问层用了代码生成的路子SQL 写在单独的 .sql 文件里通过生成工具产出类型安全的 Go 代码。AI 在这个环节的角色是写 SQL 和写调用方两边都需要人把关。SQL 这边AI 特别容易漏的几件事分页查询没加稳定的排序字段导致翻页重复或漏数据统计查询忘了处理 NULL 导致扫描报错批量更新没限制影响行数导致全表更新。第二条我印象很深一个 sum 查询在数据为空时返回 NULL扫进 int64 直接报错测试环境数据多没暴露一上线就炸了。后来我要求所有聚合查询必须显式处理 NULL在 SQL 里用 COALESCE 或者在 Go 侧用 sql.NullXxx二选一不能含糊。调用方这边重点验收三件事事务边界对不对、错误有没有正确包装、ctx 有没有一路传下来。事务边界我一般会画个简单的时间线在脑子里过一遍——哪些操作必须在同一个事务里哪些可以异步。AI 很容易把发消息、调外部接口这类操作塞进事务里导致事务时间被拉长锁竞争加剧。这个必须人工改。另外索引这块AI 写的查询语句有时会出现在索引列上做函数运算导致索引失效。我养成了一个习惯写完关键查询后用 EXPLAIN 看一眼执行计划。这一步花不了一分钟但能挡住相当一部分性能问题。慢查询在开发环境几乎看不出来因为数据量小全表扫也是毫秒级一上线就是秒级。4.5 启动流程与依赖装配让 AI 帮你画依赖图依赖装配这块我用了显式注入没用反射那套自动注入框架。原因是排查问题时显式注入的代码你能一眼看出谁依赖谁自动注入虽然写得爽但出问题的时候调试成本高。AI 在这块的用法是让它根据构造函数签名帮我生成装配顺序和缺失依赖的提示。具体做法是让 AI 扫描所有 service 和 repo 的 New 函数列出依赖关系然后按照拓扑顺序生成启动代码。它做得比我手动排列靠谱而且当我新增一个模块忘了注册时让它对比一遍就能发现。启动流程我要求全部显式报错返回不用 panic因为启动失败的消息需要清晰可读比如配置项 db.max_open_conns 缺失而不是一个栈追踪。还有个小细节优雅退出。这部分 AI 经常写得不够完整只处理了 HTTP 服务的 Shutdown忘了后台任务、定时器、连接池的关闭顺序。我的做法是明确在任务单里列出要关闭的组件和顺序让它照着写。顺序错了会导致正在处理的任务被中途打断日志里出现一堆context canceled排查起来很烦。5. 踩坑实录与排查速查表5.1 常见问题速查我把两个月里反复出现的坑整理成了一张表基本上每周都会翻一次。现象常见根因快速排查处理方式编译过但运行报错字段名对不上旧结构全局搜字段名先对齐 ARCH.md 再改错误判断失效用了 fmt.Errorf 没加 %w搜 fmt.Errorf换成 errors.Wrap 或加 %w分页数据重复排序字段不唯一看 ORDER BY加主键作为兜底排序批量任务打挂下游并发无上限看 go 关键字数量换 errgroup 加 SetLimit内存缓慢增长goroutine 泄漏看 goroutine 数量曲线检查 ctx 退出路径事务超时事务里做了网络调用看事务代码块把外部调用移出事务日志缺字段用了全局 logger搜日志调用点统一走 ctx 里的 logger空数据扫描失败聚合返回 NULL看 SQL 聚合函数COALESCE 或 Null 类型这张表的价值在于它是我自己项目里真实出现过的问题不是通用清单。你的项目大概率会有一张不同的表但整理方法是一样的每次修完 bug问自己一句这个坑能不能被一句话描述、被一句话排查能就记下来。5.2 几条不太上桌面的经验第一条让 AI 改代码时别用优化一下这种动词。优化是主观的它会把能跑的代码改成另一种能跑的代码风格还变了review 起来比重写还累。我会具体说减少一次循环内分配或者把这里的两次数据库查询合并成一次。第二条涉及金额、时间、权限的判断永远自己写。不是不信任 AI是这三类逻辑出错成本太高而且错得不明显。金额的单位转换、时间的时区处理、权限的边界条件我全部手写AI 只负责写测试来验证我的手写实现。第三条AI 生成的注释要删掉一部分。它写的注释风格是这个函数做什么而好的注释应该写为什么要这么做。前者在代码改名后会立刻过期变成误导。我现在的做法是让 AI 不要写解释性注释只在有非显然决策的地方写一行理由其余交给清晰的函数命名。第四条定期做一次AI 视角自检。具体做法是把当前模块的目录结构和一个典型文件丢给 AI问它如果让你在这个模块加一个新功能你会加在哪个文件、用什么命名、遵循什么错误处理方式。如果它的回答和你的预期一致说明这个模块的约定足够清晰如果它答错了说明你的目录不够自解释或者约定没落到文件里只存在你脑子里。这个自检我做了三次每次都发现了一些可以改进的地方。第五条关于测试数据。AI 生成的测试用例边界值往往只覆盖了 0 和负数但真实业务里的边界更微妙刚好达到限额、刚好超出一天、刚好在有效期最后一秒。这类边界需要你把业务规则明确写出来它才能生成。所以我在写任务单时会把相关业务规则原文贴进去哪怕它看起来跟当前函数没关系。两个月 59 篇日志写下来我最大的体会是AI 提效的前提是你自己先想清楚。想清楚接口签名、想清楚错误语义、想清楚并发边界这些事它替不了你也不该替你。它真正擅长的是把你想清楚的东西快速落成一致的代码以及在你想漏的地方帮你挑刺。所以我现在的工作节奏变成了先花十分钟把任务单和边界写清楚再让它写测试测试确认后让它写实现最后让它挑自己的刺我负责拍板和验收。这套流程跑顺之后一个中等复杂度的模块从设计到可测基本能压在半天之内而代码质量比我以前纯手写时更稳定因为约束是提前定死的不会写着写着就跑偏。如果你刚开始搭这套流程我建议先从一个独立的小模块试起把日志模板和提示词骨架固定下来跑通两三轮之后再往核心业务上铺这样试错成本最低。