重构基本功:提取函数、内联函数、提炼变量、内联变量
说实话,很多C语言开发者写了几年代码,重构的概念还是停留在「把代码改好看点」这个层面。但真正的重构,是有章法的。今天我要讲的这四个基本功——提取函数、内联函数、提炼变量、内联变量,就是重构的「四则运算」。你掌握了它们,代码质量至少能上一个台阶。
我在项目中见过太多「面条代码」了。一个函数几百行,变量名全是 a、b、tmp。这种代码,别说维护,连读都费劲。而重构,就是从这些小地方开始改起的。
提取函数:把大块逻辑拆成小单元
提取函数,说白了就是把一段独立的逻辑从当前函数里「拎」出来,变成一个新的函数。为什么要这么做?因为一个函数只做一件事,才是好函数。
举个例子,你写了一段代码,用来计算两个复数的乘积:
// 重构前
void process_complex_data(double a_re, double a_im, double b_re, double b_im) {
// 计算复数乘积
double result_re = a_re * b_re - a_im * b_im;
double result_im = a_re * b_im + a_im * b_re;
// 后面还有几十行其他逻辑...
// ...
}
这段代码的问题在哪?计算复数乘积的逻辑和主流程混在一起。如果后面还要在其他地方计算复数乘积,你就得复制粘贴。嗯,复制粘贴是代码腐烂的开始。
重构后:
// 重构后
typedef struct {
double re;
double im;
} Complex;
Complex complex_mul(Complex a, Complex b) {
Complex result;
result.re = a.re * b.re - a.im * b.im;
result.im = a.re * b.im + a.im * b.re;
return result;
}
void process_complex_data(Complex a, Complex b) {
Complex result = complex_mul(a, b);
// 后面继续处理...
}
核心要点:提取函数时,函数名要能准确描述「它做了什么」。我个人的习惯是,如果我想不出一个好名字,说明这段逻辑可能还不够独立,需要再想想。
小技巧:当你发现一段代码需要写注释来解释「它在干什么」时,这就是提取函数的好时机。把注释变成函数名,代码自解释。
内联函数:有时候拆得太碎也不是好事
有提取就有内联。内联函数是提取函数的逆操作。当一个函数体非常短,而且只被调用一次时,把它内联回去反而让代码更清晰。
我曾经接手过一个项目,里面有个函数只有一行:
int is_positive(int x) {
return x > 0;
}
这个函数在整个项目里只被用了一次。你想想看,为了一个 x > 0 的表达式,你得多跳转一次、多维护一个函数声明。何必呢?直接内联掉:
// 内联后
if (x > 0) {
// ...
}
注意:内联函数不是盲目合并。如果这个函数有明确的业务含义,即使只有一行,也值得保留。比如 is_admin_user() 这种,语义清晰,留着更好。
提炼变量:给「魔法值」和复杂表达式起个名字
提炼变量,就是把一个复杂的表达式或者一个硬编码的值,提取成一个有名字的变量。这样做的好处是:代码可读性提升,而且后续修改只需要改一个地方。
看这个例子:
// 重构前
if (status & 0x04) {
// 处理写权限
}
这个 0x04 是什么?如果你不熟悉这个协议,你根本看不懂。提炼变量后:
// 重构后
const uint8_t WRITE_PERMISSION_MASK = 0x04;
if (status & WRITE_PERMISSION_MASK) {
// 处理写权限
}
你看,代码瞬间就「说话」了。我在项目中经常遇到这种魔法数字,每次看到都忍不住重构掉。
再来看一个复杂表达式的例子:
// 重构前
if ((a > 0 && b > 0) || (a < 0 && b < 0)) {
// a和b同号
}
提炼变量后:
// 重构后
int is_same_sign = (a > 0 && b > 0) || (a < 0 && b < 0);
if (is_same_sign) {
// a和b同号
}
避坑指南:我曾经提炼变量时,把变量名起得太长,比如 is_user_has_write_permission_to_the_document。结果代码被拉得很长,反而不好看了。变量名要准确,但也要简洁。
内联变量:去掉多余的中间人
内联变量是提炼变量的逆操作。当一个变量只被使用一次,而且变量名并没有比表达式本身更清晰时,就应该内联掉。
举个例子:
// 重构前
int temp = x + y;
return temp;
这个 temp 变量完全多余。直接返回表达式:
// 重构后
return x + y;
再比如:
// 重构前
int result = compute_value(a, b);
return result;
如果 compute_value 这个名字已经足够清晰,那 result 这个变量就是多余的。直接 return compute_value(a, b); 就好。
我的经验:内联变量最常见的场景是在调试阶段。你为了看某个中间值,特意提取了一个变量。调试完了,这个变量就该删掉。别留着,留着就是技术债务。
知识体系总览
这四个基本功,其实构成了一个完整的「重构循环」。我画了一张图,帮你理清它们之间的关系:
总结一下
这四个操作,说白了就是「拆」和「合」的艺术。提取是拆,内联是合。提炼是命名,内联是简化。你不需要一次性把所有代码都重构完。我的建议是:每次修改代码时,顺手做一个小重构。比如加一个新功能前,先把周围的烂代码清理一下。
记住一个原则:让代码读起来像在讲故事。函数名是章节标题,变量名是角色名字。好的代码,不需要注释也能看懂。
最后说一句:重构不是炫技,而是为了让下一个接手你代码的人(很可能就是三个月后的你自己)少掉几根头发。这四个基本功,值得你花时间练熟。