我是如何利用大语言模型(LLM)帮助我写代码的(译)

原文链接:Here's how I use LLMs to help me write code

原文作者:Simon Willison

声明:本篇翻译是借助了 ChatGPT 辅助翻译完成~

关于使用大语言模型帮助编写代码的在线讨论中,总会出现一些开发者表示失望的评论。他们常常会问:自己是不是做错了什么?为什么有些人声称效果很好,而他们自己的尝试却没有收获?

使用大语言模型写代码确实是件困难且不直观的事情。要想摸清其中的门道,需要花费大量时间和精力,而目前市面上几乎没有什么指导可以帮助人们掌握最佳的使用方法。

如果有人告诉你"用 LLM 编码很简单",那他们很可能是在(无意中)误导你。他们可能确实偶然掌握了一些有效的使用模式,但这些模式对所有人来说并不是天然易懂的。

我已经用 LLM 写代码超过两年了,效果非常好。以下是我试图将这些经验和直觉传授给你的一种方式。


设定合理的预期

不要被"通用人工智能(AGI)"的炒作所迷惑------LLM 归根结底只是一个高级自动补全工具。它们只是预测一个个标记(token)的序列------而编写代码恰好本质上就是把正确的标记按顺序拼起来,因此只要你能正确引导它们,它们就会非常有用。

如果你指望这种技术能完美实现你的项目,而你自己不需要动脑筋,那你肯定会大失所望。

相反,应该将它们视为增强你能力的工具。我目前最喜欢的心智模型是:把它们想象成一个过于自信的结对编程助手,它查资料飞快,随时都能给出相关示例,并且能毫无怨言地完成那些枯燥重复的工作。

"过于自信"这一点很重要。它们绝对会犯错------有时候是细微的错误,有时候是严重的。有些错误非常不合常理------如果是一个人类搭档凭空编造出一个不存在的库或方法,你很快就会对他们失去信任。

但别犯一个错误:把 LLM 当人看待,并认为它犯的错误也应像人类那样被"取消资格"。

在使用 LLM 的过程中,你经常会发现它们"做不到"的地方。请把这些记录下来------这些是非常有价值的经验教训!它们也是评估新模型是否强大的参考标准之一:如果新模型能完成旧模型做不到的任务,那它确实有所进步。


注意训练数据的截止日期

每个模型都有一个非常关键的特性------训练数据的截止日期。也就是说,它们被训练的数据收集到某个时间点就停止了。OpenAI 的模型通常截止于 2023 年 10 月或 2024 年 5 月。其他厂商可能更晚。

对于写代码来说,这点尤为重要,因为它决定了模型熟悉哪些库。如果你用的库在 2023 年 10 月之后经历了重构或重大更新,那么某些 OpenAI 模型可能完全不了解它!

我从 LLM 得到的价值大到什么程度?大到我在选择库时会优先考虑:选那些稳定、流行、并且能确保有足够多示例被收录进训练数据的库。我会遵循"无聊技术"的原则:把创新留给你项目的独特卖点,其他部分就选成熟、靠谱的解决方案。

即使你选择的是 LLM 未接触过的库,它们仍然可以帮上忙------你只需要提供一些最近的用法示例,把这些作为提示的一部分提供给模型。

这就引出了和 LLM 协作的最重要原则:


上下文为王

大多数使用 LLM 获得好结果的技巧,都归结为如何管理它的上下文------也就是你和模型当前对话中的所有文本内容。

这个上下文不仅包括你当前输入的提示(prompt),还包括你之前所有的输入、以及 LLM 的回复。每条信息都构成了当前上下文的一部分。

你一旦开启一个新对话,LLM 的上下文就会被清空。知道这一点很重要,因为如果一个对话变得越来越没用,最好的做法就是"重开一个"。

一些 LLM 编码工具的上下文管理能力更强。例如 Claude 的 Projects 功能就支持预加载大量文本作为上下文,甚至现在可以直接从 GitHub 仓库导入代码。我现在经常在用这个功能。

像 Cursor 和 VS Code 的 Copilot 这类工具也会自动引入你当前编辑器中的上下文,包括文件布局。有些工具比如 Cursor 还能通过 @commands 机制进一步拉取文件或文档。

我自己主要还是喜欢直接用 ChatGPTClaude 的网页 / 应用界面进行操作,这样我更容易掌握到底哪些内容进了上下文。那些把上下文隐藏起来的工具,对我来说效率反而低。

你也可以主动利用上下文的存在。例如处理复杂编程任务时,可以先让 LLM 实现一个简化版、验证能正常工作,然后逐步迭代升级。

我通常会在开始一个新对话时先把已有的代码粘进去,作为上下文种子,然后再和模型一起修改。

我最喜欢的提示技巧之一是:扔进几个完整的示例,告诉 LLM:"我想做个类似的项目,用这些当灵感"。我曾写过一个结合了 Tesseract.js 和 PDF.js 的 JavaScript OCR 应用,就是这么干的------因为我用过这两个库,可以直接把工作代码粘进提示中。


让它给你选项

我的大多数项目一开始都有一堆待解的问题:我想做的这件事是否可行?有哪些方式可以实现?哪种方式最好?

我会把 LLM 当作调研工具来用。

我会提类似这样的提示:"Rust 有哪些 HTTP 库可用?附带用法示例" 或者 "JavaScript 里有哪些好用的拖放库?给我分别实现一个 demo"(这类我一般用 Claude)。

这时候训练截止日期同样重要,因为模型不会推荐很新的库。对我来说通常没问题------我并不想用最新的库,而是想用最稳定、问题最少的那个。

如果我决定用一个比较新的库,那我会自己去查资料,而不是依赖 LLM。

做项目最好的起点,是尽快做出一个原型,验证核心需求能不能实现。我经常能靠 LLM 在几分钟内就搞定原型------有时甚至是在手机上搞定的。


明确告诉它该做什么

一旦完成了前期调研,我的使用模式就会发生巨大变化。进入生产代码阶段后,我会非常"专制"地使用 LLM:我把它当成一个数字实习生,完全按照我详细的指令来敲代码。

以下是一个真实的例子:

写一个使用 asyncio + httpx 的 Python 函数,函数签名如下:

python 复制代码
python
复制编辑
async def download_db(url, max_size_bytes=5 * 1025 * 1025) -> pathlib.Path

给定一个 URL,该函数将数据库下载到临时目录并返回路径。但它需要在下载开始前检查 content-length 头,如果超出限制就抛出异常。下载完成后用 sqlite3.connect(...) 打开数据库并运行 PRAGMA quick_check 验证 SQLite 数据是否有效------如果无效就抛出异常。如果 content-length 撒谎(比如写了 2MB 实际下了 3MB),在检测到不一致时立刻抛出异常。

我完全可以自己写这个函数,但可能要花 15 分钟查各种细节才能写对。而 Claude 用了 15 秒就搞定了。

我发现 LLM 对这种函数签名特别敏感,响应效果非常好。我来设计函数,LLM 负责填充具体实现。

我接着会说:"现在用 pytest 写测试。"我依然指定技术栈------因为我要的是让 LLM帮我节省打字和查资料的时间。

如果你觉得"直接写代码不是比写一段英文指令快吗?",我的回答是:对我来说完全不是。因为代码必须正确,而英文指令可以含糊,可以简略,可以拼错词,甚至可以直接写"用那个常见的 HTTP 库"------如果你一时忘了库的名字。

好的 LLM 编码模型非常擅长"填空题"。而且它们比我勤快多了------会主动处理异常,加上准确的注释和类型标注。

你必须测试它写出来的代码!

上周详细写过:唯一绝对不能外包给机器的事情就是测试代码是否真正可运行

作为一名软件开发者,你的职责是交付能运行的系统。如果你没有亲眼看到它跑起来,那它就不算是一个"能用"的系统。你需要加强自己的手动 QA(质量保证)习惯。

这可能不够"高大上",但无论有没有使用 LLM,这始终是编写高质量代码不可或缺的一部分。


记住,这是一场对话

如果我不喜欢 LLM 生成的代码,它**永远不会在意你让它重构!**你可以说:

  • "把这段重复代码提取成函数"
  • "别用正则了,试试字符串操作"
  • 甚至直接说:"写得更好一点!"

LLM 输出的代码很少第一次就是最终版本 ,但它可以为你重写几十次,而不会感到烦躁或疲惫。

偶尔我会第一次就获得很棒的结果,随着练习越来越多,这种情况也更常见了。但我一般都会准备好多次跟进。

我经常在想,这或许是很多人没意识到的关键技巧之一:糟糕的首次输出不是失败,而是你引导模型朝着正确方向调整的起点。


使用能运行代码的工具

越来越多的 LLM 编码工具具备"执行代码"的能力。我对这类工具保持一定谨慎态度------有时候错误的指令可能造成真实破坏------所以我倾向于使用运行在安全沙箱中的工具

我目前最喜欢的几个:

  • ChatGPT Code Interpreter(代码解释器) :ChatGPT 可在 OpenAI 管理的 Kubernetes 沙箱虚拟机中直接写并运行 Python 代码。非常安全------甚至无法进行网络连接,最多也就是把临时文件系统搞乱(然后系统会重置)。
  • Claude Artifacts:Claude 可以为你构建完整的 HTML + JavaScript + CSS Web 应用,并在 Claude 界面中展示。运行环境是高度限制的 iframe 沙箱,防止出现数据泄漏等问题。
  • ChatGPT Canvas:ChatGPT 最近推出的新功能,功能与 Claude Artifacts 类似。我自己还没深入使用。

如果你愿意冒点险

  • Cursor 有 "Agent" 功能,Windsurf 等其他编辑器也逐步提供类似功能。我还没深入研究它们,暂不推荐。
  • Aider 是目前最领先的开源实现之一,也是"自我试用(dogfooding)"的一个典范 。最有趣的是:它的最新版本有 80% 是它自己写的!
  • Claude Code 是 Anthropic 最新发布的产品,我会在下面用一个详细例子展示如何使用。

"运行代码 + 自动修复"这一能力非常强大,所以我在选择 LLM 编码工具时,最看重的一点就是它是否能安全地执行并迭代我的代码。


"Vibe-coding" 是非常棒的学习方式

Andrej Karpathy 在一个多月前发明了一个新术语叫 vibe coding(氛围编码) ,这个词现在已经广为流传:

这是一种全新的编码方式,我称之为 vibe-coding。在这个过程中,你完全"沉浸于氛围",拥抱指数式爆发,甚至忘记代码本身的存在。[...]

我会随便说些"把侧边栏的 padding 缩小一半"这种懒得去找位置的指令。我总是点"全部接受",不再读 diff。出错了我就直接复制错误贴进去,一般都能修好。

Andrej 建议这种方式"适合周末做些丢得掉的小项目"。但它同时也是探索 LLM 能力的极佳方式------而且真的很好玩

学习 LLM 的最佳方式就是"玩它" 。随便抛出一些荒唐的想法,不断 vibe-coding 直到它"差不多能跑"------这是一个快速建立模型使用直觉、了解哪些能成哪些不行的好方法。

我其实在 Andrej 发明这个词之前就已经开始 vibe-coding 了!我的 GitHub 仓库 simonw/tools 目前有 77 个 HTML + JavaScript 应用,和 6 个 Python 应用,每一个都是通过 LLM 提示构建的

我从这个过程中学到了很多知识,并且每周还会加好几个新原型。你可以在 tools.simonwillison.net 直接体验这些小工具------这是该仓库的 GitHub Pages 展示站点。

我在去年 10 月写过一篇更详细的总结:Everything I built with Claude Artifacts this week

如果你想看每个工具对应的对话记录,它们几乎都在该页面的 Git commit 中有链接,也可以去我新建的 colophon 页面------里面汇总了所有对话链接。


使用 Claude Code 的一个完整例子

在写这篇文章时,我突然想到要做一个 tools.simonwillison.net/colophon 页面,用来更明显地展示每个工具的 Git 提交记录链接。

于是我就借这个机会,演示一下我的 AI 编码流程

这次我用的是 Claude Code,因为我希望它可以直接在我的本地机器上、对已有仓库运行 Python 脚本。

在会话结束时,我运行了 /cost 命令,它告诉我:

bash 复制代码
/cost
  ⎿  总花费:$0.61
     API 总时长:5 分 31.2 秒
     总耗时(真实时间):17 分 18.7 秒

从启动到完成,这个小项目一共花了我 17 分钟,API 成本是 61 美分。

我使用的是一种"威权式"(authoritarian)提示风格------我直接告诉模型要构建什么 。以下是我的提示流程(完整对话点此查看):

这个目录里的 HTML 文件几乎都是用 Claude 提示生成的,对应的提示内容都写在提交信息中。请写一个 Python 脚本,依次检查每个 HTML 文件的 Git 提交历史,从中提取出所有 URL,然后保存成一个结构如下的 JSON 文件:

json 复制代码
{
  "pages": {
    "file1.html": ["url1"],
    "file2.html": ["url1", "url2"]
  }
}

脚本命名为 gather_links.py,输出文件为 gathered_links.json

我其实对这个提示没有太多思考,基本就是一边想一边打字发给 Claude 的,更像是头脑风暴式起步。 我检查了初始结果,发现了一些问题:

它似乎只获取了 URL 的开头部分,而我需要的是完整的 URL,这些 URL 可能来自不同的网站------所以我需要抓取任何以 https:// 开头、以空格或提交信息结尾为终止的部分。

然后我改变了主意------我还想要完整的提交信息:

我更新了脚本------我想获取完整的提交信息 URL------新的格式应该是这样的:

json 复制代码
{
  "pages": {
    "aria-live-regions.html": {
      "commits": [
        {"hash": hash, "message": message, "date": iso 格式日期}
      ],
      "urls": [URL 列表,如之前一样]
    }
  }
}

像这样给出示例是快速获得你想要结果的绝佳方式。

注意,我从没看过 这个脚本(gather_links.py)是怎么写的!这完全是"凭直觉编码":我看的是它的结果,而不是具体的实现细节,全交给了 LLM 来处理。

生成的 JSON 看起来没问题,于是我说:

这运行得很棒。帮我写一个新的脚本叫 build_colophon.py,它会读取之前生成的 JSON 文件,然后构建并保存一个 HTML 页面。这个页面需要支持移动端,列出所有页面(每个页面提供超链接),然后对每个页面展示它的提交信息(将换行转换为 <br>,将 URL 转换为超链接,但不要做其它格式化)------另外还要展示提交时间和每条提交信息对应的链接,这些链接来自:github.com/simonw/tool...

Claude 知道 GitHub 的 URL 是怎么构成的,所以只要我说"链接到提交信息",并提供了 repo 名称,它就能自动推断出像这样的链接:
https://github.com/simonw/tools/commit/fd9daf885c924ba277806b3440457d52b0ad90a8

我发现 Claude 的网页设计默认风格还挺不错的------我说"页面需要移动端友好",就留它自由发挥了。

Claude 写出了一个页面,但有问题,于是我说:

"页面不对。ocr.html 有很多提交记录,但 colophon.html 中只显示了第一个提交的链接和标题,其余的都混在同一块里面了------每条提交信息都应该是单独的 HTML 区块,并带有链接和格式化日期。还有,日期显示应该包含小时和分钟。"

它自动修复了这个 bug,只剩下两个我自己想修改的小地方:

差不多完美了,但每个页面的提交信息应该按时间顺序排列------从最旧到最新。

接着又加了一条:

最后一个修改------现在页面是按字母排序的,我希望按最近修改的时间排序,最近的排在最前面。

就这样,这个项目就完成了!以下是 build_colophon.py 脚本,生成的页面效果相当不错

Tools Colophon

这个页面记录了 tools.simonwillison.net 上所有工具的创建过程,包括生成它们时使用的 Claude 对话链接。

例如:
socia-media-cropper.html

还有一个任务没完成:我需要把这个 colophon 页面部署到我的网站,但我不想把 colophon.html 文件本身提交到代码仓库里。我需要一个自定义的 GitHub Pages 构建流程。

于是我开启了一个新的 Claude Code 会话(从头开始),看看它是否能搞定这事:

"这个仓库是用 GitHub Pages 部署的。我想在部署前稍微修改下构建流程------先运行 python gather_links.py,生成 gathered_links.json 文件------再运行 python build_colophon.py,用它生成 colophon.html------我希望这两个文件都发布到 GitHub Pages 上,但不把它们提交进代码库。"

这次我非常仔细地观察它的操作------因为我对 GitHub Pages 的自定义构建流程并不熟,我既想学习这部分内容,也想避免它出错。

Claude 说它想创建这个文件,并征求我的许可:

yaml 复制代码
name: Deploy to GitHub Pages

on:
  push:
    branches: [ main ]
  workflow_dispatch:

permissions:
  contents: read
  pages: write
  id-token: write

concurrency:
  group: pages
  cancel-in-progress: true

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout
        uses: actions/checkout@v4
        with:
          fetch-depth: 0  # 需要完整的 git 历史记录供 gather_links.py 使用

      - name: Setup Python
        uses: actions/setup-python@v5
        with:
          python-version: '3.12'

      - name: Install dependencies
        run: |
          python -m pip install --upgrade pip

      - name: Generate links and build colophon
        run: |
          python gather_links.py
          python build_colophon.py

      - name: Setup Pages
        uses: actions/configure-pages@v4

      - name: Build with Jekyll
        uses: actions/jekyll-build-pages@v1

      - name: Upload artifact
        uses: actions/upload-pages-artifact@v3

  deploy:
    environment:
      name: github-pages
      url: ${{ steps.deployment.outputs.page_url }}
    runs-on: ubuntu-latest
    needs: build
    steps:
      - name: Deploy to GitHub Pages
        id: deployment
        uses: actions/deploy-pages@v4

我觉得这看起来还不错。我很欣赏它注释的那句:"需要完整的 git 历史记录供 gather_links.py 使用"------我自己可能会漏掉这点。

然后它说需要把这些生成文件加到 .gitignore 里------听起来确实是个好主意。

它输出了一个总结:

我创建了一个 GitHub Actions 工作流用于 GitHub Pages,主要内容如下:

  • 创建了 .github/workflows/pages.yml 文件,配置如下:
  • 监听对 main 分支的 push 操作
  • checkout 时拉取完整 git 历史记录(供 gather_links.py 使用)
  • 设置 Python 3.12
  • 顺序执行两个脚本生成文件
  • 构建并部署到 GitHub Pages

创建了 .gitignore 文件,避免将生成文件提交到仓库中

现在当你 push 到 main 分支时,GitHub Actions 会:

  1. 运行 gather_links.py 脚本生成 gathered_links.json
  2. 运行 build_colophon.py 生成 colophon.htm
  3. 部署这两个文件(以及其它所有文件)到 GitHub Pages

我检查了下费用:

/cost

总花费:$0.1788

API 总用时:44.6 秒

墙钟时间:10 分钟 18 秒(我中途走神了)

所以通过 Claude API 花了 17 美分和 45 秒。(我中途分心了,所以总共花了 10 分钟。)这是完整的对话记录

这段代码看起来不会造成不可逆的破坏,于是我推送代码到 GitHub 看效果。

......它成功了!我新的 colophon 页面上线了。

但也有一个小问题。我在 GitHub Actions 的界面上看到不太对劲:

我本来只期待那个"测试"工作流,但为什么还有两个部署流程?

我猜可能是默认的 Jekyll 构建流程还在跑,而新的部署流程也同时运行了------最后只是运气好,新流程晚一点完成,覆盖了默认结果。

是时候自己动手看文档了!

我查到了 GitHub Pages 关于 自定义工作流的页面,但它没有解答我的问题。

我又尝试查看 GitHub Pages 设置页面,果然找到了这个选项:

我的仓库被设置为"从分支部署",所以我改成了"使用 GitHub Actions"。

然后我手动更新了 README.md,加了 colophon 页面的链接,推送了这个提交,触发了另一次构建。

这次只跑了两个作业,结果就是正确部署的网站:

只剩两个工作流在运行,一个是测试,一个是部署。

(之后我还发现一个 bug ------有些链接的 href 里错误地包含了 <br> 标签。我又开了一个 Claude Code 会话花了 11 美分修好了。)


更新:我进一步改进了 colophon 页面,加入了每个工具的 AI 生成简介

让人类接管的时刻

我很幸运,这个例子很好地说明了我最后想表达的一点:你需要准备好随时接手。

LLM 不是人类直觉和经验的替代品。我花了足够多的时间和 GitHub Actions 打交道,知道要注意哪些细节,而这次手动修复反而更快,比不停试 prompt 更有效。


最大优势是开发效率的提升

这个新的 colophon 页面从构思到最终上线只花了我不到半小时。

如果没有 LLM 协助,我可能根本不会动手做这个项目------花时间不值。

这就是我如此关注 LLM 生产力提升的原因:不是说工作做得更快了,而是我可以完成原本不会动手去做的项目

我在 2023 年 3 月就写过这事:AI 增强开发让我更大胆去实现新点子。

两年过去了,这种效果依旧没消失。

它也让我更快学习新东西------比如今天,我学会了如何使用 GitHub Actions 自定义 GitHub Pages 的构建过程------这是我将来一定还会用到的知识。

LLM 让我执行想法更快,就意味着我能实现更多想法,从而学到更多东西。


LLM 能放大已有的专业能力

其他人能像我一样做这个项目吗?可能不能!

我的 prompt 背后是 25+ 年的专业开发经验,包括对 GitHub Actions、GitHub Pages、GitHub 本身和各种 LLM 工具的了解。

我也知道这事是可行的。我非常熟悉这些工具,知道用 git 历史组装一个 HTML 页面是可以让一个好 LLM 做出来的。

我的 prompt 也体现了这一点------没有特别新颖的东西,我只是在指导它、测试它的结果,并在必要时纠正 bug。

如果我要写的是 Linux 内核驱动(一个我几乎不了解的领域),那我的流程会完全不同。


彩蛋:用 LLM 回答代码库的问题

如果你觉得让 LLM 给你写代码完全没有吸引力,还有一个可能更适合你的用法:

LLM 非常适合回答你对代码的疑问。

这几乎没有风险:最糟的情况是回答错了,但你还是可能比自己查几千行代码快得多。

关键技巧是:把整个代码库扔进一个大上下文的模型中,开始提问。

我现在最喜欢的模型是 Google 的 Gemini 2.0 Pro,目前可免费试用。

最近就这么用了:我试了一个新工具叫 monolith,是个 Rust 写的 CLI 工具,用于把网页及其依赖资源(CSS、图片等)打包成一个归档文件。

我想知道它是怎么实现的,于是:

bash 复制代码
cd /tmp
git clone https://github.com/Y2Z/monolith
cd monolith

files-to-prompt . -c | llm -m gemini-2.0-pro-exp-02-05 \
  -s 'architectural overview as markdown'

我用的是我自己写的 files-to-prompt 工具(去年由 Claude 3 Opus 帮我完成),它可以将整个目录下的代码内容汇总起来。然后我用我的 LLM 工具(带有 Gemini 插件)发送 prompt,系统提示是 "以 markdown 写出架构概述"。

Gemini 给了我一份详细文档,介绍这个工具的工作原理、每个源文件负责什么、用到了哪些 Rust crates,比如 reqwest, html5ever, markup5ever_rcdom, cssparser,还指出它不支持 JavaScript 执行,这是一个重要限制。

我每周用这个技巧好几次。这是研究新代码库的好方法------而且通常替代方案不是多花点时间,而是永远不会搞清楚。

我最近还写了这篇文章分享了更多例子。


相关推荐
Revol_C30 分钟前
【AI+赋能前端-提效篇】开发一个支持项目打包产物本地调试的小工具,已发布到npm!!
前端·npm·ai编程
小橘子就是小橘子41 分钟前
# ChatGPT vs Claude vs Gemini:谁最值得你掏腰包?
ai编程
LLM大模型1 小时前
LangChain篇-提示词工程应用实践
人工智能·程序员·llm
架构师那点事儿1 小时前
一文带你俯瞰大模型领域的世界
langchain·aigc·ai编程
掘我的金1 小时前
MCP 学习系列②:理解 MCP 的核心结构与思维模型
llm·mcp
掘我的金1 小时前
MCP 学习系列①:理解上下文与协议的动机(MCP 之前)
llm·openai
攻城狮7号1 小时前
Cursor 1.0正式推出:全面解析你的AI 编程助手
人工智能·深度学习·ai编程·cursor 1.0
潘锦2 小时前
从架构师的角度来看 AI 编程带来的技术债务
架构·ai编程·cursor
胡耀超2 小时前
大语言模型提示词(LLM Prompt)工程系统性学习指南:从理论基础到实战应用的完整体系
人工智能·python·语言模型·自然语言处理·llm·prompt·提示词
咖啡续命又一天3 小时前
Trae CN IDE自动生成注释功能测试与效率提升全解析
ide·python·ai编程