软件开发发展历程(一)

从单体程序到模块化 —— 软件架构的起点

本系列将回顾软件架构的发展演进,本文是第一篇,讲述软件架构最初的形态:单体程序,以及模块化和结构化编程的兴起。

单体程序 → 结构化编程 → 模块化编程


一、软件的起源:单体程序(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 #include "mathutils.h"

int main() { printf("2 + 3 = %dn", add(2, 3)); printf("4 * 5 = %dn", mul(4, 5)); return 0; }

编译过程详解(基于 GCC)

模块化项目的编译可以分为以下几个步骤:

  1. 预处理(Preprocessing) 替换头文件中的声明和宏定义。 gcc -E main.c -o main.i
  2. 编译(Compilation) 将源文件转为汇编代码,再转为目标文件(.o)。 gcc -c main.c add.c mul.c
  3. 静态链接(Static Linking) 将所有目标文件和标准库连接,生成可执行文件。 gcc main.o add.o mul.o -o app
  4. 运行: ./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 命令即可自动完成编译和链接,修改单个文件也只会重新编译对应模块。

动态连接与可维护性

若将 addmul 模块编译为共享库(.so),其他程序也可引用,避免重复打包,支持模块热更新:

gcc -fPIC -c add.c mul.c gcc -shared -o libmathutils.so add.o mul.o gcc main.c -L. -lmathutils -o app

模块化编译的好处总结

  • 解耦合: 每个模块职责明确、易于替换和扩展;
  • 加快构建: 改动只需编译部分源文件;
  • 便于测试: 可独立测试每个模块;
  • 利于复用: 公共模块可以被多个项目引用。

📎 参考文档: 《编译器的工作过程》


已发布

分类

来自

标签:

评论

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注