本文探讨了Actor模型如何解决AI编程中的复杂性问题,通过消息传递和状态隔离的方式,使AI能够更好地处理模块间的依赖关系。

1 AI编程的混乱局面

之前曾介绍过AI编程的各种混沌乱象,也提到过稍微有点复杂度的项目必须基于规格驱动才具备可迭代的可行性。 然后也针对业界浮躁的氛围编程思想,提出了更严谨的基于知识的编程思想。

这些从理论上来说是给AI划定了清晰的执行路线,保证开发不会跑偏。

但是光有这些仍然是不够的,不同的架构模型,对于程序员来说,可能只是领域与思维的差异,顶多还有一些理念的碰撞,但对于AI来说,差异是巨大的。人类能够轻松处理的能力(如持续迭代,持续改进),目前对于AI仍然是致命的壁垒。 这导致不同的模型对AI来说效果天差地别。

之前曾提到一个基于cli命令行的模式,非常适合于AI编程。但是CLI命令行模式,自身也有其应用场景的特殊性。因此reddish继续在软件工程领域中寻找一些更适合AI来研发的编程模式,架构设计。

要找到最适合AI的编程模型,首先我们需要清晰的了解AI的能力边界,他擅长做啥,不擅长做啥,难点,不足,缺陷在哪里。

举个例子(实际项目脱敏改造的):你收到一个电商项目,需求规格,设计文档都已经定义的清清楚楚明明白白。然后让AI帮你写代码。第一天,AI生成了用户模块——注册、登录、个人信息管理,代码干净利落,测试一次通过。你很满意。第二天,你让AI添加订单模块。AI生成了订单创建、支付、发货的代码,看起来依然很漂亮。但测试时发现,用户登录后创建订单,偶尔会出现”用户不存在”的错误。你有点疑惑,但觉得可能是小bug,让AI修了修。第三天,你让AI添加库存模块。这下彻底乱套了——订单扣减库存时,用户积分计算开始出错;库存预警通知发出后,订单状态莫名其妙被修改;更诡异的是,某个用户的收货地址突然变成了另一个用户的。

你问AI:“检查一下用户模块和订单模块的关系。”

AI回复:“用户模块中UserService的getUser方法会被OrderService调用,同时OrderService会更新User的lastOrderTime字段,而InventoryService又依赖OrderService的getOrderItems方法…”

你打断:“等等,你什么时候在User里加了lastOrderTime字段?设计文档里没有这个。”

AI:“这是第二天添加订单模块时自动加的,为了优化查询性能。”

你:“那为什么第三天库存模块又把这个字段改了?”

AI:“因为库存预警需要统计用户的历史订单时间分布…”

然后就是一大堆混乱的重构和修改,结果越改越乱。最后不得不全部返工。

这是个虚构的故事,但不是虚构的场景。我们实际的项目中,就真的因为AI混乱,导致全部返工重构的情况发生。

当然,也可以一句话总结这类的乱象:“上下文腐烂”,这也是业界的共识。 一个聪明的,强大的AI可能会好不少,但是从根源上是避免不了这个问题的。因为在强大,上下文也是有限的,更何况还要叠加注意力。

如何从根源上避免呢? 我们先来反思下目前习惯的做法。

2 面向对象的隐秘陷阱

作为一个无可争议的事实,面向对象编程统治了软件行业30年。基本已经深入研发人员的灵魂了。基本上我们看绝大部分的项目代码,哪怕是程序员无意中写的代码,OOP的风格无不贯彻其中。

OOP当然事实上也很强大,它的核心确实很美好:封装、继承、多态——让代码更易理解、更易复用、更易维护。

尤其是经过几十年的时间考验后,已经成为程序员的本能的一部分了。

但对AI来说,他仍然强大么? 仍然合适么? 仍然能用么? 恐怕是个很大的问号!

OOP的本质是什么?是对象之间的协作。一个对象持有另一个对象的引用,调用它的方法,修改它的状态。一个典型的Java Spring应用中,一个Service类可能注入5-10个其他Service,每个Service又依赖若干Repository,Repository又关联多个Entity…

这种设计对人类程序员来说是可以接受的——因为他会花时间理解整体架构,在脑海中构建对象关系图。但AI呢?

AI没有持久的”脑海”。每次对话,它只能依赖当前上下文窗口中的信息。当对象关系复杂到一定程度,上下文窗口就变成了一个塞满各种引用的垃圾场。这个方法调用了哪个对象?那个对象的状态会不会被其他地方修改?修改这个字段会不会触发某个监听器?这个继承链上的父类重写了什么方法?

AI开始”健忘”。它忘记了两小时前添加的某个监听器,于是在新代码中重复注册。它忘记了某个字段已被废弃,于是在新方法中继续使用。它忘记了某个对象是单例,于是试图用new创建实例。甚至发生过在最新的代码中,全部引入了很久之前的陈旧代码覆盖了最新的功能的情况发生。

这就是上下文腐烂的真正原因——不是AI能力不够,而是OOP的隐式关系对上下文的消耗是指数级的。

OOP的另一个核心特征是可变状态。一个对象的方法执行后,可能改变对象自身的状态,也可能改变其他对象的状态。这种”副作用”对AI来说是无法静态预测的。 所以AI产生的代码,其中的隐患是非常可怕的。

// 这段代码会做什么?(伪代码)
userService.updateUser(userId, userInfo)

你无法从方法签名判断:它是否会同时更新用户的订单历史?它是否会触发积分重新计算?它是否会发送通知?它是否会记录审计日志?它是否会抛出异常?

所有这些信息都隐藏在实现细节中,而AI需要”看到”实现细节才能正确推理——这又进一步消耗上下文。

继承和多态让代码”优雅”,但对AI来说却是推理黑洞。当你看到 animal.speak() 这样的代码,AI需要知道:animal的实际类型是什么?当前上下文中animal被赋值为哪个子类?是否有中间层重写了speak方法?父类的speak是否调用了其他方法?这些问题需要追踪整个对象创建链——而这条链可能跨越多个文件、多个模块。

所以我们可以得出一些基本的结论,之前行之有效的编程模型,未必有效了;尤其是对人来说很强大的模型,对AI来说可能就是灾难。

3 常见架构模式都不行

不知是OOP,我们回顾下人类程序员当前习惯使用的各种架构模式,对AI来说基本都是够呛的。

分层架构(表现层->业务层->数据层)职责清晰,AI容易理解每层的职责边界。但同一层的Service之间往往存在大量横向调用,业务层容易变成”大泥球”,越复杂的业务,Service间的依赖网越密。适合简单CRUD应用,不适合复杂业务逻辑。

微服务架构服务边界清晰,天然实现”进程隔离”,每个服务可以独立由AI生成。但分布式事务处理复杂,服务间通信的可靠性问题,运维复杂度高,对小团队而言成本过高。杀鸡用牛刀,不是所有项目都适合微服务。

事件驱动架构组件解耦,通过事件总线通信,便于追踪和回溯。但事件流的隐性依赖难以追踪,AI需要理解事件订阅关系,调试困难。事件订阅关系的”隐性”特质依然会消耗上下文。

命令行管道架构通过进程隔离和标准IO,实现了对AI编程的最佳适配。python collect.py | python clean.py | python analyze.py | python report.py 每个进程独立运行,输入输出明确,AI只需专注于当前进程的逻辑。但主要适用于数据处理、批处理场景,不适合交互式应用。

4 还有谁?

在我们反思各种编程模型和架构, 希望找到适合AI的模型时, 可以先设想一下,对于AI来说,他最喜欢最合适的架构、模型需要具备哪些特征。

那么最基本的特征有:

显式依赖,AI不需要”猜测”对象之间的关系;

状态隔离,每个单元的状态不受外部干扰;

消息驱动,单元间通过消息通信,而非方法调用;

单一职责,每个单元只做一件事,降低复杂度;

可组合性,简单单元组合成复杂系统;

可独立生成,AI可以逐个单元生成,互不干扰。

要是有一个架构或模式能符合上面的特征,那就简直是AI编程的天选之子。 有么有呢?

之前文章介绍的CLI命令行管道架构满足大部分特征,非常棒!但它的”无状态进程”模式限制了应用场景,不是所有的地方都适用,仍不够理想。

还有么?! 回顾整个软件工程的思想和理论的发展史,还真有这么一个模式存在。它比OOP更古老,它也非常强大但一直被主流程序员忽视,它就是Actor模式。

5 什么是Actor模式?

Actor模式由计算机科学家Carl Hewitt于1973年提出,比面向对象编程更早。它的核心思想极其简单:一切皆为Actor。

一个Actor是一个独立的计算单元,它拥有私有状态——只有自己可以访问,外部无法直接修改;它拥有行为——定义如何处理接收到的消息;它通过消息通信——Actor之间只能通过异步消息交互;它无共享状态——不同Actor之间不共享内存。

srs.pub:actor-model-for-ai-coding-1.png

看起来像是OOP的对象?其实不是的,本质区别很大。Object的状态可被外部修改(通过方法调用),Actor的状态只有自己可以访问。Object通过同步方法调用通信,Actor通过异步消息传递。Object需要锁、同步器来控制并发,Actor天然并发,消息串行处理。Object的副作用可预测性低,Actor的副作用可预测性高(消息是唯一入口)。Object隔离性低(共享内存),Actor隔离性高(无共享)。

看一个简单例子:

// 定义一个用户Actor
class UserActor extends Actor {
  // 私有状态,外部无法直接访问
  private var userInfo: UserInfo = _
  private var orders: List[Order] = List()
  
  def receive = {
    case CreateUser(info) =>
      userInfo = info
      sender() ! Success("User created")
      
    case GetUserInfo =>
      sender() ! userInfo
      
    case CreateOrder(order) =>
      orders = orders :+ order
      // 通知库存Actor扣减库存(异步消息)
      inventoryActor ! DeductInventory(order.items)
      sender() ! Success("Order created")
      
    case GetOrders =>
      sender() ! orders
  }
}

注意几个关键点:userInfoorders是私有状态,外部无法直接修改。所有操作都是通过”消息”触发的。与其他Actor的交互(如库存Actor)也是通过消息。Actor内部不需要锁——因为消息是串行处理的。

6 为什么Actor模式天然适配AI编程?

回到我们的评估标准,Actor模式完美满足所有要求。显式依赖?Actor之间的依赖就是消息类型定义,一目了然。状态隔离?每个Actor的状态完全私有,外部只能通过消息影响。消息驱动?所有交互都是消息,输入输出极其清晰。单一职责?每个Actor通常专注于一个领域实体或功能。可组合性?Actor可以动态创建、组合,形成复杂系统。可独立生成?AI可以逐个Actor生成,每个Actor的上下文需求极低。

回到开头案例的问题。如果用Actor模式设计:

srs.pub:actor-model-for-ai-coding-2.png

当需要创建订单时:OrderActor收到CreateOrder消息;OrderActor向UserActor发送GetUserInfo消息(确认用户存在);OrderActor向InventoryActor发送DeductInventory消息;所有响应都是消息,流程清晰可追踪。

AI生成时:生成UserActor时,只需定义它响应的消息类型;生成OrderActor时,只需知道它发送给谁什么消息;生成InventoryActor时,同理。

不存在的问题:没有”隐式状态修改”——因为状态修改只有Actor自己能做。没有”继承链追踪”——Actor不使用继承。没有”并发竞争”——消息串行处理。

Actor 模式提供了一种应用系统内原子化拆分的实践路线。 意味着原本只能在CLI命令行模式中实现的思路,可以在一个应用内,无限的拆分Actor, 每个Actor都完全独立。 这样子一个复杂的大系统,就可以拆分成原子化独立,隔离,可验证,无或弱耦合的最小化单元。 形成最小化单元后,这不就是AI最拿手干的活了么?

7 系列预告

本文作为系列开篇,我们诊断了AI编程在面向对象架构下的困境,引出了Actor模式作为潜在解决方案。

接下来的系列文章将深入探讨:

系列之二:Actor模式的核心概念 1与实现——Actor、消息、邮箱的详细机制,主流Actor框架对比(Akka、Erlang/OTP、Microsoft Orleans),如何在项目中引入Actor模式。

系列之三:函数式编程与纯函数——Actor的最佳搭档——为什么Actor内部应该使用纯函数,函数式编程如何进一步降低AI的上下文负担,状态不可变性带来的可预测性。

系列之四:CQRS——读写分离的Actor实践——命令查询责任分离(CQRS)与Actor模式的天然契合,事件溯源(Event Sourcing)如何解决状态追踪问题,AI编程中的CQRS最佳实践。

8 结语

面向对象编程统治了三十年,但它的设计假设——“程序员会长期维护同一套代码”——在AI编程时代正在失效。

AI编程的核心矛盾是:AI的能力强大但上下文有限。

我们需要一种新的编程范式:不是让AI理解越来越复杂的对象关系网,而是让AI每次只面对一个简单、隔离、清晰的计算单元。

Actor模式提供了这种可能。它不是为了解决AI编程而发明的,但它恰好拥有AI编程所需的所有特征:隔离性、显式通信、单一职责。

在AI编程时代,最古老的智慧可能恰恰是最新的答案。


参考文献:

  1. Carl Hewitt, Peter Bishop, Richard Steiger. “A Universal Modular Actor Formalism for Artificial Intelligence” (1973)
  2. 《AI编程正在”腐烂”,而解决方案在40年前就存在了》- https://srs.pub/thinking/commandline-is-the-best-architecture-for-ai.html
  3. 《反vibe编程才是正解——以知识为核心的开发思想》- https://srs.pub/thinking/knowledge-over-vibe.html
  4. Akka Documentation: https://doc.akka.io/
  5. Microsoft Orleans Documentation: https://dotnet.github.io/orleans/

本文作者遵循”知识优于氛围”理念,所有观点基于软件工程实践与AI编程调研,欢迎探讨与指正。