Palantir FDSE 面试:再工程轮与学习轮怎么练
这两轮都不看你多快找到 bug,看的是你怎样一步步定位问题,以及在别人注视下怎样学会新东西。
要点速览
- 再工程轮通常给一个 200–1,000 行、输出结果有误的代码库。bug 出在逻辑而非语法,面试官看的是你调试是否有章法。
- 表现好的候选人从错误输出出发,一路倒推到输入。随便找个地方猜、改、再运行,往往就是被拒的原因。
- 学习轮要认真读文档,把自己的理解说出来,并提出高层次的问题。长时间沉默很容易被当成卡住了。
你拿到一个从没见过的代码库,200 到 1,000 行。它输出的结果是错的,你有 60 分钟在 CodePair 里把它修好。bug 可能只是一个减号变成了加号。
这就是 Palantir 前线部署软件工程师(FDSE)现场面试中的再工程(Re-engineering)题。现场面试最多四轮,从五种题型中抽取:问题拆解(Decomposition)、再工程、编码(Coding)、学习(Learning)和系统设计(System Design)。本文讲其中两种:再工程和学习。
这两轮值得认真练,因为它们与实际工作很接近。再工程意味着面对一个现有系统:先理解,再评析,然后扩展或修复。这相当真实地模拟了前线部署工程师(FDE)处理客户遗留系统时的工作。
学习轮给你一个陌生的工具或 API,要求你马上用起来。不得不使用客户现有工具时,这种情形同样很常见。
评分看过程,不看速度
Aced/Exponent 的指南写得很明确:面试官关注的是你的调试过程是否系统,而不是你多快发现第一个 bug。据 TechPrep 介绍,这一轮还会衡量你要花多久才能摸清一个杂乱的代码仓库并开始产出有用的工作。
所以光靠大量刷题不够,需要把一套流程练成条件反射。下面几步介绍如何在自己的笔记本上搭一小套练习来做到这一点。
准备:Python 3、一个编辑器、一个计时器,以及录下自己的工具。有朋友愿意扮演面试官更好。
第 1 步:在代码里预埋 bug
Palantir 题目里的 bug 属于逻辑 bug:符号反了,对排序顺序做了错误假设,或者变量在循环迭代之间没有重置。下面的代码专为练习而写,并非真实面试题,其中故意埋了这三类 bug 中的两类。
# ledger.py — 自建练习,预埋了 bug
def net_by_customer(txns):
result = {}
total = 0
current = None
for cust, kind, amount in sorted(txns):
if cust != current:
if current is not None:
result[current] = total
current = cust
if kind == "refund":
total += amount
else:
total += amount
result[current] = total
return result
txns = [("A", "sale", 100), ("A", "refund", 30), ("B", "sale", 50)]
print(net_by_customer(txns))
运行 python3 ledger.py。正确结果应是 A 为 70,B 为 50,实际输出却是 {'A': 130, 'B': 180}。检查:如果你看到的正是这两个数,练习就准备好了。
第 2 步:从错误输出倒推到输入
表现好的候选人会沿着数据从输出倒推回输入;靠猜测和试错的人往往被拒。先从错得最明显的数字下手。
B 的结果是 180,可客户 B 只有一笔 50 的交易。多出来的 130 一定来自别处,而 130 恰好是 A 的值。往上追,你会发现客户切换时 total 没有重置为 0,于是把 A 的全部余额带到了 B。
再看 A。它显示 130 而不是 70,相差 60,正好是 30 元退款的两倍。差额恰好是某个值的两倍,这是符号反转的典型信号:代码本该减去 30,却加上了 30。现在你有了两个假设,都是从数字推出来的,而不是凭感觉。
检查:每修一个 bug 就重新运行,看哪个数字变对了。一次只改一处,这样才能确定是哪处改动带来了哪个结果。
第 3 步:修之前先通读整段代码
Aced/Exponent 建议在确定修法之前,从头到尾通读整段代码,因为最显眼的问题未必是真正的问题。在 ledger.py 里,很多人一看到 sorted(txns) 就怀疑排序有问题。其实按客户排序恰恰是循环正确分组所必需的。
如果想单独练习排序顺序类 bug,可以另写一个函数,把 txns[0] 当作某个客户的第一笔交易,再喂给它不按时间顺序排列的数据。然后问自己:这段代码假设输入长什么样?谁来保证这个假设成立?
第 4 步:把每个假设说出来
做题时按这个句式说:“哪里的输出错了;我为什么怀疑这里;我打算怎么验证。”把完整的 60 分钟录下来。回放时数一数,有多少次你没先说出假设就改了代码。每一次都是在猜。
学习轮:读文档要比你以为的更仔细
学习轮给你一个陌生的概念或小型 API,要求你用它做出点东西。据 Leonstaff 介绍,最常见的失败原因是文档草草扫一眼,然后去猜 API 的行为。
练习方法:在你所用语言的标准库里挑一个从没碰过的模块。先留 20 分钟只读文档,然后写一个目标明确的小脚本。每次调用函数前,先说出你预期它返回什么,再运行验证。
先预测,再运行:试一试 groupby
下面是一段使用 Python itertools.groupby 的示例练习,假设你从没用过它。自定任务:统计每个客户的交易笔数。
from itertools import groupby
custs = ["A", "B", "A"]
print([(k, len(list(g))) for k, g in groupby(custs)])
只扫了一眼的人看到 “group by” 这个名字,会按 SQL 的思路预测:[('A', 2), ('B', 1)]。运行后得到的却是 [('A', 1), ('B', 1), ('A', 1)]。预测错了,而这正是需要修正思维模型的时刻。
回头查文档会发现,groupby 只对键相同的连续元素分组,所以输入必须先排序。改成 groupby(sorted(custs)),就得到 [('A', 2), ('B', 1)]。这也解释了为什么 ledger.py 里的 sorted(txns) 不是 bug。
练习后检查:你至少记下了一处预测与文档不符的地方,并能解释原因。在面试现场,一句“我原以为它会把所有相同项归到一组,结果它只合并相邻的元素”,就能让面试官看到你是怎么学习的。
少问,但问在点子上
说明你目前对问题的理解,让面试官能在你选定方案之前纠正你的思维模型。但细节问题问得太多,会显得你需要别人手把手带。
比较两个问题。“这个函数返回的是 list 还是 dict?”这种你自己就能查到。“我的理解是这个 API 按批处理;如果不对,我会调整设计。”这能让面试官看出你思考的层次。
长时间沉默很容易被当成卡住了,即使你其实是在思考。如果需要安静读文档,先说一声:“给我两分钟读一下认证部分。”
常见错误
第一个是看到第一处可疑的地方就改,然后重新运行看看会怎样。第二个是一次改好几处,结果即使对了也说不清原因。
第三个出现在学习轮:凭对某个看起来相似的库的印象去填补文档里的空白,groupby 的 SQL 陷阱就是这样。
这项技能在客户现场怎么用
设想你刚到一家客户那里,对方反映一张汇总表的数对不上。数据管道是别人几年前写的,眼下也找不到人问。做法和 ledger.py 一样:拿一条具体的错误记录,量出差额,逐个转换步骤往回追,在动手修复之前先向客户清楚说明你的假设。
简历上不要只写“调试过遗留系统”,而要写出结构:错误输出是什么样子,你如何定位,修了什么。读岗位描述时,“existing systems”“unfamiliar tools”这类措辞说明这个岗位需要的正是本文练习的两项技能。
Palantir 的面试官不会因为你多快抓到第一个 bug 而给分,看的是你怎样去找它。这种系统地找 bug 的方法,也正是你面对客户系统时需要带上的。
这篇文章对你有帮助吗?
感谢你的反馈!