一、防御式编程基础:什么是防御式编程、核心思想、与常规编程的区别、适用场景
各位同学好,我是老李。在嵌入式这行摸爬滚打了十几年,今天咱们来聊聊防御式编程。
说实话,我刚入行那会儿,写代码的风格就是「能跑就行」。直到有一次,一个温度采集模块在现场跑着跑着,突然采集到-273.15℃的数据,系统直接死机了。嗯,从那以后,我才真正理解了什么叫「你永远不知道用户会怎么用你的代码」。
1.1 什么是防御式编程?
防御式编程,说白了就是——写代码时假设所有可能出错的地方,都会出错。
你想想看,一个传感器可能断线、一个指针可能为空、一个数组可能越界、一个定时器可能溢出。常规编程只考虑「正常情况」,而防御式编程把这些「异常情况」都当成常态来处理。
核心定义:防御式编程是一种编程范式,它要求程序员在编写代码时,主动预测并处理所有可能的异常、错误和非法输入,而不是假设外部环境或调用者会「按规矩来」。
我个人习惯把防御式编程比作「开车时的防御性驾驶」——你不光要自己开得好,还得防着别人突然变道、闯红灯、急刹车。代码也是一样,你不能假设调用者会传合法参数,不能假设硬件永远正常,不能假设内存永远够用。
1.2 核心思想
防御式编程的核心思想,可以归纳为四个字:先验后行。
什么意思?就是在执行任何操作之前,先验证前提条件是否满足。我把它拆成三条原则:
- 信任但验证——哪怕是你自己写的函数,调用时也要检查返回值
- 失败要优雅——系统崩溃不是最可怕的,可怕的是崩溃前产生了错误数据
- 边界即战场——所有边界条件(数组边界、时间边界、数值边界)都是最容易出问题的地方
举个例子,你写一个读取温度传感器的函数:
// 常规编程
int read_temperature(void) {
return *(volatile int*)0x4000_2000; // 直接读寄存器
}
// 防御式编程
int read_temperature(int *temp) {
if (temp == NULL) {
return -1; // 指针检查
}
if (hardware_is_ready() != 1) {
return -2; // 硬件状态检查
}
int value = *(volatile int*)0x4000_2000;
if (value < -40 || value > 125) {
return -3; // 数值范围检查
}
*temp = value;
return 0; // 成功
}
你看,防御式编程多了一堆检查,但正是这些检查,让系统在异常时不会「死得不明不白」。
1.3 与常规编程的区别
我整理了一个表格,方便大家对比:
| 对比维度 | 常规编程 | 防御式编程 |
|---|---|---|
| 假设前提 | 输入合法、硬件正常 | 一切皆可能出错 |
| 错误处理 | 出错了再修(事后) | 提前预防(事前) |
| 代码风格 | 简洁、直奔主题 | 多检查、多断言 |
| 调试难度 | 出问题难定位 | 出问题能快速定位 |
| 运行效率 | 高(无额外开销) | 略低(但可接受) |
| 维护成本 | 后期修bug成本高 | 前期写代码成本略高 |
这里我要多说一句:防御式编程不是让你把代码写得又臭又长。我见过有人每个函数开头写20行参数检查,那叫过度防御。真正的防御式编程,是在关键路径上做必要的检查,而不是无脑堆砌。
我的经验:嵌入式系统中,I/O操作、内存分配、中断处理、通信协议解析这四个地方,是防御式编程的「必检区」。其他地方可以适当放松。
1.4 适用场景
防御式编程不是万能的,它有自己的适用边界。我根据实际项目经验,把场景分成了三类:
✅ 强烈推荐使用
- 嵌入式裸机/RTOS系统——没有操作系统兜底,一个野指针就能让整个系统挂掉
- 安全关键系统——汽车电子、医疗设备、工业控制,出问题会出人命
- 通信协议栈——你永远不知道对端会发什么乱七八糟的数据包
- 驱动层代码——直接操作硬件,寄存器值可能因为电磁干扰而跳变
⚠️ 酌情使用
- 性能敏感的内循环——比如音频编解码、图像处理,每多一个if都可能掉帧
- 原型验证阶段——快速出活比防御更重要,但记得后面要补上
❌ 不建议过度使用
- 纯算法实现——比如排序、查找,输入由内部保证,不需要层层检查
- 一次性脚本——用完就扔的代码,没必要防御
避坑指南:我曾经在一个项目中,对每个函数都做了参数合法性检查,结果代码量膨胀了40%,而且因为检查逻辑本身也有bug,反而引入了新问题。后来我学乖了——只在模块的「入口函数」做检查,内部函数靠断言。
1.5 知识体系总览
下面这张图,把本章的核心内容串起来了。我建议你保存下来,后面每学一章都可以回来对照看看:
这张图把防御式编程的「是什么、为什么、怎么用」都串起来了。你仔细看,中心是防御式编程,往外发散出核心思想、与常规编程的区别、适用场景,再往下是三大原则和关键实践。
我个人建议你把这个框架记在脑子里。以后写代码的时候,遇到一个函数,先问自己三个问题:
- 这个函数的输入可能出什么问题?
- 如果出问题了,系统能不能优雅地处理?
- 边界条件我检查了吗?
养成这个习惯,你写的代码就会越来越「皮实」。嗯,这就是防御式编程的入门课。后面我们会一步步深入,讲断言、讲错误处理策略、讲如何在性能和防御之间找到平衡。
一句话总结:防御式编程不是让你变得「胆小」,而是让你变得「专业」。专业的程序员,不是不出bug,而是出了bug能快速定位、系统不会崩溃、数据不会损坏。
公众号:蓝海资料掘金营,微信deep3321