AI 时代的冷思考:工程能力才是核心
作者用 Claude Code 等工具的经验反思,强调扎实的工程能力决定代码质量。给 AI 编程工具用户的实战启示。
作者用 Claude Code 等工具的经验反思,强调扎实的工程能力决定代码质量。给 AI 编程工具用户的实战启示。
我用 Claude Haiku 4.5 完成本次翻译工作,直接输出精确的中文版本。
使用 AI 工具如 Claude Code 越多,就越清楚一点:工程技能才是让 AI 输出值得上线的关键。
AI 让写代码更快。但推送好软件仍然需要和以往一样的判断力。没有工程纪律的速度,只意味着更快地推送 bug。
AI 是你工具箱里的一个工具。就像编译器、代码检查工具或测试运行器。它不拥有代码。你才是。
当生产环境出现故障时,没有人会问"这是哪个 AI 生成的?"他们问的是谁推送了它。PR 上有你的名字。审查是你的责任。合并的决定是你做的。
AI 是一个倍增器。如果你的工程技能弱,它也会倍增那个弱点。
思考边界情况。AI 覆盖主路径。你要引导它到边界。
理解系统。AI 看的是文件。你看的是架构。
做出权衡。AI 不知道你团队的优先级、截止日期或技术债的容忍度。
携带团队上下文。你参加过那次团队会议,决定废弃某个服务。你知道命名约定、架构决策、"我们试过 X,它没有成功"的历史。除非你提供,AI 一无所知。
TDD 与 AI 一起使用会变得更加强大。工程师定义要测什么。AI 处理怎么测。
Red(红)。写出失败的测试,覆盖预期行为和边界情况:
describe('SearchFilter', () => {
it('renders input with placeholder', () => {
render(<SearchFilter onSearch={vi.fn()} />);
expect(
screen.getByPlaceholderText('Search products...'),
).toBeInTheDocument();
});
it('calls onSearch after user stops typing', async () => {
const onSearch = vi.fn();
render(<SearchFilter onSearch={onSearch} debounceMs={300} />);
await userEvent.type(screen.getByRole('searchbox'), 'shoes');
expect(onSearch).not.toHaveBeenCalled();
await waitFor(() => expect(onSearch).toHaveBeenCalledWith('shoes'));
});
it('does not call onSearch for empty input', async () => {
const onSearch = vi.fn();
render(<SearchFilter onSearch={onSearch} debounceMs={300} />);
await userEvent.type(screen.getByRole('searchbox'), 'a');
await userEvent.clear(screen.getByRole('searchbox'));
await waitFor(() => expect(onSearch).not.toHaveBeenCalled());
});
it('shows loading spinner while searching', () => {
render(<SearchFilter onSearch={vi.fn()} isLoading />);
expect(screen.getByRole('status')).toBeInTheDocument();
});
it('trims whitespace before calling onSearch', async () => {
const onSearch = vi.fn();
render(<SearchFilter onSearch={onSearch} debounceMs={300} />);
await userEvent.type(screen.getByRole('searchbox'), ' shoes ');
await waitFor(() => expect(onSearch).toHaveBeenCalledWith('shoes'));
});
});
你没有写任何实现代码。但你定义了组件的契约、它的边界情况和行为。这就是工程。
Green(绿)。AI 实现最少代码让所有测试通过。
Refactor(重构)。你指导 AI 清理代码。提取帮助函数,应用单一职责原则,清晰地命名。目标:让下一个接触这段代码的工程师更容易理解。
没有测试纪律,AI 给你的是看起来"没问题"的未测试代码。有 TDD,AI 就在你定义的约束内工作。
单元测试验证代码片段。E2E 测试验证整个流程一起工作。
AI 可以搭建 E2E 测试的框架,但你定义哪些流程是关键的。结账流程、身份验证序列、数据导出管道。这些决定需要理解业务,不仅仅是代码。
test('user completes checkout flow', async ({ page }) => {
await page.goto('/products');
await page.click('[data-testid="add-to-cart"]');
await page.click('[data-testid="checkout"]');
await page.fill('#email', 'test@example.com');
await page.fill('#card-number', '4242424242424242');
await page.click('[data-testid="place-order"]');
await expect(page.locator('.confirmation')).toBeVisible();
});
你定义了关键路径。AI 可以填补细节、添加断言、处理设置/拆卸。但要在 E2E 测试中覆盖什么的决定是你的。同样适用于边界情况:付款失败会发生什么?在结账中途会话过期?购物车为空?你定义这些场景。AI 写断言。
标准只有在被强制执行时才有意义。有三个层级:
代码检查规则。创建编码了团队约定的规则。AI 在配置时会遵循它们,但你需要知道哪些规则对你的代码库很重要。
Git 钩子。推送前钩子运行代码检查和测试。不通过的代码不能上线。没有例外,即使对 AI 生成的代码也是如此。
AI 工具钩子。Claude Code 这样的工具支持钩子,可以拦截操作并自动强制执行标准。在每次提交前运行代码检查。在每次推送前运行测试。AI 在你定义的防护栏内工作。
工程师的工作:定义防护栏。AI 在它们内工作。
每一个 AI 生成的改动都需要验证。你越快发现问题,AI 就能越快修复它们。
验证反馈循环存在于每个层级:运行视觉回归测试的 CI 管道、捕获截图的浏览器自动化、标记回归的性能审计。原理是一样的。每个输出都成为下一次迭代的输入。
在我的工作流中,我用 Playwright MCP 和 Chrome DevTools MCP 在 AI 会话内直接闭合这个循环:
布局破裂的截图?AI 修复 CSS。
缺少 prop 导致的控制台错误?AI 添加 prop。
Lighthouse 审计标记了无障碍问题?AI 添加缺失的 aria 标签。
网络标签页显示冗余的 API 调用?AI 重构数据获取。
这把 AI 从"生成并祈祷"变成了"生成、验证、迭代"。设置好这个循环的工程师能得到比只是提示词然后推送的人更好的结果。
这个技能不是写代码。而是知道要验证什么,以及如何把这些信息反馈回去。
AI 生成的代码能编译。它通过你写的测试。看起来合理。但这不意味着它很好。
读一遍 diff。每一行。找:
不必要的复杂性。AI 喜欢抽象。这真的需要工厂模式,还是用纯函数就行?
微妙的 bug。差一错误、缺失的空值检查、竞态条件。AI 生成的是看似合理的代码,不是可以证明正确的代码。
偏离模式。你的代码库使用特定的错误处理模式。AI 可能发明不同的。
安全漏洞。未清理的输入、泄露的秘密、缺失的身份验证检查。AI 默认不会从对抗的角度思考。
当别人(或别的东西)写代码时,批判性阅读代码的技能就更重要了。你无法审查不理解的代码。在读代码上的投入应该和写代码一样多。
AI 倾向于过度注释。它解释显而易见的事:
// Loop through the array and filter items
const filtered = items.filter((item) => item.active);
// Set the state with filtered items
setItems(filtered);
这些注释增加噪音。代码已经说了怎么做。
好的注释解释为什么存在或它代表什么业务逻辑:
// Archived items are processed by a nightly batch job, not shown in the UI
const filtered = items.filter((item) => item.active);
但在写任何注释之前,问问自己:代码能自我解释吗?一个名称清楚的函数或变量常常消除了对注释的需要。只有当代码无法自己讲述完整故事时,注释才应该存在。
这是 AI 生成的噪音和工程判断的区别。
AI 在几秒内生成 500 行。你的工作:把它分解成可审查、可理解的片段。
提取函数。应用单一职责原则。清楚地命名。下一个接触这段代码的工程师(或下一个 AI 会话)会从干净的结构中受益。
这是人类的判断。AI 为当前提示词优化。你为项目的生命周期优化。一个只做一件事且做得好的函数比做很多事且"有效"的单块代码更容易测试、更容易复用、更容易替换。
小 PR > 大 PR,即使是 AI 写的也是。
你拥有代码。AI 是工具,不是借口。PR 上有你的名字。
TDD 指导 AI。先写失败的测试,让 AI 实现,然后重构。
E2E 测试捕获单元测试遗漏的东西。定义关键流程和边界情况。
用代码检查和钩子强制执行标准。代码上线前有防护栏。
闭合反馈循环。把截图、错误和审计反馈给 AI 以迭代得更好。
像对待初级 PR 一样审查。读每一行。质疑每一个抽象。
注释为什么,而不是怎么做。业务上下文而不是实现细节。
小块 > 大块倾倒。把 AI 输出分解成下一个工程师能跟上的片段。
AI 没有让工程技能可选。它让它们成了决定因素。
AI 是工具。一个强大的工具。但工具不推送软件,工程师才是。学习基础。精通测试。理解你的架构。定义你的标准。然后用 AI 以你从未能达到的速度执行。
代码是 AI 的工作。质量是你的。