代码、工具与人:AI 时代的程序员

引子

很多人喜欢问 AI 会不会取代程序员?然而这个问题问得太急了。它默认了一件事:程序员的核心工作就是写代码。

这个默认前提,值得先停下来看看。

编码只是职责之一

有没有 AI,编码都只是程序员职责的一部分。

这就像会计并不是“制表的人”。制表当然是会计工作中很重要的一环,但会计真正负责的是账务是否准确、规则是否被遵守、风险是否被识别,以及经营事实是否被可靠地表达出来。表格只是承载这些判断与结果的工具。

程序员也是如此。代码很重要,但代码不是工作的全部。程序员真正负责的是理解问题、建立模型、拆分边界、识别约束、设计系统行为,并让这个系统在现实环境中可运行、可维护、可验证。

AI 让编码这件事变得更快,也让“写出代码”这件事显得没有过去那么稀缺。但这并不意味着程序员的责任消失了。相反,它会把程序员的价值更清楚地推回到那些更难外包给工具的部分:判断该不该做,决定怎么做,验证做得对不对,以及承担系统长期演进的后果。

我们应该使用 AI 写代码吗

如果一个工具能够帮助我们更快地表达想法、查找资料、生成样例、整理思路、补全代码,那么程序员没有理由拒绝它。拒绝 AI 本身,并不会让一个人变得更专业。就像拒绝 IDE、调试器、搜索引擎,也不会让人更懂软件工程。

但使用 AI 写代码,并不意味着把程序员的责任交给 AI。AI 可以生成代码,但它不会替你理解业务目标;它可以给出方案,但它不会替你承担系统后果;它可以补全实现,但它不会自动知道这个实现是否适合当前上下文。

AI 更像是一个能力放大器。你想得清楚,它可以放大你的效率;你想得模糊,它也会放大你的混乱。它能让正确的方向更快落地,也能让错误的方向更快堆积成代码。

所以问题不在于“要不要用 AI 写代码”,而在于“用 AI 写代码时,程序员是否仍然掌握判断权”。

AI 时代应该如何写代码

AI 时代写代码,最重要的变化不是少写几行代码,而是写代码之前和之后的工作变得更重要了。

过去我们常常把注意力放在“怎么实现”上:接口怎么写,循环怎么写,数据结构怎么组织,异常怎么处理。这些问题当然仍然重要,但 AI 已经能在很多局部实现上提供不错的帮助。真正容易出问题的地方,反而是那些 AI 不一定知道、也不一定能替你判断的部分。

首先,要先把问题说清楚。不要一上来就让 AI 写代码,而是先描述目标、约束、输入输出、边界条件和不能破坏的现有行为。问题描述得越清楚,AI 给出的代码越可能接近你真正需要的东西。程序员在这里做的不是“下命令”,而是在整理自己的判断。

其次,要把 AI 的输出当作草稿,而不是答案。AI 生成的代码可以作为一个起点,但它需要被阅读、质疑和验证。变量名是否表达了业务含义,抽象是否过度,边界条件是否遗漏,错误处理是否符合系统习惯,这些都需要程序员重新接管。

再次,要用测试和运行结果约束 AI。只靠“看起来对”是不够的。越是借助 AI 快速生成代码,越应该补上可执行的验证:单元测试、集成测试、日志、断言,或者最小可复现的运行结果。AI 可以帮你写测试,但测试要覆盖什么,仍然取决于你对问题的理解。

最后,要保持对上下文的敏感。很多代码不是孤立存在的,它会受到项目风格、历史包袱、性能要求、部署环境、团队习惯和业务规则的影响。AI 很容易给出一个局部上漂亮、整体上不合适的方案。程序员需要判断这段代码能不能放进当前系统,而不仅仅是能不能单独运行。

所以,AI 时代写代码,不是把“思考”交给 AI,然后自己负责复制粘贴。更好的方式是:人负责定义问题、拆分边界、判断取舍和验证结果,AI 负责加速搜索、表达、生成和试探。这样使用 AI,代码会写得更快,但责任仍然留在应该承担责任的人那里。

我使用 AI 的经验

很多时候,我们会反复对 AI 说同样的东西:先理解上下文,不要急着改;修 bug 前先复现;写完以后要验证;不要随便宣布完成;重要结论要沉淀到文档里。我把这些重复要求整理成了 skill:https://cnb.cool/ryuu64/skills。

形成标准流程

AI 很擅长生成内容,但它不一定天然知道什么时候该停下来理解上下文,什么时候该先验证假设,什么时候该把结论写回文档。skill 的作用,就是把这些流程显式写出来。

当我还没有想清楚方案时,grill-then-docs 对我很重要。它先用 grill 的方式把一个决策点问透:有哪些选项,各自的代价是什么,推荐哪一个,为什么。等结论稳定以后,再把这些结论写回项目文档。这样 AI 不只是陪我聊了一轮方案,而是把这轮讨论变成项目之后还能复用的上下文。

比如看不懂一段代码时,我不希望 AI 直接解释当前文件,而是先使用 zoom-out 拉高一层,弄清楚它在系统里的位置:上游是谁、下游是谁、核心概念是什么、下一步应该读哪里。这样后面的修改才不会只盯着局部。

修 bug 时,我会更希望它进入 diagnose 的流程:先建立反馈循环,再复现问题,然后提出可证伪的假设,用日志、测试或调试器去验证。很多 bug 真正难的地方不在改动本身,而在确认自己没有修错方向。

如果是在实现一个明确的新行为,我会倾向于用 tdd。先列业务规则、边界条件和验收范围,再写失败测试,再写最小实现。AI 可以很快生成代码,但测试会把它拉回到具体行为上,不让它只写出一段“看起来像那么回事”的实现。

任务做完以后,我还会要求 verification-before-completion。AI 不能只说“已经完成”,而要先运行对应的验证命令,读输出,再根据结果判断能不能做完成断言。这个 skill 对我很重要,因为 AI 很容易在语气上显得很确定,但工程上真正需要的是证据。

如果过程中形成了稳定结论,比如某个模块的约束、某种排障方法、某个架构决策,我会再用 project-docs-syncproject-docs-writing 把它同步回项目文档。否则这次对话结束以后,经验又散掉了,下次还要重新解释。



flowchart TD
    A[遇到需求] --> B{当前最需要澄清什么}
    B -->|方案还不清楚| K[grill-then-docs<br/>先问透决策,再写回文档]
    B -->|不熟悉代码| C[zoom-out<br/>先建立上下文地图]
    B -->|排查 bug| D[diagnose<br/>复现、假设、验证、收敛根因]
    B -->|实现新行为| E[tdd<br/>测试先行,按行为推进]
    K --> F
    C --> F[修改或继续分析]
    D --> F
    E --> F
    F --> G[verification-before-completion<br/>用新鲜证据确认结果]
    G --> H{形成稳定结论?}
    H -->|是| I[project-docs-sync / project-docs-writing<br/>写回项目文档]
    H -->|否| J[结束任务]
    I --> J

这些流程看起来琐碎,但它们决定了 AI 是在帮你推进工程,还是只是在生成一堆看起来合理的文本。

处理 AI 的失控

上下文污染

我使用 AI 的过程中也遇到过不少问题。最常见的一类,是上下文变得太长、太杂以后,AI 开始不理解业务。它会抓住对话里某个局部信息,然后沿着一个不对的方向继续推理,甚至越解释越偏。

这种时候,继续在原来的对话里纠正,不一定是最好的办法。因为问题有时不在某一句提示词,而在整个上下文已经被污染了。我会选择新开一个对话,或者先清理上下文,再把真正重要的背景、约束和当前目标重新说清楚。如果项目里已经有文档,就让 AI 先看文档,而不是继续依赖一段越来越长的聊天记录。

调试时跳过证据

另一类问题出现在 debug 时。AI 很容易想直接根据代码猜原因,然后给出一个修复方案。这个过程看起来很快,但风险很高,因为它可能只是在修一个“看起来像问题”的地方。我遇到过 AI 死活不肯先插桩、不肯根据日志复现,而是反复尝试直接改代码。

后来我用 diagnose 去约束这个过程,并且反复调整它的规则:先建立反馈循环,先复现,再提出假设;需要日志就插有明确目的的日志;每次观察结果以后更新假设,而不是直接跳到修复。这样做会慢一点,但它能把 AI 从“猜一个答案”拉回到“拿证据收敛问题”。

混淆计划和事实

还有一类更隐蔽的问题:AI 看起来像是知道自己在干什么,但实际上它可能只是选择了一个“看起来合理”的位置。

比如我的 grill-then-docs,它的目标是先把方案问清楚,再把稳定结论沉淀下来。可在一开始,AI 经常会把 grill 之后“接下来要做什么”写进 ./docs/features./docs/architecture。从表面看,这很合理:方案讨论和功能、架构都有关。但实际上这是错误的,因为 grill 后续要做的事情只是计划,不是已经成立的功能事实,也不是架构事实。它应该先进入 ./docs/plan,等实现完成、结论稳定以后,才有资格进入 features 或 architecture。

这个错误很有代表性。AI 并不一定知道“计划”和“事实”之间的价值差异。它能根据文本相似性判断某段内容和哪个目录更相关,却未必能判断这段内容在项目知识体系里的身份:它是待办、假设、决策、事实,还是长期约束。对程序员来说,这些边界很重要;对 AI 来说,如果没有明确规则,它可能只会选择概率上最像的地方。

这里说的价值判断,不是“我更喜欢哪种写法”这种偏好判断,而是基于事实状态做定性:这段内容现在到底是什么。它是已经实现的功能事实,还是尚未执行的计划?是已经被系统采纳的架构约束,还是一次讨论中的候选方案?这些判断依赖项目当前状态,也依赖团队对文档的定义。

GPT 可以根据文字相似性把内容放到看起来相关的地方,也可以在规则足够明确时照着规则执行。但如果规则没有把这些事实身份说清楚,它就很容易把“语义相关”误当成“事实身份正确”。./docs/features./docs/architecture./docs/plan 的区别,本质上不是路径选择,而是项目知识治理中的事实判断:什么已经成立,什么还只是计划,什么可以作为长期约束沉淀下来。

所以问题不在于 AI 能不能给出一个看似合理的分类,而在于它有没有足够事实依据做这个分类,以及分类标准是否被明确表达。对程序员来说,“这个内容应该写到哪里”背后其实是在判断它在项目中的状态;对 AI 来说,如果没有明确规则,它可能只是选择概率上最像的目录。

所以我后来在 skill 里明确约束:grill 之后形成的后续计划,应该写到 /docs/plan,不能直接写进 /docs/features/docs/architecture。这不是一个简单的目录偏好,而是在告诉 AI:什么东西还只是计划,什么东西才可以被当作项目事实。

这些问题也让我更确定一件事:AI 不是一直稳定的。上下文、提示方式、任务阶段都会影响它的表现。程序员需要识别什么时候该继续推进,什么时候该停下来重置上下文,什么时候该强制它回到验证流程。

把偏好变成约束

使用 AI 写代码时,很多问题不是 AI 不会,而是它不知道你在意什么。你在意命名是否贴合领域,注释是否解释意图而不是复述代码,提交信息是否清楚,文档是否跟着代码一起更新,这些偏好如果每次都临时说明,就很容易遗漏。

skill 的价值在于把这些偏好变成稳定约束。它不是让 AI 替我做判断,而是让 AI 在我设定的边界里工作。

让 AI 更像协作者

skill 是一种协作协议。它让 AI 知道在什么场景下该采用什么工作方式,也让我不用每次都从零开始解释自己的工程习惯。

这也对应了前面说的:AI 可以加速表达、生成和试探,但程序员仍然要负责判断、边界和验证。skill 只是把这些责任拆成更清晰的流程,让 AI 更稳定地参与其中。