重构基本功:提炼函数、内联函数、提炼变量、内联变量、改变函数声明
重构这件事,说白了就是让代码变得更好懂、更好改。我见过太多项目,一开始写得飞起,三个月后连自己都看不懂了。嗯,今天咱们就聊聊几个最基础、也最常用的重构手法。
提炼函数:把大块逻辑拆成小单元
你想想看,一个函数如果超过50行,读起来是不是很累?我个人的习惯是,一个函数最好只做一件事。如果它做了两件以上,就该拆了。
什么时候该提炼?
- 一段代码有明确的业务含义,比如“计算折扣”、“验证用户权限”
- 一段代码被重复使用两次以上
- 一段代码的注释可以概括它的功能
// 重构前
void processOrder(Order& order) {
// 计算总价
double total = 0;
for (auto& item : order.items) {
total += item.price * item.quantity;
}
// 应用折扣
if (order.isVIP) {
total *= 0.9;
}
// 生成订单号
order.id = generateId();
// 保存到数据库
saveToDB(order);
}
// 重构后
double calculateTotal(const Order& order) {
double total = 0;
for (auto& item : order.items) {
total += item.price * item.quantity;
}
return total;
}
double applyDiscount(double total, bool isVIP) {
return isVIP ? total * 0.9 : total;
}
void processOrder(Order& order) {
double total = calculateTotal(order);
total = applyDiscount(total, order.isVIP);
order.total = total;
order.id = generateId();
saveToDB(order);
}
我的经验:提炼函数时,命名是关键。函数名要能准确描述它做了什么。我见过有人提炼出叫
doSomething 的函数,那还不如不拆。
内联函数:当函数本身比调用还简单
有提炼就有内联。有时候你发现一个函数只有一两行,而且调用它的地方也不多。这时候,把它内联回去反而更清晰。
什么时候该内联?
- 函数体比函数名还短
- 函数只在唯一一处被调用
- 函数名没有提供额外信息
// 重构前
int getMax(int a, int b) {
return a > b ? a : b;
}
void process() {
int result = getMax(x, y);
}
// 重构后
void process() {
int result = x > y ? x : y; // 直接写,更直观
}
注意:内联函数不是无脑操作。如果函数有复杂的业务逻辑,或者将来可能变化,保留函数反而更好。我曾经因为过度内联,导致后来改需求时改得想哭。
提炼变量:给表达式起个名字
复杂的表达式读起来很费劲。提炼变量就是给中间结果取个名字,让代码自解释。
// 重构前
if (user.getRole() == Role::ADMIN &&
user.getStatus() == Status::ACTIVE &&
user.getLoginCount() > 10) {
// ...
}
// 重构后
bool isActiveAdmin = user.getRole() == Role::ADMIN &&
user.getStatus() == Status::ACTIVE;
bool isExperienced = user.getLoginCount() > 10;
if (isActiveAdmin && isExperienced) {
// ...
}
你看,提炼之后,条件判断的意图一目了然。我建议你养成习惯:只要表达式超过两个条件,就考虑提炼变量。
内联变量:当变量比表达式还啰嗦
和函数一样,变量也有内联的时候。如果一个变量只被使用一次,而且它的名字没有比表达式提供更多信息,那就直接内联。
// 重构前
double basePrice = quantity * itemPrice;
double finalPrice = basePrice * 1.2;
// 重构后
double finalPrice = quantity * itemPrice * 1.2; // 少了一个变量,更简洁
核心原则:提炼和内联不是对立的。它们的目标一致——让代码更容易理解。该提炼时提炼,该内联时内联,没有绝对的对错。
改变函数声明:让接口更清晰
函数声明是代码的“门面”。一个好的函数名和参数列表,能让调用者一眼就知道怎么用。我见过最糟糕的情况是:一个函数有6个参数,其中3个是布尔值,调用的时候全是 true、false,根本不知道每个参数什么意思。
常见的改进方式:
- 改名:让函数名更准确地描述功能
- 调整参数顺序:把常用的参数放前面
- 用枚举代替布尔参数
- 把多个相关参数封装成结构体
// 重构前
void updateUser(int id, string name, bool isActive, bool sendEmail, bool notifyAdmin);
// 调用时
updateUser(42, "张三", true, false, true); // 鬼知道这些true/false是什么意思
// 重构后
struct UserUpdateOptions {
bool isActive = false;
bool sendEmail = false;
bool notifyAdmin = false;
};
void updateUser(int id, string name, const UserUpdateOptions& options);
// 调用时
UserUpdateOptions opts;
opts.isActive = true;
opts.notifyAdmin = true;
updateUser(42, "张三", opts); // 一目了然
避坑指南:我曾经在一个项目中,把一个函数的参数从3个改到5个,结果导致所有调用处都要改。后来我学乖了:如果参数可能变化,就用结构体或者参数对象来封装。
知识体系总览
下面这张图总结了本章的核心逻辑。你可以看到,提炼和内联是互逆的操作,而改变函数声明则是更高层次的接口优化。
总结
这五个重构手法,说白了就是让代码更“像人话”。提炼函数和提炼变量是把复杂的东西拆开、命名;内联函数和内联变量是把多余的东西去掉;改变函数声明是让接口更友好。
我个人的建议是:每次改代码时,顺手做一次小重构。不用追求一步到位,慢慢来,代码质量会越来越好。记住,重构不是炫技,而是为了让明天的自己少掉几根头发。
一句话记住:好的代码不是写出来的,是改出来的。
可可精品资料库