【读书活动感悟分享】《重构:改善既有代码的设计》重构_文章

【读书活动感悟分享】《重构:改善既有代码的设计》重构

许栋梁
发表于 2025-11-10 09:13:39
首先何为重构,文章中指出,重构是在不改变软件可观察行为的前提下,改善其内部结构。这里有两个核心要素:不改变可观察行为和改善内部结构。
不改变可观察行为:对外部功能、接口、性能表现保持一致;用户感受不到任何变化;测试用例应继续通过

改善内部结构:让代码更易理解、维护和扩展;消除坏味道代码;提升设计质量,代码质量

只改变内部结构,如果行为变了,就是"重写"或"修复Bug";只不改变行为,如果结构没变,只是"整理代码";两者结合,才算是真正的重构。

一个小例子

 // 重构前
 public static decimal GetPrice(int quantity, decimal price)
 {
     if (quantity > 100)
    {
         return quantity * price * 0.8m;
    }
     else if (quantity > 50)
    {
         return quantity * price * 0.9m;
    }
     else
    {
         return quantity * price;
    }
 }
 // 重构后
 public static decimal GetPrice(int quantity, decimal price)
 {
     decimal discount = CalculateDiscount(quantity);
     return quantity * price * discount;
 }
 ​
 public static decimal CalculateDiscount(int quantity)
 {
     if (quantity > 100) return 0.8m;
     if (quantity > 50) return 0.9m;
     return 1.0m;
 }

    行为不变:输入输出完全一样;

    结构改善:逻辑更清晰,提取的 CalculateDiscount 方法职责单一,也可独立测试

    书中也有一章节来介绍代码坏味道。“我们并不试图给你一个何时必须重构的精确衡量标准。从我们的经验看来,没有任何量度规矩比得上见识广博者的直觉。”为什么“味道”比规则更有效? "味道"是依赖开发者对上下文的整体感知,而非刻板的量化指标,这是其更有效的原因 。

    依靠精确去识别问题,这种方案是不可靠的。比如超过 50 行的函数必须重构 ,但是49 行也可能很烂;两个以上实例变量就提取基类的话,可能会导致过多的类,这就过度设计了。这样就是为了重构而重构,产生机械式的处理。

    依靠“味道”去识别,综合考虑复杂度,上下文,意图是否明显,不同场景下合理的结构不同,这段代码到底要不到做出修改,是哪里让我觉得不舒服的,风险是否可控。

    以下是文章指出哪些代码是坏味道。

    1. 神秘命名(Mysterious Name)
    不能够清晰表达变量、函数、模块等等的功能,用法。
    2. 重复代码(Duplicated Code)

    出入一样,处理一样的代码重复出现。
    3. 过长函数(Long Function)
    函数越长、越难理解,后期也难以维护、扩展。
    4. 过长参数列表(Long Parameter List)
    太长的参数列表往往会造成前后不一致,不易使用。
    5. 全局数据(Global Data)
    全局变量使用混乱,读取与写入掺杂。

    6. 可变数据(Mutable Data)
    数据变动、副作用修改,容易产生难以发现的 bug,数据变动难以追溯。
    7. 发散式变化(Divergent Change)
    8. 霰弹式修改(Shotgun Surgery)
    在修改程序某一点的时候,需要在多个上下文中切换、修改,每个地方都进行一点修改。
    9. 依恋情结(Feature Envy)
    某个函数为了计算某个值,从另一个对象中调用一大堆取值函数。
    10. 数据泥团(Data Clumps)
    几个数据项在不同的地方重复地一起出现。 如果删掉其中的一个数据项,那么其他数据因此而失去意义。
    11. 基本类型偏执(Primitive Obsession)
    考虑把总是被放在一起的字段抽出到类中
    12. 重复的switch(Repeated Switches)
    switch 语句的问题在于重复,同样的 switch 语句散步在不同地方。考虑使用多态代替 switch。
    13. 循环语句(Loops)
    拥抱函数式变成,改用 reduce、filter、map 等等,能够减少循环带来的中间变量,更容易进行函数提取、组合、流水线操作等等。
    14. 冗赘的元素(Lazy Element)
    类的维护需要精力。
    15. 夸夸其谈通用性(Speculative Generality)
    过早的臆想、缺少从实际需求考虑写代码,各种非必要的参数、钩子会让系统更加难以维护、理解。
    16. 临时字段(Temporary Field)
    令人迷惑的临时字段
    17. 过长的消息链(Message Chains)
    一个对象请求一个对象,然后再去请求另一个对象,函数调用链过长。
    18. 中间人(Middle Man)
    一个类的接口中很多函数都委托给了其他类。
    19. 内幕交易(Insider Trading)
    两个模块之间有大量的特有逻辑交互,增加了耦合度。
    20. 过大的类(Large Class)
    跟过大函数一样,做的事情多了,就不好理解、维护。
    21. 异曲同工的类(Alternative Classes with Different Interfaces)
    两个类做的事情越来越像、重复。
    22. 纯数据类(Data Class)
    只包含数据、getter 和 setter 的类
    23. 被拒绝的遗赠(Refused Bequest)
    子类复用了超类的一些行为,但又不支持超类的另一些接口。
    24. 注释(Comments)
    写注释是好事,但是如果需要很长的注释去说明一段代码,或许这段代码很槽糕了。在写注释的时候,如果发现需要很长篇幅、或者自己都说不明白,那也许需要重构了。


    一开始举得小例子有哪些坏味道呢?

    过长函数,重复代码,神秘命名


    123 0

    评论
    

    意见反馈