AI编程工程化:上下文工程与类型驱动提升代码可维护性
直接做正事。最近圈子里关于AI编程的讨论又热了一轮最核心的那篇讨论里几位一线开发者围绕“AI到底怎么才能老老实实写出可维护的工程代码”这一堆问题给了一套很务实的解法。标题里的Matt Pocock是TypeScript领域比较有影响力的开发者他没有去聊那些玄乎的“AI取代程序员”的话题反而把注意力放在提示词设计、上下文管理、类型集成这些看起来不性感但真正决定产出的细节上。这篇文章就是我把他那套思路的五个关键问题拆开结合我自己在几个真实项目里踩过的坑整理出来的一份实操笔记。如果你也在用AI写代码但总是遇到“看起来差不多但一跑就废”的情况或者觉得AI生成的代码像一次性筷子——能用但完全没法维护那这篇文章适合你。我不打算做那种AI入门科普而是想聊清楚一件事同样是让AI写代码为什么有些人能稳定地拿到可用的工程代码有些人却只在制造下一批技术债。1. 五个关键问题从表象到根因先把Matt Pocock在讨论里抛出的五个问题列出来。听起来都是老生常谈但每个问题背后都藏着一个真实的工程事故现场。问题一为什么AI生成的代码“能用但很脆”大多数AI编程工具给出的代码第一眼看过去语法正确、结构完整你甚至能在本地跑通。但拉到代码评审的时候你发现这代码既没有边界处理也没有错误恢复更没有考虑到并发和重试。说白了它能对付“happy path”但一遇到真实世界的异常就崩。这个问题的根子在于AI没法主动感知你的业务里哪些场景是常见的、哪些是必须防御的除非你在提示词里把这些信息喂给它。问题二上下文不足导致的“代际失忆”。我和AI协作了半年多最深的体会是AI没有记忆它只有上下文窗口。你和它聊完一个模块的要求等它开始写下一个函数的时候前面聊的内容可能已经被截断或者冲掉了。于是它开始“自由发挥”用了一套和之前完全不同的命名方式、参数风格、错误处理策略。这就是所谓的“代际失忆”——每一轮对话中生成的东西都是孤立的没有延续性。问题三类型缺失导致“看起来通但跑起来炸”。如果你用的是JavaScript或者没有严格类型标注的Python这个问题尤其明显。AI写出来的代码你肉眼扫过去觉得逻辑对但类型在悄悄地失配这个函数接受string那个函数返回number中间一拼接就出了运行时错误。动态语言让AI更容易蒙混过关因为解释器不抓类型错误。而TypeScript这类的静态类型语言反而能逼着AI在生成代码时就考虑接口契约。问题四验证缺失修一个bug带出三个bug。这是AI编程最隐性的成本。你让AI修一个bug它改完一看原bug确实没了但旁边两个本来正常的用例挂了。为什么因为它没有测试保护网它看到的是局部代码不知道自己在动哪根神经。你如果没有自动化的回归测试AI每改动一次你都要花大量时间做人工回归效率上甚至比你自己写还低。问题五缺少领域知识代码没有“业务味”。AI可以写出规范的增删改查但它不知道你所在的行业有哪些特殊规则。比如支付系统里的幂等逻辑、库存系统里的超卖防护、审批流里的状态机约束这些“业务气味”很难靠泛化的预训练模型自己悟出来。你必须把这些约束显式地装进它的上下文里不然它只会给你一套教科书式的、完全没考虑真实业务约束的骨架。这五个问题不是孤立的。它们有一条共同的暗线AI编程的核心矛盾不是模型不够聪明而是协作流程没有把信息密度和反馈闭环做对。接下来我要逐个拆解Matt那套解法的关键动作全部来自可落地的实践。2. 上下文工程AI编程的核心杠杆2.1 为什么上下文直接决定输出质量很多人会觉得AI写得好不好取决于模型强弱。确实模型能力是下限但实际体验下来上下文才是你能控制的最大变量。打个比方AI像一个能力很强但完全不了解你公司情况的新同事。你不给背景资料他就只能按通用套路做做出来的东西当然不贴需求。你把背景、约束、代码风格、接口文档全摆在他面前他才能给出像样的解决方案。我见过太多人拿着AI工具直接说“帮我写一个订单模块”然后得到一坨泛型代码。问题不在于AI不行而在于这提示词的信息密度太低了。你想让它写出匹配你项目的代码至少得给它三层上下文第一层是业务上下文。这个模块是做什么的什么场景下被调用有哪些核心规则必须遵守。 第二层是技术上下文。你用了什么框架、什么版本、目录结构是什么、依赖了哪些内部库。 第三层是风格上下文。你的团队代码风格是什么、命名习惯是什么、有没有必须遵循的目录约束。这三层信息如果全部在提示词里AI输出质量会有质的提升。分享一个我实际用过的模板【任务】为订单服务新增一个“部分退款”接口。 【业务规则】 - 仅允许退款未发货的订单。 - 退款金额不能超过订单实付金额减去已退款金额。 - 退款操作需要保留完整操作日志。 - 支不支持部分退款由订单类型决定。 【技术约束】 - 项目使用 TypeScript ExpressORM 为 Prisma。 - 服务层和路由层分离业务逻辑必须放在 service 层。 - 金额计算必须使用 decimal 类型禁止直接使用 float。 【风格要求】 - service 层方法命名使用动词开头createRefund。 - 错误使用 Result 类型返回不要抛异常。 - 中文注释只解释“为什么”不解释“是什么”。你可能会觉得这样写提示词太啰嗦。但实测下来给足上下文之后AI生成的代码一次通过的率能提高一倍不止。省下来的调试时间远超你多花的那两分钟。2.2 上下文管理工具与流程有了上下文模板还不够要解决“代际失忆”的问题得把上下文变成一个持续维护的资产而不是每次临时写一坨。我用的办法很简单在项目里建一个ai/context.md文件或者AIDocs/目录把项目背景、技术栈、目录结构、约定的模式、常见约束全放进去。每次会话开始的时候先把这份文档喂给AI再开始提需求。这个习惯看起来不高深却是解决AI“忘事”最朴素有效的方法。后来我又加了一层在Git仓库里维护docs/decisions/把一些关键的架构决策和AI协作的约定沉淀下来。比如“禁止AI修改Prisma schema”这种硬约束、哪些目录属于AI可以自由改动的、哪些是禁区——都写清楚。有了这份文档你开新会话时就不用重复解释直接让AI先读文档再干活。再进一步就是利用好AI工具的“项目规则”能力。很多现代AI编程工具都支持在项目根目录放规则文件让每次对话自动加载。这类规则文件最适合放三样东西一句话定义项目边界这是后端服务/前端应用/CLI工具。必须使用的技术栈和版本。必须遵守的硬性限制比如“不要用any”“不要用相对路径引用超过两层”等。把规则文件当成代码库的一部分来管理跟普通代码一样走评审、走合并。团队里新成员看一遍规则文件就知道怎么和AI协作知识传递成本也低很多。一个小提醒上下文不是越多越好。给AI塞了几千行的文档它反而会“注意力稀释”抓不住重点。更有效的做法是分层投喂会话开始时喂一个精简版的项目概况一两百行以内具体到某个任务时再把相关的模块文档、接口定义贴进这一轮的对话里。这样AI的注意力能被聚焦在当下真正重要的信息上。3. 任务拆分与验证闭环把AI变成协作者而非盲写手3.1 从“大而全”到“小而专”AI编程最忌讳的是贪大。很多人的第一反应是“给我写一个完整的管理后台”这种提示词基本等于把控制权全交给概率出来的东西往往松散、重复、没有统一设计。一个成熟的协作方式是把任务拆到以“单个函数”“单个组件”“单个接口”为单位的粒度。越小AI越容易在上下文约束下输出正确代码你也越容易评审和测试。听起来像是把AI当成了实习生事实上这就是正确的用法。你不是让AI替你做架构设计而是让它帮你高效实现你已想清楚的模块。我自己常用的一种拆分节奏是这样的先写好接口定义DTO、函数签名让AI补全实现。或者先写好实现让AI补测试用例。或者先写测试用例让AI补实现来通过测试。这三种模式有一个共同点人对输入输出有清晰预期AI负责填充中间过程。架构和契约是人定的AI的自由度被限制在安全范围内。这比直接说“帮我写个支付流程”要稳妥得多。用TypeScript举个例子。我经常先写类型再让AI实现type RefundResult { success: boolean; refundId?: string; error?: RefundError; }; async function createRefund(params: CreateRefundParams): PromiseRefundResult { // AI 补全 }类型先行的好处是AI在生成函数体的时候必须按你给定的输入输出契约来走这就把很多“编造出来的接口”问题提前消灭了。如果目标是Python项目也可以采用类似的思路用abc抽象基类或者typing.Protocol定义好接口再让AI实现。3.2 类型驱动让编译器当第一道关卡说到类型这是Matt Pocock那套思路里我最认同的一点。他的核心观点就是AI写代码最怕的不是语法错误而是契约不匹配。而编译器是帮你查契约的第一道防线。我自己的感受是用TypeScript之后AI生成代码的可用性明显提升了。原因很简单你让AI生成一个函数它必须先满足你给定的签名如果你项目里开了strict模式它会尽量避免使用any因为用了any也会被ESLint或代码评审拦下。这些约束就是给AI的“紧箍咒”让它不敢乱来。如果你所在的团队还在用纯JavaScript写核心业务其实可以考虑在这类AI协作场景里先引入TypeScript或者至少JSDoc。不用推倒重来只在关键模块上加上类型标注AI生成那部分代码的质量就会有肉眼可见的提升。另外建议你在项目的ESLint配置里把no-explicit-any、no-non-null-assertion这类铁律打开。AI生成代码时如果碰到这些规则工具会直接标红你可以让AI自纠。这比事后人工审查高效得多——AI改自己的代码比人改它的代码快。3.3 测试先行让回归跑起来前面提到过AI改一个bug带出三个bug根子在于缺少验证闭环。如果想让AI稳定地产出可用代码就必须把自动化测试嵌入到协作循环里。具体做法分两步。第一步在提示词里要求AI在交付代码的同时给出对应的测试用例。第二步本地跑测试把失败信息反馈给AI让它修。一收一放之间形成一个“AI写码→测试反馈→AI自修”的闭环。注意这里要小心的坑是AI能写出“针对性太强”的测试也就是专门为了让它自己的代码通过而写的测试测试本身没什么意义。所以我会在提示词里加一句“测试必须覆盖正常路径、异常路径和边界条件避免只测实现细节。”这样AI会输出更健壮的测试套件且即使实现重构了测试也不会因为无关原因报废。实测下来“红色测试循环”先写会失败的测试再让AI写实现让它变绿这套实践非常有价值。它不是我的发明早就是传统TDD的核心但套到AI编程场景里就成了极为有效的“护栏”。对于目前流行的AI编程工具大多数都支持“让AI修复失败测试”的工作流。你只需要把失败信息粘贴给它通常它就能给出修复方案。需要注意的是修复方案可能引入新的问题所以一定要回归跑完整测试套件而不是只跑那一条刚才失败的用例。4. 常见问题与排查技巧实录4.1 提示词漂移问题你明明在同一个会话里反复说“遵循前面的约定”但AI写到第三第四个文件时开始把prompt里的约束忘了。这个现象我称之为“提示词漂移”。原因通常是上下文窗口溢出或者AI按概率生成的偏好压倒了早期给你的承诺。解决思路不外乎两条。一是把关键的硬约束写进项目级规则文件让它每条消息都自动带着二是把约束写成代码层面的检查而不是依赖AI自觉。比如“禁止在service层直接操作数据库”这种约定光靠提示词约束不够你可以在ESLint自定义规则或者用CodeRabbit这类工具在CI上加一道检查。铁律变成机器检查AI想飘也飘不出去。4.2 代码改错方向的纠偏方法有一个高频场景AI本来在改一个bug改着改着开始“顺手”重构旁边的代码结果改动范围失控。我踩过几次坑之后养成了一个习惯在提示词里明确禁止“无关改动”。比如只修改指定文件不要动其他文件。只解决描述的问题不要做额外的重构或格式化。如果发现必须改动外部依赖或数据库结构先停下来告诉我原因。就算这样AI也偶尔会自作主张。解决办法是在操作前先看一下diff用“git diff”评估改动范围。如果AI擅自扩大了改动范围就扔回重写不要跟它商量太久。代码评审的第一原则是“阻止不必要的变化”在AI协作中这个原则尤其重要。4.3 工具链对AI输出质量的影响工具链的选型也会显著影响AI输出的质量。我试过好几个AI编程工具各自对上下文的理解、对代码仓库的索引、对文件间关系的感知都不同。有的工具会主动读项目的目录结构和相关文件这比只靠提示词喂文档要靠谱得多。选型上我建议至少满足这三个标准能感知当前文件所在目录和附近的代码。支持项目规则文件或自定义指令。支持在编辑器中直接运行测试并反馈结果。满足这三条AI才可能真正“工作在你的项目里”而不是“工作在一个真空里”。此外团队里如果使用远程开发环境也要注意AI工具能不能访问到正确的代码索引和配置。下面把我在实践中常遇到的问题和速查处理方法整理成一个表格方便你对照使用。症状可能原因快速处理生成代码风格和项目差别极大缺少风格上下文补充代码风格规范或提供已有文件作为参考多次提醒后仍重复犯同一错误约束只写在提示词里没落地成代码检查把约束写进ESLint或CI规则改动范围越来越大没有明确禁止无关改动在提示词中声明“只修改目标文件”测试案例太弱怎么改都能过AI在“逆向拟合”测试要求补边界、异常、正常路径三类测试同一会话后面代码越来越差上下文窗口溢出把长对话拆成独立子任务每个任务重新给精简上下文AI不理解业务术语缺少领域词表和业务规则文档在上下文里加业务术语表和字段解释4.4 领域知识注入的实践最后一个关键问题怎么把“业务味”塞给AI。光写“这是订单系统”没用你要把业务规则转成AI能理解的语言最好是转成代码边界条件。我通常的做法是写一个business-rules.md里面用“如果...必须...”的句式描述规则。比如“如果订单状态为已发货必须拒绝退款申请”“如果退款金额超过可退金额返回错误码REFUND_AMOUNT_EXCEEDED”。这些规则直接作为提示词上下文AI生成的代码就会带着这些判断逻辑而不是只输出一个空的接口壳子。有时候你不需要把这些规则全写出来你只需要引导AI让它“发现问题”。比如你可以问它“在实现这个接口时有哪些边界情况需要考虑”它会列出一串你挑出符合项目实际的让它实现这种方式更轻也能逼AI主动思考。结尾我在实际和AI协作中发现Matt Pocock那套思路之所以值得被讨论不在于它多炫技而在于它把AI编程重新拉回到工程本身的常识上上下文要清晰、接口要有契约、验证要闭环、改动要受控。这些东西在没有AI的时候也是好工程的标准只是AI的加入让每一项都变得更容易被忽视——因为AI总能在字面上显得像那么回事从而骗过你本该做的审查。最后再分享一个小技巧每次和AI协作之前先想清楚一个句子——“我需要一个什么输入、经过什么逻辑、产生什么输出的东西”。如果这句话你写不出来AI大概率也写不出对的东西。能把这个句子想清楚AI编程就从“碰运气”变成“按图索骥”剩下的只是细节层面的事了。