4、详细设计文档:模块接口设计、数据结构定义、算法伪代码规范、异常处理流程描述
各位同学,咱们今天聊聊详细设计文档怎么写。说实话,很多工程师觉得概要设计写完就差不多了,详细设计随便糊弄一下。我当年也这么想过,直到有一次项目联调,接口对不上,数据结构改了三版,差点把整个系统推倒重来。嗯,从那以后,我再也不敢轻视详细设计文档了。
详细设计文档,说白了就是把概要设计里的「黑盒子」拆开,把每个模块的里里外外都讲清楚。它要回答三个问题:模块怎么跟外界打交道?内部数据怎么组织?核心逻辑怎么跑?
4.1 模块接口设计
接口设计是详细设计的门面。我个人习惯,先画接口图,再写接口表。接口图用UML就够了,别整太花哨。
接口设计要包含以下要素:
- 接口名称:动词+名词,比如
GetSensorData、WriteConfig - 输入参数:参数名、类型、取值范围、是否必填
- 输出参数:返回值类型、成功/失败标志、数据内容
- 调用方式:同步/异步、阻塞/非阻塞、超时时间
- 前置条件:调用前模块必须处于什么状态
- 后置条件:调用后模块状态和数据的变化
举个例子,我之前做的一个音频处理模块,接口设计是这样的:
| 接口名称 | 输入参数 | 输出参数 | 调用方式 | 前置条件 | 后置条件 |
|---|---|---|---|---|---|
ProcessFrame |
frameData: int16[256], sampleRate: uint32 | status: enum(OK, ERROR), processedData: int16[256] | 同步阻塞,超时50ms | 模块已初始化,缓冲区非空 | 输出缓冲区写入新数据 |
SetGain |
channel: uint8(0-7), gain: float(-20.0 ~ +20.0) | status: enum(OK, INVALID_CHANNEL, INVALID_GAIN) | 同步,立即返回 | 模块已初始化 | 增益系数更新,下次处理生效 |
4.2 数据结构定义
数据结构是模块的骨架。我建议在详细设计阶段就把数据结构定死,不要等到编码时再拍脑袋。
数据结构定义要包含:
- 结构体/类定义:成员变量、类型、注释
- 全局变量:作用域、生命周期、访问权限
- 常量定义:宏定义或枚举,要有业务含义
- 缓冲区管理:大小、对齐方式、溢出保护
来看一个实际例子,这是我做雷达信号处理时定义的数据结构:
/* 雷达回波数据帧结构 */
typedef struct {
uint32_t frame_id; /* 帧序号,从0开始递增 */
uint64_t timestamp_us; /* 时间戳,微秒精度 */
uint16_t chirp_count; /* 本帧包含的chirp数量 */
int16_t i_data[256]; /* I路采样数据 */
int16_t q_data[256]; /* Q路采样数据 */
uint8_t quality; /* 信号质量,0-100 */
uint8_t reserved[3]; /* 保留字节,对齐到32位 */
} RadarFrame_t;
/* 模块状态枚举 */
typedef enum {
MODULE_IDLE = 0, /* 空闲状态 */
MODULE_INIT, /* 初始化中 */
MODULE_RUNNING, /* 正常运行 */
MODULE_ERROR, /* 错误状态 */
MODULE_CALIBRATING /* 校准中 */
} ModuleState_t;
4.3 算法伪代码规范
伪代码是沟通的桥梁。它不需要能编译通过,但要让人一眼看懂逻辑。我个人习惯用类C风格的伪代码,因为团队里大部分人都熟悉C语言。
伪代码的规范:
- 控制结构:用
if-else、for、while,不要用goto - 变量命名:有业务含义,比如
peakIndex而不是p - 注释:关键步骤要写「为什么这么做」,而不是「做了什么」
- 复杂度:标注时间复杂度和空间复杂度
来看一个峰值检测算法的伪代码:
/**
* 功能:检测信号中的峰值
* 输入:data[] - 输入信号数组,长度 N
* threshold - 峰值阈值
* 输出:peakIndices[] - 峰值位置数组
* peakCount - 检测到的峰值数量
* 复杂度:O(N),单次遍历
*/
function FindPeaks(data[], N, threshold):
peakCount = 0
for i = 1 to N-2: // 跳过首尾点
// 检查是否为局部最大值
if data[i] > data[i-1] AND data[i] > data[i+1]:
// 检查是否超过阈值
if data[i] > threshold:
peakIndices[peakCount] = i
peakCount = peakCount + 1
// 跳过相邻的峰值点,避免重复检测
i = i + 1
end if
end if
end for
return peakIndices, peakCount
end function
为什么要写伪代码? 说白了,伪代码是给人看的,不是给机器看的。它让你在写实际代码之前,先把逻辑理清楚。我见过太多人直接上手写代码,写到一半发现逻辑有漏洞,然后推倒重来。先写伪代码,相当于先搭个骨架,后面填肉就轻松多了。
4.4 异常处理流程描述
异常处理是详细设计里最容易忽略的部分。很多文档写了一大堆正常流程,异常情况就一句「按错误处理」。这怎么行?
异常处理要描述清楚:
- 异常类型:参数错误、资源不足、超时、数据校验失败、硬件故障
- 检测方式:返回值检查、异常捕获、看门狗、心跳检测
- 处理策略:重试、降级、复位、记录日志、上报告警
- 恢复流程:如何从异常状态回到正常状态
举个例子,我之前做的一个通信模块,异常处理流程是这样的:
| 异常场景 | 检测方式 | 处理策略 | 恢复流程 |
|---|---|---|---|
| 接收数据超时 | 定时器超时检查 | 重试3次,间隔100ms | 重试成功则继续,失败则上报链路中断 |
| 数据校验失败 | CRC32校验 | 丢弃该帧,请求重传 | 收到正确帧后继续处理 |
| 缓冲区溢出 | 写入前检查剩余空间 | 丢弃最旧数据,写入新数据 | 记录溢出次数,超过阈值则告警 |
| 硬件初始化失败 | 返回值检查 | 重试初始化3次 | 全部失败则进入安全模式,仅保留基本功能 |
最后,我建议在详细设计文档里加一个异常处理流程图。不用太复杂,用简单的流程图把异常发生后的决策路径画出来就行。比如:检测到异常 → 判断异常类型 → 执行对应策略 → 记录日志 → 尝试恢复 → 如果恢复失败则升级处理。这样,整个异常处理逻辑就一目了然了。
好了,详细设计文档的核心内容就这些。记住,接口要清晰,数据结构要严谨,伪代码要易懂,异常处理要全面。把这四点做好,你的详细设计文档就是一份高质量的工程交付物。