滥用OO 被拒绝的遗赠 Refused Bequest 是什么 子类不需要父类的数据、方法 有什么问题 滥用继承,会造成理解困难,不必要的耦合 场景 子类只用父类部分方法 搞一个兄弟类,把不用的都放进去 Push Down Method Push Down Field 子类用了父类的实现,但接口不一样 可能这个时候要用代理,而不是继承 Replace Inheritance with Delegation 异曲同工的类 Alternative Classes with Different Interfaces 是什么 两个函数做一样的事,却有不同的签名 所以主要是接口不同,跟类的关系不大 有什么问题 缺乏抽象,引发不必要的复杂度 场景 两个函数做的事一样,签名不同 通过命名的修改来凸显问题 Rename Method 通过不停地把行为内置,让两边协议一样 Move Method 最后可能提取到父类里 Extract Superclass 临时字段 Temporary Field 是什么 为了特定情况建立的字段 有什么问题 跟其他字段生命周期不一样容易造成bug 场景 用一个或几个field当临时变量 应该创建一个新类 Extract Class 然后传参 为null设置一个field Introduce Null Object 一个复杂算法,要用很多变量,但是作者不想传太多变量,所以把他们都搞成了field,这些变量只对这个算法有意义 提取新对象 Extract Class 新版被去掉的坏味道 不完美的类库 Incomplete Library Class 手法 Introduce Local Extension Introduce Foreign Method Move Method 场景 类库有些API不提供,或API不好用 switch惊悚现身 switch statements 是什么 分支判断很多,而且到处出现 不仅仅是switch关键字 有什么问题 因为四处出现,每改一个分支就要改好多地方 遗漏而出bug 即便只有一个地方,太长的分支列表也会有问题 改一个新的,不小心动到了旧代码 就算没动到新代码,也会扩散变化,无法证明整个列表中其他分支没有被改变,要做全量回归 场景 一个明显需要多态的场景 需要一连串的手法来实现一个多态替换掉它 先把整个分支列表移到一个新函数里 Extract Method 再移到你计划制造多态的新类里 Move Method 然后决定是否需要用继承体系替换掉 如果是,选择哪一种继承体系,如下: 一组State/Strategy Replace Type Code with State/Strategy 一些子类 Replace Type Code with Subclasses 最后用继承体系替换掉 多态类的创建场景 用enum或抽象工厂或loC容器 手法同上 有时候调用端其实是只在此自己要什么的,没必要隐藏 你可以把分支里的行为抽取成具体的函数 调用段只要直接调用具体的函数就可以 Replace Parameter with Explicit Methods 有时候只是判个空 可以让调用端传入一个NullObject对象 Introduce Null Object 膨胀剂 神秘的命名 新版新加的 可以对应magic number 过长的参数列表 long parameter list 是什么 就是形参太多了 有什么问题 不容易理解 你从接口上看不出为啥需要这些数据 不容易定义出函数的职责 造成职责不单一,各种耦合 接口会经常变动 场景 几个参数明显属于一个整体 手法:组合成一个类 Introduce Parameter Object Preserve Whole Object 参数里有对象和对象的属性 手法:那就不要传属性,直接读对象 Replace Parameter with Method 有时候自己的属性可以被对方读属性读到 就直接传自己,手法同上 Preserve Whole Object Replace Parameter with Method 例外场景 你不需要被调对象和其他某个对象(比如自己)产生依赖(纯函数流行后,这个可能越发的多了) 这个时候,传属性是对的 过长的函数 long method 是什么 一个函数的函数体内代码行数过长 一个函数建议在20行以内 有什么问题 程序越长越难理解,自然容易出bug 场景 一个函数内有很多段逻辑,函数本身职责不单一,每段代码彼此可以通过数据传递隔离(绝大多数) 分割成小函数,并起一个合理的名字 Extract Method 如果函数内临时变量太多,或函数本身的参数太多。提取函数会制造大量的参数,搞不好就有long param list坏味道 可以用函数替代临时变量 Replace Temp with Query 这样可以在其他函数里直接调用这个新函数,省去传参 可以用对象来封装参数 Introduce Parameter Object 有时你从一个对象里去除几个数据传给函数A,这个时候,你就直接传这个对象,在函数A里面取 Preserve Whole Object 如果以上手法还不能拯救你,就可以祭出大杀器,用函数对象代替函数 Replace Method with Method Object 不要滥用 毕竟OO不是FP 不知道哪段代码,该被提取 寻找注释 哪怕只有一行代码,既然值得注释,就值得提取 发现循环 整个提取 Extract Method 新时代可以用lambda替换 发现条件表达式 把表达式提取成函数 Decompose Conditional 数据泥团 Data Dump 是什么 数据项总是扎堆出现,扎堆小时,说明他们是一个类 有什么问题 丢失的概念 往往这里有个概念,但是我们在需求传递中丢失了,造成不内聚 场景 有几个数据项,总是同时被某一个或几个函数使用 有几个数据项,总是同时被一个或几个代码块使用 手法是一样的 先把数据项抽取成对象 Introduce Parameter Object 直接传对象 Preserve Whole Object 过大的类 Large Class 是什么 单个类做了太多的事情 类的行数很长,field也很多 有什么问题 这也是百病之源 理解困难 职责不单一,代码容易耦合 场景 有些时候,这些变量适合放在子类里 那就抽取一个子类 Extract Subclass 仅仅是行数太长 也是需要提取一个新类 Extract Class Extract Subclass 可能是职责不单一,根据客户端的使用情况不同,先提取接口,然后在动刀 Extract Interface 这个大类是GUI的类 有可能需要把业务逻辑和显示逻辑分离 哪怕你需要duplicate一套数据,两边同步 Duplicate Observed Data 如果在服务端,不要对View层的TO过于吝啬 Controller可以有自己专属的一组ViewObject 基本类型偏执 Primitive Obsession 是什么 该用结构类型来抽取概念的时候,没用,非要用基本类型 有什么问题 不内聚 逻辑泄漏的到处都是 场景 不愿意为了小任务建立小对象 比如 money 用浮点数是由风险的 contact ZipCode Range URL 类型码 手法 Replace Data Value with Object Replace Type Code with Subclass Replace Type Code with State/Strategy Extract Class Introduce Parameter 数组中的不同下标代表不同含义 举例 [“张三”,18,“男”] 手法 Replace Array with Object 可有可无 夸夸其谈的未来性 Speculative Generality 是什么 过度设计的产物 认为我们未来会用到,但其实谁知道呢 有什么问题 理解困难 维护困难 看到一个精巧的设计,人们会本能的害怕,这个东西是不是有谁在用,于是一旦开放的接口只会越来越大,没有人敢删 场景 为一个简单的场景做了复杂的设计 分解成下面具体的场景,然后逐个干掉 抽象类没啥用(接口类似) 那就直接用具体类就好了 Collapse Hierarchy 不必要的委托(A通过B访问C) 那就直接依赖被委托的类(A直接访问C) Inline Class 函数的参数没啥用 删掉没人用的参数 Remove Parameter 函数名听着太抽象 重命名 Rename Method 类和函数只有测试用例用,且这些测试用例业务上并不存在 直接删除就好了 这些没啥用,是说在当前需求中没用, 都是为了某个想象出来的需求做的设计 重复代码 Duplicated Code 是什么 在一个以上的地点看到同样的程序结构 有什么问题 重复代码是万恶之源,会衍生出其他绝大多数的坏味道 最常见的是一旦变化了,到处修改,漏改一个就是bug 场景 同一个类的两个函数含有相同的表达式 把重复的部分提取出一个函数 Extract Method 两个互为兄弟的子类中看到相同的表达式 先 Extract Method 再 Pull Up Method 前两种情况下,两部分的代码相似却不相同 首先提取相同部分 Extract Method 然后制造模板方法,把差异的部分交给子类实现 Form Template Method 有时候,代码确实不同,但其实在做同一件事,只是用了两种算法 用比较清楚的一种替代掉另一种 Substitute Algorithm 两个毫不相关的类里出现重复 先提炼到一个类里去 Extract Class 然后在另一个类里使用 注意:要决定这个函数在哪合适 可能在A 可能在B 也能在第三个类 冗余类 Lazy Class 是什么 不必要存在的类,不管是过度设计还是已经过时了 有什么问题 增加理解复杂度,已经没用的东西要尽早清理 场景 没用的类 其实方法没人用了,也改干掉 把类的代码内联回调用端 Inline Class 没用的子类 把子类和父类合成一个类,直接用 Collapse Hierarchy 莫名其妙的接口 删除接口,直接用具体实现 Collapse Hierarchy 过多的注释 Comments 是什么 因为代码很糟糕,不得不写注释 不是什么 不写注释 有什么问题 掩盖坏味道 场景 需要注释一段代码干了什么 Extract Method 需要注释一个函数干了什么 Rename Method 需要注释说明使用规格 Introduce Assertion 纯数据类 Data Class 是什么 只有数据,没有方法 有什么问题 可能是错误的抽象 场景 一堆public field 没什么好说的,封装起来 Encapsulate Field Encapsulate Collection 只有setter和getter 把使用这些数据的函数慢慢挪到类里 Remove Setting Method Move Method Extract Method Hide Method 例外 起步阶段,你可能不知道有啥未来的方法, 那就先用纯数据类,等业务出现的时候再重构 难以修改 平行的继承体系 Parallel Inheritance Hierarchies 是什么 这种情况下,假设有两个类,没给A增加一个子类,就要给B增加一个子类 有什么问题 还是因为不内聚,而容易出bug 场景 一个体系的前缀和另一个体系的前缀一模一样 先引用 再 Move Method Move Field 虽然类一样,但不继承,还是不要过早下判断 分层架构下Domain层和View层、持久化层虽然会有类似对象,但是不继承 最主要的是,他们往往是为了处理不同的变化 比如一个是业务变化,一个是存储变化 霰弹式修改 Shotgun Surgery 是什么 一种原因改变多个类 优秀的设计是,同一种变化应该只改一个类 最多因为这个类的修改,而逻辑上合理扩散到相关的类 有什么问题 很难找到 也容易遗忘 忘一个就是一个bug 场景 来一个需求,变一处逻辑,要四处修改 把需要修改的代码放到一个类里,如果这个类不存在,就创造一个 手法 Move Method Move Field Inline Class 缺点 会造成发散式变化 比起霰弹式修改,还是好一点 但不是说就不管了 你可以先集中在一处,然后再慢慢重构 发散式变化 Divergent Change 是什么 多个原因改变一个类 优秀的设计是,同一个类应该只响应一种变化 不是什么 一种变化,不是一个变化 多个变化可能属于一种变化,比如业务逻辑修改,数据库替换,这是两种变化 上下文也很重要,这就要结合DDD讲了 有什么问题 违反单一职责,这种类很容易代码耦合,不容易变 也不容易理解 一个业务实体类因为缓存变一变,因为数据库变一变,因为业务逻辑变一变,最后你看这代码想看出业务逻辑到底是啥就比较费劲 场景 一个技术需求和一个业务需求都要改同一个类 不同上下文的业务,都要改同一个类 库存 产品 识别变化原因 然后Extract Class 耦合 依恋情结 Feature Envy 是什么 一个函数对另一个类的数据感兴趣的程度远超过自己所在的类 有什么问题 不够内聚 逻辑分散 场景 就是函数放错地方了,它用到的数据基本全在另一个类里,自己类里的数据反而可能只用一两个 Move Method 一段代码,用到的数据全在另一个类里 先提取函数 Extract Method 再挪到另一个类里去 Move Method 例外 visitor strategy 这是为发散式变化付出的代价 过度耦合的消息连 Message Chains 是什么 a调b,b调c,c调d 只是为了拿个数据 有什么问题 客户端过度了解被调用端结构 被调用端改变,客户端不得不修改 场景 要通过一长串的访问拿到最终对象 识别 看看最终对象到底拿来做啥 这个方法是否属于客户端 是否可以直接把逻辑放到消息链里 或其中一个对象上 手法 Hide Delegate Extract Method Move Method 中间人 Middle Man 是什么 对于委托的滥用 有什么问题 莫名其妙的出现了一个委托,每次查找问题的时候会很困扰,从而难以定位bug 场景 一个类大多数的逻辑都委托给了另一个类 举例 当客户端需要指导两个概念 Person Department 却只能通过Person拿到Department 改为 既能访问Person,也能访问Department 还能通过两者互取对方 手法 Replace Delegation with Inheritance Inline Method Remove Middle Man 狎昵关系 Inappropriate Intimacy 是什么 一个类对另一个类的了解太多了 不管是private的field 还是函数内在逻辑 包括私有函数 以至于自己的逻辑完全建立在对方的逻辑假设之上,从而变得两边隔空耦合 有什么问题 耦合,难以变动 场景 当两个类设计太深,变一个就得改另一个的时候 先通过搬移Method和Field看能不能隔离变化 Move Method Move Field 如果存在双向关联,看看是不是要转为单项关联 Change Bidirectional Association to Unidirectional 看看是不是能提取出一个公共的类,双方都依赖它 Extract Class 或者这个公共的类,来替它们两个通信 Hide Delegate 子类不再适合做子类的时候 除了上述手法,有一个专题手法 Replace Inheritance with Delegation Replace Parameter with Explicit Methods 手法是一样的 先把数据项抽取成对象 Introduce Parameter Object 直接传对象 Preserve Whole Object 这些没啥用,是说在当前需求中没用, 都是为了某个想象出来的需求做的设计 识别变化原因 然后Extract Class 这是为发散式变化付出的代价