|

关注公众号,回复“入门资料”获取单片机入门到高级开挂教程
开发板带你入门,我们带你飞
文 | 无际(微信:2777492857)
全文约3036字,阅读大约需要 10 分钟
说起“if/else屎山代码”,相信很多工程师都有一肚子苦水要吐,包括我。
以前做过一个项目,客户老是改需求,那会水平有限,全是if/else,没有任何架构可言,每次改这里,那里就出问题,让我直呼救命!这啥玩意儿??
i1qb1x43qg2640519036.png
屎山代码不仅是个技术问题,更是心理折磨!维护难、debug苦、怕改需求。
身为工程师,必须找到优雅的办法,把代码变清晰,把"屎山"变"金山"。
经过这么多年产品的锤炼,我钻研出了一些招数,也想分享给你,别再走我的老路。
一、"屎山"代码的起源
咱们先得搞清楚,这堆“屎山”到底是怎么堆起来的。说白了,就是代码里的if-else用得太多太乱。
单片机开发里,咱们经常要处理硬件状态、传感器数据,还有各种业务逻辑。比如,温度高了开风扇,湿度大了开除湿器,逻辑稍微复杂一点,if-else就一层套一层,代码瞬间变成一团乱麻。
想象一下,你打开一个项目文件,屏幕上全是if-else-if-else,分支多得数不过来,改一行代码得小心翼翼,生怕哪里崩了。
调试的时候,bug不知道藏在哪个角落,找起来费时费力,如果领导催得急,心态直接爆炸。
这种代码不仅让人效率低下,还严重影响心情。你也不想每天对着这堆东西,一边debug一边在心里吐槽自己吧?
其实,这种问题不只是单片机开发里有,任何需要处理多条件判断的场景都可能遇到。但在单片机里尤其突出,因为资源有限、硬件交互频繁,一个不留神,代码就失控了。
二、用状态机把逻辑理顺
第一个招数,叫状态机。听起来有点高级,其实没那么玄乎。
简单来说,就是把系统的状态定义清楚,然后根据当前状态和输入,决定下一步干什么。
这种方法特别适合有明确状态转换的场景,比如温控系统、按键处理、通信协议啥的。
假设咱们在做一个温控系统,要根据温度和湿度控制风扇和加热器。传统的if-else写法可能是这样:
if (temperature > 30) { if (humidity > 60) { turnOnFan(); turnOnHeater(); } else { turnOnFan(); }} else if (temperature > 25) { if (humidity > 50) { turnOnFan(); }} else { turnOffFan(); turnOffHeater();}
这代码看着就晕,嵌套得让人眼花缭乱。咱们用状态机改一下:
// 定义状态枚举typedef enum { HIGH_TEMP_HIGH_HUM, // 高温高湿 HIGH_TEMP_LOW_HUM, // 高温低湿 NORMAL // 正常状态} SystemState;// 全局状态变量volatile SystemState currentState = NORMAL;// 硬件控制函数(单片机风格)void turnOnFan() { PORTB |= (1 // 开启风扇}void turnOffFan() { PORTB &= ~(1 // 关闭风扇}void turnOnHeater() { PORTB |= (1 // 开启加热器}void turnOffHeater() { PORTB &= ~(1 // 关闭加热器}// 更新状态void updateState(uint8_t temperature, uint8_t humidity) { if (temperature > 30 && humidity > 60) { currentState = HIGH_TEMP_HIGH_HUM; } else if (temperature > 25) { currentState = HIGH_TEMP_LOW_HUM; } else { currentState = NORMAL; }}// 系统控制void controlSystem() { switch (currentState) { case HIGH_TEMP_HIGH_HUM: turnOnFan(); turnOnHeater(); break; case HIGH_TEMP_LOW_HUM: turnOnFan(); turnOffHeater(); break; case NORMAL: turnOffFan(); turnOffHeater(); break; }}// 主函数int main(void) { DDRB |= (1 1 // 配置PB0和PB1为输出 uint8_t temp = 0, hum = 0; while (1) { temp = readTemperature(); // 假设的温度读取函数 hum = readHumidity(); // 假设的湿度读取函数 updateState(temp, hum); controlSystem(); }}
你看,改完之后是不是清爽多了?状态定义得明明白白,每个状态的逻辑都集中在一起,改起来也方便。
想加个新状态,直接加个case就行,完全不用翻那堆if-else。而且在单片机里,咱们用volatile修饰状态变量,防止中断干扰,硬件控制直接操作GPIO,非常贴合实际开发。
三、用查表法干掉判断
第二个招数,叫查表法。顾名思义,就是用表格(比如数组)把条件和对应的操作存起来,省得写一堆if-else。
这种方法特别适合处理固定数量的条件或命令,比如串口命令解析、事件处理啥的。
假设咱们在做一个命令解析器,要根据串口收到的命令码调用不同函数。传统的if-else可能是这样:
if (command == 0x01) { functionA();} else if (command == 0x02) { functionB();} else if (command == 0x03) { functionC();}
这代码看着就烦,命令一多就彻底乱了。咱们用查表法改一下:
// 函数指针类型typedef void (*CommandFunction)(void);// 命令处理函数void functionA() { PORTD |= (1 // 点亮LED0}void functionB() { PORTD |= (1 // 点亮LED1}void functionC() { PORTD |= (1 // 点亮LED2}// 命令表const CommandFunction commandTable[256] = {functionA,functionB,functionC, // 未定义的命令默认为NULL};// 执行命令void executeCommand(uint8_t command) { if (commandTable[command] != NULL) { commandTable[command](); } else { PORTD |= (1 // 未识别命令,点亮错误指示灯 }}// 主函数int main(void) { DDRD = 0xFF; // 配置PORTD为输出 uint8_t cmd; while (1) { cmd = receiveCommand(); // 假设从串口接收命令 executeCommand(cmd); }}这下多简单!每个命令对应一个函数,存在表里,想加新命令直接在表里加一行,代码都不用大改。
这种查表法使用也很灵活,比如多按键,多LED检测或控制的场景,比如我们无际单片机项目的多按键检测。
0fo45vhkean640519137.png
pwchuy0wemk640519237.png
四、组合使用
以上几种方法,可以组合使用,效果更好,为了更加直观展示,咱们来个实战案例,假设有个单片机项目,要根据传感器数据做不同处理,演示下状态机和查表法组合用法,原始代码可能是这样:
void processData(int sensorValue) { if (sensorValue 10) { if (mode == 1) { // 处理A } else { // 处理B } } else if (sensorValue >= 10 && sensorValue 20) { if (mode == 1) { // 处理C } else { // 处理D } } else { if (mode == 1) { // 处理E } else { // 处理F } }}
这代码嵌套得让人头晕,逻辑乱七八糟。咱们用状态机和查表法来收拾它。先定义状态:
typedef enum { LOW, MEDIUM, HIGH} SensorState;SensorState getSensorState(int value) { if (value 10) return LOW; else if (value 20) return MEDIUM; else return HIGH;}再用查表法整理逻辑:
typedef void (*ProcessFunction)();void processA() { /* 处理A */ }void processB() { /* 处理B */ }void processC() { /* 处理C */ }void processD() { /* 处理D */ }void processE() { /* 处理E */ }void processF() { /* 处理F */ }typedef struct { SensorState state; int mode; ProcessFunction func;} ProcessTable;const ProcessTable table[] = { {LOW, 1, processA}, {LOW, 0, processB}, {MEDIUM, 1, processC}, {MEDIUM, 0, processD}, {HIGH, 1, processE}, {HIGH, 0, processF}};void processData(int sensorValue, int mode) { SensorState state = getSensorState(sensorValue); for (int i = 0; i sizeof(table)/sizeof(table[0]); i++) { if (table.state == state && table.mode == mode) { table.func(); return; } } // 未定义的处理}这下逻辑清清楚楚,改起来也简单,想加新处理方式直接在表里加一行就行。状态和行为分开管理,完全不会乱成一团。
这里只是简单举个例子模型,除此以外,还有很多灵活的应用场景,比如无际单片机项目3在做多级菜单和防盗报警模式的时候,就采用了状态机+查表法,结合链表去管理多个菜单和模式。
0ssextrkpb0640519337.png
obheou4xmsc640519437.png
五、防患于未然的小建议
除了这些方法,我再给你点小建议,帮你以后少踩坑。
就是别等屎山堆得老高才收拾,定期看看代码,发现if-else变多就赶紧优化。
一个函数尽量只干一件事,别把所有逻辑塞一起,保持简单明了。
用设计模式能省不少事,不光策略模式,工厂模式、命令模式也能帮你少写if-else。如果能写点测试,那就更好了,改代码时心里有底,不怕崩盘。
六、写在最后
消灭if-else屎山不是一天两天的事,但只要你把这些招数用熟了,再复杂的逻辑也能理得清清楚楚。从现在开始,让你的代码变得优雅又专业,像艺术品一样,既好看又有用。记住,代码是写给人看的,顺便让机器跑。别让if-else拖了后腿。
end
nbrrr5bz5kw640519537.jpg
下面是更多无际原创的个人成长经历、行业经验、技术干货。
1.电子工程师是怎样的成长之路?10年5000字总结
2.如何快速看懂别人的代码和思维
3.单片机开发项目全局变量太多怎么管理?
4.C语言开发单片机为什么大多数都采用全局变量的形式?
5.单片机怎么实现模块化编程?实用程度让人发指!
6.c语言回调函数的使用及实际作用详解
7.手把手教你c语言队列实现代码,通俗易懂超详细!
8.c语言指针用法详解,通俗易懂超详细! |
|