关于AI编程
最近也是有一段时间没有更新杂谈了。除了本人现实生活比较繁忙的缘故之外,也有最近还在写一些别的东西的因素在。不过因为还没有校对完毕,所以一时半会儿之内估计也还发不出来。总之感觉就是开的坑越多,就越容易出现顾头不顾腚的情况。按下葫芦浮起瓢之下,各种事也就都耽误了。
咳咳跑题了,让我们说回正文。那么这次促使我写下这篇杂谈的原因,主要来自于前段时间,我在制作一个卡牌对战游戏期间的一些所思所想。因为重度手残的缘故,所以本人从小到大玩的通常都是一些回合制游戏,尤其是卡牌对战游戏;但在这些年的游戏经历中,我又总是苦恼于这些游戏里的先后手问题。那么,能不能设计一款不分先后手的回合制卡牌游戏呢?这个想法自从在我的脑海中冒出之后就再也按捺不住,之后又在无数次的洗澡或者发呆时逐渐编织成型。时至今日,虽然并没有撰写什么严谨的设计文档,但至少也算是构建出了一套比较完整的游戏系统和世界观。按照正常的游戏开发流程,我下一步的做法本来应该是新建项目,然后在不到三天的热乎劲之后弃坑。这当然不是因为这个游戏的思想有多么伟大,而是因为我清楚地知道自己最大的问题在于时间不够。虽然一个所谓的“不分先后手的卡牌游戏”听上去结构非常简单,但任何一个实际写过游戏的人都知道,光是卡牌效果引擎这种纯后端的逻辑就足以让人抓狂,而前端的UI交互等内容实际做起来就更容易让人破防,更何况还有数据持久化和联网对战之类琐碎的东西都要考虑。在如此大工作量的淫威之下,这个企划也就自然是处于一个不敢开坑的状态。
直到最近,看到网上到处都在吹所谓的vibe coding,我突然产生了一个十分危险的想法:不如就拿这个项目试试水?反正就算失败了,除了浪费点token之外也没什么损失。所谓vibe coding,从定义上说就是指人类用自然语言描述需求、让AI去生成和迭代代码,而开发者主要负责表达意图、测试结果和调整方向的一种编程方式。由于一直有在白嫖别人的OpenAI会员,所以我使用的模型也顺理成章的就是它旗下的Codex;而这次vibe coding的结果就是在不到两周的时间里,大模型就根据我的需求,从一个全空的Godot项目开始写出了接近一万行C#脚本,外加还不错的前端和无数个json卡牌配置。更可怕的是,这还并不是我每天高强度开发的结果,而是在做别的事情之余顺手把一段需求发过去;等其他事情干得差不多了,再顺手回来“收菜”的结果罢了。就是在这种拖拖拉拉的效率之下,大模型仍然能够在如此短暂的时间内完成一个庞大到让我几年都不敢开坑的“大作”,这种效率着实让我吃了一惊。更难以想象的是,整体来看AI写的代码质量其实出乎意料地还可以。虽然它写的东西总是在语义层面上突破现有框架,比如把和A方法功能相近的B方法也塞进A的类里,或者把前后端逻辑杂糅在一起;但不管怎么说,在大部分时候它的输出还是能算得上“勉强完成需求”。在一些对代码质量要求不是太高的情况下,我估算一个Codex Agent的效率至少能顶得上十个熟练的人类程序员。
于是,一个很不体面的想法自然而然地冒了出来:人类程序员是不是快完蛋了?
这里的所谓“不体面”,指的当然不是这个想法本身有多么愚蠢,而是我觉得它有点过于急功近利,有点像是教科书里写的那种“在资本主义发展到极致的情况下,资本家总是会迫不及待地使用更高效率的生产工具来代替落后的劳动方式,从而快速获取超额剩余价值”的描述。写到这里,我也不由得回想起自己之前在博客里锐评AI冲击其他行业的情景。那时候我的态度虽然不能说是幸灾乐祸吧,至少也称得上是隔岸观火,毕竟相比绘画和音乐,AI至少还是更偏向于我这一侧的。直到这一次,当“先进生产力”的大锤终于结结实实地砸到了自己未来的饭碗上,我才终于后知后觉地体会到这种“屠龙者终成恶龙”的感觉。不过,如果这篇文章只是写到“程序员要失业了,我很焦虑”,那未免也太无聊了一点。所以,我还是想像之前分析AI绘画和AI音乐时那样,借着这个机会稍微细致地谈一谈自己作为一个科班程序员、半吊子AI研究者和未来准码农的一些粗浅看法。
在详细探讨AI编程这个话题之前,我们首先得承认一件事,就是它其实并不是最近才突然出现的。在LLM发展的早期(具体时间我说不好,也许是在GPT-3的时候吧),虽然那时候的大模型仅从输出的东西来看完全配得上一句“傻子”的称呼,但此时它们已经能够根据用户的需求来生成一些简单的脚本了,比如批量修改文件名、或者生成一个网页之类的稍微复杂一点的工作。只不过那时候的模型其实也并不能从语义或者抽象语法树的角度来理解这些代码,只是因为读过太多相似的东西,所以在概率采样的时候能够生成出一个似是而非的东西来,所以生成的东西有时候连编译都过不了。如果从这个角度说,AI编程其实可以算得上是某种天崩开局了。然而很不幸(至少对于人类程序员来说很不幸)的是,代码这种东西天然就极其适合被大语言模型学习。首先,互联网上存在着海量的代码,并且高度集中于少数几个代码网站上,因此非常便于收集。虽然这些代码里有相当一部分都是由初学者所完成,存在各种各样的问题,或者在架构设计、可读性或者时效性上有所欠缺,但这对于大模型来说问题不大,因为它们只是训练数据。其次,代码比自然语言写就的文章更容易被机器检查。和各种自然语言不同的是,代码有着明确的语法结构,因此也就不会有自然语言中“写错一个字对于整段话的语义也没什么影响”的情况出现,因为编译器在面对错字时总是会立刻翻脸。因此,只要在古早的LLM架构上增加一些编译器、解释器之类的可调用工具,那它对于代码的学习能力就能得到迅速的提升。而这些工具的使用又让AI编程在评价标准这一点上和我们之前讨论过的AI绘画、AI音乐之间产生了重要的区别。在AI绘画和AI音乐模型的训练过程中,当然也使用了大量的人类作品,但它们的评价标准通常更加暧昧,因为我们很难设计一种方法来对一张图像或一段旋律的“好坏”进行评分。而代码在这方面则非常粗暴:能不能跑?有没有报错?测试结果对不对?虽然“能跑”还远远不等于“写得好”,但它毕竟给模型提供了一条比较清晰的梯子。
然而如果仅仅说“代码适合被大模型学习”,其实还不足以说明AI编程真正特殊的地方。真正让我觉得它和AI绘画、AI音乐有所不同的地方在于:AI编程生成的是程序。这句话听起来像是一句废话,但问题在于AI自己也是一种程序。AI绘画模型输出的结果是图像,AI音乐模型输出的是音频,无论生成的效果多么惊人,它们也不能直接用于下一步的模型迭代,最多只能用来辅助制作训练数据,然后才能间接地进入下一轮的训练。而AI编程则完全不同,因为它生成的代码可以作为软件生产体系的一部分。它可以生成脚本,可以生成测试工具,可以生成自动化的部署管线,然后这些代码可以进一步被用来改进AI自己。当然,我在这里必须先补充一句(以免读者觉得我对于深度学习方面过于不专业):目前AI写出来的程序和“大模型自我训练”的目标之间还差得很远,并不是说今天Codex能写出几个Godot脚本,明天它就能顺手搓出一个GPT-6了。但这并不妨碍AI编程具有一种很特殊的性质:它生成的东西,至少在类型上和改进AI系统所需要的东西属于同一个世界。如果一个工具生产出来的东西能够反过来改进这个工具本身,那可能带来某种正反馈循环,进而让整件事情都变得微妙起来了。
在中学生物中我们都学过,在一个系统当中,负反馈循环通常倾向于让整个系统维持在一个相对稳定的范围内,比如温度高了就降温,温度低了就升温;而正反馈则恰好相反,因为它会把一个微小的变化不断放大。因此,如果某个系统的输出恰好能够增强它自身的生产能力,那么这种循环带来的就未必只是单纯的增长,而是在一系列机缘巧合之下,可能最终产生类似于“复杂系统”意义上的“涌现”现象(对于这一点不了解的读者,可以去阅读一下我之前的一篇博客)。就算不从这么玄学的角度讨论,我们也可以借助一个更广泛的案例,也就是现代工业基石之一的机床的生产过程来理解。现代的高精度机床当然不可能是某个原始人徒手搓出来的,因为人类总是先用简单工具制造粗糙工具,再用粗糙工具制造更稳定的零件,再用更稳定的零件制造更精密的工具。于是,在一个粗糙的机床和由它生产出来的下一代更精密一点的机床之间,就形成了一个不断自我强化的链条。到了最后,我们看到的就不再只是某一个原始人工匠的手艺有多么高超,而是整个工业体系在左脚踩右脚的过程中原地飞升,并最终在光刻机等高精尖领域中达到过去的原始人们所完全无法想象的程度。这个比喻当然并不严谨,但它至少说明了一个很重要的问题:如果生产工具能够参与生产工具自身的话,那整个工具链条就都获得了某种自我加速的可能性。所以,当我们现在讨论AI编程时,如果还只是总盯着一些诸如“代码写得好不好”、“会不会把前后端逻辑混在一起”之类的小问题,就多少显得有点像在第一次工业革命时嘲笑蒸汽机效率太低,锅炉还容易爆炸的工人一样。那些锅炉的原型机当然可能真的效率很低,也真的会爆炸;但只要这些东西能够进入生产体系,那它们在生产体系中就会持续地得到改进。而当这些锅炉的效率终于得到提高,也不那么容易爆炸的时候,曾经那些只会嘲笑它爆炸的工人们,就该开始担心自己的工作会不会被它炸飞了。
说到这里,事情似乎又有点容易滑向另一种极端,就好像AI编程马上就要带来新的工业革命,而人类程序员明天就要集体下岗一样。不过至少在今天事情还没有这么简单,因为AI编程目前仍然存在很多非常现实的问题。当我们从“生产工具的自我进化”这种宏大叙事逐渐回到每天写代码的日常中时,就很容易发现AI编程首先冲击的,其实也只是程序员们最朴素的一道防线:我会写代码,而普通人不会。在过去,这件事几乎构成了程序员职业神秘性的一大来源。虽然程序员们自己都知道,绝大多数的业务代码其实并没有多么高深,在很多时候无非就是查文档、调接口、Debug;但对于那些完全不会编程的人来说,“能让电脑按照自己的想法运行起来”这件事本身就已经足够神奇,就像他们看到黑乎乎的终端窗口里刷出一堆字符时,会本能地觉得你在进行某种赛博巫术一样。然而,vibe coding的出现正在快速拆掉这层神秘性。既然AI已经能写代码,那么普通人是不是也能编程了?从现阶段看,答案显然是:能,而且比过去容易太多。在vibe coding出现之前,如果一个完全不懂编程的人想做什么东西的话,那他大概率会在安装环境、配置依赖和第一个报错之间快速地结束自己短暂的编程生涯;而现在,他只需要简单地说出需求,就可以让AI生成出一个大概能跑的东西。哪怕运行的时候出了错,他也可以把报错复制回去,让AI自己继续去修。这件事的意义其实远比大多数人想象中要大,因为它第一次让很多没有技术背景的人都获得了某种“用语言生成软件”的能力。虽然这套流程还远远谈不上稳定,但在体感上,它已经足以让很多人产生一种对程序员祛魅的感觉:原来程序员的工作,就是把需求翻译成代码。现在既然AI也会翻译了,那程序员的工作是不是没什么了不起?
从某种意义上说,这种想法并不完全错误。很多时候,程序员确实是在把需求翻译成代码。只不过这句话里最容易被忽略的地方在于:需求并不是天然清楚的,代码也不是天然正确的,而“翻译”这个过程里包含了大量不显眼但非常要命的判断。于是,网上的这样一张梗图开始迅速在各个以程序员为主体的群聊中火爆起来:

这句话当然很刻薄,而且很容易被理解成程序员群体内部惯常的傲慢,但它之所以能传播开来,恰恰是因为它用一种极其粗暴的方式说出了很多人正在经历的真实恐惧。石头在猴子手里只是石头,但枪在猴子手里就很容易变成事故。更麻烦的一点在于,AI编程给人的感觉比枪要温和得多。枪至少看起来就很危险,拿在手里也会让人产生一点基本的敬畏;但vibe coding的输入框看起来实在是太人畜无害了。用户只要把自己的愿望打进去,代码就会从另一边自动输出出来,在这个过程中既没有火药的爆炸,也没有恐怖的后坐力,甚至模型还会在语气上对你十分礼貌,这对于猴子来说就十分恐怖了。
在我看来,如果当今的生成式AI已经能够直接赋予任何人用语言文字实现愿望的权柄,那么“猴子持枪”的隐喻就已经超越了单纯的职场吐槽,而是直指人与工具之间那古老却历久弥新的矛盾:工具越强,使用者能力的差异也会被同时放大,因此就越需要使用者理解自己在做什么。然而在现实中,情况却往往与之相反:工具越强,就越容易让人误以为自己什么都会。于是,我们正越来越多地看到,无论是办公室、创业团队还是互联网开源社区都正逐渐变成“猴子枪击案”频发的原始丛林。这当然不是说AI编程真的带来了什么物理意义上的伤亡,只是说有很大一批原本不懂编程、但又总是自视甚高的人(尤其是某些领导和策划),开始用他们的破烂叙述高效率地生成大量代码。这些代码也许看起来像是正常代码,运行起来也像是正常代码,但它们为什么这么写、会不会有安全隐患?这些问题则是完全没有人知道。这种情况在一些单人的小项目里也许还算可控,因为在这种项目当中,AI的破坏力最多也就只能把使用者一个人的电脑搞得鸡飞狗跳;但一旦这种方式进入更大的商业项目甚至关键系统当中,问题就会迅速变得复杂起来。代码不是一次性的消费品,它们在未来的很长一段时间内都会被依赖,会被调用,所以今天的人们在未经审查的情况下让AI生成出来的一段逻辑,在明天就可能变成另一个人的Debug地狱。更可怕的是,当不懂编程的人使用AI编程时,最大的问题往往就是因为他们不懂实现,导致提示词过于模糊甚至干脆自相矛盾,然后反过来又会误导本就在理解方面存在问题的大模型。模糊的需求当然也能让AI生成东西,可问题在于其中的很多关键问题都没有被说明,所以AI在面对这种需求时,就只能先自行脑补,也就是按照训练数据里最常见的方式先对付一版。然后用户一看:哎,好像能跑?于是继续追加需求,AI也就只能在这种脑补上继续叠加脑补。然后,这坨屎山通常会被送到某个倒霉的程序员手里,与之相伴的往往还有这样一句话:“我已经写得差不多了,你稍微改改就行。”想想也是十分可怕了。
从这个意义上说,现阶段程序员使用AI的优势,也许就在于他们更知道AI的回答错在什么地方。对于那些不会编程的人来说,他们往往只能提出一个整体性的愿望;而会编程的人则更容易意识到,这个愿望背后其实包含了数据结构、架构设计、可扩展性、性能要求和用户交互等一大堆看不见的需求。于是,他们在审查AI生成的代码时,总是能更多地指出其中不对、不合理、不符合语义边界的部分,以防止项目快速滑向语义地狱。这个概念也是我自己造出来的,指的是一种代码表面上还能跑,但在整体架构和命名概念上已经开始变质的情况。比如一个类本来只该负责A,但AI在完成需求的时候顺手把和A毫无关系的BCD也塞了进去;再比如一个状态本来只该由后端维护,结果AI为了省事让UI也在那里偷偷修改,这些例子在实际的AI编程中简直不要太常见。虽然刚开始时这些问题都不明显,但如果人类没能及时指出来的话,那整个项目就会以极高的速度从“能跑”滑向“没人知道它为什么能跑”,最终形成一坨超过AI最大上下文长度,导致它自己也修理不了的屎山。
当然,如果就这么把所有问题都归咎于“猴子有枪”,其实也有点太简单粗暴了。在我看来,现在的AI编程之所以容易变成屎山,并不只是因为使用者太菜,而是因为现阶段的AI本身也确实存在一种根本上的局限:它擅长完成小目标,但不擅长完成大目标,这一点至少在我的实际使用中感受非常明显。如果只是让AI写一个json解析器之类的东西,那它通常能完成得相当不错,甚至通常比人类程序员还要规范一点;但如果让它完成一个更大的目标,比如“完成全部的战斗流程”,就算已经把需求说得非常详细,最终得到得到东西也仍然会十分微妙。这种微妙感如果硬要去形容的话,大概就类似于是一种非常隐蔽的“偷懒”倾向,或者说AI总是会倾向于优先满足提示词里那些容易实现的部分,然后把其余不重要的需求全都糊弄完成或者干脆省略。然而,由于项目中的很多长期约束本来就很难在提示词里一次性说清楚,而是在不断积累的过程中逐渐形成一个项目的骨架,因此一旦把整个项目都交给AI完成,最后就很容易把整个项目都搞成一种“每个模块单独看起来都不错,但合起来就是一坨”的状态。对此,一个很自然的解决思路便是:既然AI擅长小目标而不擅长大目标,那就不要把大目标直接丢给它,而是先由人类把大目标拆成一连串的小目标,再让它一步一步完成不就好了?这其实就是所谓CoT的思想,也就是Chain of Thought,在中文里一般会翻译成“思维链”或者“链式思考”。当然严格来说,随着这个词在使用过程中的语义逐渐漂移,如今的CoT已经不完全是最初论文里那个狭义概念了。不过今天我们不用纠结这个定义,只从具体使用的角度进行分析。当下的程序员们之所以能比编程小白更好地完成vibe coding工作,当然不是因为他们真的懂什么赛博咒语,而是因为接触过的项目多,所以更清楚怎样更好地把一个大到没法直接动手的目标分解成一堆可以处理的小目标。这其实也只是人类被LLM逼出来的结果,因为你不能指望LLM能自动替你规划好路线,所以你只好像个老妈子一样告诉它:先不要急着一次性生成一大坨,把整体架构定下来之后再写。这种人类被现阶段大模型逼疯之后所不得不总结出来的各种“沟通技巧”,其实就是所谓“提示词工程”的雏形。虽然名字上叫做prompt engineering,但它并不是一种严格的engineering学科,而是一种研究“我应该怎么跟这个模型说话,它才更可能给出我想要的结果”这种抽象问题的经验集合。从这个角度看,CoT当然就是提示词工程的一种典型形式。也许在外行人看来,“Think step by step”真的就像是一句能够让大模型输出变好的咒语,但它其实很好地体现了提示词工程的基本精神:通过各种巧妙的“沟通技巧”,来诱导大模型完成原本不擅长的任务。除此之外,我们还可以让模型扮演某个角色,比如让它“作为资深程序员”来回答问题;也可以让多个智能体进行合作;再比如我在之前文章中提到过的RAG等等。只不过,由于各家大模型的训练方式都不同,而且就算是同一个大模型的输出也带有随机性,所以这些提示词工程的效果其实也很难量化评估。
也正因如此,在我们当下这个“猴子有枪”的危险的原始丛林里,提示词工程作为一种临时的解决方案被匆忙推上了前台。有些人宣称这是在建立“射击俱乐部”,认为既然AI已经把枪发到了猴群手里,那至少应该首先让猴子们学会怎么瞄准;但也有很多人对此嗤之以鼻,因为在他们看来,所谓提示词工程不过是在给猴子们递上更精美的子弹匣。如果一个人本来就不知道自己要做什么,也不知道自己生成的东西意味着什么,那么再精致的提示词模板也未必能让他变成一个合格的工程师;相反,它很可能只是让他以更高的效率、更大的规模,生产出更加难以收拾的错误。原本这些人的破坏力可能还受限于打字速度,在提示词工程的帮助下可能连这个限制也被取消了。我个人对此的看法大概介于两者之间。一方面,只要模型还需要通过自然语言和人类互动,那么如何更好地使用自然语言去约束模型,就一定具有其现实意义;但在另一方面,提示词工程也确实不应该被神化,因为现在很多提示词中的所谓“技巧”,在本质上都只是在迁就当前模型的缺陷。所以,与其说提示词是新时代的编程语言,不如说它是自然语言还没有变成真正可靠的编程接口之前的临时替代品。它在当下当然具有不可忽视的价值,但这种价值很可能只是过渡性的。随着模型能力增强、上下文窗口变大、项目级记忆更可靠,很多今天看起来很有用的提示词技巧,未来也许会像维多利亚时期的电报技术一样,从某种必备技能变成少数爱好者才会怀念的时代眼泪。
然而,如果提示词工程的价值只是过渡性的,那么一个很自然的问题就会随之出现:如果有一天,AI不再需要人类替它写提示词了呢?提示词本身并不是什么神圣不可侵犯的东西,因为它也只是现阶段人类能够理解、能够编辑、能够传递给模型的一种控制信号。而既然它也只是一种控制信号,那么只要AI系统能够通过反馈判断某种提示词是否更有效,它就完全有可能自己生成提示词、修改提示词,然后比较不同提示词之间的效果。而如果一个AI系统在未来的某一天能够做到自己观察日志、自己调整上下文、自己生成测试、自己比较不同方案的话,那它为什么不能自己得出类似的控制策略?到那时候,prompt engineering大概就会从一种人类技能,变成系统内部的一个优化环节。甚至更进一步说,那时的所谓“提示词”可能未必还会保持自然语言的形式,因为自然语言提示词的唯一优点就在于人类能看懂,但它对于模型来说未必就是最高效的控制方式。对于模型来说唯一重要的目标永远只有一点,即“什么样的内部状态最能导向正确输出”,而这种内部状态完全可能是某种隐式的上下文,或者端到端学习出来的张量表示,而不是“提示词”这样一层专门用来给人类看的操作面板。如果“我会写代码”是程序员面对AI编程时失守的第一道防线,那么在此时,“我更会让AI写代码”就成了程序员们即将被迫放弃的第二道防线。
更麻烦的是,程序员的专业经验本身也未必永远是优势,因为这些经验也同样可以被理解为是一种偏见。程序员们在编程过程中所累积的各种“经验”,比如设计模式、架构原则、工程习惯,说到底也只是人类在特定历史条件下形成的折中方案,是人类在自身心智限制、团队协作限制、编程语言限制、工具链限制下总结出来的生存技巧。因此,一个写Java的人,总是会倾向于把问题翻译成类、接口和继承;一个写Haskell的人,可能会倾向于把问题翻译成纯函数、组合和不可变数据;而一个写C++的人,没准会在任何问题里都先闻到内存布局、生命周期和性能开销的味道。这些对人类来说当然都可能是好方法,但这种预设立场对于AI来说却未必总是正确。比如DeepMind公司开发的第一代围棋bot AlphaGo就学习了大量人类棋谱,但它很快就被完全不依赖人类经验,只依靠规则和强化学习不断迭代的AlphaGo Zero打得找不着北。当然,编程和围棋并不完全一样,毕竟围棋规则封闭,胜负明确,而软件工程面对的需求则要复杂得多,所以人类经验在软件工程中可能也很重要,但这个例子至少提醒我们:如果AI未来真的能够在更大的搜索空间里探索工程结构,能够自动生成、测试、验证和重构系统,那么程序员带着传统工程经验给出的提示词,未必一定比普通人朴素表达出的目标更接近最优解。到了那时候,人类程序员基于自身的专业知识给出的需求,到底是在帮助AI接近目标,还是在反过来在给AI拖后腿?这个问题其实也很难说。
如果这一步真的发生,那么“人在回路”的意义就会被进一步压缩,程序员们也将真正面临身后无险可守的窘境。在这样的系统里,人类就算不会真的完全消失,最多也只能退守到两个位置上。第一个位置是在最上游,提出那个最原始、最模糊的目标,比如:“我要一个好玩的卡牌游戏。”这些目标不是纯粹的技术问题,因为它们背后包含着欲望、审美、商业价值和现实约束等更复杂的判断。而第二个位置则是在最下游,承担最终责任。这个定位也很简单,如果AI生成的代码出了Bug,导致银行系统崩溃了,自动驾驶撞人了,医疗系统误诊了,这时候由谁来负责?AI本身显然负不了责,因为它没有财产可供赔偿,也没有社会身份可供惩罚;而部署AI的公司当然也不会轻飘飘地说一句“这是AI写的,所以谁都没有责任”就能逃脱惩罚。一个更可能发生的情况是,这些公司会拿出一整套流程文件,然后指着上面某个签过字的人说:“我们有完整的代码审查流程,这位工程师确认过系统可以上线。”于是,人类程序员最后的铁饭碗,可能就会与他们曾经赖以生存的技术不再相关,而是来自他们作为法律责任主体的存在,在AI自动生成、自动测试、自动部署之后,作为一个具备法律人格和组织身份的人类,在最后的验收文件上签一个字。这个签字当然也不是完全没有技术含量,毕竟敢签字就至少说明自己知道系统大概没有问题;但从社会分工上说,这个职位已经不再是传统意义上的“开发者”,而更像是代码的审计员,又或者说是一个自动化流水线的人类担保物。我知道这听起来很荒诞,但其实细想起来这种事在我们当下的社会中又似乎并不完全陌生,因为在很多行业中其实早就是这样了。很多项目最后都会有人负责签字验收,尽管这个验收者本人对项目中的大部分内容是怎么实现的可能都毫不知情。所以签字这个动作并不一定代表这个人真的亲手完成了全部工作,而更像是一种社会制度上的责任锚点,毕竟出了事以后总得有个人来负责。而这也许才是整个行业最黑色幽默的地方。
但即便暂且不去考虑这么阴暗的未来,仅仅从现实就业的角度来看,AI编程带来的冲击也已经足够明显了。就算只有中间的重复劳动部分能被AI接管,公司在完成同样工作量时所需要的程序员数量,也会不可避免地下降。这其实是一个很简单的算术问题,假设过去一个项目平均需要十个程序员,而现在只需要一个熟练使用AI的程序员就能完成这些工作的话,那公司就会不可避免地“向社会输送9个人才”。对此,乐观派们当然可以说,历史上的每一次技术革命都会创造新的需求、新的岗位和新的市场,比如工业革命虽然让很多传统手工业者失业,但也催生出了现代工厂、铁路、电力、化工等新的产业体系。这个说法当然有道理,但问题在于总需求增长并不等于每一个原有劳动者都能被重新吸收。如果单个程序员的生产力增长了十倍,但软件需求却只增长了200%,那剩下的七个人又该何去何从呢?他们当然可以转向新的领域,承担新的职责,但这中间的转型成本、时间成本和淘汰成本,并不会因为一句“先进生产力必然创造新岗位”就自动消失。生产力革命从来都不是请客吃饭。它当然会带来更便宜的商品、更高效的生产和更大的社会总财富,但它同样会把一部分原本掌握旧技能的人抛到时代车轮底下。站在宏观历史角度看,这当然可以被概括为“社会进步的阵痛”;但如果那个正在阵痛的人正好是我自己,那事情就没那么洒脱了。更何况,最先受到冲击的往往不是那些已经站在行业上游,负责复杂决策的人,而是大量中低端的、负责增删改查的外包编程工作。这些东西从技术上说当然不算多么高深,但它们过去恰恰构成了大量程序员赖以谋生的基础,也构成了新手程序员进入行业的入口。而这又引出了一个更加棘手的问题:如果初级任务全都被AI吃掉,那公司还愿意招初级程序员吗?在过去,一个新手程序员之所以能成长起来,并不是因为他刚入行时就什么都会,而是在于他可以从一些相对简单重复、边界清晰的工作开始做起。可如果AI把这些初级任务大量接管,那整个IT行业就可能会出现一个非常尴尬的断层:公司越来越需要能驾驭AI的高级程序员,可市场上却只剩下越来越多的“未来会成长为高级程序员”的新手。这虽然听起来有点矛盾,但在资本主义世界里却又相当合理,毕竟公司并不是程序员学校,也没有义务替整个行业培养下一代。过去公司之所以愿意雇佣初级程序员,是因为初级程序员虽然水平有限,但至少能在低成本岗位上完成一部分实际工作;可一旦AI能用更低的成本、更快的速度完成这些工作,初级程序员的训练价值就会和他的劳动价值脱钩。于是,我们极有可能在未来看到这样一个荒诞的现象:所有公司都在以高薪聘请那些具有AI协作经验的高级程序员,而大量刚刚毕业、只会问AI怎么写增删改查的新手则无人问津。当然,我们也有理由相信,也许在未来的教育体系中AI也会承担一部分培养功能,也许下一代程序员不是通过公司里的初级岗位,而是在和AI一起构建个人项目的过程中成长起来。就像我现在用Codex写游戏一样,过去需要一个小团队才能完成的东西,未来一个学生也许就能在宿舍里做出原型。但问题在于,一个在真实团队中成长起来的人通常会被迫面对协作、维护和历史包袱等现实约束;而一个人如果只是在AI的陪伴下独自完成项目,那他就很容易沉浸在“我能让东西跑起来”的幻觉里。前者会让人学会尊重屎山,后者则可能让人学会高速制造屎山。这句话说得也许有点刻薄,毕竟我自己现在也正在用AI做个人项目,所以严格来说,我可能也只是正在丛林里练枪的猴子之一;但无论如何,个人项目和真实工程之间的差距都不会因为AI而消失,反而可能变得更加隐蔽。
写到这里,其实就又兜兜转转回到了我在本文开头提到的那种“不体面”的感觉。之前写AI绘画的时候,我多少还能站在旁观者的位置上,讨论画师为什么会抵触AI,讨论“创造”和“拼接”的职业特性差异;后来写AI作曲的时候,我也可以相对轻松地说,AI作曲或许不像AI绘画那样直接砸在现有从业者的饭碗上,反而更像是给普通人和音乐人都提供了一种新的灵感来源。那时候我当然知道,自己并不是什么真正置身事外的历史观察者,但至少在情绪上,我还是很容易站到“先进生产力”的一边,仿佛只要说一句“时代总是在发展”,很多具体的痛苦就可以被轻飘飘地归入宏观叙事。然而轮到AI编程时,这种轻松的姿态就很难继续维持下去了。“屠龙者终成恶龙“,这句话放在这里当然很合适,过去程序员总是挥刀斩向别人的重复劳动;如今这把刀转了一圈,终于架到了程序员自己的脖子上。这个故事听起来也没有什么不对,甚至有种非常爽快的感觉;可AI编程真正特殊的地方在于,它还能让这个故事继续走到一个更加陌生的阶段。我们总说,“屠龙者终成恶龙“,然后呢?也许之后的答案不再是“会出现新的屠龙者,杀掉屠龙者变成的恶龙”这样的循环,而是“之后再也没有新的屠龙者了”。因为就像我说的那样,AI编程生成的不是图像、音乐或者其他外部作品,而是程序本身,是AI系统、开发工具、训练框架、测试流程和自动化管线赖以存在的基础,是在参与改造替代劳动的机器本身。一旦这条链条真正闭合,故事就不会再局限于“一批人用新工具替代另一批人”,而是“工具开始参与自己的迭代”。到那时候,新的屠龙者就未必还是人,甚至“屠龙者”这个位置本身都可能被系统内部的自我优化过程吞掉。当然,如今的大模型还远远没有达到能够自我迭代的水平,AI的运转仍然依赖算力、数据、资本和工程系统,但这种方向上的差异又已经足够明显。而在这样的系统当中,如果继续往黑暗方向去想的话,那么未来的人类可能就会变得有点像是在FGO 2.6版本的剧情里,被妖精豢养的人类那样。到了那时,人类就不再是生产系统真正的主人,而更像是某种被保留下来的资源,用来提供欲望,提供审美,提供目标,提供灵感,提供价值判断,提供训练数据,最后再提供法律和道德责任的接口。这个比喻当然有些过分夸张,但这个夸张的比喻至少提醒我们一件事:当我们说AI是先进生产力,能够优化掉某些职业的时候,不能只想象它能替别人省力,也要想象它优化到我们自己头上的那一天。
话虽如此,我大概还是希望AI编程能够继续发展。毕竟,就算我嘴上说得再忧国忧民,但当Codex帮我把一个拖了几年的游戏企划从脑子里拉进Godot工程的时候,我还是会感到一种非常朴素的快乐。那种快乐和第一次用AI作曲生成BGM、第一次看到AI绘画把文字变成图像时其实并没有本质区别,它让一个原本因为时间、精力、技能和成本限制而无法实现的想法,突然变得似乎可以触摸。问题只在于,每一种这样的快乐背后,似乎都附赠了一张时代的账单。当画师看到AI绘画时,那张账单寄到了画师手里;当音乐人看到AI作曲时,那张账单也许还没有那么沉重,但它同样已经在路上;而当程序员看到AI编程时,那张账单终于也寄到了我们自己的手上。我当然希望这场AI革命来得更快、更彻底,前提是被革命的不是我。但很遗憾,时代从来不会因为我的双标而停止向前奔腾的脚步。先进生产力这种东西,念在嘴里的时候总是很轻松,只有砸到饭碗上的时候才会发出比较真实的声音。所以,如果非要给这篇文章一个不太积极但也不算完全绝望的结论,我大概会这么说:也许我们这一代程序员最终极的工作,并不是继续写代码,甚至也不是继续教AI写代码,而是在一个越来越不需要人类的系统里,找到自己还能被允许存在的位置。至于那个位置究竟是目标的提出者、灵感的供应者、责任的签署者,还是恶龙胃里负责盖章的器官,那就只能交给时间回答了。