2. 软件架构基础:分层架构思想、模块化设计原则、接口与抽象

好,我们正式开始聊软件架构的基础。说实话,很多刚入行的工程师觉得架构这东西太虚,不如写代码实在。但我做了十几年嵌入式,越来越觉得——架构才是决定项目生死的关键。今天咱们就掰开揉碎,把分层、模块化、接口抽象这三块讲透。

2.1 分层架构思想:把复杂问题拆成“层”

分层架构,说白了就是“各司其职”。你想想看,一个域控制器里,有底层驱动、有操作系统、有中间件、有应用算法。如果全揉在一起,改一个地方就得动全身,那维护成本就太高了。

我个人习惯把域控制器的软件分成四层:

  • 硬件抽象层(HAL):屏蔽MCU/SOC的寄存器差异,提供统一的读写接口。
  • 操作系统层(OSAL):封装任务调度、中断管理、时间同步等OS服务。
  • 中间件层(Middleware):负责通信(SOME/IP、DDS)、诊断(UDS)、存储管理。
  • 应用层(Application):实现具体的控制逻辑,比如车身控制、自动驾驶决策。

我在项目中遇到过一个问题:某次客户要求换MCU,从英飞凌TC3xx换成瑞萨RH850。如果当初没有做HAL层,那底层驱动几乎要重写。但因为我们把HAL层抽象得足够干净,应用层一行代码都没改,只换了HAL的实现文件就搞定了。嗯,这就是分层的好处。

核心原则:上层依赖下层,但下层绝不能依赖上层。每一层只通过接口与相邻层通信,跨层调用是大忌。

2.2 模块化设计原则:高内聚、低耦合

模块化,听起来简单,做起来难。很多团队写着写着就变成了“意大利面条式代码”。我总结了几条实战中必须遵守的原则:

  1. 单一职责:一个模块只做一件事。比如CAN通信模块,就别把诊断逻辑也塞进去。
  2. 接口最小化:暴露给外部的接口越少越好。我见过一个模块有50多个公开函数,那跟没封装有什么区别?
  3. 依赖倒置:高层模块不应该依赖低层模块,两者都应该依赖抽象。这个后面会细讲。

举个例子,我曾经接手过一个项目,里面的“车身控制模块”既管车灯,又管雨刮,还管车窗。一个函数2000行,改个bug要三天。后来我把它拆成三个独立模块:LightCtrl、WiperCtrl、WindowCtrl。每个模块只暴露3-5个接口,代码量减少了40%,bug率直线下降。

我的小技巧:写模块之前,先画好接口图。如果两个模块之间的连线超过3条,说明耦合太高,需要重新设计。

2.3 接口与抽象:定义好“契约”

接口是什么?说白了就是一份契约。你承诺给我什么输入,我保证给你什么输出。至于内部怎么实现,调用者不需要关心。

在C语言里,我们通常用函数指针结构体来实现接口。比如:

// 定义一个CAN通信接口
typedef struct {
    int32_t (*Init)(uint32_t baudrate);
    int32_t (*Send)(uint32_t id, uint8_t* data, uint8_t len);
    int32_t (*Receive)(uint32_t* id, uint8_t* data, uint8_t* len);
} CanInterface_t;

// 具体实现:CAN1
CanInterface_t Can1_Interface = {
    .Init = Can1_Init,
    .Send = Can1_Send,
    .Receive = Can1_Receive
};

// 具体实现:CAN2
CanInterface_t Can2_Interface = {
    .Init = Can2_Init,
    .Send = Can2_Send,
    .Receive = Can2_Receive
};

这样,上层应用只需要调用 pCan->Send(),根本不用管底层是CAN1还是CAN2。我在项目中用这个模式做过一个“通信中间件”,支持CAN、LIN、以太网三种总线。上层算法完全不知道底层是什么,换总线就像换插件一样简单。

避坑指南:我曾经犯过一个错误——接口设计得太“灵活”。参数全是void*指针,结果调用者传错了类型,查bug查了两天。接口一定要强类型,宁可多写几个重载函数,也别偷懒用void*。

2.4 实战中的架构权衡

理论说完了,聊聊现实。分层和模块化不是银弹,有时候过度设计反而坏事。

场景 建议 原因
原型验证阶段 适当减少分层 快速迭代,验证功能为主
量产项目 严格分层+模块化 可维护性、可测试性优先
性能敏感模块 允许跨层优化 比如中断处理,可以绕过中间层直接调用HAL

我个人习惯是:先按理想架构设计,再根据性能瓶颈做局部优化。但优化一定要加注释,说明为什么打破分层,否则后来者会一脸懵。

2.5 小结

嗯,这一章内容不少。总结下来就三句话:

  • 分层让系统结构清晰,每层只关心自己的事。
  • 模块化让代码可复用、可替换,高内聚低耦合是目标。
  • 接口与抽象是模块之间的“契约”,定义好了,协作就顺畅了。

下一章我们会聊具体的域控制器架构模式,比如中央计算平台、区域控制器等。到时候你会发现,今天讲的这些基础,全都会用上。