从AI Coding到AI Engineering:16万行代码的工程化实践
2026/9/23 7:52:58 网站建设 项目流程

1. 项目背景:16 万行代码,从“手写”到“AI 协同”的转折点

先交代一下背景。这个项目是一个中大型业务系统,涵盖管理后台、用户端 API、定时任务、消息推送、数据对账等多个模块。按传统开发方式估算,16 万行代码大概是一个 6 到 8 人团队忙活大半年的产出。但这次我们实际投入的核心开发只有 3 个人,周期压缩到了 4 个月,而且代码量还比最初预估的多出了将近 20%。

这 16 万行不是硬凑出来的。它包括了前端 TypeScript 代码约 4.8 万行、后端 Go 代码约 6.2 万行、Python 脚本与数据迁移约 1.6 万行、SQL 与数据库迁移文件约 1.2 万行,以及测试代码、配置文件和流水线定义约 2.2 万行。其中,由 AI 直接生成或辅助生成的比例,我后来粗略统计了一下,大概在 70% 以上。但真正有意思的不是这个数字,而是从“让 AI 写代码”到“把 AI 写代码这件事工程化”之间,我们踩出来的那条路。

如果你是刚接触 AI Coding 的开发者,或者正在犹豫要不要把 AI 引入团队日常开发,这篇文章会比较适合你。我会把从工具选型、规范制定、多智能体协作,到代码审查、质量兜底、常见坑位排查的完整过程都拆开讲,尽量少讲虚的,多给能直接落地的东西。

先说一个核心观点:AI Coding 不等于“AI 帮你按回车”。早期我们确实走过一段“让 AI 自由发挥”的弯路,结果就是代码风格混乱、模块边界模糊、接口调用像迷宫,甚至出现同一个业务逻辑在三个文件里各写一遍的情况。后来才意识到,AI Coding 能不能产生价值,依赖的不是模型有多强,而是你给它划定的工程边界有多清晰。当我们把这件事从“写代码”升级成“定义问题、编排任务、审查产物”的工程流程后,效率才真正起了飞。这个过程,就是所谓的从 AI Coding 到 AI Engineering。

2. 整体设计拆解:为什么 AI 写代码需要一套“工程宪法”

2.1 没有约束的 AI Coding,代码会失控到什么程度

先聊聊我们吃过的一个亏。项目启动第一周,我们图省事,直接把一个模块的需求文档扔给 AI 工具,让它“看着写”。产出的代码确实能跑,单测也过了,但问题出在几个隐蔽的地方。

第一个是代码风格分裂。同一个项目的不同文件里,有的用 interface 定义数据类型,有的用 type 别名,有的干脆直接 any;错误处理一部分返回 error,一部分返回 null,一部分直接 panic。代码 review 的时候,每个人都觉得“这不太对”,但又说不出具体哪里不对,因为每一段单独看都说得通。

第二个是重复造轮子。用户权限校验这个逻辑,AI 在我们没提醒的情况下,先后写了三份,一份在中间件里,一份在 service 层,还有一份直接写在 handler 里。表面上每份都能跑,但改一个需求就要改三处,漏改一处就是线上 bug。

第三个是过度设计。AI 很喜欢把简单的功能包装成复杂的抽象。比如一个简单地按 ID 查询用户的操作,它给封装了接口 + 实现类 + 泛型仓库 + 缓存装饰器,还加了一层事件发布。看起来“干净”,实际维护成本高得离谱。

这些问题的根源在于,AI 的训练数据来自海量开源项目,而开源项目里既有写得好的,也有写得烂的。如果不给它一套明确的、跟当前项目绑定的约束规范,它就会在这些风格之间随机游走。所以我们的第一个结论是:上 AI Coding 之前,必须先有一份“工程宪法”。

2.2 我们定义的“工程宪法”包含什么

这份工程宪法不是泛泛而谈的开发规范,而是围绕 AI 工作方式定制的。主要包括五个方面:

第一,目录结构与分层约定。规定每个模块的标准结构是 handler、service、repository、model 四层,谁可以依赖谁,谁不能反向调用,数据模型统一放在 model 下,DTO 转换只在 handler 层做。这些约束写进文档只是第一步,更重要的是要让 AI 在每次生成代码前都“读到”这份约定。

第二,命名风格与代码风格。统一使用小驼峰命名、类型定义用 interface、错误必须返回 error 值而非 panic、日志必须走统一封装、禁止直接打印。这些规则不难理解,难的是让 AI 每次都遵守。我们的做法是把规范浓缩成一段固定提示词,附在每个模块的生成请求里,稍后会细说。

第三,接口优先原则。先定接口再写实现。这个原则特别适合 AI 协作,因为接口一旦定死,AI 生成的实现代码就很难跑偏。哪怕是临时的内部模块,我们也强制要求先写接口签名和主要数据结构,评审确认后再让 AI 填充实现。

第四,测试先行。每个业务逻辑模块必须附带至少一个测试文件,测试用例由我们定义关键场景,AI 负责补充边缘情况。我们不在 review 时讨论“要不要写测试”,只讨论“测试够不够”。

第五,变更记录。AI 每次生成或修改代码,必须输出一份简短的变更说明,格式固定在每个代码文件头部注释里,包含变更人(AI)、变更日期、变更动机。这个习惯刚开始觉得没必要,后来排查问题时帮了大忙。

2.3 为什么“工程化”比“选哪个工具”更重要

很多人问我用的哪个 AI Coding 工具。说实话,工具重要,但没重要到决定成败。我们尝试过好几款主流产品,包括 GitHub Copilot、Cursor、以及其他基于大模型的代码生成服务,最终并行使用的是一个支持多文件上下文理解的 IDE 插件和一个可以在终端对话的 CLI 工具。前者负责写代码,后者负责分析和重构。

真正决定产出的,是我们在每个工具外面包了一层“工程壳”。也就是上面说的规范、任务拆解方式、审查流程、反馈闭环。同样的模型,有人拿它写出来的东西是一堆漂亮的垃圾,有人拿它写出来的东西可以直接进生产环境。差距不在模型,在工程流程。

这也是从 AI Coding 到 AI Engineering 的第一层转变:从“我能用 AI 写代码”变成“我能让 AI 稳定地产出符合项目标准的代码”。前者是个人技巧,后者是团队能力。

3. 核心细节与实操要点:任务拆解、提示词模板、代码审查

3.1 任务拆解:把“大需求”切成“AI 能理解的中块”

AI 上下文窗口越来越大,但并不意味着你可以把整个项目的需求一次性扔进去。实践中我们发现,最有效的拆解粒度是“一个能独立交付的业务模块”,比如 “用户注册接口”、“订单列表查询”、“定时对账任务”。粒度太大,AI 容易迷失,生成代码与已有架构脱节;粒度太小,频繁切换上下文消耗大量时间,效率反而不如手写。

以用户注册接口为例,我们拆成下面这些子任务:

  1. 定义 User 结构体和数据库迁移文件,字段包括 ID、手机号、密码哈希、昵称、状态、创建时间、更新时间。
  2. 编写注册接口的 handler,负责解析请求参数、调用 service、返回统一响应结构。
  3. 编写注册逻辑的 service,包含手机号查重、密码哈希、验证码校验、创建用户、发送欢迎通知。
  4. 编写 repository 层,负责用户数据的增查,事务处理由 service 层传入的 transaction 对象控制。
  5. 补充注册流程的测试用例,覆盖验证码过期、手机号已注册、密码强度不足等场景。

每一个子任务都对应一次独立的 AI 会话。会话开始前,我们先把接口定义和相关的 model 结构贴进去,然后才是具体的任务描述。这样 AI 生成的代码基本能跟已有代码对齐。

实际操作中,我用的是这种三段式提示词结构:

第一段是上下文,包括项目分层说明、相关文件路径、本模块要遵循的接口定义。第二段是任务描述,包括输入输出格式、功能点、异常场景处理要求。第三段是约束条件,包括禁止使用某种写法、必须补充哪些日志、测试要求等。

举个例子,生成用户注册 service 时的提示词大致长这样:

背景:这是一个使用 Go 语言开发的管理系统,标准分层为 handler / service / repository / model。 现有文件: - internal/model/user.go - internal/repository/user_repo.go 请实现 internal/service/user_service.go 中的 Register 方法。 需求: - 输入参数为 RegisterRequest:Phone、Password、VerifyCode - 校验验证码,验证码从 Redis 读取,key 为 "verify:register:手机号",不匹配返回业务错误 ErrVerifyCodeInvalid - 校验手机号是否已注册,已注册返回 ErrPhoneExists - 密码使用 bcrypt 哈希后存储 - 创建用户后,通过消息队列发送一条欢迎通知,消费方在其他模块 - 返回 userId 约束: - 错误必须返回业务错误码,禁止直接返回裸 error - 日志统一使用 logger.Info / logger.Error,禁止使用 fmt.Println - 不修改 handler 与 repository 接口

这种结构下,AI 生成的第一版代码质量,已经比之前“自由发挥”高了不止一个档次。原因也好理解,它看到了边界,知道哪些事情不能做,自然不会跑偏。

3.2 多智能体协作:架构师、实现者、审查者分工

项目中期,我们引入了多智能体协作的玩法。所谓多智能体,其实就是让不同角色定位的 AI 各司其职,而不是一个 AI 从头包到尾。概括起来分为三类:架构师型 AI、实现者型 AI、审查者型 AI。

架构师型 AI 的任务是根据需求文档生成模块设计方案。它不写具体业务代码,只产出数据结构定义、接口签名、模块边界、关键流程描述。我们用的是带较长上下文理解能力的对话模型,把项目全局规范和本次需求贴进去,让它输出一份设计文档,再进行人工评审。

实现者型 AI 就是我们前面说的主力输出,它会严格按照评审通过的设计文档生成代码。这里有很关键的一点:设计文档一旦确认,实现环节不允许 AI 擅自改动接口签名和数据结构。如果它发现设计中存在问题,可以提出来,但修改必须回到架构师环节重新评审。

审查者型 AI 则专门挑毛病。在正式代码审查开始前,我们先用 AI 对 AI 产出的代码做一轮静态检查。检查内容包括:是否漏了错误处理、是否有重复代码、是否遵循分层规范、是否有明显的并发安全问题、是否有资源未释放。审查者输出的是一份问题清单,标出问题位置、原因、修改建议。

这三类角色由同一个底层模型驱动的不同配置组合来承担,区别在于上下文附加内容和输出格式要求不同。分角色之后,一个很直观的改变是:实现者不会一边写代码一边“想太多”去调整架构,审查者不会因为“代码是我自己写的”而手下留情。

不过这里要提醒一句,不要期待全自动闭环。目前的多智能体协作,流程编排还是得靠人来控制。我们就用过工作流引擎把这些 Agent 串起来,但很快发现,需求理解偏差、边界情况处理、规范冲突这些问题,当前模型还做不到自我仲裁。最终我们的做法是“人定规则,AI 跑流程,人工管异常”。执行层面的重复劳动交给 AI,涉及判断和决策的场景必须有人把关。

3.3 AI 辅助生成代码后的快速自查清单

AI 生成代码后,不是直接提交就完事。我们总结了一份快速自查清单,每次提交前过一遍,能挡住大部分低质量问题。清单如下:

  • 接口签名是否与设计文档一致,有没有偷偷改了参数类型或返回值?
  • 错误处理是否完整,有没有直接把 err 忽略掉(比如_ = doSomething())?
  • 是否引入了超出项目依赖范围的新库?如果引入了,是必要的吗?
  • 日志有没有包含足够上下文(比如用户 ID、订单号、请求追踪 ID)?
  • 有没有直接用魔法数字或魔法字符串而不定义常量?
  • 有没有把敏感信息打日志?
  • 数据查询是否可能出现 N+1 问题?
  • 并发场景下有没有共享可变状态?
  • 新加的代码有没有对应的测试覆盖?

这份清单不是给人“朗诵”的,而是作为输入条件喂给审查者型 AI,让它按清单逐项检查。同时,人在做代码审查时可以只关注逻辑设计层面,不用浪费时间挑风格和数据格式问题,效率提升明显。

4. 实操过程与核心环节实现:从空目录到一个模块完整落地的全记录

4.1 实操场景:做一个月度统计报表模块

为了把过程说得更具体,我拿一个实际做过的“月度统计报表模块”作为例子,完整走一遍实操流程,包括所有关键代码生成、配置方法和组织方式。模块需求一句话就能说清:每个月 1 号凌晨统计上个自然月的订单金额、订单量、用户增量、退款量、退款率,产出汇总数据并写入报表表。

先分析一下这个需求的特点:逻辑不复杂,但涉及从业务库读取大量数据、做聚合计算、清洗去重、最后写表。如果让 AI 直接写,它很可能会写出一个个非常直观但性能糟糕的查询,比如在循环里关联查询用户资料、统计订单时把全月订单一次性捞到内存里再慢慢算。

所以我们先让架构师型 AI 产出模块设计。设计文档文档包含四个部分:数据源说明、计算口径、表结构定义、调度触发方式。

数据源这边,订单表、用户表、退款表都在同一个 MySQL 集群里,但订单表数据量比较大,月度统计时全表扫描会拖慢主库,所以统计逻辑不再直接查业务表,而是从提前同步好的数据仓库宽表读取。计算口径方面,订单金额按照支付成功这个状态来统计,退款金额按照退款完成这个状态统计,用户增量按每天新增用户数去重后求和。表结构是核心,我们统一使用月度、订单总额、订单量、用户增量、退款总额、退款率、统计时间这几个字段。调度方式用系统自带的多节点定时任务,串行执行避免重复统计。

这份设计文档前后走了一轮人工评审,主要是我们把统计口径里“订单金额按支付成功状态”确认了一遍。这个过程比较重要,如果口径错了,后面 AI 写出来的查询再漂亮也没用。

4.2 分步生成代码与配置

设计定稿后,开始实现。整个模块的代码量大概在 800 行左右,分成这样几个文件:

  • internal/model/monthly_report.go:定义 MonthlyReport 结构体,字段同上。
  • internal/repository/monthly_report_repo.go:负责查询宽表数据、写入报表表。
  • internal/service/monthly_report_service.go:核心计算逻辑、数据组装与落库。
  • cmd/job/monthly_report.go:定时任务入口,调用 service。
  • internal/service/monthly_report_service_test.go:配套测试。

我们用一次会话让 AI 生成 model 和 repository,另一次会话生成 service,再一次会话生成定时任务入口和测试。每次生成前,都把上一轮生成的代码文件内容贴进去,保证上下文连续。

举个例子,repository 层的关键代码,在生成时我们额外要求考虑分页查询与合并,避免一次加载全量数据。AI 当时给出的方案是把订单数据按天分批查询,每天的数据汇总后合并到内存中,而不是拉全月数据。这个方案符合我们的要求,也符合数据量实际情况。

代码片段大致是这样:

func (r *MonthlyReportRepo) FetchDailyOrderStats(ctx context.Context, start, end time.Time) ([]DailyOrderStat, error) { var stats []DailyOrderStat for day := start; !day.After(end); day = day.AddDate(0, 0, 1) { dayStart := time.Date(day.Year(), day.Month(), day.Day(), 0, 0, 0, 0, time.Local) dayEnd := dayStart.AddDate(0, 0, 1) rows, err := r.db.QueryContext(ctx, ` SELECT COUNT(*) AS order_count, COALESCE(SUM(total_amount), 0) AS order_amount, COUNT(DISTINCT user_id) AS user_count FROM order_info WHERE pay_status = ? AND pay_time >= ? AND pay_time < ? `, payStatusPaid, dayStart, dayEnd) ... } return stats, nil }

service 层里拿到这些每日统计之后,再进行求和与聚合。这里 AI 一开始忘记处理空数据场景,也就是某天完全没有订单时的除零问题。我们通过审查者 AI 的检查捕捉到了这个点,要求它补上当订单量为 0 时的默认处理逻辑。

关键部分在于,它是怎么被发现的。审查者型 AI 在检查的时候,看到stat.OrderCount计算退款率时的除法,直接标注“分母可能为 0”,并给出修改建议。我们确认后,让实现者 AI 改了计算方式,加上了空数据保护。类似这种节点上的“机器把关”,如果完全靠人肉 review 也不是不行,但十几万行代码下来,注意力会被消耗得很厉害,漏网之鱼会越来越多。

4.3 流水线与任务的衔接

代码写完只是第一步,定时任务要接入流水线、把依赖的配置、编译产物、部署方式都做好。我们用的 CI/CD 流水线是 Jenkins,项目里新增了一个从上而下的任务配置,把cmd/job/monthly_report.go编译为独立二进制,部署到任务机上,然后注册到调度中心。

调度时间配置为每月 1 日凌晨 1 点,避开业务高峰期。首次上线时我们还手动触发了一次任务,核对报表数据与手工统计结果,确认无误后才放开自动调度。这一步不能省,AI 写出来的计算逻辑哪怕代码层面全对,也得拿真实业务数据验证“算得对不对”。我们第一次跑就发现了口径不一致的问题:宽表里的订单状态枚举跟线上业务库的不完全一致,导致部分订单没有被统计进去。这个坑不是代码逻辑问题,而是数据口径对齐问题,但如果不实际跑一次,靠看代码根本发现不了。

4.4 任务重试机制的设计

定时任务还有一个特殊场景需要处理:如果任务执行到一半挂了(比如数据库连接超时、依赖的数据源还没就绪),重跑时不能重复统计、不能漏统计。

为了让 AI 写出符合预期的任务代码,我们在提示词里明确要求使用“先清后插”的策略:统计开始前清空目标月份历史数据,全部重新计算。同时,在报表表上对month字段建了唯一索引,重复跑的话同一月份只会有一条记录,用INSERT ... ON DUPLICATE KEY UPDATE保证幂等。

这个设计是我们在做第一个定时任务模块时踩过坑之后总结出来的。当时没有幂等设计,结果任务重跑一次后报表数据翻倍,排查了很久才发现是重复插入。后来所有批量写数据的任务都统一按这个模式来,省了很多事。

5. 常见问题与排查技巧实录:AI Coding 项目里绕不开的坑

5.1 代码质量下降,是 AI 的锅还是流程的锅

相关热搜里有人问“AI Coding 的到来会不会让代码质量下降”。我的答案是:会,如果你不干预的话。AI 默认产物通常有“能用但混沌”的特征,它擅长在一个文件里完成你要求的功能,但不擅长在十几个文件的复杂项目里维持风格统一和逻辑一致性。

我们做了一次实验:同一个模块,一组用 AI 直接生成并提交,另一组用我们前面说的完整流程(规范约束 + 设计先行 + 审查闭环)来做。两周后对比,直接生成那组的缺陷密度大概是完整流程组的 2.6 倍,其中很大一部分集中在重复代码、遗漏错误处理、违反分层依赖这些方面。

这不是 AI 能力问题,而是工程系统设计问题。如果你把 AI 当成一个水平不差但容易忘事的新同事,那你要做的就是给它明确的工牌、统一的代码规范、清晰的任务清单、认真的 code review。如果你直接让它放飞自我,那代码质量只能靠运气。

所以,与其问“AI 会不会让代码质量下降”,不如问“我的交付流程是否已经适配了 AI 协作”。

5.2 提示词写了,AI 还是乱来?怎么办

我们经常收到类似的抱怨:“我已经把规范写得很清楚了,AI 还是无视,乱定义接口。”这种情况很常见,尤其是在上下文窗口较长、中间穿插多轮修改的时候。

排查思路有三步:

第一步,确认提示词是否在“生成动作”附近。把规范放对话开头,AI 生成到一半时它可能已经把早期上下文淡忘了。我们的做法是:核心约束在每次输入中都重复一遍,宁可牺牲一点 token,也要保证关键规则在生成的前部出现。

第二步,确认是否存在规则的互相矛盾。比如你既说“所有错误统一返回业务错误码”,又在示例代码里写了return err。AI 会倾向学习示例代码而不是抽象规则。所以给示例代码的时候,必须保证示例本身就是符合规范的,否则就是自己拆自己的台。

第三步,确认输出的可验证性。如果 AI 遵守了规则,你要能通过某个方式确认。我们的做法是加脚本检查,比如通过正则扫描新代码文件,检查是否有裸fmt.Println、是否有未注册的路由函数、是否缺少注释头。只要反馈是一个自动化的“通过/不通过”,AI 的遵守率会明显提升,因为模型天然会避免被判定为“错误”。

5.3 上下文窗口不够用,模块之间开始打架

做大型模块的时候,AI 的上下文窗口确实紧张。我们遇到过一个情况:用 AI 维护一个包含十几个函数的 service 文件,前面几轮还能正常修改,到后面它突然把之前约定好的某个方法名改了,因为它们之间的关联信息超出了上下文记忆范围。

应对方式有几个。第一,尽量拆小文件,一个文件职责单一、行数控制在 300 行以内。第二,修改之前先把关键接口定义和调用关系贴给 AI,让它重新加载。第三,重要结构用单独的文件存一份“契约快照”,每轮操作开始时让 AI 先读快照,在快照基础上修改,而不是靠对话历史记忆。

我们最终的规范是,当单次会话无法承载整个模块时,不要硬刚,直接换一个新会话,把必要的上下文重新粘进去。这样多花几秒,但产出的质量稳定得多。

5.4 测试覆盖率虚高,但业务漏洞不少

AI 很会写测试,写出来的测试往往覆盖了所有方法。但问题是,这些测试大多是按实现逻辑生成的用例,实现如果有逻辑漏洞,测试也发现不了,因为测试是和实现一起生成的,天然同源。

比如一个统计函数把>=写成>,AI 生成的测试用例里也跟着用>,跑起来全绿,但实际边界值少算了一条。可以靠审查者型 AI 从业务场景独立生成测试用例,尽量从需求出发而不是从实现出发。最简单的办法是让两个不同会话分别生成实现和测试,测试会话只知道需求文档,不看实现代码。这个“测试与实现隔离”的做法,我们强力推荐。

5.5 线上出了问题,如何快速定位是 AI 写的还是人为埋的

还有个实际场景。项目后期,代码量上来了,真出问题的时候,第一反应总是“是不是 AI 写的这段有问题”。但实践经验告诉我们:除非是低级的逻辑边界错误,否则 AI 生成的代码出问题的原因,大概率跟人写代码出问题的原因是一样的——需求理解偏差、接口约定变更、数据异常导致状态机走到未预期分支。

我们的排查流程是这样的:先看变更注释(前面提到 AI 生成代码会把变更信息写进文件头,这里派上了用场),确定这段代码是什么时候新增/修改的、由哪个会话产生的;然后看关联的测试用例,判断测试是否覆盖了出问题的分支;再看线上日志,结合 trace ID 把请求链路串出来。这套流程跟排查人工代码差别不大,只是 AI 代码的文件头注释更规整,变更有据可查,反而比某些同事的提交记录更清晰。

5.6 常见问题速查表

症状可能原因解决方式
AI 生成的代码风格飘忽缺少统一规范约束在提示词中附上项目规范,通过脚本检查风格
改了需求 AI 却按旧逻辑实现上下文窗口记忆丢失新开会话,重新粘入关键接口和现状描述
代码全绿但业务数据不对实现与测试同源缺陷测试生成与实现生成隔离,按需求描述独立写测试
重复代码过多任务拆解粒度不合适把公共逻辑先抽象成独立模块,再让 AI 引用
使用了未声明的新依赖依赖声明约束缺失在提示词里写明“禁止新增依赖”,或通过依赖扫描工具拦截
接口调用链断裂设计稿与实现稿不同步设计阶段先定接口并评审,实现阶段禁止改接口

6. 一点个人体会

做这个项目之前,我对 AI Coding 的态度是“能用,但只能用来补点工具脚本”。做完 16 万行之后,我的想法变成了:AI Coding 不是“帮你写代码”那么简单,它其实是逼着你把工程流程想得更清楚。因为当产出代码的速度被 AI 放大了十倍,你花在定义问题、划定边界、审查结果上的精力也得以同样幅度提升,否则混乱和 bug 也会被放大十倍。

具体到团队落地,如果让我给一个最简短的建议,那就是:先把“工程宪法”写好,再让 AI 开工;先让一个人做全流程试点,把规范调顺了,再推广到团队。不要在没有任何约束的情况下直接把 AI 扔给全员,也别指望招一个“会用 AI 的人”就能解决所有问题,AI 只是放大器,你原来的工程水平决定它放大的是价值还是风险。

最后再分享一个小技巧。如果团队里有人觉得 AI 生成的代码“不靠谱”,又说不清哪里不靠谱,那就让他做一次审查者型 AI 的提示词设计。当他把“哪些代码是不可以接受的”逐条写清楚时,他其实已经把自己对工程质量的理解系统化了一遍。这比任何培训都有效,也是我们从 AI Coding 走向 AI Engineering 过程中最意外的收获。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询