前言:推荐大家阅读 Martin Fowler的《重构------改善既有代码的设计》第2版。本文谈一谈本人阅读几章节之后的一点理解。
目录
一、什么是重构
重构的是指 在不改变代码外部行为的前提下,优化内部结构,让代码更易维护、扩展和理解。
二、为何需要重构
1)使代码易于理解
可能是为了节省时间,很多开发者没有写注释的习惯。在复杂的庞大系统编程中,重构告诉我们 需要为复杂函数写清楚注释,便于后面新人快速接手。
2)使代码便于扩展维护
我们平时开发项目时,由于项目周期较短,资源紧张等客观因素,使得我们的开发只关注功能的实现与完成,可能中间出现了重复代码、无用代码、危险的指针等。当有新的需求或是修改bug,可能都在原有代码结构中直接进行修改,使得代码堆砌,变得杂乱,难以扩展维护
3)使代码不易变质
有一个经典的"破窗理论",即:如果一扇窗户破了,如果你不及时去修补,时间长了,经过的路人可能认为这里一直是个破烂的地方,可以丢垃圾,而后很多人在这里附近丢垃圾,导致成了垃圾堆。代码也是一样,如果不及时重构,那么就会变成垃圾,堆积如山,慢慢变质。
三、何时需要重构
代码出现"坏味道"的时候可能就需要重构了。
"坏味道"包括 变量/函数命名不清晰、耦合度高、大量的裸指针、条件分支过于复杂等。
事不过三,三则重构:如果你第一次改一处代码觉得可以下手,第二次修改勉强可以下手,第三次难以下手,那么这就提醒你是时候需要进行重构了。
四、重构的前提
想要重构,必须得先有可以自测试的代码 。用书中的观点便是:重构的第一块基石是自测试代码。前面说过重构是在不改变代码外部行为的前提下进行的,那如何才能保证不改变代码外部行为呢?答案是只能通过自测试。因此在没有自测试代码之前,不能随意开始重构。
五、重构的常用方法
1)提取重复代码为函数
避免重复造轮子,将公有部分提炼成函数。
重构前:
cpp
#include <iostream>
#include <cmath>
using namespace std;
int main() {
// 计算圆1的面积和周长
double r1 = 5.0;
double area1 = M_PI * r1 * r1;
double circumference1 = 2 * M_PI * r1;
cout << "圆1面积:" << area1 << ",周长:" << circumference1 << endl;
// 计算圆2的面积和周长(重复代码)
double r2 = 8.0;
double area2 = M_PI * r2 * r2;
double circumference2 = 2 * M_PI * r2;
cout << "圆2面积:" << area2 << ",周长:" << circumference2 << endl;
return 0;
}
重构后:
cpp
#include <iostream>
#include <cmath>
using namespace std;
// 提取重复逻辑为函数,复用性提升
double calculateCircleArea(double radius) {
return M_PI * radius * radius;
}
double calculateCircleCircumference(double radius) {
return 2 * M_PI * radius;
}
int main() {
double r1 = 5.0;
cout << "圆1面积:" << calculateCircleArea(r1)
<< ",周长:" << calculateCircleCircumference(r1) << endl;
double r2 = 8.0;
cout << "圆2面积:" << calculateCircleArea(r2)
<< ",周长:" << calculateCircleCircumference(r2) << endl;
return 0;
}
2)优化命名
清晰的命名能够显著提高代码的可读性。
重构前:
cpp
#include <iostream>
using namespace std;
// 函数名模糊,参数名无意义
int f1(int a, int b) {
int c = a * b; // c的含义不明确
if (c > 100) {
return c - 10;
} else {
return c;
}
}
int main() {
int x = 15;
int y = 8;
cout << f1(x, y) << endl; // 不知道f1是做什么的
return 0;
}
重构后:
cpp
#include <iostream>
using namespace std;
// 函数名+参数名语义化,一眼能懂功能
int calculateDiscountedProductTotal(int unitPrice, int quantity) {
int totalPrice = unitPrice * quantity;
// 超过100减10的逻辑明确标注
if (totalPrice > 100) {
return totalPrice - 10;
} else {
return totalPrice;
}
}
int main() {
int phonePrice = 15;
int buyCount = 8;
cout << calculateDiscountedProductTotal(phonePrice, buyCount) << endl;
return 0;
}
3)简化复杂条件判断
重构前:
cpp
#include <iostream>
#include <string>
using namespace std;
bool canLogin(string username, string password, int age, bool isVerified) {
// 条件表达式冗长,逻辑不清晰
if (username != "" && password.length() >= 6 && age >= 18 && isVerified == true) {
return true;
} else {
return false;
}
}
int main() {
cout << boolalpha << canLogin("zhangsan", "123456", 20, true) << endl;
return 0;
}
重构后:
cpp
#include <iostream>
#include <string>
using namespace std;
// 提取子条件为语义化函数,简化主逻辑
bool isUsernameValid(string username) {
return !username.empty();
}
bool isPasswordValid(string password) {
return password.length() >= 6;
}
bool isAdult(int age) {
return age >= 18;
}
bool canLogin(string username, string password, int age, bool isVerified) {
// 条件逻辑清晰,一眼能懂判断维度
return isUsernameValid(username)
&& isPasswordValid(password)
&& isAdult(age)
&& isVerified;
}
int main() {
cout << boolalpha << canLogin("zhangsan", "123456", 20, true) << endl;
return 0;
}
4)拆分高耦合类
如:一个类同时处理网络请求、数据解析、日志打印。
重构前:
cpp
#include <iostream>
#include <string>
// 高耦合:一个类承担多个职责
class UserService {
public:
void getUserInfo(const std::string& userId) {
// 1. 网络请求(职责1)
std::string rawData = "userId=" + userId + "&name=zhangsan&age=20";
std::cout << "发送网络请求,获取原始数据:" << rawData << std::endl;
// 2. 数据解析(职责2)
std::string name = "zhangsan";
int age = 20;
// 3. 日志打印(职责3)
std::cout << "[LOG] 解析用户信息:name=" << name << ", age=" << age << std::endl;
// 4. 业务逻辑
std::cout << "用户信息:" << name << "(" << age << "岁)" << std::endl;
}
};
int main() {
UserService service;
service.getUserInfo("1001");
return 0;
}
重构后:
cpp
#include <iostream>
#include <string>
// 职责1:网络请求
class NetworkClient {
public:
std::string requestUserRawData(const std::string& userId) {
std::string rawData = "userId=" + userId + "&name=zhangsan&age=20";
std::cout << "发送网络请求,获取原始数据:" << rawData << std::endl;
return rawData;
}
};
// 职责2:数据解析
class UserDataParser {
public:
struct UserInfo {
std::string name;
int age;
};
UserInfo parse(const std::string& rawData) {
// 简化解析逻辑,实际可使用正则/字符串分割
return {"zhangsan", 20};
}
};
// 职责3:日志打印
class Logger {
public:
static void printUserLog(const UserDataParser::UserInfo& info) {
std::cout << "[LOG] 解析用户信息:name=" << info.name << ", age=" << info.age << std::endl;
}
};
// 职责4:业务逻辑(依赖其他类,但耦合度低)
class UserService {
private:
NetworkClient networkClient;
UserDataParser parser;
public:
void getUserInfo(const std::string& userId) {
std::string rawData = networkClient.requestUserRawData(userId);
UserDataParser::UserInfo info = parser.parse(rawData);
Logger::printUserLog(info);
std::cout << "用户信息:" << info.name << "(" << info.age << "岁)" << std::endl;
}
};
int main() {
UserService service;
service.getUserInfo("1001");
return 0;
}
六、重构与性能优化的关系
有部分开发者可能认为重构一定是性能优化的,其实不然。
重构的首要目标不是提升性能,但良好的重构会让性能优化更易实现。
结束语:以上仅是个人对重构的一些理解。文中提到的重构手法只列举了一些简单常见的,欢迎读者根据自身项目经验在评论区补充留言。