发展流程图

# 软件开发发展历程(一)

7 min read
Table of Contents

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

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

一、软件的起源:单体程序(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 <stdio.h>
#include "mathutils.h"
int main() {
printf("2 + 3 = %d\n", add(2, 3));
printf("4 * 5 = %d\n", 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),其他程序也可引用,避免重复打包,支持模块热更新:

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

模块化编译的好处总结

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

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

My avatar

感谢您阅读我的博客文章!可随意查看我的其他帖子或通过页脚中的社交链接与我联系😊


More Posts

# 软件开发发展历程(二)

10 min read

本系列第二篇,介绍 1980~1990 年代软件架构的第一次系统性演进。随着软件规模扩大与图形界面兴起,单纯的模块化已难以支撑需求,软件架构迈入了以“分层”为核心的系统性设计阶段。本文将梳理从模块化设计到 MVC 架构的演进逻辑,并介绍其中关键概念的优势与应用。

Read

Comments