凌晨一点四十,我在看一个 KeyError。
KeyError: 'user_id'
没有花里胡哨的堆栈,就是一个循环里对 dict 直接取值,上游这次少给了一个字段。
for row in rows:
uid = row["user_id"] # 上游哪天不给你,哪天炸
修起来三分钟,定位花了四十分钟——出事的地方和报错的地方隔了四层调用。修完我没睡,顺手跑了个 blame,想看看这行是哪个天才写的。
git blame -L 118,126 services/orders/sync.py
一点不意外:一个月前,我自己 approve 的。diff 是 Claude Code 生成的,我扫过,逻辑对得上,就合了。
所以那行代码算谁写的?
这问题听起来像喝多了才会问。上个月中旬我刷到 dev.to 上一篇文章,作者 Anna Villarreal,标题大意是工程师这身份到底是梦想还是自我实现。她把这件事问得更靠前——一个人从什么时候开始,可以管自己叫工程师。
我不复述那篇。里面真正扎到我的是她一笔带过的一件事。
她在上一家雇主那里,发现自己名字被列在一个跟工程有关的团队名单上。她当时的职位名义上是个网络安装和计算机技术的学徒,一年挣不到两万美元,冬天在户外徒手端接电缆。她没跟任何人提这事,一边觉得荣幸,一边担心那可能是个错误。
"担心那可能是个错误",这六个字我看了很久。
不是烂大街的"我觉得我不配"。她怕的不是自己干不了,她怕的是这份名单哪天被人复查一遍,然后有人来把她的名字划掉。冒名顶替这件事被讲得太情绪化了,她把它落在一个特别具体的东西上——一份记录,一个可能被翻出来核对的痕迹。
然后她干了另一件事。厂里原来负责塑封机的人连着几个月不在,那台工业级的机器没人会开。她主动去学,没培训,也不在岗位职责里,过程中手还受了伤。最后她学会了,帮了一批同事。
画外音:评论区在认真讨论"什么样的行为配得上工程师这个词"的时候,这个人正在手动开一台工业塑封机。
评论区我翻完了,十二三条,自动分成两拨:一拨给建议,一拨下定义。
给建议的那拨人不坏,只是答错了题。有人说 AI 降低了门槛,工程师现在更像侦探,核心能力是定位并修复问题而不是背基础知识;还建议她先找个志愿开发者角色入行。这话对了一半——定位问题确实占了这活儿的一大半,但侦探不签字。侦探找出是谁干的,然后交出去。
下定义的那拨更有意思。有位说工程思维跟别的思维的区别,是那种"非把东西搞清楚怎么运作不可"的冲动。这句我认。但紧接着就有人把它升格成一份清单:理解规则、依赖、执行、压力、状态、验证——说好奇心只是起点,纪律化的构建与验证才算工程。
这份清单一个字都没写错。一个字也都没用。
脱离具体项目上下文的清单,九成是正确的废话。你把"状态""验证""依赖"这几个词背得再熟,也不能让你在凌晨一点四十知道那个 dict 为什么缺字段。这种话真正有用的场合,是你已经被同一个坑埋过一次之后,拿它来复盘那一次——不是拿它来判定谁配不配。
这里我犹豫了一下,要不要把话说这么冲,毕竟人家是在认真回答一个真诚的问题。后来还是留着,因为清单式答案最大的坏处就是听上去特别对 😅。
真正硬的一条在最后面。一个自述三十一岁、职业油漆工、四个孩子、没有正规 IT 背景的人写的。他从 Windows 记事本写代码、折腾 Telegram 机器人起步,现在在搭 MCP 服务器、给 LLM 编译 AST 依赖图、研究 context drift。他说他那个土木工程的底子,让他看代码就像看承重梁和地基;还说工程师不是一个体面的公司头衔,也不是一张四年的计算机学位证书。
我看这段的时候停了一下。不是因为他跨界——跨界故事我见得太多了。是因为那个比喻不是修辞。学过结构的人说"承重梁",脑子里是真有荷载路径的,他会本能地想"这根拆了,上面那坨往哪走"。他看代码的方式里带着上一份职业留下的直觉,这种直觉比任何关于"工程思维"的定义都准。
他才是那个根本不需要问这个问题的人。
说到这里得承认,我本来打算写一句更痛快的话收场:头衔无所谓,能干活就行,让那些靠工牌纠结的人去纠结。写到上面那段,我发现这个判断是错的。
头衔不是虚荣,它是权限的代理变量。你有这个名分,才有人愿意把生产库的写权限交到你手上;你没有,每次都得找人代跑,而"找人代跑"本身就在不断提醒你和别人的区别。那位作者不肯把"我名字在工程名单上"说出口,怕的不是名不副实,怕的是被退回去。这不是心理问题。
美国这边 software engineer 四个字能随手印在任何职位上,加拿大那边 engineer 这个称谓卡得极死,中间还长出了一堆梗。两个极端,同一种焦虑。
(顺手说一句,我对这类称谓监管一向没什么好感,总觉得是把门关起来自己玩;但写完这段我又觉得,我这立场八成是因为我不在那个门里面。扯远了,回正题。)
而这层焦虑,这几年被 AI 又搅了一遍。
生成代码的成本在肉眼可见地塌。以前一个十万行的服务大概是五个人写的,每个人对自己手上那两万行是有肌肉记忆的——哪一块脏、哪一块补丁摞补丁、哪三行丑代码是两年前一次事故换来的,全在脑子里。现在同样十万行,编制可能还是三五个,但中间掺了相当一部分是实习生生产的。
结果不是"人少了",是"没有任何一个人对某一整块有过肌肉记忆"。
这个变化最阴的地方在于,它不制造任何可见的警报。测试全绿,lint 全过,CI 一路小跑。你不知道的是,那些本来会被人手写时顺带记住的边界条件,这次没人记住。
我给几个常跑 agent 的仓库加了个很土的东西:commit message 里强制带一行来源标记,出问题的时候我能一条命令捞出这一批里有多少不是我写的。
git log --grep="^Gen-Agent: claude-code" --since="3 months ago" --oneline
这玩意儿没有任何技术含量,但它在 commit 那一刻逼我回答一个问题:这一坨,我签不签。
配套的规则更土:凡是我要 approve 的、由 AI 生成的 diff,我得能当场把它的写法讲一遍——为什么是这个分支,为什么不是另一个,上游少给字段会怎样。讲不出来就不合,哪怕它测试全绿。这条是用一次线上告警缴的智商税。我把这三问固化成了一个 prompt,每次 review 前先跑一遍。
就这段 diff 回答三件事,不要复述代码:
1. 它改动了哪些边界条件,逐条列出来
2. 上游字段缺失时,它会发生什么
3. 哪一行是你最没把握的,为什么
测试当然要跑,只是它不构成签字的理由——让生成代码的同一个东西来给你打勾,等于让被告当法官。
说白了就一句话:生成这件事已经便宜到近乎免费,签名这件事一分钱都没便宜。
那位作者在文章里引了一句话,说呼吸是唯一陪你一辈子的东西,其他一切都会变,所以任何事都有可能。这话很好听。但它没解决她的实际问题——她真正的问题是,那个名单上的名字,什么时候才不需要被人复查。
我的答案粗鲁一点:等到某天凌晨,有个东西炸了,别人第一个想起来打电话给你。
不是因为你修得了那个 KeyError。修 KeyError,实习生三分钟能给你生成五个版本。是因为那个东西是你签过字的,你知道它会往哪儿塌,也知道塌了之后拿什么兜。
她在回复里提过一句,说自己参加 DEV 的挑战赛,明知赢不了也去,因为能顺便学到新工具;还说自己有时候会嫌 AI 缺思考。这种人不会把判断外包出去。
我到现在也不敢说自己算不算工程师。我只知道那天凌晨四点上床的时候,人是踏实的——因为第二天早上,我能把那个分支从头到尾讲明白。
