依恋情结(Feature Envy):坏味道识别与重构实战指南
依恋情结(Feature Envy):坏味道识别与重构实战指南
24种代码坏味道系列 · 第9篇
1. 开篇场景
你是否遇到过这样的方法:它在一个类中,但大部分时间都在访问另一个类的数据,就像一个人住在A城市,但每天都要跑到B城市去工作?
1 | class OrderCalculator { |
这就是依恋情结的典型症状。一个方法过度使用另一个类的数据,应该移到那个类中。方法对另一个类的”依恋”超过了对自己所在类的依赖。
当你需要修改计算逻辑时,你必须在 OrderCalculator 中修改,但逻辑实际上应该属于 Order 类。这种设计使得代码的组织结构不合理,增加了维护的难度。
2. 坏味道定义
依恋情结是指一个方法过度使用另一个类的数据,应该移到那个类中。
就像一个员工总是跑到其他部门去工作,虽然完成了任务,但组织结构不合理。
核心问题:方法应该和它操作的数据放在一起。如果方法频繁访问另一个类的数据,说明它应该属于那个类。
3. 识别特征
🔍 代码表现:
- 特征1:方法频繁访问另一个类的字段
- 特征2:方法对另一个类的访问次数超过对自己类的访问
- 特征3:方法名暗示它应该属于另一个类(如
calculateOrderTotal在Calculator类中) - 特征4:方法的主要逻辑都在操作另一个类的数据
- 特征5:方法需要另一个类的多个字段才能工作
🎯 出现场景:
- 场景1:快速开发时,将方法放在错误的类中
- 场景2:重构不彻底,只移动了部分代码
- 场景3:缺乏设计,没有考虑方法的归属
- 场景4:从过程式编程迁移到面向对象时,没有重构方法位置
💡 快速自检:
- 问自己:这个方法访问哪个类的数据最多?
- 问自己:如果这个方法移到另一个类中,是否更合理?
- 工具提示:使用代码度量工具检测方法对类的依赖关系
4. 危害分析
🚨 维护成本:修改逻辑时需要在错误的类中修改,时间成本增加30%
⚠️ 缺陷风险:方法位置不合理,容易产生理解错误,bug风险增加40%
🧱 扩展障碍:添加新功能时不知道应该放在哪个类中
🤯 认知负担:需要理解方法为什么在这个类中,增加了心理负担
5. 重构实战
步骤1:安全准备
- ✅ 确保有完整的单元测试覆盖
- ✅ 创建重构分支:
git checkout -b refactor/move-method - ✅ 使用版本控制,便于回滚
步骤2:逐步重构
重构前(问题代码)
1 | class Order { |
问题分析:
calculateTotal方法在OrderCalculator类中,但主要操作Order的数据printOrder方法也在OrderCalculator类中,但主要操作Order的数据- 这些方法应该属于
Order类
重构后(清洁版本)
1 | // 好的做法:将方法移到数据所在的类 |
关键变化点:
移动方法(Move Method):
- 将
calculateTotal方法从OrderCalculator移到Order类 - 将
printOrder方法从OrderCalculator移到Order类
- 将
封装数据:
- 将
Order的字段改为私有 - 通过方法访问数据,而不是直接访问字段
- 将
提高内聚性:
- 方法和数据在同一个类中
- 类的职责更清晰
步骤3:重构技巧总结
使用的重构手法:
- 移动方法(Move Method):将方法移到它应该属于的类中
- 封装字段(Encapsulate Field):将字段封装为私有,通过方法访问
注意事项:
- ⚠️ 确保方法移到目标类后,不会破坏现有功能
- ⚠️ 如果方法需要访问原类的其他成员,考虑使用组合
- ⚠️ 重构后要更新所有调用处,确保行为一致
6. 预防策略
🛡️ 编码时:
即时检查:
- 方法是否频繁访问另一个类的数据?
- 方法是否应该属于另一个类?
- 使用IDE的代码分析工具,检测方法的依赖关系
小步提交:
- 发现方法位置不合理时,立即移动
- 使用”移动方法”重构,保持方法在正确的类中
🔍 Code Review清单:
重点检查:
- 方法是否过度依赖另一个类的数据?
- 方法是否应该移到另一个类中?
- 类的职责是否清晰?
拒绝标准:
- 方法频繁访问另一个类的数据
- 方法的主要逻辑都在操作另一个类的数据
- 方法名暗示它应该属于另一个类
⚙️ 自动化防护:
IDE配置:
- 使用代码度量工具检测方法的依赖关系
- 启用方法位置警告
CI/CD集成:
- 在CI流水线中集成代码度量工具
- 检测方法的依赖关系,生成警告报告
下一篇预告:数据泥团(Data Clumps)- 如何将总是同时出现的数据组合成对象
本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 青羽川!
评论
