
你是不是也遇到过这种情况用 Cursor、GitHub Copilot 或任何 AI 编程助手时明明给了很详细的 prompt但 AI 生成的代码要么跑不通要么逻辑完全不对要么干脆“理解”错了你的意图你可能会想“是不是我的 prompt 写得不够好” 于是开始疯狂学习各种 prompt engineering 技巧试图用更精确、更冗长的指令去“驯服”AI。但结果往往是prompt 越写越长效果却时好时坏甚至更糟。Matt Pocock一位知名的 TypeScript 专家和开发者教育者最近的一个观点可能点破了这个问题的核心。他认为在 AI 编程时代影响 AI 助手输出质量的首要因素可能不再是你的 prompt 技巧而是你的代码库本身的“可读性”。这里的“可读性”不是指代码风格而是一种更深层的、被称为“深模块”Deep Module的架构设计思想。这篇文章我们就来深入拆解 Matt Pocock 的这个核心观点。我们会讲清楚为什么在 AI 眼中一个混乱的代码库比一个糟糕的 prompt 更致命“深模块”架构到底是什么它如何让 AI 更好地“理解”你的代码作为普通开发者我们如何从现在开始用“深模块”的思想重构或编写代码来显著提升 AI 编程助手的协作效率这不是一篇空谈理论的文章。我们会结合具体的 TypeScript/JavaScript 代码示例对比“浅模块”和“深模块”的写法让你直观地看到差异并给出可立即落地的重构建议和最佳实践。1. 问题的本质AI 不是人它“读”代码的方式很特别首先我们必须建立一个基本认知AI 编程助手如基于 GPT 的模型理解代码的方式与人类开发者有本质不同。人类依靠多年积累的领域知识、设计模式直觉和对业务上下文的理解可以快速跳过细节抓住核心逻辑。我们能容忍一定程度的代码坏味道因为我们可以脑补和推理。AI大语言模型本质上是一个基于海量代码和文本训练出的“模式匹配器”和“概率预测器”。它没有真正的“理解”而是根据你提供的上下文当前文件、打开的文件、项目结构和 prompt预测最可能符合你意图的下一个 token代码片段。关键就在这里AI 的“上下文”是有限的并且是线性的、基于 token 的。当它面对一个庞大的、结构混乱的代码库时信息过载与丢失AI 的上下文窗口比如 128K tokens可能无法容纳所有相关文件。即使能容纳杂乱无章的信息也会稀释关键逻辑。无法进行高层抽象推理如果函数、模块之间的关系隐藏在层层嵌套和复杂的依赖中AI 很难推断出它们的真实意图和契约。被表面细节误导AI 会倾向于复制它看到的最近、最频繁的模式。如果代码库里充满了重复、紧耦合的逻辑AI 生成的代码也会是这种风格。所以当你精心撰写了一个 prompt但 AI 却给出了离谱的答案时很可能不是 prompt 的问题而是 AI 从你的代码库里“读”到的信息让它得出了一个符合它所见“模式”但不符合你真实需求的结论。结论优化代码库的结构使其对 AI “友好”本质上是在优化提供给 AI 的“上下文信息”的质量。这比在嘈杂的上下文中撰写一个精确的 prompt 要有效得多。2. 核心解药“深模块”架构思想Matt Pocock 提出的解决方案是采用“深模块”Deep Module的设计思想来组织代码。这个概念源自 John Ousterhout 的经典著作《A Philosophy of Software Design》。简单来说浅模块Shallow Module接口复杂有很多公开的函数、类、配置项但内部实现简单。它提供了很多细碎的“工具”但每个工具的功能都很单一需要使用者自己组合。这增加了使用者的认知负担。深模块Deep Module接口简单暴露的 API 很少、很清晰但内部实现复杂、功能强大。它隐藏了内部的复杂性对外提供一个强大且易用的抽象。使用者无需关心内部细节。在 AI 编程的语境下“深模块”的价值被无限放大对 AI 而言清晰的接口就是清晰的“指令”。一个深模块暴露的简单 API相当于给 AI 一个明确、无歧义的“使用说明书”。AI 不需要理解模块内部复杂的流转只需要知道“调用这个函数传入这些参数就能得到那个结果”。减少了 AI 需要关注的“表面面积”。混乱的代码库有无数个入口点和纠缠的依赖AI 无从下手。深模块通过封装将复杂的内部逻辑隐藏起来只暴露少数几个关键的“交互点”极大地简化了 AI 需要处理的上下文。提升了代码的可预测性。深模块遵循“单一职责”和“明确契约”其行为是稳定的。AI 基于此类代码进行模式匹配和生成时结果也会更稳定、更符合预期。3. 从“浅”到“深”一个代码对比示例让我们看一个具体的例子感受一下“浅模块”和“深模块”对 AI 友好度的天壤之别。场景我们需要一个功能根据用户的国家和商品价格计算最终含税价格。税率规则可能很复杂。3.1 浅模块的写法对 AI 不友好// 文件src/utils/tax.ts (一个典型的“工具类”文件) export function getUsTaxRate(state: string): number { // 一堆复杂的美国各州税率逻辑... if (state CA) return 0.0925; if (state NY) return 0.08875; // ... 更多判断 return 0.06; // 默认税率 } export function getEUTaxRate(countryCode: string): number { // 欧盟税率逻辑... if (countryCode DE) return 0.19; if (countryCode FR) return 0.20; // ... return 0.21; } export function getAsiaTaxRate(countryCode: string): number { // 亚洲税率逻辑... // ... } export function applyTax(price: number, taxRate: number): number { return price * (1 taxRate); } export function formatPrice(price: number, currency: string): string { return new Intl.NumberFormat(en-US, { style: currency, currency }).format(price); } // ... 可能还有更多零散的“工具函数”问题分析接口复杂暴露了getUsTaxRate,getEUTaxRate,getAsiaTaxRate,applyTax,formatPrice等多个函数。认知负担重AI或新队友需要知道1) 该调用哪个getXxxTaxRate函数2) 如何获取state或countryCode3) 拿到税率后要手动调用applyTax。4) 最后可能还要调用formatPrice。上下文依赖当你在另一个文件写业务逻辑时为了让 AI 生成正确的代码你必须在 prompt 里详细说明“先调用 getUsTaxRate再调用 applyTax...”。一旦步骤顺序或函数名说错AI 就可能出错。3.2 深模块的写法对 AI 友好// 文件src/domain/tax/index.ts (一个“领域模块”) export interface TaxCalculationRequest { countryCode: string; stateOrProvince?: string; // 可选用于美国/加拿大等 price: number; currency: string; } export interface TaxCalculationResult { originalPrice: number; taxAmount: number; finalPrice: number; formattedPrice: string; // 直接返回格式化好的字符串 } // 核心一个简单的、功能强大的接口 export function calculateFinalPrice(request: TaxCalculationRequest): TaxCalculationResult { const taxRate determineTaxRate(request.countryCode, request.stateOrProvince); const taxAmount request.price * taxRate; const finalPrice request.price taxAmount; const formattedPrice new Intl.NumberFormat(en-US, { style: currency, currency: request.currency }).format(finalPrice); return { originalPrice: request.price, taxAmount, finalPrice, formattedPrice, }; } // 内部实现可以非常复杂但对使用者和AI隐藏 function determineTaxRate(countryCode: string, state?: string): number { // 这里可以整合所有复杂的税率逻辑甚至调用外部API // 可能是巨大的 switch-case 或策略模式 // 但外部完全不需要知道 if (countryCode US state) { // 调用内部的美国税率逻辑 return getUsTaxRateInternal(state); } if ([DE, FR, IT].includes(countryCode)) { return getEUTaxRateInternal(countryCode); } // ... 更多地区逻辑 return 0; // 默认零税率 } // 以下都是内部私有函数不导出 function getUsTaxRateInternal(state: string): number { /* ... */ } function getEUTaxRateInternal(countryCode: string): number { /* ... */ }优势分析接口极其简单整个模块只暴露一个核心函数calculateFinalPrice和两个接口。这就是一个“深模块”。功能强大且易用使用者和 AI只需要组装一个TaxCalculationRequest对象调用一个函数就能得到包含所有信息含格式化价格的结果对象。无需关心内部是查表、调用 API 还是复杂计算。对 AI 极其友好当你在业务代码中需要计算含税价时你的 prompt 可以非常简单“调用 calculateFinalPrice 函数”。AI 看到这个清晰的接口很容易就能生成正确的调用代码。它不需要知道税率计算的细节那些细节被安全地封装在模块深处。4. 如何为 AI 友好性设计“深模块”实操指南理解了概念我们来看看具体怎么做。以下是一些可立即行动的原则4.1 原则一以“领域”或“功能”为核心组织模块而非以“技术工具”避免utils,helpers,common这种大杂烩文件夹。它们就是“浅模块”的温床。推荐按业务领域组织如src/user/,src/order/,src/payment/。每个领域模块内部提供该领域完整的、深度的功能。4.2 原则二追求“最小化、最明确”的公开 API每个模块一个文件夹或一个文件思考对外提供的核心价值是什么将这个核心价值封装成1 个或极少数几个函数/类暴露出去。使用 TypeScript/JavaScript 的export严格控制可见性。内部辅助函数一律用function关键字或const定义不要export。4.3 原则三使用强类型接口Interface定义“契约”深模块的威力很大程度上依赖于清晰的输入输出定义。为模块的核心函数设计明确的请求Request和响应Response接口。这相当于给 AI 一份强类型的、无歧义的 API 文档。示例上面的TaxCalculationRequest和TaxCalculationResult就是完美的契约。4.4 原则四隐藏复杂性提供“开箱即用”的默认行为复杂的配置、繁琐的初始化步骤、需要按特定顺序调用的多个函数——把这些都封装起来。提供一个“一站式”函数接受必要的参数内部处理好所有细节返回最终结果。AI 最喜欢这种“傻瓜式”的接口。5. 在现有项目中应用渐进式重构策略你可能会说“我的项目已经是一团乱麻了怎么办” 不必推倒重来可以渐进式重构识别痛点找到那些你频繁使用 AI 生成代码但效果总是不好的模块或工具函数集合。创建新模块不要直接修改旧代码。在旁边创建一个新的领域文件夹如src/order/new/。设计深接口针对这个领域设计一个理想的、简单的、功能强大的 API。先写接口.d.ts文件或直接写函数签名。实现并替换实现这个新模块。然后在业务代码中找一个简单的调用点手动或让 AI 协助将其改为调用新模块。验证功能。逐步迁移重复第4步逐步将旧代码的调用替换为新模块。旧工具函数最终无人调用后可以安全删除。6. 结合 AI 助手进行重构的实战技巧重构过程本身也可以让 AI 参与形成正向循环给 AI 清晰的上下文当你决定重构一个“计算价格”的混乱逻辑时不要直接说“重构这段代码”。而是说“我正在重构项目的价格计算逻辑。目标是创建一个深模块对外只暴露一个简单的calculateFinalPrice函数。这个函数应该接受{ countryCode, price, currency }参数返回包含原始价格、税费、最终价格和格式化字符串的对象。目前相关的旧代码分布在utils/tax.js和utils/priceFormatter.js中。请先帮我设计这个新模块的 TypeScript 接口。”让 AI 实现内部复杂逻辑接口设计好后你可以说“根据我们设计好的TaxCalculationRequest和TaxCalculationResult接口请实现calculateFinalPrice函数。内部需要集成旧代码中getUsTaxRate,getEUTaxRate,applyTax的逻辑。注意所有辅助函数都应该是模块内私有的。”让 AI 生成迁移示例新模块写好之后你可以让 AI 帮你生成调用示例或者直接修改一个简单的调用点“这是我的一个旧调用代码片段const tax getUsTaxRate(state); const final applyTax(price, tax);。请将它改为使用新的calculateFinalPrice函数。”7. 常见问题与误区澄清问题现象可能原因排查与解决思路AI 生成的代码总是调用错误的函数或遗漏步骤。代码库中存在多个功能相似、命名模糊的“浅”工具函数AI 被混淆了。1. 审查相关函数进行重命名和职责澄清。2. 更根本的是将这些零散函数封装到一个“深模块”中提供统一入口。即使给了很长的 promptAI 也无法理解复杂的业务规则。业务规则分散在多个层控制器、服务、工具函数AI 的上下文无法捕捉全貌。将完成一个特定业务目标所需的所有逻辑封装到一个领域模块中。让 AI 只需要和这个模块的简单接口打交道。项目很大感觉无从下手重构。试图一次性改造整个项目。采用“渐进式”策略。从你当前正在开发或修复的功能点开始为其创建一个小型的、独立的深模块。积少成多。担心“深模块”会导致内部过于复杂难以测试和维护。误解了“深模块”。“深”指的是功能强大而不是代码混乱。内部实现依然要清晰、可测试。只是复杂性被封装起来了对外不可见。深模块的内部同样需要良好的设计和单元测试。这恰恰促进了代码质量的提升。8. 最佳实践总结写给未来的“人机协作”代码接口即文档为你模块的核心功能设计像calculateFinalPrice这样自解释的、强类型的接口。这是给 AI 和未来维护者最好的文档。拥抱“领域驱动设计”DDD思想按业务领域组织代码天然地容易形成“深模块”。每个聚合根Aggregate或领域服务Domain Service都可以成为一个对 AI 友好的深模块。减少全局状态和隐式依赖AI 很难追踪全局变量或隐藏在深处的依赖如通过依赖注入容器动态解析的复杂对象。尽量让函数的输入输出都是显式的。保持模块的“纯净性”一个模块最好只做一件事并把它做到极致。避免创建那种既处理 HTTP 请求、又操作数据库、还进行复杂计算的“上帝模块”。这样的模块接口必然复杂。迭代优化与 AI 协作本身就是一个测试你代码结构好坏的“试金石”。如果某个模块你总是需要写很长的 prompt 才能让 AI 正确使用那这个模块就是重构的候选对象。9. 总结从“优化 prompt”到“优化代码”Matt Pocock 的观点给我们指出了一个更根本的方向在 AI 编程时代优秀的软件设计原则如深模块、高内聚低耦合、清晰接口的价值被重新放大甚至成为了人机协作效率的关键瓶颈。过去我们为“人”设计代码的可读性现在和未来我们需要同时为“AI”设计代码的可读性。这并非两种不同的标准而是同一种优秀设计原则的一体两面。一个对人类开发者清晰、易维护的代码库几乎必然也对 AI 助手友好。所以下次当你的 AI 编程助手再次“犯傻”时先别急着修改 prompt。停下来看看它正在阅读的代码上下文。也许一次小的重构将一个“浅模块”转化为一个“深模块”就能永久性地解决这一类问题让你和 AI 的协作效率提升一个数量级。从现在开始尝试用“深模块”的思维去审视和编写你的下一段代码。你会发现这不仅让 AI 变得更聪明也让你自己的代码设计能力变得更强大。