超越DRY:AI原生软件工程的思考

半年前,我写了这篇文章。当时有两个论断:Claude Code虽然刚发布没人重视,但这种方便集成的的Agentic能力很重要;未来软件分发和开发的方式会有很大变化,Library as a Service会成为一种重要的新方式。这半年来,我一直在实践这些思考,成为了Claude Code类工具的重度用户,也为我们的AI课程开发了自己的LaaS产品。

回顾过去一年高强度的 AI 开发,我发现了一个反直觉的现象:我并没有有意识地避免重复工作,甚至有意违背了软件工程最核心的原则——DRY(Don't Repeat Yourself,不要重复自己)。比如,每次我想画流程图、做简单的可视化的时候,会直接让 AI 帮我写代码,而没有把它们抽象成一个可以复用的库。可是奇怪的是,我的开发效率并没有降低。

我的这种工作方式,正是用户生成软件(User Generated Software,UGS)时代的一个缩影。我们的需求,不再是大众的普遍需求,而是个性化的临时需求。我们用的软件,不再是大公司预先开发的,而是AI现场写出来的。可是,在这个时代,一个软件公司交付的产品到底是什么?过去这件事很显然,Office 安装包、Gmail 网站,用户可以使用这些成品来完成自己的任务。但是现在,当用户通过和 AI 对话撰写代码来完成任务的时候,软件公司或者平台方,交付的到底是什么东西?

我逐渐意识到,我之所以可以违背 DRY 原则,可能是因为交付的产品从本质上已经改变了。我们交付的不再是一个需要长期维护、以最小化不确定性为目标的静态成品。这篇文章就想去理清这种新型交付物的本质,去思考用户生成软件的时代,我们应当如何构建一套新的软件工程框架。

场景之变 - Why

回顾我们过去几十年的软件开发过程,它和电视台录制节目的逻辑是非常类似的。产品经理首先找到一个足够大、有商业潜力的用户群,搜集在他们多样化的需求。然后抓大放小,找到头部需求,把这一系列需求按照逻辑抽象出来,作为开发目标。这是产品层面的抽象。接着工程师团队再对需求进行分析,进行工程层面抽象——如何使用尽可能简单优雅的系统来支持这些需求,从而保证我们有限的开发资源可以复用。

与此相比,AI时代的软件开发从场景上就有很大变化。一种新的场景是,我们的目标往往不是做一个服务很多人、维护很多年的软件,而是一个轻量级的,甚至可以只为我一个人服务,可以用完就丢的临时软件。比如每天看一下泡泡玛特网站有没有上新,如果有的话短信通知我;或者干脆就是把我手头这三十个视频按照一定规律重命名。

在 AI 出现之前,这样的场景听起来完全不现实。不可能养一个外包团队专门绕着一个人的临时需求转,每天开发5个app,用完就扔。但想想抖音根据个人喜好为每个人构建了自己的个性化app体验,这件事在十年前听起来很离谱,但今天已经司空见惯。AI 的出现也和抖音类似,它让软件的开发或者更抽象的电脑算力的调度成一件特别轻量级的事。我们已经看到了很多例子,只要跟 AI 说一句话,它就会写脚本帮你把这 30 个视频批量重命名,甚至几分钟就做出来一个泡泡玛特网站的迷你外挂。

和电视台精雕细琢的电视节目相比,这些软件就像是抖音上的短视频,它们成本低廉,很难有持久的影响力。但和YouTube、抖音类似,这些AI生成的日抛软件,构建了一个新的软件门类,User Generated Software。它解决的是用户的长尾需求,让普通人不写代码、不用已有的商业App,也能自由调用电脑的算力,享受信息化自动化的好处。它的商业价值同样广阔,甚至更大。

因此,一个明显的矛盾就产生了:我们传统的软件工程是为了满足上万用户用几年的重量级 app 发明的。它们强调可维护性、可扩展性、可复用性。核心目标是在交付的软件和构建的流程上最小化不确定性。但不论是目标还是方法,它对这种新的场景都已经不再适应了。新的场景更强调最大化生成潜力(Generative Potential)。如果我们想走出 DRY 的思维局限,在 User Generated Software 的新时代仍然构建出高质量的软件,应当遵循哪些原则,用什么框架进行思考呢?

交付之变 - What

要回答这个问题,首先得搞清楚,在UGS时代一个软件公司交付的到底是什么?我的答案是,是一个支撑AI进行代码生成的内核(Generative Kernel)。这和传统软件的交付方式就类似成品家具和宜家家居的区别。

对于传统软件,用户买到的类似一个功能形态完全固定的椅子。开箱即用,但用户没办法轻易改变它的高度或者颜色。为了把这个椅子卖给尽可能多的人,它的设计要满足最广大群众的需求,追求普适性。因为它会带来更大的经济效益,所以由一个专家团队设计和制造。

但相比之下,AI生成的个性化软件则完全是另一个套路。类似宜家家具,用户买到的不是一个开箱即用的椅子,而是一个套件。套件里有几个关键的核心部件,比如在特定的位置事先开好孔的椅面和椅腿。这些部件我们在家没办法自己造出来,而它们的质量决定了最终成品的品质。还有一张详尽的说明书,指导用户/AI一步步组装。用户可以按照说明书按部就班装出来一个椅子,也可以选择在这个基础上改装,换个颜色,把腿锯短一点,甚至只用三条腿,来满足自己的个性化需求。

在这个过程中,用户的角色不再是纯粹的使用者/User,而是一个Builder。我们作为软件公司的角色也变了。我们做的不再是成品家具,而是这样的半成品套件。我们的成功不再由卖了多少椅子决定,而在于我们设计的套件,能够让用户多么轻松、高效、创新地组装出他们想要的各式各样的椅子。

因此,从更抽象的角度来说,这个基础套件的价值在生成潜力:作为与AI协作的基础,能够生成多少种高质量、个性化应用的能力。这可以从三个更具体的维度来看待:

  1. 表达范围:基于所提供的能力,用户与AI能够Build出多丰富的应用?是限制在一个狭窄的功能集合里,还是在广阔的应用场景里都有组件支持?这是个广度上的指标。
  2. 意图保真:当用户有一个明确意图时,最终生成结果和这个初始意图有多吻合?是必须要做一些妥协,还是可以完美实现?这是个深度上的指标。
  3. 生成效率:这个平台跟AI配合的时候,AI够不够贴心?是一次成功,还是需要频繁手工介入,做大量定制?这是个效率上的指标。

它和传统软件的根本区别,就是直接用户从人类转成了AI,目标从开箱支持广泛需求,变成了定制后支持灵活需求。为了实现这种生成潜力的最大化,我们交付的就不是传统的被动的给开发者调用的软件库,而是一个“生成内核”(Generative Kernel)。它是一个给AI看的工具包。根据我的第一手探索经验,一般可以包括下面三个部分:

  1. 核心套件:这是生成内核的基础,提供了不可替代的核心能力。比如对宜家家具来说,这个核心套件就是上面提到的椅子腿椅子面。
  2. 引导知识:和传统的、写给人类看的文档不同,这种引导知识是一套为AI设计的知识体系,甚至可以是一个搜索引擎本身(比如作为一个MCP tool给AI提供文档搜索接口)。在AI工作时,这套知识会直接注入上下文窗口,引导代码生成。这个知识可以很详尽,包括设计哲学、最佳实践、常见陷阱等等等等。因为虽然人类读完一本书可能需要几天,但AI几秒钟就搞定了。对宜家家居来说,这可能是个100页的说明书。这个说明书不是给人看的,是给一个组装机器人看的。
  3. 杠杆工具集:对于一些AI在概念上能理解,但具体实现上既繁琐又容易出错的任务(比如计算复杂UI布局的坐标),我们不强求AI从零生成,而可以提供一个高层工具就好(比如类似Mermaid的布局引擎)。这个本质是把一些成功率不高的不确定性的任务转化为确定性的任务,在关键瓶颈上提升AI的生成效率。对宜家家居来说,这是内六角螺丝刀。因为机器人虽然也可以用机械臂打螺丝,但有个螺丝刀可以让它打得又快又好。

下面我们来看一个具体的例子:支付平台Stripe。大家如果做过支付的话,都知道接入Stripe有多复杂:业务逻辑、安全考量和前后端协同,每个都要考虑,接入的时候非常痛苦。如果Stripe想要做一个给UGS用的开发包,它的这个生成内核也需要包括这三个部分:

  1. 它的核心套件就是Stripe现在的支付API。这个和银行结算的能力是别家替代不了的,是它的核心价值。
  2. 引导知识就是一套工程最佳实践,比如怎么保护安全隐私,怎么构建前端来保证良好体验。
  3. 杠杆式工具集可以是一个程序,用户用JSON输入我想要什么样的订阅套餐,自动新建SKU并且做好配置,保证100%正确。

在这个基础上,用户就可以跟Cursor说,我想要接入XX stripe账号,实现7天免费试用,之后30天每个月收费30美元,然后Cursor就可以狂写代码,自己测试迭代,接入Stripe了。这就是UGS时代的软件交付。

方法之变 - How

目标和形式的根本区别决定了在具体实现上,UGS的软件工程也和传统的软件工程有诸多不同,甚至需要一套全新的设计原则。

传统API设计面向的是人类开发者,核心是以保护为目的的抽象——隐藏复杂性,提供简洁接口,防止用户犯错。但AI原生设计面向的是AI,其核心思想恰恰相反,应该是一种激进式透明(Aggressive Transparency)——最大化地提供信息。这是因为人看了几百行的报错可能会畏难,会崩溃,但AI不会。它可以毫无心理负担的一杆子捅到底层源码里,在几秒内搞清楚问题在哪,然后继续迭代。有足够的出错信息,AI才能进行自我修正。

我举三个具体例子:

  1. 提供精细原始的反馈

    传统API在捕获底层错误后,一般会抛出一个抽象的高层异常。这对于AI的Agentic工作流是致命的。AI的有效性依赖于一个尝试-反馈-修正的循环。不论是对人还是对AI,模糊的“操作失败,请稍后再试”的错误信息会直接中断这个循环,让人无所适从。

    因此,对AI原生的软件,我们要提供富含技术细节的原始错误信息。当封装异常时,尽量保留完整的上下文。比如宁愿rethrow一个带stacktrace的ConnectTimeoutError,也不要重包装成一个通用的APIFailureError。前者AI一看就知道哦某个特定步骤网络超时了,后者得从模糊的自然语言里面推断错误的底层原因,效率低很多。精细原始的反馈,是提升生成效率和意图保真度的基础。

  2. 暴露细粒度的控制

    为了降低人类的学习和误用成本,传统API设计一般会隐藏底层的、细粒度的接口。但这个约束对AI完全不适用,它可以3秒看完100页文档,学习成本几乎为零。相反,这些低级接口是生成内核表达范围的直接保障。当高层抽象没办法满足用户的长尾需求时,AI可以利用这些细粒度的接口进行自由组合和微调,创造出标准接口实现不了的解决方案。

  3. 交付足够详细的知识

    传统软件的文档是写给人看的,是代码的外部参考,总觉得比代码低一头。但生成内核中的引导知识,是写给AI看的,是交付物本身的一部分,是一等公民。因为过去需要开发者通过数年经验才能内化的最佳实践、设计哲学,现在可以被系统性地编码成Prompt,作为内核的一部分一同交付。想象一下一个AI看没看过Effective C++,写出来的代码质量是不可同日而语的。这些知识是提升生成效率和意图保真度的强大杠杆。

小结

所以,回到最初的问题:我们是否要抛弃DRY原则?

答案是否定的,但它的适用范围发生了根本变化。我们依然要不遗余力地避免在核心能力上重复自己,但我们允许,甚至鼓励AI在生成最终应用时,为了满足个性化需求而进行有意义的重复。

UGS这个范式的转变,最终将重塑开发者的角色和价值。我们的核心工作,不再是穷尽所有需求,编写出面面俱到的成品,而是去设计一个优雅、强大且透明的内核,为AI的创造力提供尽可能广阔的舞台和尽可能精良的工具。

当然,这一切还处在非常早期的阶段。我们需要全新的工具来测试和分发这些生成内核。如何系统性地评估一个内核的生成潜力?围绕它的商业模式又会是怎样?这些问题没有现成的答案,但它们定义了AI原生时代软件工程需要去探索的新边界。

我们正在从构建软件,走向构建软件的潜力。

Comments