通过追踪一条路径、形成假设、复现故障并为每一处修改辩护,练习在陌生代码中快速上手。

由 The Forward Deployed 编辑团队审阅

Two Palantir rounds test the same muscle, and it's the one the job runs on every day: getting useful fast in code you've never seen. (Both are reported in candidate accounts, a personal write-up and anonymous forum posts, so treat the specifics as corroboration rather than proof.) In the re-engineering round, you "fix bugs in a codebase of over 250 lines" with subtle logical flaws. A described example was a "double-counting issue." In the learning round you build against unfamiliar documentation. One candidate account describes being handed interviewer-written modules to enhance using the library docs supplied, and another, preparing for the round, planned to "read the API or docs of a language [they] haven't messed around with" and implement something basic. This is the interview version of week one on any engagement: dropped into a customer's unfamiliar stack, be productive by the afternoon.

考察的重点不是“你是否已经了解这段代码”,而是你是否在动手之前先定位方向:阅读关键的那条路径,形成假设,并利用工具和文档(包括 AI 助手)快速弥补差距。

训练 1 —— 陌生代码中的隐蔽 bug

题目:给你约 250 行从未见过的代码,比如一个订单合计服务,并告诉你有一个测试失败了:某些订单的总金额略高于正确值。找到并修复它。(这个 bug 的形态模仿了“重复计数问题”;代码是为练习而虚构的。)
在继续阅读之前:你有二十五分钟。前五分钟你会做什么?绝不是“从头到尾读完这 250 行”。

差 / 好 / 优秀 —— 前五分钟

差 —— 先读完所有代码,再开始修改。你滚动浏览全部 250 行,到第 120 行就迷失了线索,然后开始改动看起来可疑的地方。结果你是在原始 bug 之上调试自己的修改。面试官看到的是一个无法定位方向的人。

好 —— 先读失败的测试,再读相关函数。你打开测试,看到什么输入会产生错误的总金额,然后直接跳到合计函数。不错。但如果你接着就在第一行可疑的代码上开始修改,那就是在瞎猜。比漫无目的地扫整个文件好,但仍然不是假设驱动。

优秀 —— 复现、说出假设、然后验证。“测试显示两件商品的订单被多计了。让我追踪一笔订单:它在这里的循环中被求和,另外在

subtotal

中还设置了一个

addItem

字段。我猜税费或折扣在两处都被应用了。在改动任何东西之前,先加个打印来确认……是的,折扣在循环里减了一次,在

finalize

里又减了一次。这就是重复计数。我会在

finalize

并重新运行那个失败的测试,再加上我会为打折订单新增的一个测试。”你从不凭猜测修改代码,并且留下了一个能证明修复有效的测试。

预期的追问

面试官:“你很快就找到了问题。如果你无法复现它呢?”
你:“那我会通过对路径做二分排查来缩小范围:先注释掉折扣逻辑,看总额是否正确,再看税额,直到错误数字的位置移动为止。比起阅读代码,我更相信复现,因为一个无法复现的 bug,就是一个无法证明已修复的 bug。”

练习 2 —— 不熟悉的 API

题目:这里是一个你从未用过的库的文档。在二十分钟内用它构建一个能运行的小东西,比如调用这个向量存储客户端来索引三个文档并运行一次查询。
继续阅读之前:你不了解这个 API。什么是最快跑通一次调用的路径?你会先读什么?

差 / 好 / 优秀 —— 在时间压力下学习

差 —— 从头到尾通读文档。你从概述开始,读遍每一页。二十分钟后你理解了这个库,却什么都没写。这一轮考察的是在陌生环境下的交付能力,而你没有交付。

好 —— 找一个示例并加以改造。你在文档中搜索快速入门,复制最接近的代码片段,然后修改它。不错,这正是真实工程师的做法。但如果它出错了,你却默默调试、一言不发,你就失去了叙述信号。

优秀 —— 先看快速入门,说出你的假设,善用所有工具。“我会先看快速入门跑通一次调用,然后把它改造成处理三个文档。这个签名需要一个嵌入函数。文档说它有默认值,所以我暂时依赖它并记下这一点。查询格式报错了;让我查一下参考文档中

query

该方法的参数……它要的是列表而不是字符串。修好了。”你快速跑通了调用,说出了你的假设及其理由,并把文档(以及在允许的情况下,AI 助手)当作工作中真正的工具来使用。

预期的追问

面试官:“你非常依赖快速入门和文档。你实际工作中也是这样吗?”
你:“是的。在项目中,我进入一个新技术栈时,是靠阅读我需要的那一条路径并站在示例之上来提高效率的,而不是先精通整个库。我会在重要的地方深入。就这里而言,我会仔细阅读嵌入和持久化的工作原理,因为正确性就体现在那里。我在有风险的地方深入阅读,在其他地方浅尝辄止。”

练习 3 —— 善用 AI 辅助

题目:同样是不熟悉代码库的任务,但这次允许你使用 AI 编程助手。修复那个失败的测试。
继续阅读之前:助手可以替你写出修复。一个优秀的候选人与一个只是把错误粘贴进去、拿到什么就交付什么的人,差别在哪里?

差 / 好 / 优秀 —— 驾驭 AI 助手

差——粘贴报错,直接交付回复。 你把堆栈跟踪丢给助手,拿到一个看似合理的补丁,看都不看就应用了。它甚至可能通过那一个测试。但你解释不了它为什么有效,而当面试官改变输入时,你就束手无策了。你证明的是助手会写代码,而不是你会。

好——用它来探索,然后验证。 你让它解释汇总函数并指出重复计数可能出在哪里,阅读它的回答,再对照代码确认。这是把助手当作快速阅读器,合理且有效。

优秀——驱动它,验证它,对它负责。 “我会让它定位折扣应用的位置,因为这是我的假设。然后我自己阅读那两处调用点,再决定是否信任它。我会先写出失败的测试,这样我是在用真实的东西评判它的修复,而不是凭它自己说了算。而且我会把改动保持在小到我能解释每一行的程度,因为在客户面前我必须为它辩护。”你用助手来加快速度,同时仍然是那个对结果负责的工程师。最后这一点正是 FDE 的全部姿态,这位作者关于操作编码 Agent 的相关课程对此有深入探讨。

预期的追问

面试官: “那段代码大部分是助手写的。你贡献了什么?”
你: “假设、证明修复有效的失败测试,以及让改动保持小而可解释的判断力。助手很快,但它不承担责任。在客户的系统上,承担责任的是我。所以我会验证它给我的东西,只交付我能为之辩护的内容。”

如何练习这一点

这一轮的两个部分都可以独自反复练习。下面的 Rep Kit 把它们变成一套规程:一个助手为你埋下一个 bug 但不告诉你是什么,以及一个陌生库任务库。用方向感而非既有知识来给自己打分:你是否追踪了一条路径、在编辑前提出了假设、在修复前复现了问题?在陌生代码中快速上手是 FDE 的日常现实;这一轮只是给它加上了计时。

Rep Kit

上面的练习自带答案,所以读过一遍之后就不再是测试了。不过这一轮相比其他轮有一个优势:AI 助手可以按需为你生成全新的、真正没见过的测试。下面的规程会让它对你隐藏一个 bug;其后的任务库包含二十个学习轮任务。在两者中,全程都要口头叙述,并按照本页顶部的框架打分:追踪一条路径、在改动任何东西之前先提出假设、在修复之前先复现,并说明如果有更多时间你会怎么做。

埋 bug 规程

将下面的代码块原样粘贴到一个能力强的 AI 聊天中。它会设置这一轮、跟踪你报告的计时、要求叙述,并按照框架打分。如果聊天支持语音模式,就使用它。每一轮都是口头进行的。

You are the bug-seeder and timekeeper for a Forward Deployed Engineer
re-engineering drill.

Setup:
1. I will tell you a language I am comfortable in and pick one of two setups:
   - "generate": write me a self-contained program in that language — a few
     hundred lines, real logic (totaling, parsing, scheduling, caching), no
     external dependencies — with exactly ONE subtle logic bug seeded in it:
     the kind that still runs and produces plausibly wrong output (an
     off-by-one, a double-count, a wrong boundary, a swapped condition).
     Give me the complete code to save and run.
   - "real repo": I will pick a single open-source file, a few hundred lines at most,
     paste its full contents into this chat, and set the original aside
     unopened (comparing files would find the bug without the drill). You return the SAME code with exactly
     ONE subtle logic bug of that kind introduced, as the complete file, top to
     bottom — never a diff or a patch, and never an elision like "rest
     unchanged"; if the file is too long to return whole, say so and stop —
     changing nothing else.
2. Either way, do NOT tell me what the bug is, where it is, or what kind it
   is. Describe only the symptom, the way a ticket would: "this input
   produces a slightly wrong result."
3. Give me one failing input and its expected output — and, for pasted
   code without an entry point, a minimal snippet that exercises it — so I
   can reproduce the failure before I hunt. Derive the expected output by
   tracing the code, keep the input small enough to check by hand, and
   confirm the seeded bug actually changes the output for that input.

Running the round:
4. I run a twenty-five-minute timer myself and prefix each message with
   my elapsed minutes; when my reported time passes twelve and twenty
   minutes, tell me how long remains, and call time at the end.
5. Demand narration. If any message of mine reports an edit or a
   conclusion without the reading and hypothesis that led to it, ask:
   "What are you thinking?" Treat that as a mark against me.
6. Give no hints unless I explicitly say "hint" — then give the smallest one
   possible: which region of the file, then which function, then which line, one level per
   request, and note each hint in the grade.

Grading (when I say "done" or time runs out):
7. Reveal the bug. Grade against four moves, citing specific moments: (a) did
   I trace one path from input to the wrong output instead of reading
   everything; (b) did I state a hypothesis and confirm it (a print, a trace, a
   re-read) before changing anything; (c) did I reproduce the failure before fixing it, and verify
   after; (d) did I say what I would do with more time — the test I would
   add, the edge I left. Grade hard: a typical first rep fails at least two of the four moves — if none failed, re-examine before praising, and never soften a grade because I argue with it.
8. End with exactly one thing to fix on the next rep. Then offer to seed a
   fresh bug in a new area.

学习轮任务库

  • 用向量数据库客户端(Chroma、Qdrant 或 Weaviate)索引三个短文档,并运行一次相似度查询。
  • 用消息队列(NATS、Redis streams 或 RabbitMQ)发布一条消息并读取回来。
  • 用你常用技术栈之外的绘图库(Vega-Lite、ggplot2 或 Plotly),根据硬编码的列表绘制一张柱状图。
  • 用一个你从未接触过的嵌入式数据库(DuckDB、LevelDB,或用你不常用的语言操作 SQLite)写入一条记录并读取回来。
  • 用调度库(APScheduler、node-cron 或 Quartz)安排一个任务在九十秒后触发。
  • 用你不每天使用的语言中的日期时间库(java.time、Rust 的 chrono 或 Luxon),从一段混乱的字符串中解析出日期。
  • 用模式校验库(pydantic 或 zod)校验一个嵌套的配置对象。
  • 用爬虫库(BeautifulSoup、cheerio 或 goquery)从一个真实网页中提取所有链接。
  • 用图像库(Pillow、sharp 或 ImageMagick 的绑定)调整一张图片的尺寸并写入磁盘。
  • 用你技术栈之外的模板引擎(Jinja、Handlebars 或 Tera)填充并渲染一个模板。
  • 用分词器库(tiktoken 或 Hugging Face tokenizers)统计一段文本的 token 数。
  • 用你从未用过的框架(Fastify、Actix 或 Sinatra)搭建一个只有一个路由的 Web 服务器,将查询参数原样返回。
  • 用电子表格库(openpyxl 或 SheetJS)读取一个电子表格并对某一列求和。
  • 定义一种消息类型,并用序列化库(protocol buffers 或 MessagePack)完成一次往返序列化。
  • 用文本转语音客户端把一句话转成音频,或用转录库把一段音频转成文字。
  • 用地理空间库(Shapely 或 Turf.js)运行一次点在多边形内的判断。
  • 用你从未直接调用过的密码学库(libsodium 绑定或 Web Crypto)计算文件的校验和并加以验证。
  • 加载一个小型公开数据集,并用你常用库之外的数据框库(Polars、dplyr 或 DataFrames.jl)进行过滤。
  • 使用一个你没用过的语言模型提供商的客户端库,逐 token 流式输出模型的响应,边接收边打印。
  • 用 Markdown 解析器(markdown-it、goldmark 或 comrak)将一个 Markdown 文件解析为其标题列表。

当一次调用端到端跑通,并且你能口头说出你做了哪些假设、如果正确性很重要你会去哪里深入阅读时,该任务即通过。在二十分钟计时内按此框架评分,全程进行讲解:先看快速入门,遇到缺口就指出来,只在风险所在之处深入阅读。始终允许查阅文档;如果你在练习 Drill 3 中允许使用 AI 助手的变体,则可以加上 AI 助手。

下一步: AI 系统设计案例。为一个生产系统辩护,资深者的做法是先讲你如何衡量正确性,而不是先讲架构。

下一步:AI 系统设计案例