# 代码之丑 ## 1. 命名 ### 不好的命名方法 - 不精准的命名 - 命名过于宽泛,不能精准描述 - 命名要能够描述出这段代码在做的事情。 - 用技术术语命名 - 是一种基于实现细节的命名方式 - 如:xxxMap、xxxSet ### 命名遵循原则 - 🔥描述意图,而非细节。 - 比如,StartTranslation - 面向接口编程,接口是稳定的,实现是易变的 - 技术名词的出现,往往就代表着它缺少了一个应有的模型 - 一个技术类的项目中,这些技术术语其实就是它的业务语言。 - 但对于业务项目,这个说法就必须重新审视了 - 用业务语言写代码 - 建立团队的词汇表 ### 总结 - 好的命名,是体现业务含义的命名 ## 2. 乱用英语 ### 常见的命名规则 - 类名是一个名词,表示一个对象 - 方法名则是一个动词,或者是动宾短语,表示一个动作 ### 几种乱用英语的点 - 违反语法规则的命名; - 不准确的英语词汇; - 英语单词的拼写错误。 ### 如何从实践层面上更好地规避 - 制定代码规范,比如,类名要用名词,函数名要用动词或动宾短语; - 要建立团队的词汇表; - 要经常进行代码评审。 ## 3. 重复代码 ### 重复代码 - 复制粘贴的代码 - 重复的结构 - if 和 else 代码块中的语句高度类似 ### 消灭重复原则 - Don't Repeat Yourself - 每一处都有单一、明确、权威的描述 ### 实践 - 在一个职业的队伍里,谁发现谁修改 - 可以用设计模式和工具框架去解决 ## 4. 长函数 ### 🔥一个好的程序员面对代码库时要有不同尺度的观察能力,看设计时,要能够高屋建瓴,看代码时,要能细致入微。 ### 长函数的产生 - 以性能为由; - 性能优化不应该是写代码的第一考量 - 部分优化交给编译器 - 可维护性比性能优化要优先考虑 - 平铺直叙; - 一次加一点。 ### 消灭长函数的原则 - 定义好长函数的标准:如最长20行 - 做好『分离关键点』 - 坚守『童子军军规』 - 让营地比你来时更干净 ### 重构手法 - 提取函数 ### 一句话 - 把函数写短,越短越好 ## 5. 大类 ### 大类的产生 - 职责不单一 - 字段未分组 ### 所谓的将大类拆解成小类,本质上在做的工作是一个设计工作 ### 软件设计原则 - 单一职责 ### 实践 - 对象健身操 - 聚合层扮演的角色其实一个防腐层,它本身的职责就是和请求应答去做一一应对。一般来说,这样类行为很单一,主要的职责就是数据转换。对于这种类,大一点是可以的,因为它不会对业务造成什么影响。重点在于,这个类里没有业务。 - 重点是,简单的聚合并不是一个好办法,而是需要谨慎地设计通信协议,这才是保证类比较小的根本办法。 ### 一句话 - 把类写小,越小越好。 ## 6. 长参数列表 ### 参数数量多导致的长参数 - 变化频率相同 - 将参数列表封装成一个类 - 一个模型的封装应该是以行为为基础的 - 多个变化频率 - 封装成多个类 - 静态不变 - 成为软件结构的一部分 - 方法:动静分离 - 分离关注点 - 动数据(bookId)和静数据(httpClient 和 processor),是不同的关注点,应该分离开来 - 静态不变的数据完全可以成为这个函数所在类的一个字段,而只将每次变动的东西作为参数传递 ### 标记(flag)参数导致的长参数 - 标记(flag)存在的形式 - 布尔值 - 枚举值 - 直接的字符串或者整数 - 根据标记参数的不同拆分成多个函数 - 重构的手法 - 移除标记参数(Remove Flag Argument) ### 一句话 - 减小参数列表,越小越好。 ## 7. 滥用控制语句 ### 嵌套的代码 - 重构手法 - 以卫语句取代嵌套的条件表达式(Replace Nested Conditional with Guard Clauses) ### 重复的 switch(Repeated Switch) - 重构的手法 - 以多态取代条件表达式(Relace Conditional with Polymorphism) ### 衡量代码复杂度常用的标准 - 圈复杂度(Cyclomatic complexity,简称 CC) - 圈复杂度越高,代码越复杂,理解和维护的成本就越高。 - 工具:Checkstyle ## 8. 缺乏封装 ### 过长的消息链(Message Chains) - 火车残骸(Train Wreck) - 链式调用不一定都是火车残骸。比如 builder 模式,每次调用返回的都是自身,不牵涉到其他对象,不违反迪米特法则。 - 火车残骸的代码和流畅(Fluent)代码的重要差别是,火车残骸描述的具体怎么做,而流畅(Fluent)代码描述的是做什么,是声明式的。 - 重构手法 - 隐藏委托关系(Hide Delegate) - 指导原则 - 迪米特法则(Law of Demeter) - 按照迪米特法则这样写代码,可能让代码里有太多简单封装的方法?不过,这也是单独解决这一个坏味道可能带来的结果。这种代码的出现,根本的问题是缺乏对封装的理解,而一个好的封装是需要基于行为的,所以,如果把视角再提升一个角度,我们应该考虑的问题是类应该提供哪些行为,而非简简单单地把数据换一种形式呈现出来。 ### 以基本类型为模型的坏味道称为基本类型偏执(Primitive Obsession) - 重构手法 - 以对象取代基本类型(Replace Primitive with Object) - 也就是提供一个模型代替原来的基本类型 ### 封装之所以有难度,主要在于它是一个构建模型的过程 - 作为这个类的使用者,你并不需要知道这个类到底是怎么实现的 - 有任何业务上的调整,都会发生在类的内部,只要保证接口行为不变,就不会影响到其它的代码。 ## 9. 可变的数据 ### 可变数据最直白的体现就是各种 setter - 问题 - 相比于读数据,修改是一个更危险的操作 - 比可变的数据更可怕的是,不可控的变化 - 缺乏封装再加上不可控的变化,setter 几乎是排名第一的坏味道 - 重构手法 - 移除设值函数(Remove Setting Method) ### 在实践中,完全消除可变数据是很有挑战的 - 一个实际的做法是,区分类的性质。 - 值对象就要设计成不变类 - 所有的字段只在构造函数中初始化; - 所有的方法都是纯函数; - 如果需要有改变,返回一个新的对象,而不是修改已有字段。 - 实体类则要限制数据变化 ### 函数式编程的本质 - 对于赋值进行了约束 - 很多编程语言都引入了值类型,而让变量成为次优选项 ### 全局数据 - 一个与数据相关的坏味道 ### 一句话 - 限制可变的数据 ## 10. 变量声明与赋值分离 ### 变量的初始化 - 问题 - 先声明后赋值 - 变量初始化与业务处理混在一起 - 基本原则 - 变量一次性完成初始化 - 尽可能使用不变的量 ### 集合初始化 - 问题 - 先声明后添加元素 ### 声明式代码 - 区别 - 命令式的代码 - 告诉你 “怎么做” 的代码 - 声明一个集合,然后添加一个元素,再添加一个元素 - 声明式的代码 - 告诉你 “做什么” 的代码 - 要一个包含了这两个元素的集合 - 总结 - 声明式的代码体现的意图,是更高层面的抽象,把意图和实现分开,从某种意义上来说,也是一种分离关注点 - 用声明式的标准来看代码,是一个发现代码坏味道的重要参考 ### 一句话 - 一次性完成变量的初始化 ## 11. 依赖混乱 ### 缺少防腐层 - 让请求对象传导到业务代码中,造成了业务与外部接口的耦合 - 通过防腐层将外部系统和核心业务隔离开来 ### 依赖倒置原则 - 高层模块不应依赖于低层模块,二者应依赖于抽象。 - High-level modules should not depend on low-level modules. Both should depend on abstractions. - 抽象不应依赖于细节,细节应依赖于抽象。 - Abstractions should not depend on details. Details (concrete implementations) should depend on abstractions. ### 其他 - 习惯于用业务术语去表达,而不仅仅是技术术语。封装是为了概念,而不单纯是为了封装。 ### ArchUnit - 把架构层面的检查做成了单元测试 ### 一句话 - 代码应该向着稳定的方向依赖 ## 12. 不一致的代码 ### 命名中的不一致 - 类似含义的代码应该有一致的名字 - 一旦出现了不一致的名字,通常都表示不同的含义 ### 方案中的不一致 - 应对同一个问题出现了多个解决方案 - 一般是随时间演化造成的 - 随着时间流逝,有新的解决方案了,而原有解决方案虽存在的各种问题,但已深入人心 ### 代码中的不一致 - 不要测私有方法 - 🔥很多程序员纠结的技术问题,其实是一个软件设计问题,不要通过奇技淫巧去解决一个本来不应该被解决的问题。 ### 应对策略 - 团队统一编码风格 - 团队统一编码解决方案 ### 一句话 - 保持代码在各个层面上的一致性 ## 13. 落后的代码风格 ### Java8 Optional - 避免引发空指针问题 - 示例: author?.name ### 函数式编程 - 循环语句是在描述实现细节,而列表转换的写法是在描述做什么,二者的抽象层次不同。 - 列表转换的本身就完全变成了一个声明,这样的写法才是能发挥出列表转换价值的写法。 ### 编程规则 - 声明式编程 ### 一句话 - 不断学习 “新” 的代码风格,不断改善自己的代码