prompt-engineering-production-code.html
< BACK 一张暗淡的开发者办公桌在夜间的照片,显示器发着光,散落的便签,冷掉的咖啡杯,采用沉郁的编辑式光线

生产代码的提示工程:惨痛的教训

星期四晚上11点43分,我盯着 GPT-4 生成的 340 行 React 代码。代码很整洁。注释完整。在生产环境中彻底崩溃。自定义 hook 以导致静默重新渲染循环的方式管理状态,这种循环不会抛出错误——它只是悄悄地摧毁你的性能,直到客户在周五早上给你打电话,问为什么他们的结账页面需要九秒钟才能加载。

那晚教我的关于提示工程的东西,比任何 YouTube 教程或 Twitter 帖子都多。从那以后我经历过很多这样的夜晚。

我在网络领域已经开发了九年。在 Seahawk Media,我们交付了超过 12000 个网站——WordPress、无头架构构建、定制 React 应用、处理真实交易量的 WooCommerce 商店。AI 编码助手正式进入我的工作流大约是在 2023 年初,之后我一直在觉得它们神奇和想把笔记本扔进泰晤士河之间摇摆不定。Seahawk Media we've shipped well over 12,000 sites -- WordPress, headless builds, bespoke React apps, WooCommerce stores handling serious transaction volume. AI coding assistants entered my workflow properly around early 2023, and I've swung between thinking they're miraculous and wanting to throw my laptop into the Thames.

这是我实际学到的。付出了惨痛代价。

---

模型不了解你的代码库。你必须告诉它。

这听起来很明显。但在实际操作中并非如此。

我看到开发者犯的最大错误——包括我最初六个月的错误——是把 LLM 当作已经读过所有代码的资深工程师。你问"写一个处理用户认证的函数",它在真空中写出技术上正确的东西。但你的项目用的是 Supabase,不是 Firebase。你的 token 存在 httpOnly cookie 里,不是 localStorage。你的错误格式是 { status, message, data },不是模型默认的那样。Supabase, not Firebase. Your tokens live in httpOnly cookies, not localStorage. Your error format is { status, message, data }, not whatever the model defaulted to.

模型没有错。它只是不了解你。

每一次都要给它一个项目前言

现在我每次进行有意义的编码会话时,都会从我称之为"上下文块"的东西开始。写起来大约需要 90 秒。看起来大概是这样:

  • 技术栈:Next.js 14(App Router)、TypeScript、Supabase、Tailwind CSS 3.4Next.js 14 (App Router), TypeScript, Supabase, Tailwind CSS 3.4
  • 状态管理:Zustand,完全不用 Redux
  • 身份验证:Supabase Auth,通过中间件的 httpOnly cookies
  • 错误格式:{ success: boolean, error?: string, data?: unknown }{ success: boolean, error?: string, data?: unknown }
  • 样式约定:优先级优先,除非绝对必要,否则不使用自定义 CSS 文件

在任何非平凡的请求之前粘贴这个。我在 Cursor 中通过在项目根目录保留一个 _context.md 文件来做这个。两个按键就能粘贴。输出质量会明显提升——假设减少,需要撕掉的东西也减少。Cursor by keeping a _context.md file in the project root. Two keystrokes to paste. The output quality jumps noticeably -- fewer assumptions, fewer things I have to rip out.

---

具体性就是一切

回到 2022 年,在我还没有大量使用 AI 的时候,一个客户给我一份简报,内容就是两句话:"为我们构建一个预订系统。要做好。"我们花了三周时间来回讨论范围。那次经历给了我深刻印象,直接影响了我现在如何编写提示词。

模糊的提示 → 模糊的代码。每次都是这样。

"写一个获取订单的函数"能给你一些东西。"写一个名为 fetchOrdersByUser 的 TypeScript 异步函数,接受 userId: string 参数,查询 Supabase 中的 orders 表,其中 user_id 匹配且 status 不是 cancelled,按 created_at 降序排列结果,返回 Order[] 或抛出类型化错误"会给你一些你真正能发布的东西。fetchOrdersByUser that accepts a userId: string, queries the orders table in Supabase where user_id matches and status is not cancelled, orders results by created_at descending, and returns Order[] or throws a typed error" will get you something you can actually ship.

区别不在于模型的能力。在于提示词的具体性。

代码提示词中应该包含什么

  1. 函数名和签名——不要让模型自创命名规范 -- don't let the model invent naming conventions
  2. 输入类型和输出类型——如果相关就用 TypeScript 泛型 -- TypeScript generics if relevant
  3. 数据源——哪个表,哪个 API 端点,哪个缓存层 -- which table, which API endpoint, which cache layer
  4. 你已经知道的边界情况——"处理数组为空的情况" -- "handle the case where the array is empty"
  5. 不要做什么——"不要为此使用 useEffect,改用 server action" -- "don't use useEffect for this, use a server action"

最后一点比人们意识到的更重要。告诉模型要避免什么可以节省大量时间。我开始为每个项目保留一份小的"反模式"笔记——比如"除非用户交互需要,否则不要使用客户端组件"——我在该项目的提示中包含相关行。

---

链接你的提示。不要一次性要求所有内容。

Seahawk 在 2023 年末有一个金融科技客户——我不会说是谁——我们当时在构建一个多步骤 KYC 流程。复杂的东西。文档上传、活体检测集成、状态轮询。我早期犯了一个错误,要求 GPT-4 "构建完整的 KYC 流程组件"。它生成了 600 行看起来很宏大的垃圾代码。逻辑混乱,关注点混杂,UI 状态和业务逻辑之间没有真正的分离。

所以我放弃了,重新开始用链的方式。

第一个提示:"为 4 步 KYC 流程设计状态机。步骤:身份、文档上传、活体检测、审查。只给我状态类型和转换,不需要 UI。""Design the state machine for a 4-step KYC flow. Steps: identity, document upload, liveness, review. Give me the state type and transitions only, no UI."

第二个提示:"给定这个状态机[粘贴],编写Zustand store。""Given this state machine [paste], write the Zustand store."

第三个提示:"给定这个store[粘贴],编写StepIdentity组件。就这一步。""Given this store [paste], write the StepIdentity component. Just this step."

链式方法的输出是可用的。不完美——我仍然重写了大约 30%——但可用。单片方法什么都没给我。

Anthropic自己关于提示的指导讲述了将复杂任务分解为子任务,老实说,这正好与我通过反复试验发现的相吻合。在你破坏代码库之前,先把问题分解。 talks about breaking complex tasks into subtasks, and honestly, this aligns exactly with what I found through trial and error. Break the problem down before you break your codebase.

---

让它自我争论

这是我完全意外发现的。我在审查一个生成的实用函数时,没有直接运行它,而是添加了一个后续提示:"你刚才写的代码有哪些潜在的bug或边界情况?""What are the potential bugs or edge cases in the code you just wrote?"

模型发现了三个它没有考虑到的问题。其中一个是真实存在的问题——异步循环中的竞态条件,在生产环境中调试会很困难。

现在我定期这样做。写代码,然后要求它批评代码。然后要求它修复批评。这看起来有点荒谬——要求模型审查自己的工作——但它始终能发现我在痛苦的调试过程之后才会发现的问题。

你可以更进一步。得到一个有效的函数后,尝试:"重写这个代码以关注性能"或"这在高并发下会如何表现?"答案并不总是适用的,但大约40%的时间它们会浮出值得采取行动的东西。"Rewrite this with a focus on performance" or "How would this behave under high concurrency?" The answers aren't always applicable, but about 40% of the time they surface something worth acting on.

---

"角色 + 约束"框架

有一个提示词模式我现在经常使用,我真希望在第一年就想到了。它是这样的:"你是一个[具体类型的工程师]。你的约束是[硬性规则]。现在[任务]。""You are a [specific type of engineer]. Your constraint is [hard rule]. Now [task]."

示例:"你是一名后端工程师,深切关心数据库查询效率。你的约束是不能获取超过此渲染所需的内容——不要过度获取。为管理仪表板编写一个 Supabase 查询,返回订单计数、总收入和五个最近的订单。""You are a backend engineer who cares deeply about database query efficiency. Your constraint is that you cannot fetch more than what's needed for this render -- no over-fetching. Write a Supabase query for the admin dashboard that returns order count, total revenue, and the five most recent orders."

这个框架做两件事。它将模型的"角色"与我实际需要的东西对齐。约束充当护栏——模型在生成时明确针对其进行检查的东西。

OpenAI 的提示词最佳实践描述了类似的想法,即给模型一个具有明确指令的人设。如果你还没读过值得一看,不过我得说约束部分在他们的文档中被强调不足。 describe a similar idea around giving the model a persona with explicit instructions. Worth reading if you haven't, though I'd say the constraint piece is underemphasised in their docs.

比较那个框架化提示词的输出和"为管理员仪表板写一个 Supabase 查询"的输出。天差地别。真的。

---

何时停止提示词编写,直接写代码

这是没人想公开说的部分。

AI编码工具在以下方面表现出色:样板代码、CRUD操作、工具函数、为已有代码编写测试、格式转换(JSON schema转TypeScript类型、SQL转Supabase查询等),以及需要大幅修改的初稿。

它们的真正弱点是:理解应用的真实架构、判断哪种权衡对你的具体规模最重要、编写涉及复杂有状态交互的代码(需要大量指导)、以及规范本身就模糊不清的场景。

我现在有个个人规则:如果我为了让一段代码正常运行而发送超过四个后续提示,我就关闭对话,自己写。提示词调试的时间成本可能超过直接编写的时间成本,尤其是对于大约50行以下的代码。

Stack Overflow 开发者调查 2024 发现,76% 的开发者正在使用或计划使用 AI 工具——但相同的数据显示对准确性的信任相对较低。使用和信任之间的这种差距正是优秀提示工程所在的地方。Stack Overflow Developer Survey 2024 found that 76% of developers are using or planning to use AI tools -- but the same data showed relatively low trust in accuracy. That gap between usage and trust is exactly where good prompt engineering lives.

---

像版本化代码一样版本化你的提示词

去年我开始在需要重要AI辅助的项目中创建prompts/文件夹。Markdown文件。每个主要功能区域一个。当一个提示词产生特别好的输出时,我会保存它。当我找到更好的版本时,我会更新这个文件。prompts/ folder in projects where AI assistance is significant. Markdown files. One per major feature area. When a prompt produces particularly good output, I save it. When I find a better version, I update the file.

听起来很执着。在最后一个大项目中,它可能为我节省了大约六个小时——一个面向从 Shopify 迁移的零售商的无头 WooCommerce 构建。我在四个不同的组件中重用了一个产品查询提示(进行了小编辑),而不是从头开始重新设计上下文。

用 Git 追踪它。认真的。如果你把提示词当作工件而不是一次性输入,提示词质量是可复现的。LangChain 的提示词模板在框架层面规范化了这个想法,但你不需要任何框架——一个 Markdown 文件夹对大多数代理工作流来说已经足够了。LangChain's prompt templates formalise this idea in a framework context, but you don't need any framework -- a folder of Markdown files is enough for most agency workflows.

---

常见问题

提示工程到底是真正的可转移技能,还是只适用于特定模型?

基本上可转移。核心原则——具体性、上下文设置、链式连接、批评循环——适用于 GPT-4、Claude 3.5 Sonnet、Gemini,以及接下来出现的任何模型。语法会有细微差别,某些模型对特定框架的反应更好,但底层逻辑是一致的。我发现 Claude 倾向于对明确的约束做出良好反应;GPT-4 对示例反应良好。细微差异,相同的基础。syntax varies a bit and some models respond better to certain framing, but the underlying logic holds. I've found Claude tends to respond well to explicit constraints; GPT-4 responds well to examples. Minor differences, same fundamentals.

你在代码审查中如何处理 AI 生成的代码?

与其他代码相同。如果要投入生产,就要接受审查。句号。我已经停止在 Seahawk 的 PR 中标记"这是 AI 生成的",因为它成了一个红鲱鱼——审查者会以不同的方式审查它,有时不公平,有时不充分。代码凭自己的优点成立或失败。我标记的是:任何逻辑非显而易见且我没有添加内联注释解释推理的部分。

你用系统提示还是只用聊天提示?

两者兼有。在 Cursor 中,我依赖 .cursorrules 文件来处理持久的项目级指令——那些我否则会每次都粘贴的东西。对于 ChatGPT 或 Claude 网页版中的一次性任务,全部在聊天中进行。.cursorrules 方法已经明显减少了重复,模型在长会话中保持更一致。.cursorrules file for persistent project-level instructions -- things I'd otherwise paste every time. For one-off tasks in the ChatGPT or Claude web UI, it's all in the chat. The .cursorrules approach has meaningfully reduced repetition and the model stays more consistent across a long session.

你对 AI 替代开发者的真实看法是什么?

它在替代某些任务,不是替代开发者。判断调用——构建什么、如何架构、哪种权衡适合这个客户的实际情况——那些远未实现自动化。如果说有什么的话,受到挤压的是那些纯粹面向执行的开发者,而不是设计或架构导向的开发者。强化判断层。那是可防守的部分。tasks, not developers. The judgment calls -- what to build, how to architect it, which trade-off fits this client's actual situation -- those are nowhere near automated. If anything, the developers getting squeezed are the ones who were purely execution-oriented, not design or architecture-oriented. Sharpen the judgment layer. That's the defensible bit.

---

在网络上建造东西九年了,一个根本的教训不断重复:你的输出质量取决于你的输入质量。当时是客户简报,现在是提示词。the quality of your output is determined by the quality of your inputs. That was true when it was client briefs. It's true now with prompts.

这个模型是一个快速的、偶尔聪慧、频繁过度自信的初级开发者。相应地管理它。

< BACK