01-从指令到业务系统
从指令到业务系统——软件组织的第一条演化线
从一条指令开始,经过结构化、函数、文件、分层,最终长成一个业务系统——由模块组成,每个模块 = 数据结构 + 算法 + 接口。这是所有软件的第一条演化路径。
一、从一条指令开始
最开始,程序就是几条连续的指令:
1 | MOV EAX, 5 ; 把 5 放进寄存器 |
CPU 按地址顺序取指令,一条执行完再执行下一条。没有分支、没有循环、没有函数。
二、第一次拐弯:判断与跳转
要让程序根据情况选择不同处理,需要比较 + 条件跳转:
1 | CMP EAX, 10 |
程序第一次有了「逻辑」——根据输入/状态,走不同的路。这就是 if-else 的硬件本质。
三、重复与复用:循环和函数雏形
循环 = 往回跳
1 | MOV ECX, 0 |
函数 = 跳过去再跳回来
1 | CALL add_three ; 跳到函数,压栈返回地址 |
循环让程序能处理任意数量的数据;函数让一段逻辑能被多处调用。
四、C 语言:把硬件包装成人类语言
C 语言把汇编的跳转和栈操作,包装成三大结构:
1 | int main() { |
但 C 语言刚诞生时,所有代码都写在 main() 里。
五、main 膨胀:职责混乱的起点
功能增加后,main 开始膨胀——接收参数、读文件、多个业务逻辑、输出格式化全挤在一起。改任何一个都要在 main 里找半天。
职责混乱是拆分的第一个驱动力,但不是唯一一个。 拆分的驱动力有四种,后面的每一步拆分都是其中一种在起作用:
1 | 职责混乱 → 拆分(一件事做了太多事) |
接下来的章节,每一步拆分都会标注它由哪种驱动力逼出来。
六、第一个拆分:函数
把 main 里的职责拆成独立函数:
1 | int main(int argc, char** argv) { |
main 变成了流程描述——只说「做什么」,具体实现藏进函数。
七、第二个拆分:文件
驱动力:扩展。 一个文件装不下所有函数了,职责从一处分到多处。
按职责把函数分到不同文件,用 .h 声明接口、.c 放实现:
1 | main.c → main() + parse_args() |
.h 文件就是接口的第一次物理出现——只写「能做什么」,不写「怎么做」。
八、第一次分层:入口与业务分离
把「接收参数」和「具体处理」拆到不同层:
1 | // main.c(入口层)—— 只管「接收、调用、输出」 |
main.c 只管「怎么接收、调用谁、怎么输出」;业务层只管「具体怎么处理」。
驱动力:边界。 入口会因交互方式变化,业务会因业务需求变化——两者变化原因不同,所以拆开。所有拆分(拆函数、拆文件、拆系统)都沿边界切,边界的完整类型有独立的线展开。
九、入口的骨架:接受 → 解析 → 分发 → 调用
驱动力:扩展。 main.c 里「解析参数」和「知道调用哪个函数」混在一起,加第二个命令就爆炸。拆成四个阶段:
1 | Request req = parse(argc, argv); // 解析 |
1 | 接受 → 解析 → 分发 → 调用 → 业务 |
十、业务按功能分模块
驱动力:职责。 业务层里的 scan.c 既有「读进程列表」又有「扫描内存」,混在一起。按功能切:
1 | process_list.c / .h → 进程列表相关 |
十一、依赖方向:谁可以调用谁
驱动力:依赖。 分层后,依赖方向必须单向:
1 | 入口层 → 业务层 → 基础设施层 |
进一步,当业务层只依赖接口、不依赖具体实现时,就出现了依赖倒置:
1 | 业务层 → 接口 ← 基础设施层 |
依赖关系的完整演化有独立的线展开。
十二、模块的三要素:数据 + 算法 + 接口
模块拆出来后,内部至少包含三个不同的东西:
1 | typedef struct { uint8_t* bytes; size_t size; } MemoryBlock; // 数据结构 |
功能函数 = 数据结构 + 算法 + 库提供的接口。 这是模块的基本形态。
算法本身还要分类型——不同算法表达重点不同:步骤类用流程图,递归类用递归树,数学类用公式推导,状态转移类用状态转移表。形式的选取独立成线。
十三、面向对象:封装与类
驱动力:重复。 数据和操作它的函数散在各处,同样的状态处理重复出现。C++ 用类把数据和操作它的函数包在一起:
1 | class MemoryBlock { |
类带来的不止「封装」一件事,类的作用有五个:
1 | 1. 封装 谁能碰内部数据(private 挡住外部) |
前两个(封装、生命周期)最常被提到,但后三个同样重要——状态管理让对象有自己的数据,访问控制把接口和实现分开,模块边界让类成为组件的基本单元。
命名空间的作用和类不同:类区分组件边界,命名空间区分概念归属。
1 | network::Connection |
三个都叫 Connection,network:: 表示「网络领域的 Connection」。命名空间不只是封装库——它给名称建立一层概念边界,让 app::cli::、app::domain::、app::infrastructure:: 这些名称直接反映系统结构。
当「同一段逻辑,只是类型不同」的函数越写越多时,C++ 的模板把类型也变成参数——一个函数覆盖所有类型。这是「重复代码就封装」在类型维度上的延伸,独立成线。
十四、用类实现分层
有了类,分层从文件之间的分层变成了类之间的分层:
1 | Application(最外层组装) |
Application 只调 service_.listProcesses(),不碰 processList_——层级边界由类的访问控制保证。
十五、从模块到业务系统
驱动力:边界。 模块拆出来是零散的——每个模块只做自己的事,但没人把它们组织成一个整体。业务层需要作为一个系统被入口调用,于是模块被组织成业务系统。
这一步做四件事:
1 | 1. 建立模块层级 按业务领域和依赖方向把模块组织成层级 |
1 | 业务系统 |
关键:业务系统的组织单位是模块,模块 = 数据结构 + 算法 + 接口。 模块组成业务系统,业务系统作为一个整体被入口调用。
业务系统可以做成库:
1 | business/ |
入口系统(CLI/GUI/Server)调用它,不需要知道内部有哪些模块:
1 |
|
到这里,第一条演化线完成:从一条指令,经过函数、文件、分层、模块,最终长成一个业务系统。 业务系统由模块组成,模块由数据结构、算法、接口组成。
收束:从一条指令到业务系统
1 | 一条指令 顺序执行 |
每一步都是上一步「职责混乱 / 重复 / 边界 / 依赖」四种驱动力之一逼出来的。 理解了演化过程,就理解了业务系统为什么长成现在这个样子。
下一篇看第二条演化线:同样的路径,但终点是网络系统由组件组成。
