程序 = 算法 + 数据结构算法 = 逻辑 + 控制两个老公式,一个来自 Niklaus Wirth 1976 年的同名书,一个来自 Robert Kowalski 1979 年的论文 Algorithm = Logic + Control。能流传五十年,是因为它们抓住了一个非常稳定的洞察:写程序本质上就是「描述问题」+「安排资源去解决问题」这两件事的组合。
由此可以推出一把 code review 的尺子——好代码的关键,是把 Data、Logic、Control 三者干净地分开。不纠结命名和行数,而是反过来问:
- 这段里哪部分是业务规则(Logic)?
- 哪部分是流程编排(Control)?
- 数据结构选得对吗?
比起 ESLint 能管的那一层,这是更深一层的提问。Linus 那句「好的程序员关心数据结构和它们之间的关系」也是真话——把”代码”等同于”流程语句”、对数据建模没感觉,是一个真实存在的盲点。
这个框架的局限
但只用这把尺子是不够的:
一、它是 1976 年的世界观。 那个年代的程序大致是”输入 → 算法 → 输出”。今天的代码——尤其是前端——大量内容根本不在这个公式里:状态管理、副作用编排、异步并发、错误处理、可观测性、用户交互的时序。一个 React 组件的复杂度往往不在算法或数据结构,而在”什么时候 re-render、副作用怎么 cleanup、状态归属在哪一层”。把这些都塞进 Control 这个桶里,桶大到失去了指导意义。
二、Logic / Control 完美分离,在真实业务里很难。 表单校验用 metadata 描述字段规则是教科书级好例子,但它”好”的前提是问题足够规整。一旦遇到「字段 A 改变要触发 B 的异步校验,B 失败时要回滚 C」这种东西,控制就会反过来渗透进逻辑,纯声明式描述不下来。
真正的工程判断不在”要不要分离”,而在”这次值不值得分离”——表驱动的代价是抽象层,抽象层有它自己的维护成本。
三、它漏掉了”好代码”里很大一块:边界与命名。 模块边界划在哪、什么东西放一起、抽象的名字起得准不准——这些不是 ESLint 能管的,也不属于 Logic/Control/Data 任何一个,但却是大型代码库可读性的真正决定因素。
四、GoF 23 模式有点 2005 年的味道。 在前端实战里能用上的不多,现代前端的复用单位是 hook、组合、reactive primitive。当作历史背景知识可以,当作”必备清单”对着学,性价比不高。
在 vibe coding 时代,它反而更值得读
Vibe coding 的常态是:你描述意图 → AI 写出一坨能跑的代码 → 你扫一眼觉得 OK → commit。问题是 AI 生成的代码有一个非常稳定的特征:Logic、Control、Data 高度耦合。因为它的训练语料里大部分人写的就是这样。
它会写出一个 200 行的 handleSubmit,里面既有业务规则、又有 DOM 操作、又有 fetch、又有状态更新,全部缠在一起。表面读起来通顺,结构上很糟。
所以在 vibe coding 时代,核心能力从”能不能写出来”变成了”能不能识别出 AI 写得不好的地方”。这套框架在这里就是一把还算好用的尺子:
- 问”这段里哪部分是业务规则,哪部分是流程编排”——AI 经常会把两者揉在一个函数里,你可以让它拆;
- 问”用什么数据结构最合适”——这是 AI 的弱项,因为它倾向于沿用 prompt 里给的形状,很少主动重构数据模型。你如果先把数据结构想清楚再让它写,产出质量会高一个量级;
- 问”控制流能不能声明化”——表驱动、配置驱动这类思路,AI 不会主动用,但你点出来它能写得很好。
换句话说,vibe coding 没有让”会写代码”变得不重要,而是让”知道什么是好代码”变得更重要。
手敲代码的时候,结构问题会被打字的物理成本逼着你想清楚;让 AI 写的时候没有这个摩擦,所以你必须主动带着结构感去 review,否则烂代码积累的速度会比以前快十倍。
一句话收尾
把数据 / 逻辑 / 控制三分当成最低标准而不是全部清单——它是底盘,真实工程里的”好代码”还要叠上边界设计、命名、状态管理、错误处理、可测试性这些东西。
在 vibe coding 时代,这个底盘的价值不降反升。因为它正好是 AI 最容易做错、而你最容易看漏的地方。