程序即数据流:C++构建3D编辑器的架构范式思考

从对象到程序:一次架构思维的转变
一位开发者在厌倦了传统的 ASP.NET 与面向对象架构后,选择用 C++ 从头构建一个 3D 编辑器。这个看似普通的技术项目,却在 Reddit 上引发了一场关于"程序本质"的深度讨论:程序究竟是对象的集合,还是数据流的图谱?
作者的核心观点是:他不再把程序设计成一堆相互调用的对象,而是把它建模成一个数据流图(dataflow graph)——每个节点有输入和输出,节点之间的连线本身就是应用程序的逻辑。这种思路在可视化编程领域早已存在,只不过作者用 C++ 代码而非"盒子和连线"来表达它。
数据流图作为一种计算模型,其理论根基可追溯至1960至70年代MIT和兰德公司的早期研究。与冯·诺依曼架构(指令按顺序执行)不同,数据流模型中节点的执行由数据的就绪状态驱动——只要输入数据到位,节点便可立即计算,天然支持并行。现代深度学习框架如TensorFlow和PyTorch的计算图,正是这一思想的工业级实现:模型被表达为一张有向无环图(DAG),每个算子是节点,张量是边上流动的数据,这使得自动微分(autograd)和GPU并行调度成为架构的自然产物,而非后期叠加的功能。
似曾相识的节点式编程
对于不熟悉这一范式的人,社区给出了几个绝佳的类比:
- Unreal Engine 的 Blueprints:游戏开发者最熟悉的可视化脚本系统,用连线组件替代代码。
- Grasshopper:在建筑与结构/机械工程行业广泛使用的节点式工具,供建筑师和工程师使用。
- Blender 的节点系统:3D 建模与游戏美术工作流中随处可见的节点化工具。
Unreal Engine的Blueprints系统并非孤例,而是可视化编程语言(VPL)几十年演进的集大成者。早在1980年代,Prograph和LabVIEW已开始尝试用图形化节点表达程序逻辑。LabVIEW至今仍是工业测控领域的主流工具,其数据流执行模型与作者的架构思路高度吻合。Blueprints的核心贡献在于将这一范式带入了实时渲染的游戏引擎:执行引脚(Execution Pin)负责控制流,数据引脚负责值传递,两套引脚并存,解决了纯数据流模型在处理副作用和时序时的尴尬——这也是几乎所有工程级数据流系统最终必须面对的核心张力。
换言之,作者做的事情并非凭空发明,而是把一个在设计与工程界成熟已久的范式,重新用系统级语言实现了一遍。
为什么"程序即工作流"更接近本质
这场讨论中最受认同的观点之一是:所有程序归根结底都是工作流(workflow)。数据从输入流向输出,经过一系列变换,最终产生结果。
有评论者精辟地指出,作者的架构"通过电子表格的隐喻,把这种工作流本质变得更加显式"。这也解释了为什么作者会被调侃为"把 C++ 变成了 Excel"——一个运行时的电子表格式虚拟机,每个单元格都是一个可计算的节点。
面向对象不是架构的万能骨架
有意思的是,作者并没有全盘否定 OOP。社区特别欣赏他的态度:面向对象很有用,但不应成为整个程序的核心组织原则。
面向对象编程(OOP)的兴起有其深刻的历史背景。Alan Kay在设计Smalltalk时,其本意是以「消息传递」为核心——对象是独立的计算单元,彼此通过消息通信,类似于生物细胞。这与后来C++/Java将OOP简化为「数据加方法的封装」有本质差异。Kay本人曾多次表示,他发明的「面向对象」被严重误解了。真正的问题在于:当OOP成为程序的组织骨架时,「继承层次」往往演变为刚性的耦合,「封装」有时反而阻碍了数据的可见性。数十年来兴起的函数式编程(FP)复兴、DDD领域驱动设计、ECS(实体组件系统)等架构范式,都在不同维度上反思和修正OOP的过度使用,作者的数据流架构是这一反思浪潮的最新变体。
那么,我们为什么需要对象?一位评论者给出了理性解释:对象意味着通过"传递消息"来通信。你不需要知道具体调用了哪个函数,只需向某个对象发送消息,就会有相应的函数被执行。这种解耦能力——不依赖具体实现——正是对象的核心价值。
几乎所有主流编程语言都支持对象,这说明它绝非一时的流行风潮。作者的贡献在于,他把对象降格为"数据结构 + 附加函数"这一朴素定位,而非把它当作组织一切的骨架。
Lisp 的幽灵与 Greenspun 第十定律
有趣的是,这场讨论很快滑向了编程语言史的经典话题。有人半开玩笑地说:"每种编程语言都在渐近地接近带花括号的 Common Lisp。"
Greenspun 第十定律的深层含义
随即有人指出,这呼应了著名的 Greenspun 第十定律:
任何足够复杂的 C 或 Fortran 程序,都包含了一个临时拼凑的、非正式规范的、充满 bug 的、缓慢的 Common Lisp 实现的一半。
Greenspun第十定律由Philip Greenspun于1993年提出,表面是对C/Fortran程序员的讽刺,深层揭示了一个软件工程规律:复杂系统在演化过程中会自发产生对「可编程性」的需求。当业务规则变得复杂且频繁变化时,硬编码逻辑的维护成本急剧上升,开发者往往会引入配置文件、规则引擎、DSL(领域特定语言)乃至完整的脚本运行时——这个过程本质上是在宿主语言内部实现另一门语言的解释器。Common Lisp的宏系统(Macro System)之所以被反复提及,是因为它提供了「以数据表达代码」(homoiconicity,同像性)的能力,让元编程成为语言的一等公民,而非临时补丁。作者的节点图运行时,正在经历这条从「程序」走向「元程序」的必由之路。
这条定律的深意在于:任何足够复杂的项目最终都会需要**元编程(metaprogramming)**能力。Common Lisp 正是以其强大的宏系统、函数式编程能力和 CLOS(对象系统)著称——在闭包尚未普及的年代,它遥遥领先。
评论者也澄清了一个常见误解:原始 Lisp 确实只有原子(atom)和列表(list)两种数据结构,但 Common Lisp 早已在标准库中集成了数组和哈希表。作者的编辑器架构,某种程度上正是在"重新发现"这些前人智慧。
可视化带来的调试红利
抛开范式之争,数据流架构最实际的好处得到了广泛认可:每个值都可被检视,撤销/重做能力天然内建。
当程序被表达为数据流图时,你可以直接观察数据如何在节点间流动、如何被逐步变换。这让 bug 定位变得直观——你能看到中间状态,而不是面对一堆黑盒对象调用。撤销/重做也从"需要额外设计的功能"变成了架构的自然产物。
数据流架构天然支持撤销/重做,其背后的理论基础是函数式编程中的不可变数据(Immutable Data)与持久化数据结构(Persistent Data Structure)。传统命令式程序的撤销/重做需要开发者手动实现Command模式,维护操作历史栈,处理复杂的状态回滚逻辑——这是游戏存档、文档编辑器等系统中著名的工程难题。而当程序被建模为纯函数节点的组合,每次计算产生新状态而非原地修改旧状态时,历史状态天然得以保留。Redux(前端状态管理库)和Elm架构正是将这一思想带入工业实践的典型案例:单向数据流加上不可变状态树,使时间旅行调试(Time-travel Debugging)成为可能,开发者可以随意在历史状态间穿梭检查。
是创新,还是重新发明堆内存?
当然,也有尖锐的批评声音。有人认为作者"重新发明了堆内存"——在堆内存之上又搭了一层用于分配和释放的"堆",而这正是编程语言几十年来努力向开发者隐藏的东西。
批评者所指向的,恰好是游戏引擎领域一个成熟的替代方案:实体组件系统(Entity-Component-System,ECS)。ECS将游戏对象拆解为纯数据的组件(Component)和处理这些数据的系统(System),以数据局部性(Data Locality)为核心设计目标,将同类组件的数据连续存储在内存中,充分利用CPU缓存,显著提升大规模对象的处理性能。Unity的DOTS(面向数据的技术栈)和Bevy游戏引擎(Rust)是其最新工业实现。ECS与作者的数据流图共享「数据与逻辑分离」的核心理念,区别在于ECS更强调缓存友好的内存布局,而数据流图更强调节点间依赖关系的显式化——两者并非对立,而是从不同维度回应「程序的本质是数据变换」这一命题。
这位批评者的观察也不无道理:如果用 JavaScript 实现,你大概会用 TypedArray 来充当这个"哈希内存";而 C++ 允许直接触碰底层堆内存,抽象层更薄,内存的字节表示可以作为连续区域被可视化,清晰呈现数据如何流动与转化。
结语:从第一性原理出发的价值
即便有"重新发明轮子"的质疑,社区整体对这个项目持欣赏态度。正如一位评论者所说:"无论什么能让你充满干劲,我都欣赏从第一性原理出发的构建方式。"
这个项目的真正价值,或许不在于发明了什么全新技术,而在于它促使我们重新审视那些被视为理所当然的架构习惯:
- 对象是否真的应该是组织程序的中心?
- 显式的数据流是否比隐式的对象调用更利于理解和调试?
- 那些编程语言精心隐藏的底层机制,主动暴露出来是否反而带来了透明性?
答案没有定论,但这正是从第一性原理重新思考架构的意义所在。
核心要点
相关推荐

开源权重模型之争:安全与开放如何平衡
深入分析开源权重模型的核心争论:模型权重公开发布带来透明度与创新,但也引发安全滥用风险。本文探讨分级发布、红队测试等折中方案,解读开源AI背后的行业博弈与治理挑战。

抱怨如何侵蚀你的心智:注意力自我强化效应解析
习惯性抱怨正在训练大脑发现更多负面信息,形成恶性循环。本文从注意力自我强化机制出发,解析抱怨的心理侵蚀过程,并提供主动管理注意力、跳出负面循环的实用方法。

Steam恶意软件溯源:比特币、Cookie和外卖订单如何锁定攻击者
一起Steam恶意软件案件中,调查人员通过比特币交易链、Google Cookie和Uber Eats外卖订单三条线索交叉验证,成功溯源攻击者真实身份。深入解析数字取证技术与匿名幻觉。