知识卡片
面向接口的可替代性,与DRY条件编译反例:用HAL接口取代满天飞的#ifdef
内容
除了[[操作系统抽象层OSAL让软件脱离目标操作系统迁移RTOS的痛苦对比]]和HAL/PAL这些针对性分层,整洁嵌入式架构还该应用本书通用的面向接口、可替代性设计原则——分层架构的理念本身就建立在面向接口编程之上:模块之间只要以接口形式交互,就能把一个服务实现替换成另一个(比如自己写的小型printf只要接口和标准printf一致,就能互相替换)。嵌入式领域常用头文件充当接口定义,但要小心控制头文件内容——只放函数声明和函数需要的结构体名、常量,绝不能把只有具体实现代码才需要的数据结构、常量、类型定义塞进接口头文件;这不只是架构洁癖问题,混入这些内容会引入意想不到的依赖关系,实现细节的可见范围一定要被严格控制,因为这些细节注定会变,暴露给外部代码的细节越少,未来需要跟着变更的地方就越少——由整洁嵌入式架构构建的系统应该每一层都可测试,因为模块间靠接口通信,每个接口都是脱离目标平台测试的替换点。一个常被忽视的可替代性反例是嵌入式C/C++项目里泛滥的条件性编译命令:作者曾在一个电信应用里见过#ifdef BOARD_V2出现了几千次——这明显违反了DRY(不要重复自己)原则,单独出现一次没什么,出现几千次就是严重问题了。解法同样是HAL:把硬件类型收敛成HAL内部的一个实现细节,系统改用HAL提供的一系列接口而非条件编译语句,这样就能靠链接器或运行时加载器把软件和具体硬件结合起来,不必让每一处业务代码都散布对具体板型的判断。
参考来源
- 位置:《架构整洁之道》第29章《整洁的嵌入式架构》"面向接口编程与可替代性""DRY条件性编译命令""本章小结"(源文件:_epub-src/text/part0014_split_014.html)
- 结论依据:原文说明分层架构建立在面向接口编程之上、头文件应只含声明以避免意外依赖,并用#ifdef BOARD_V2在真实电信项目中出现几千次的反例说明用HAL接口取代条件编译能解决这个DRY违反问题,直接支撑本卡片结论。
- 原始内容:分层架构的理念是基于接口编程的理念来设计的……不要在定义接口的头文件中包含只有具体实现代码才需要的数据结构、常量以及类型定义……我曾经遇到过#ifdef BOARD_V2这条语句在一个电信应用程序中出现了几千次的情况……使用硬件抽象层如何?这样的话,硬件类型就只是HAL中的一个实现细节了。