代码重构技术:提取函数、重命名、简化条件、重构工具使用

各位同学好,今天我们来聊聊代码重构。说实话,我见过太多嵌入式项目死在「能跑就别动」的魔咒里。代码越堆越乱,最后谁都不敢改。其实重构没那么玄乎,说白了就是给代码做「日常保洁」。

我自己的习惯是:每次提交代码前,花10分钟做一次小重构。别等到代码烂成一锅粥再动手,那时候成本就高了。今天我把最常用的几招分享给你。

一、提取函数:把大块头拆成小零件

这是最基础也最有效的重构手法。我在项目中遇到过一段300行的初始化函数,里面混杂着GPIO配置、定时器设置、中断注册、还有日志打印。每次改一个外设都得从头读到尾,生怕漏掉什么。

提取函数的核心原则:一个函数只做一件事。你想想看,如果一个函数既要做A又要做B,那它改起来就特别容易出问题。

重构前:

void system_init(void) {
    // GPIO配置
    GPIO_InitTypeDef gpio = {0};
    gpio.Pin = GPIO_PIN_0 | GPIO_PIN_1;
    gpio.Mode = GPIO_MODE_OUTPUT_PP;
    HAL_GPIO_Init(GPIOA, &gpio);
    
    // 定时器配置
    TIM_HandleTypeDef htim = {0};
    htim.Instance = TIM2;
    htim.Init.Period = 1000;
    HAL_TIM_Base_Init(&htim);
    
    // 中断注册
    HAL_NVIC_SetPriority(TIM2_IRQn, 0, 0);
    HAL_NVIC_EnableIRQ(TIM2_IRQn);
    
    // 日志初始化
    log_init();
    log_info("System initialized");
}

你看,这个函数干了四件事。如果我想单独测试定时器,还得把整个函数跑一遍。重构后应该是这样:

重构后:

static void gpio_init(void) {
    GPIO_InitTypeDef gpio = {0};
    gpio.Pin = GPIO_PIN_0 | GPIO_PIN_1;
    gpio.Mode = GPIO_MODE_OUTPUT_PP;
    HAL_GPIO_Init(GPIOA, &gpio);
}

static void timer_init(void) {
    TIM_HandleTypeDef htim = {0};
    htim.Instance = TIM2;
    htim.Init.Period = 1000;
    HAL_TIM_Base_Init(&htim);
}

static void nvic_init(void) {
    HAL_NVIC_SetPriority(TIM2_IRQn, 0, 0);
    HAL_NVIC_EnableIRQ(TIM2_IRQn);
}

void system_init(void) {
    gpio_init();
    timer_init();
    nvic_init();
    log_init();
    log_info("System initialized");
}

嗯,这样清爽多了。每个函数职责单一,测试、调试、复用都方便。提取函数时有个小技巧:如果一个函数内部出现了注释块,那大概率可以拆出来。注释就是「这里应该是个独立函数」的信号。

二、重命名:让名字自己说话

我曾经接手过一个项目,变量名叫 a1b2tmp,函数名叫 do_stuffprocess_data。你猜猜 process_data 是处理什么数据的?反正我猜了三天没猜对。

好的命名应该做到:看到名字就知道它是什么、干什么。不需要注释来解释变量名,那说明名字起得不好。

坏命名 好命名 说明
int a; int sensor_value; 明确表示这是传感器读数
void calc(void); void calculate_crc(void); 具体说明计算什么
#define MAX 100 #define MAX_BUFFER_SIZE 100 说明这个最大值属于谁
uint8_t flag; uint8_t is_connected; 布尔变量用 is/has/can 开头

重命名时要注意:全局符号的改名要谨慎。我建议先用 IDE 的「重命名」功能,它会自动更新所有引用。手动替换容易漏掉,尤其是头文件里的声明。

我的命名习惯:

  • 函数名用动词开头:read_temperature()send_packet()
  • 变量名用名词:current_stateerror_count
  • 宏定义全大写:#define TIMEOUT_MS 5000
  • 布尔变量用 is_has_can_ 开头

三、简化条件:让逻辑一目了然

条件判断是代码里最容易变复杂的地方。我见过这样的代码:

if ((status == OK) && (timeout > 0) && !(flag & ERROR_FLAG)) {
    // 处理正常情况
} else if ((status == TIMEOUT) || (timeout <= 0)) {
    // 处理超时
} else {
    // 处理错误
}

这种嵌套条件读起来太费劲了。简化条件有几个常用手法:

手法一:提取条件为函数

static bool is_normal_operation(uint8_t status, uint32_t timeout, uint8_t flag) {
    return (status == OK) && (timeout > 0) && !(flag & ERROR_FLAG);
}

static bool is_timeout_occurred(uint8_t status, uint32_t timeout) {
    return (status == TIMEOUT) || (timeout <= 0);
}

if (is_normal_operation(status, timeout, flag)) {
    // 处理正常情况
} else if (is_timeout_occurred(status, timeout)) {
    // 处理超时
} else {
    // 处理错误
}

手法二:提前返回,减少嵌套

// 重构前
void process_data(Data *data) {
    if (data != NULL) {
        if (data->valid) {
            if (data->size > 0) {
                // 处理数据
            }
        }
    }
}

// 重构后
void process_data(Data *data) {
    if (data == NULL) return;
    if (!data->valid) return;
    if (data->size <= 0) return;
    
    // 处理数据
}

提前返回是我最喜欢的手法。它把异常情况提前处理掉,剩下的就是正常逻辑。代码的缩进层次少了,读起来也轻松。

注意:提前返回在函数中间退出时,要确保资源释放正确。比如有 malloc 分配的内存,要在每个 return 前都 free 掉。我建议用 goto 统一处理资源释放,或者用 RAII 风格(如果 C 语言支持)。

四、重构工具:别自己硬扛

手动重构容易出错,尤其是大型项目。我推荐几个好用的工具:

工具 适用场景 我的评价
Eclipse CDT 嵌入式C/C++项目 重命名、提取函数很稳,支持交叉引用
Visual Studio + Visual Assist Windows平台C/C++ 重构功能强大,但收费
CLion 跨平台C/C++ 重构支持好,但需要 CMake 项目
Source Insight 大型嵌入式项目 老牌工具,符号解析强,重构功能一般
sed/awk + grep 批量文本替换 适合简单重命名,但风险高,慎用

我个人最常用的是 Eclipse CDT。它有一个「预览」功能,重构前会列出所有受影响的文件,你可以逐个确认。这个功能救过我很多次——有一次重命名一个全局变量,预览发现它被 37 个文件引用,其中 3 个是注释里的,不应该改。如果没有预览,那三个注释里的名字也会被改掉,编译没问题,但文档就乱了。

使用重构工具的小建议:

  • 重构前先提交或暂存当前代码,方便回退
  • 使用「预览」功能,逐个确认变更
  • 重构后跑一遍单元测试和集成测试
  • 如果项目没有测试,先加测试再重构

五、重构的节奏:小步快跑

重构最忌讳「大跃进」。我曾经试图在一个周末重构整个通信协议栈,结果周一回来发现编译都过不了。后来我学乖了:每次只改一个点,改完就测试

推荐的重构节奏:

  1. 识别坏味道:长函数、重复代码、复杂条件、命名混乱
  2. 确定重构手法:提取函数、重命名、简化条件、提取常量
  3. 执行重构:用工具或手动,小步修改
  4. 验证:编译通过 + 测试通过
  5. 提交:写清楚重构了什么,为什么重构

你看,重构不是一次性的大工程,而是日常的小习惯。就像打扫房间,每天扫一扫,比年底大扫除轻松多了。

代码重构核心流程 识别坏味道 长函数/重复代码 确定重构手法 提取/重命名/简化 执行重构 工具辅助/小步修改 验证 未通过 → 回退或修复 提交代码 写清楚重构内容 核心原则:小步快跑,每次只改一个点,改完就测试 三大核心手法 提取函数 · 重命名 · 简化条件

好了,关于代码重构的核心技术就聊到这里。记住:重构不是重写。重写是把代码推倒重来,重构是在现有结构上逐步优化。前者风险大、周期长,后者稳健、可持续。我建议你从今天开始,每次写代码时多问自己一句:「这段代码还能不能再清爽一点?」

公众号:蓝海资料掘金营,微信deep3321