过大的类(Large Class):坏味道识别与重构实战指南
过大的类(Large Class):坏味道识别与重构实战指南
24种代码坏味道系列 · 第20篇
1. 开篇场景
你是否遇到过这样的类:它包含了用户管理、订单管理、支付管理、地址管理、购物车管理等多个功能,就像一个”万能工具箱”,虽然什么都能做,但找起工具来却要翻遍整个箱子?
class BadExample {
// 用户相关字段
std::string userName;
std::string userEmail;
// ... 更多字段
// 订单相关字段
std::vector<int> orderIds;
// ... 更多字段
// 支付相关字段
double balance;
// ... 更多字段
// 50+ 方法处理各种功能
};
这就是过大的类的典型症状。类承担了太多职责,包含太多字段和方法,就像一个”万能工具箱”,虽然功能齐全,但难以理解和维护。
当你需要修改某个功能时,你必须在庞大的类中查找和修改。当你需要添加新功能时,你不知道应该放在哪里。这种设计使得代码变得复杂,增加了维护的难度。
2. 坏味道定义
过大的类是指类承担了太多职责,包含太多字段和方法。
就像一个万能工具箱,虽然功能齐全,但找起工具来却要翻遍整个箱子。
核心问题:类应该只负责一个职责。如果类包含太多字段和方法,说明它承担了太多职责,应该拆分。
3. 识别特征
🔍 代码表现:
- 特征1:类包含大量字段(超过10个)
- 特征2:类包含大量方法(超过20个)
- 特征3:类的字段可以分组(如用户相关、订单相关)
- 特征4:类的方法可以分组(如用户管理、订单管理)
- 特征5:类的注释中提到了多个职责
🎯 出现场景:
- 场景1:快速开发时,将所有功能放在一个类中
- 场景2:重构不彻底,只修改了部分代码
- 场景3:缺乏设计,没有考虑单一职责原则
- 场景4:需求变更时,不断在现有类中添加新功能
💡 快速自检:
- 问自己:这个类是否只负责一个职责?
- 问自己:如果删除这个类的某个功能,类名是否仍然准确?
- 工具提示:使用代码度量工具检测类的复杂度
4. 危害分析
🚨 维护成本:修改某个功能需要理解整个类,时间成本增加60%
⚠️ 缺陷风险:类承担太多职责,bug风险增加70%
🧱 扩展障碍:添加新功能时不知道应该放在哪里
🤯 认知负担:需要理解所有职责才能修改,心理负担重
5. 重构实战
步骤1:安全准备
- ✅ 确保有完整的单元测试覆盖
- ✅ 创建重构分支:
git checkout -b refactor/extract-class - ✅ 使用版本控制,便于回滚
步骤2:逐步重构
重构前(问题代码)
// 坏味道:一个类做了太多事情
class BadExample {
private:
// 用户相关字段
std::string userName;
std::string userEmail;
std::string userPassword;
int userAge;
// 订单相关字段
std::vector<int> orderIds;
std::map<int, double> orderAmounts;
// 支付相关字段
double balance;
std::string paymentMethod;
std::vector<std::string> transactionHistory;
// 地址相关字段
std::string street;
std::string city;
std::string zipCode;
// 购物车相关字段
std::vector<int> cartItems;
double cartTotal;
public:
// 用户管理方法
void setUserName(const std::string& name) { userName = name; }
// ... 更多方法
// 订单管理方法
void createOrder(int orderId, double amount) { /* ... */ }
// ... 更多方法
// 支付管理方法
bool processPayment(double amount) { /* ... */ }
// ... 更多方法
// 地址管理方法
void setAddress(const std::string& st, const std::string& ct, const std::string& zip) { /* ... */ }
// ... 更多方法
// 购物车管理方法
void addToCart(int itemId) { /* ... */ }
// ... 更多方法
};
问题分析:
- 类承担了5个不同的职责:用户管理、订单管理、支付管理、地址管理、购物车管理
- 包含大量字段和方法
- 类的职责不清晰
重构后(清洁版本)
// ✅ 按职责拆分为多个类
class User {
private:
std::string name;
std::string email;
std::string password;
int age;
public:
void setName(const std::string& n) { name = n; }
std::string getName() const { return name; }
void setEmail(const std::string& em) { email = em; }
void setPassword(const std::string& pwd) { password = pwd; }
bool validate() const { return !name.empty() && !email.empty(); }
};
class Order {
private:
int orderId;
double amount;
bool isCancelled;
public:
Order(int id, double amt) : orderId(id), amount(amt), isCancelled(false) {}
void cancel() { isCancelled = true; }
double getTotal() const { return amount; }
int getId() const { return orderId; }
};
class Payment {
private:
double balance;
std::string paymentMethod;
std::vector<std::string> transactionHistory;
public:
Payment(double bal) : balance(bal) {}
void setBalance(double bal) { balance = bal; }
double getBalance() const { return balance; }
bool processPayment(double amount) {
if (balance >= amount) {
balance -= amount;
transactionHistory.push_back("Payment: " + std::to_string(amount));
return true;
}
return false;
}
};
class Address {
private:
std::string street;
std::string city;
std::string zipCode;
public:
void setAddress(const std::string& st, const std::string& ct, const std::string& zip) {
street = st;
city = ct;
zipCode = zip;
}
std::string getFullAddress() const {
return street + ", " + city + " " + zipCode;
}
};
class ShoppingCart {
private:
std::vector<int> items;
double total;
public:
void addItem(int itemId) { items.push_back(itemId); }
void removeItem(int itemId) { /* ... */ }
void calculateTotal() {
total = items.size() * 10.0;
}
double getTotal() const { return total; }
};
// ✅ 组合类,使用组合而不是把所有功能放在一个类中
class GoodExample {
private:
User user;
Payment payment;
Address address;
ShoppingCart cart;
std::vector<Order> orders;
public:
GoodExample() : payment(1000.0) {}
User& getUser() { return user; }
Payment& getPayment() { return payment; }
Address& getAddress() { return address; }
ShoppingCart& getCart() { return cart; }
void createOrder(int orderId, double amount) {
orders.emplace_back(orderId, amount);
}
};
关键变化点:
提取类(Extract Class):
- 将不同职责提取到不同的类中
- 每个类只负责一个职责
使用组合:
GoodExample使用组合而不是继承- 通过组合使用各个功能类
提高可维护性:
- 每个类职责清晰
- 修改某个功能只需修改对应类
步骤3:重构技巧总结
使用的重构手法:
- 提取类(Extract Class):将不同职责提取到不同的类中
- 移动方法(Move Method):将方法移到合适的类中
- 移动字段(Move Field):将字段移到合适的类中
注意事项:
- ⚠️ 确保每个类有清晰的职责边界
- ⚠️ 如果类之间有依赖,使用组合而不是继承
- ⚠️ 重构后要更新所有使用处,确保行为一致
6. 预防策略
🛡️ 编码时:
即时检查:
- 类是否只负责一个职责?
- 类的字段和方法是否可以分组?
- 使用IDE的代码度量工具,检测类的复杂度
小步提交:
- 发现类承担多个职责时,立即拆分
- 使用”提取类”重构,保持类的职责单一
🔍 Code Review清单:
重点检查:
- 类是否承担了多个职责?
- 类的字段和方法是否可以分组?
- 是否可以拆分为更小的类?
拒绝标准:
- 类包含大量字段和方法(超过20个方法)
- 类的字段可以明显分组
- 类的方法可以明显分组
⚙️ 自动化防护:
IDE配置:
- 使用代码度量工具检测类的复杂度
- 启用类大小警告
CI/CD集成:
- 在CI流水线中集成代码度量工具
- 设置类的复杂度阈值,超过阈值时生成警告
下一篇预告:异曲同工的类(Alternative Classes with Different Interfaces)- 如何统一功能相似但接口不同的类
本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 青羽川!
评论
