电子产业一站式赋能平台

PCB联盟网

搜索
查看: 7|回复: 0
收起左侧

如何消灭if_else屎山代码?

[复制链接]

288

主题

288

帖子

2111

积分

三级会员

Rank: 3Rank: 3

积分
2111
发表于 昨天 08:01 | 显示全部楼层 |阅读模式
关注公众号,回复“入门资料”获取单片机入门到高级开挂教程
开发板带你入门,我们带你飞

文 | 无际(微信:2777492857)
全文约3036字,阅读大约需要 10 分钟
说起“if/else屎山代码”,相信很多工程师都有一肚子苦水要吐,包括我。
           
以前做过一个项目,客户老是改需求,那会水平有限,全是if/else,没有任何架构可言,每次改这里,那里就出问题,让我直呼救命!这啥玩意儿??

i1qb1x43qg2640519036.png

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

    0fo45vhkean640519137.png


    pwchuy0wemk640519237.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

    0ssextrkpb0640519337.png

    obheou4xmsc640519437.png

    obheou4xmsc640519437.png

               
    五、防患于未然的小建议
    除了这些方法,我再给你点小建议,帮你以后少踩坑。
               
    就是别等屎山堆得老高才收拾,定期看看代码,发现if-else变多就赶紧优化。
               
    一个函数尽量只干一件事,别把所有逻辑塞一起,保持简单明了。
               
    用设计模式能省不少事,不光策略模式,工厂模式、命令模式也能帮你少写if-else。如果能写点测试,那就更好了,改代码时心里有底,不怕崩盘。
               
    六、写在最后
    消灭if-else屎山不是一天两天的事,但只要你把这些招数用熟了,再复杂的逻辑也能理得清清楚楚。从现在开始,让你的代码变得优雅又专业,像艺术品一样,既好看又有用。记住,代码是写给人看的,顺便让机器跑。别让if-else拖了后腿。
    end

    nbrrr5bz5kw640519537.jpg

    nbrrr5bz5kw640519537.jpg


    下面是更多无际原创的个人成长经历、行业经验、技术干货。
    1.电子工程师是怎样的成长之路?10年5000字总结
    2.如何快速看懂别人的代码和思维
    3.单片机开发项目全局变量太多怎么管理?
    4.C语言开发单片机为什么大多数都采用全局变量的形式
    5.单片机怎么实现模块化编程?实用程度让人发指!
    6.c语言回调函数的使用及实际作用详解

    7.手把手教你c语言队列实现代码,通俗易懂超详细!

    8.c语言指针用法详解,通俗易懂超详细!
  • 回复

    使用道具 举报

    发表回复

    您需要登录后才可以回帖 登录 | 立即注册

    本版积分规则


    联系客服 关注微信 下载APP 返回顶部 返回列表