七个分析维度——分析任何程序的完整方法

分析一个程序,不能只画一张「分层架构图」。一个真正完整的工程分析,往往是把结构、调用链、启动链、生命周期、数据流、业务流、状态变化七张图叠起来。这一条线是「分析任何程序」的完整方法论:五个观察维度扩展为七个,用 HTTP 服务器作为贯穿例子(它恰好把七个维度全部暴露得很清楚),并给出最终原则。


一、五个观察维度(后扩展为七个)

把「软件结构」与「软件运行」两套视角合起来,可以整理成:

1
2
3
4
5
1. 代码组装位置
2. 调用链
3. 启动流
4. 生命周期
5. 运行流

分层回答「代码怎么组织」;调用链回答「谁调用谁」;启动流回答「怎么起来」;生命周期回答「怎么存在」;运行流回答「运行时怎么变化」。


二、HTTP 不只是「通信协议」

更准确地说:

1
2
3
HTTP
├── 通信协议
└── 应用层请求入口
1
2
客户端 → HTTP Request → HTTP Server → HTTP Parser → Router → Handler
→ Service → Domain/Algorithm → Database

POST /login 是业务入口,HTTP 是承载这个入口的协议。

启动流 vs 运行流(完全不同)

1
2
3
4
5
6
7
8
9
启动流:
main → 创建 Server → 创建 HTTP → 创建 Router → 注册 Handler
→ 绑定端口 → 启动 EventLoop → Server Running
(程序如何从不存在进入运行状态)

运行流:
HTTP Request → Socket → HTTP Parser → Router → Handler
→ Service → Domain → Database → Response → HTTP Response
(程序运行以后,一次业务请求如何流动)

生命周期是另一条线

1
2
Server:创建 → 初始化 → 启动 → 运行 → 暂停/恢复 → 停止 → 释放
HTTP Connection:Accept → Connected → Read → Parse → Request → Response → Keep-Alive/Close

一个程序实际上同时存在多个嵌套生命周期

代码组装位置回答「谁创建谁」

1
main → Server(HttpServer:Socket/EventLoop/ConnectionManager;Router;Service;Database)

不一定意味着 Server 必须拥有所有东西,而是说明 Server 这个运行子系统的组装边界在哪里。

五维总图

1
2
3
【结构】代码组装位置 / 组件关系 → 系统组成
【行为】启动流(初始化)→ 运行流(请求处理)→ 生命周期(状态变化)
【调用】调用链

而 HTTP 网络程序恰好把这些全部暴露得非常清楚:HTTP 是通信协议;HTTP Server 是协议入口;Router/Handler 是请求分发入口;Service 是业务入口——「入口」也应该分层理解。


三、Socket 的创建流与协议使用

Socket 中 TCP/UDP 的图示至少包含两条不同的东西:

1. 创建/建立流——生命周期

1
2
TCP 服务器:socket() → bind() → listen() → accept() → connected → send/recv → close()
TCP 客户端:socket() → connect() → connected → send/recv → close()

2. 协议使用——运行流

1
2
发送:应用数据 → send() → TCP → IP → 网络
接收:网络 → IP → TCP → recv() → 应用数据

UDP 完全不同

1
socket() → bind() → sendto()/recvfrom() → close()

UDP 的「创建流」很短,而「协议使用」直接发生在数据报收发过程中。

Socket 学习图重新理解

1
2
3
4
5
Socket
├── 创建流 socket / bind / listen / accept / connect
├── 协议使用 TCP(send/recv,connection-oriented)
│ UDP(sendto/recvfrom,datagram-oriented)
└── 生命周期 创建 → 运行 → 关闭

Socket API 图并不只是「函数怎么调用」,它展示的是:资源如何创建 → 协议如何进入工作状态 → 数据如何运行 → 资源如何结束。 TCP 与 UDP 最核心的区别可以从生命周期运行流两个角度同时看,而不只是死记 connect() 有没有。


四、三种「流」:数据流 / 协议流 / 业务流

「数据流」和「运行流」不能混。至少分成三种流:

① 数据流:数据怎么走

1
业务数据 → HTTP → TCP → IP → 网卡 → 网络 → 网卡 → IP → TCP → HTTP → 业务数据

② 通信协议流:协议怎么工作

1
2
TCP:socket → bind → listen → accept → 连接建立 → send/recv → close
HTTP:建立 TCP → 发送 HTTP Request → 接收 HTTP Response → Keep-Alive/Close

③ 业务运行流:一次业务怎么完成

1
2
3
4
用户点击登录 → LoginView → LoginViewModel → AuthService → HttpClient
→ HTTP Request → TCP → 网络 → HTTP Server → Router → LoginHandler
→ AuthService → Database → 返回结果 → HTTP Response → HttpClient
→ LoginViewModel → View

数据流是「东西怎么流动」;运行流是「系统行为怎么发生」;业务流是「业务目标怎么完成」。 这几个概念分开之后,网络程序、驱动、游戏、GUI、操作系统都可以用同一套方法分析。


五、启动链也有层级

不是「有启动链的东西都必须由 main 调用」,而是每个启动层的上一级负责调用它的启动链;main 是最顶层启动链的调用者

1
2
3
4
main
├── UI.start() 创建窗口 / 创建控件 / 建立 UI 连接
├── ViewModel.start() 初始化状态 / 建立业务连接
└── Server.start() 初始化 Socket / 初始化 HTTP / 注册 Router / 开始监听

如果 ViewModel 是 UI 的组成部分:

1
main → Client.start() → UI.start() → View + ViewModel.start() → Server.start()

main 的核心职责

1
main = 组装顶层子系统 + 启动顶层子系统 + 进入顶层运行循环
1
2
3
4
5
6
7
8
9
10
11
12
int main(int argc, char* argv[])
{
QApplication app(argc, argv);

UI ui;
Server server;

ui.start();
server.start();

return app.exec();
}

main 不负责创建 Button/Socket/Router/Service/Repository、注册 HTTP Handler——这些分别属于更低一级的启动/组装边界。

1
顶层启动 → 子系统启动 → 模块启动 → 组件初始化(上一级调用下一级的启动链)

main 是系统顶层组装与启动的入口;子系统负责组装和启动自己的下属;组件负责自己的初始化和运行。 这样代码组装层级、启动链层级、生命周期层级就能对齐起来,而不是全部堆在 main()。


六、有共享状态的 Service:Session 统一拥有共享数据

Service 分成两种情况:无状态 Service(不需要组装,直接做成接口/函数集合)和有共享状态的 Service(必须存在一个统一的状态拥有者)。

方案一:Session 统一拥有共享数据

1
2
3
4
5
6
7
8
9
10
class Session
{
public:
void setToken(const QString& token) { token_ = token; }
const QString& token() const { return token_; }
bool loggedIn() const { return !token_.isEmpty(); }

private:
QString token_;
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
class AuthService
{
public:
explicit AuthService(Session& session) : session_(session) {}

bool login(const QString& username, const QString& password)
{
QString token = requestLogin(username, password);
if (token.isEmpty())
return false;

session_.setToken(token);
return true;
}

private:
Session& session_;
};

class UserService
{
public:
explicit UserService(Session& session) : session_(session) {}

void loadUser()
{
QString token = session_.token(); // 使用 token 请求用户信息
}

private:
Session& session_;
};

三个 Service 都没有自己保存 token:AuthService / UserService / OrderService → Session共享数据只有一个来源

谁创建 Session?——组装边界

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class ClientServices
{
public:
ClientServices()
: auth_(session_), user_(session_), order_(session_) {}

AuthService& auth() { return auth_; }
UserService& user() { return user_; }
OrderService& order(){ return order_; }

private:
Session session_;
AuthService auth_;
UserService user_;
OrderService order_;
};

Session + AuthService + UserService + OrderService → ClientServices,ClientServices 就是这个 Service 子系统的组装边界(组件没有组织 → 增加一个文件统一组织 → 从组件变成框架)。

ViewModel 不需要知道 Session

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class LoginViewModel
{
public:
explicit LoginViewModel(ClientServices& services)
: services_(services) {}

void login(const QString& username, const QString& password)
{
bool success = services_.auth().login(username, password);
if (success) { /* 更新自己的状态 */ }
}

private:
ClientServices& services_;
};
1
2
3
4
5
6
7
8
9
10
11
int main(int argc, char* argv[])
{
QApplication app(argc, argv);

ClientServices services;
LoginViewModel loginVM(services);
UserViewModel userVM(services);
OrderViewModel orderVM(services);
// UI...
return app.exec();
}

main 没有创建 Session/AuthService/UserService/OrderService/HttpClient/Repository——这些都属于 ClientServices 内部的组装。

无状态 Service 的例子

1
2
3
4
5
6
7
8
9
10
class PasswordValidator
{
public:
static bool valid(const QString& password)
{
return password.length() >= 8;
}
};

PasswordValidator::valid(password); // 不需要实例化

最终规则

1
2
3
4
5
6
7
8
9
Service
├── 无状态 → 函数 / 静态接口
└── 有状态
├── 状态 → Session / Context / State
├── Service
└── 统一组装器

共享状态不要散落到各个 Service 中(错误:每个 Service 各存一个 token)
而应该(正确):Session ← Auth / User / Order

无状态能力 → 接口;有状态能力 → 状态对象;多个有状态组件共享 → 统一组织;组织后的整体 → 作为上一级接口提供给 ViewModel。 这就是「什么时候需要组件、什么时候需要接口、什么时候需要组装成框架」的具体判断标准。


七、C 风格函数接口:共享状态用 Context 显式化

如果共享状态存在,组装成对象/子系统通常更自然;但坚持 C 风格的函数接口也完全可以处理。

把共享状态从「对象成员」改成「显式的 Context/State 数据」,函数通过 Context 访问它。

C 写法:Context

1
2
3
4
5
typedef struct {
char token[256];
int user_id;
int logged_in;
} Session;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
int auth_login(Session *session, const char *username, const char *password)
{
// HTTP 请求……
if (/* 登录成功 */) {
strcpy(session->token, "token123");
session->user_id = 1001;
session->logged_in = 1;
return 1;
}
return 0;
}

int user_load(Session *session)
{
if (!session->logged_in) return 0;
// 使用 session->token 发 HTTP 请求……
return 1;
}

函数本身没有状态,但 Context 是共享状态

整个程序 Context

1
2
3
4
5
6
typedef struct {
Session session;
Config config;
Cache cache;
Database database;
} AppContext;
1
2
3
4
5
6
7
8
9
10
11
12
int main(void)
{
AppContext app = {0};

app_init(&app);

auth_login(&app, "alice", "123456");
user_load(&app);
order_load(&app);

app_shutdown(&app);
}

C 和 C++ 是同一个东西

1
2
3
4
C++:Service → 成员/引用 → State
C: Function → 参数 → State

C 没有类,并不意味着没有组件组装。只是把「对象之间的关系」显式变成了结构体 + 指针 + 函数。

全局变量的代价

1
static Session g_session;

参数没了,但依赖关系隐藏了——看到 load_user(); 并不知道它依赖 g_session;而 load_user(&session); 一眼就知道。

1
2
3
全局变量:最少参数,但依赖隐藏
Context* :多一个参数,但依赖显式
类成员 :参数少 + 状态封装

对象封装会隐藏共享状态的传递;纯函数接口会把依赖显式暴露出来。C 的函数式 API 并没有消灭「组件—状态—组装」思想,只是没有 C++ 的对象语法而已:数据结构 = 状态,函数 = 能力,Context = 组件之间的共享连接,初始化 = 组装,函数调用 = 运行,shutdown = 生命周期结束。


八、共享指针:不复制,传同一个地址/句柄

1
2
3
4
5
6
7
8
9
10
11
12
13
typedef struct {
int value;
} SharedData;

void service_a(SharedData *data) { data->value += 10; }
void service_b(SharedData *data) { printf("%d\n", data->value); }

int main(void)
{
SharedData data = { .value = 100 };
service_a(&data);
service_b(&data); // 看到修改后的值
}

多个共享指针时用 Context:

1
2
3
4
5
6
7
8
9
10
typedef struct {
Session *session;
SharedBuffer *buffer;
Database *database;
Config *config;
} AppContext;

void login(AppContext *ctx);
void load_user(AppContext *ctx);
void send_data(AppContext *ctx);

层层调用时指针沿调用链传递:handler → service → repository → database(没有创建新的共享数据,只是把同一个 AppContext* 传下去)。

实用判断

1
2
3
4
共享一个数据         → 直接传指针
共享几个相关数据 → 传一个结构体指针
大量组件共享运行环境 → Context
Context + 函数集合 + 初始化/销毁 → 逐渐形成一个 C 风格的子系统

C 语言虽然没有 C++ 的对象,但一样可以做「组件 + 共享状态 + 组装 + 生命周期」:只是把隐含在对象里的东西显式写成了 struct + pointer + function。

对象 vs 函数接口的核心差异

1
2
3
4
5
6
7
C++:class Service { int value_; void a() { value_++; } void b() { value_++; } };
编译器通过隐藏的 this 让成员函数访问状态,不需要写参数

C: void service_a(Context* ctx); void service_b(Context* ctx);
共享状态必须作为显式参数传递

对象封装隐藏共享状态的传递;纯函数接口把依赖显式暴露出来。

有共享数据的 Service 更适合组装成对象,因为对象本质上就是在做一件事:把经常共同传递的数据和操作这些数据的函数绑定在一起,从而把重复参数变成对象状态。


九、服务器的完整形态

1
2
3
4
5
6
7
8
9
main → 实例化/启动 → Server
Server
├── Protocol HTTP / WebSocket / RPC
├── Network Socket / Connection / EventLoop / Buffer
├── Parser
├── Dispatcher / Router
└── Handler / Calling Layer

Business Library(Service + Domain + Algorithm + Business State)

服务器负责「解析、分发、调用」,业务负责「完成业务」。

1
2
POST /login → HTTP Request → Server → HTTP Parser → Router → LoginHandler
→ LoginService → Domain/Algorithm → 返回业务结果 → Handler → HTTP Response

业务做成静态库

1
2
3
4
5
6
business/
├── include/ LoginService.h / UserService.h / OrderService.h
├── src/ LoginService.cpp / UserService.cpp / OrderService.cpp
├── domain/
├── algorithm/
└── CMakeLists.txt → libbusiness.a
1
2
3
4
5
6
7
#include <business/LoginService.h>

void LoginHandler::handle(const Request& request)
{
auto result = LoginService::login(request.username(), request.password());
// 转换成 HTTP Response
}

服务器本身不需要知道登录业务内部怎么实现。

服务器不是「所有业务的集合」,而是一个业务执行平台/通信运行系统;业务静态库则是被这个平台调用的业务能力。 服务器完整版:通信基础设施 + 协议处理 + 请求解析 + 请求分发 + 业务调用 + 响应生成 + 生命周期管理。


十、Network ≠ OSI 会话层;生命周期 ≠ 代码结构

Network 不等于「会话层」

Server → Network(Socket/Connection/EventLoop/Buffer)程序内部的网络运行子系统,不是 OSI 七层模型中的某一层,它可能跨越多个 OSI/TCP-IP 层次:

1
应用程序 → HTTP(应用层协议)→ TCP/UDP(传输层)→ IP(网络层)→ Socket API(编程接口)

会话层(Session Layer)是 OSI 第 5 层,负责会话建立、维护、终止等概念。Network ≠ Session Layer。但产生这个联想是有道理的:Connection 的建立/运行/关闭生命周期确实具有「会话」的特征。

生命周期是不是「封装组件形成框架的代码」?

需要区分:

生命周期不是代码组织结构本身,而是被代码实现出来的一条时间维度。

1
2
结构维度:组件 → 组装 → Server(Socket + Router + EventLoop → Server)
时间维度:创建 → 初始化 → 启动 → 运行 → 停止 → 销毁
1
2
3
4
5
6
7
8
void Server::start()
{
socket_.open();
router_.initialize();
eventLoop_.start();

running_ = true;
}

这里同时发生两件事:结构(Server 由 Socket/Router/EventLoop 组成)和生命周期(Server.start() 组织各组件打开/初始化/启动)。

框架不仅封装组件,还负责组织这些组件的生命周期、调用关系和运行流程。

1
2
3
4
5
6
7
组件(Socket/Connection/Buffer/EventLoop)
↓ 结构组装
Network 子系统(初始化/启动/运行/停止 ← 生命周期)

Server(HTTP Protocol + Parser + Router + Handler)

业务 Service

「架构结构」描述空间关系;「生命周期」描述时间关系;「调用链」描述控制关系;「数据流」描述数据关系;「业务流」描述业务行为关系。 一个真正完整的工程分析,往往就是把这几张图叠起来,而不是只画一张「分层架构图」。


十一、最终原则

以后分析一个程序,可以分别问:

1
2
3
4
5
6
7
- 结构:有哪些组件、子系统,在哪里组装?
- 调用链:谁调用谁?
- 启动链:从 main 到各子系统如何启动?
- 生命周期:创建 → 初始化 → 运行 → 停止 → 销毁。
- 数据流:数据经过哪些对象、协议、层?
- 业务流:一次业务从入口到完成经过什么路径?
- 状态变化:系统/对象的状态如何变化?

(共七个维度:结构、调用链、启动链、生命周期、数据流、业务流、状态变化。)

组件负责能力,组装负责关系,框架负责生命周期和运行组织,接口负责对外能力;上一级只组装下一级的整体。

这套思路拿去看 GUI、服务器、游戏引擎、嵌入式程序、Linux 子系统,基本都能套上。


十二、这一条线的位置

1
2
3
4
5
6
7
8
9
10
11
12
设计(主题):
01 设计起点(先设计什么)
02 系统设计(谁来设计)
03 设计表示(怎么表示)
04 文档组织(怎么落成文档)
05 通信程序的设计顺序(设计线实例)
06 七个分析维度(本线:怎么分析一个程序——设计的逆向)

七条流 / 行为流:
七个维度的「启动链、数据流、业务流、状态变化」与行为十条流互补:
行为流讲行为怎么发生(控制/错误/反馈…),七个维度讲程序怎么被分析
(启动链 → 启动流;业务流 → 业务流;状态变化 → 状态流)

本线与 05 的关系:05 讲通信程序「按什么顺序设计」(协议 → 组件 → 接口 → 实现 → 框架化 → 组装);本线讲分析任何程序「从哪些维度看」(结构/调用链/启动链/生命周期/数据流/业务流/状态变化)。设计是正向展开,分析是逆向还原,两者是同一套结构的两个方向。


收束

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
七个分析维度:
结构(在哪里组装)→ 调用链(谁调用谁)→ 启动链(怎么起来)
→ 生命周期(怎么存在)→ 数据流(数据怎么走)
→ 业务流(业务怎么完成)→ 状态变化(状态怎么变)

三种流不能混:
数据流(东西怎么流动)/ 协议流(协议怎么工作)/ 业务流(业务怎么完成)

共享状态的判断标准:
无状态 → 接口(函数/静态接口)
有状态 → 状态对象(Session/Context)+ 统一组装器(组装边界)
共享多个 → Context 显式化(C)或类封装(C++)

C 与 C++ 是同一个东西:
数据结构 = 状态,函数 = 能力,Context = 共享连接,
初始化 = 组装,函数调用 = 运行,shutdown = 生命周期结束

最终原则:
组件负责能力,组装负责关系,框架负责生命周期和运行组织,
接口负责对外能力;上一级只组装下一级的整体

七个分析维度 = 分析任何程序的完整方法:结构/调用链/启动链/生命周期/数据流/业务流/状态变化七张图叠起来,才能看到程序的全貌。 它适用于 GUI、服务器、游戏引擎、嵌入式、Linux 子系统——是「设计的逆向」。