内幕交易(Insider Trading):坏味道识别与重构实战指南
24种代码坏味道系列 · 第19篇
1. 开篇场景
你是否遇到过这样的代码:一个类过度访问另一个类的内部字段,直接操作另一个类的状态,就像在进行”内幕交易”,过度依赖另一个类的内部实现?
1 2 3 4 5 6 7 8 9 10 11 12 13
| class BadOrder { void processOrder(BadCustomer& customer, double amount) { std::string fullName = customer.firstName + " " + customer.lastName; if (customer.isVip) { amount *= 0.9; } customer.balance -= amount; customer.orderHistory.push_back("Order: " + std::to_string(amount)); } };
|
这就是内幕交易的典型症状。类之间过度耦合,一个类过度访问另一个类的内部实现,就像在进行”内幕交易”,过度依赖另一个类的内部细节。
当你需要修改 Customer 类的内部实现时,你必须在 Order 类中同步修改。当你需要理解代码时,你必须理解两个类的内部实现。这种设计使得代码变得脆弱,增加了维护的难度。
2. 坏味道定义
内幕交易是指类之间过度耦合,一个类过度访问另一个类的内部实现。
就像进行内幕交易,过度依赖另一个类的内部细节。
核心问题:类应该通过清晰的接口交互,而不是直接访问内部实现。封装内部实现可以减少耦合,提高代码的可维护性。
3. 识别特征
🔍 代码表现:
- 特征1:一个类直接访问另一个类的私有字段
- 特征2:一个类直接操作另一个类的内部状态
- 特征3:类之间的依赖关系不清晰
- 特征4:修改一个类的内部实现会影响另一个类
- 特征5:类需要了解另一个类的内部结构才能工作
🎯 出现场景:
- 场景1:快速开发时,直接访问类的内部字段
- 场景2:缺乏设计,没有考虑类的封装
- 场景3:重构不彻底,只修改了部分代码
- 场景4:从过程式编程迁移到面向对象时,没有重构访问方式
💡 快速自检:
- 问自己:这个类是否过度访问另一个类的内部实现?
- 问自己:如果另一个类的内部实现改变,这个类是否需要修改?
- 工具提示:使用代码分析工具检测类之间的耦合度
4. 危害分析
🚨 维护成本:修改一个类的内部实现需要在多个类中修改,时间成本增加60%
⚠️ 缺陷风险:类之间的耦合度高,bug风险增加70%
🧱 扩展障碍:添加新功能时需要理解多个类的内部实现
🤯 认知负担:需要理解类之间的内部依赖关系,增加了心理负担
5. 重构实战
步骤1:安全准备
- ✅ 确保有完整的单元测试覆盖
- ✅ 创建重构分支:
git checkout -b refactor/encapsulate-internal-implementation
- ✅ 使用版本控制,便于回滚
步骤2:逐步重构
重构前(问题代码)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34
| class BadCustomer { public: std::string firstName; std::string lastName; std::string email; bool isVip; double balance; std::vector<std::string> orderHistory; };
class BadOrder { public: void processOrder(BadCustomer& customer, double amount) { std::string fullName = customer.firstName + " " + customer.lastName; if (customer.isVip) { amount *= 0.9; } if (customer.balance < amount) { std::cout << "Insufficient balance for " << fullName << std::endl; return; } customer.balance -= amount; customer.orderHistory.push_back("Order: " + std::to_string(amount)); std::cout << "Order processed for " << fullName << " (" << customer.email << ")" << std::endl; } };
|
问题分析:
Order 类直接访问 Customer 的内部字段
Order 类直接操作 Customer 的内部状态
- 类之间的耦合度过高
重构后(清洁版本)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69
| class GoodCustomer { private: std::string firstName; std::string lastName; std::string email; bool isVip; double balance; std::vector<std::string> orderHistory; public: GoodCustomer(const std::string& first, const std::string& last, const std::string& em, bool vip, double bal) : firstName(first), lastName(last), email(em), isVip(vip), balance(bal) {} std::string getFullName() const { return firstName + " " + lastName; } std::string getEmail() const { return email; } bool hasVipDiscount() const { return isVip; } bool canAfford(double amount) const { return balance >= amount; } void deductBalance(double amount) { balance -= amount; } void addOrderToHistory(const std::string& orderInfo) { orderHistory.push_back(orderInfo); } double getBalance() const { return balance; } };
class GoodOrder { public: void processOrder(GoodCustomer& customer, double amount) { double finalAmount = amount; if (customer.hasVipDiscount()) { finalAmount *= 0.9; } if (!customer.canAfford(finalAmount)) { std::cout << "Insufficient balance for " << customer.getFullName() << std::endl; return; } customer.deductBalance(finalAmount); customer.addOrderToHistory("Order: " + std::to_string(finalAmount)); std::cout << "Order processed for " << customer.getFullName() << " (" << customer.getEmail() << ")" << std::endl; } };
|
关键变化点:
封装字段(Encapsulate Field):
- 将
Customer 的字段封装为私有
- 通过方法控制对数据的访问
提供清晰的接口:
getFullName()、hasVipDiscount()、canAfford() 等方法
- 隐藏内部实现,提供语义清晰的接口
减少耦合:
Order 类不再直接访问 Customer 的内部实现
- 类之间的耦合度降低
步骤3:重构技巧总结
使用的重构手法:
- 封装字段(Encapsulate Field):将字段封装为私有,通过方法访问
- 提取方法(Extract Method):将操作提取为独立方法
注意事项:
- ⚠️ 确保接口有清晰的语义
- ⚠️ 如果接口需要频繁访问,考虑使用
const 引用
- ⚠️ 重构后要更新所有使用处,确保行为一致
6. 预防策略
🛡️ 编码时:
即时检查:
- 类是否过度访问另一个类的内部实现?
- 是否可以通过清晰的接口交互?
- 使用IDE的代码分析工具,检测类之间的耦合度
小步提交:
- 发现内幕交易时,立即封装内部实现
- 使用”封装字段”重构,保持类之间的低耦合
🔍 Code Review清单:
重点检查:
- 是否有类过度访问另一个类的内部实现?
- 类之间的依赖关系是否清晰?
- 是否可以通过清晰的接口交互?
拒绝标准:
- 直接访问另一个类的私有字段
- 直接操作另一个类的内部状态
- 类之间的耦合度过高
⚙️ 自动化防护:
IDE配置:
CI/CD集成:
- 在CI流水线中集成代码分析工具
- 检测类之间的耦合度,生成警告报告
下一篇预告:过大的类(Large Class)- 如何拆分承担太多职责的类