从单体程序到模块化 —— 软件架构的起点
本系列将回顾软件架构的发展演进,本文是第一篇,讲述软件架构最初的形态:单体程序,以及模块化和结构化编程的兴起。
单体程序 → 结构化编程 → 模块化编程
一、软件的起源:单体程序(Monolithic Programs)
1.1 背景:最早的计算机程序
- 时间:1940s – 1960s
- 编程语言:汇编、机器码、早期 Fortran
- 软件运行在大型主机上,输入输出通过穿孔卡、控制台等方式交互
- 程序是一个不可分割的整体,数据与逻辑交织在一起
1.2 特点
- 所有功能写在一个文件或过程里
- 高度耦合,难以维护
- 无测试、无文档、无抽象
二、结构化编程的出现(Structured Programming)
2.1 背景
- 1968 年,Edsger W. Dijkstra 发表《Go To Statement Considered Harmful》
- 编程领域开始反思程序的可读性与可维护性
- 出现了流程控制结构(如 if、while、for)
2.2 核心思想
- 避免 goto,控制结构清晰
- 将程序拆分为可复用的模块(函数/子程序)
- 强调“自顶向下设计”和“逐步求精”
三、模块化编程的兴起(Modular Programming)
3.1 为什么需要模块化?
- 程序越来越大,团队开发出现
- 不同人需要负责不同模块
- 需要划分责任边界、隐藏实现细节
3.2 模块化设计原则
- 高内聚,低耦合
- 接口与实现分离
- 信息隐藏(Information Hiding)
3.3 代表性语言/系统
- C语言(1972年):支持模块化设计 🔗查看模块化编译案例
- Modula、Pascal:直接支持模块化语法
- Unix 操作系统:典型的模块化架构(工具组合、管道、标准输入输出)
四、单体架构的延续与局限性
4.1 单体架构的优点
- 开发简单、部署方便
- 适用于小型项目和初创团队
4.2 单体架构的缺点
- 代码难以维护、难以测试
- 无法支撑多人协作开发
- 任何改动都需要重新构建整个系统
五、为下一阶段埋下种子
- 模块化为后来的三层架构、面向对象、MVC 等奠定了基础
- 软件架构开始从“代码堆积”向“有组织的系统”迈进
📝 总结
阶段 | 特征 | 优点 | 局限 |
---|---|---|---|
单体程序 | 所有功能集中在一个整体中 | 简单、直接 | 耦合严重,维护困难 |
结构化编程 | 控制结构清晰,函数化编程 | 提高代码质量 | 缺乏模块边界 |
模块化编程 | 组织代码为模块,隐藏内部细节 | 易维护、易复用 | 需良好设计 |
🛠️ 模块化项目实战:用 C 语言构建一个小型工具集
在模块化编程理念中,程序被拆分为多个功能单一的“模块”,每个模块负责一部分逻辑,并通过头文件暴露接口。相比将所有代码写在一个 .c
文件里,模块化可以:
- 降低耦合度,便于协作开发;
- 提高可维护性;
- 支持增量构建和测试;
- 为大型项目的可扩展性打下基础。
示例:编写一个简单的数学库
假设我们要开发一个包含基本运算的工具库 mathutils
,它包含两个模块:
mathutils.h
:公共头文件,声明模块接口;add.c
:加法实现;mul.c
:乘法实现;main.c
:调用工具库的主程序。
目录结构如下:
project/
├── main.c
├── mathutils.h
├── add.c
└── mul.c
每个模块只负责一个功能,头文件 mathutils.h
如下:
// mathutils.h
#ifndef MATHUTILS_H
#define MATHUTILS_H
int add(int a, int b); int mul(int a, int b);
#endif
实现文件:
// add.c
#include "mathutils.h"
int add(int a, int b) {
return a + b;
}
// mul.c
#include "mathutils.h"
int mul(int a, int b) {
return a * b;
}
主程序:
// main.c
#include
int main() { printf("2 + 3 = %dn", add(2, 3)); printf("4 * 5 = %dn", mul(4, 5)); return 0; }
编译过程详解(基于 GCC)
模块化项目的编译可以分为以下几个步骤:
-
预处理(Preprocessing)
替换头文件中的声明和宏定义。
gcc -E main.c -o main.i
-
编译(Compilation)
将源文件转为汇编代码,再转为目标文件(
.o
)。gcc -c main.c add.c mul.c
-
静态链接(Static Linking)
将所有目标文件和标准库连接,生成可执行文件。
gcc main.o add.o mul.o -o app
-
运行:
./app
输出:2 + 3 = 5 4 * 5 = 20
Makefile 构建自动化
# Makefile
CC = gcc
OBJS = main.o add.o mul.o
TARGET = app
all: $(TARGET)
$(TARGET): $(OBJS) $(CC) -o $@ $^
%.o: %.c $(CC) -c $< -o $@
clean: rm -f $(OBJS) $(TARGET)
使用 make
命令即可自动完成编译和链接,修改单个文件也只会重新编译对应模块。
动态连接与可维护性
若将 add
和 mul
模块编译为共享库(.so
),其他程序也可引用,避免重复打包,支持模块热更新:
gcc -fPIC -c add.c mul.c
gcc -shared -o libmathutils.so add.o mul.o
gcc main.c -L. -lmathutils -o app
模块化编译的好处总结
- 解耦合: 每个模块职责明确、易于替换和扩展;
- 加快构建: 改动只需编译部分源文件;
- 便于测试: 可独立测试每个模块;
- 利于复用: 公共模块可以被多个项目引用。
📎 参考文档: 《编译器的工作过程》
发表回复