精准表达:如何避免宽泛的命名
核心问题
命名过于宽泛,无法精准描述功能,是代码难以理解的根源。
三大命名陷阱
1. 不精准的命名
- 问题:
processChapter
等函数名过于宽泛,无法准确描述具体功能 - 常见宽泛词汇:data、info、flag、process、handle、build、maintain、manage、modify
- 解决:描述意图而非细节,如用
startTranslation
替代changeChapterToTranslating
2. 技术术语命名
- 问题:基于实现细节命名,如
bookList
、xxxMap
、xxxSet
- 后果:违反面向接口编程原则,实现易变时命名失效
- 根因:缺少应有的业务模型
3. 忽视业务语言
- 问题:使用技术术语而非业务语言
- 解决:建立团队词汇表,确保业务理解一致
- 价值:通过业务分析发现更高级的命名问题
核心原则
- 意图导向:好的名字应该描述意图,而非细节
- 业务语言:使用业务语言而非技术术语写代码
- 稳定性原则:命名应该基于稳定的接口而非易变的实现
- 团队一致:建立统一的业务词汇表
实践指南
- 命名时优先思考代码的真实意图
- 将技术术语从业务代码中隔离
- 建立并维护团队业务词汇表
- 通过代码评审强化命名规范
英语语法:写出符合英语规范的代码
核心挑战
程序员需要对程序中用到的英语有基本感觉,编写符合英语语法规则的代码。
三大英语问题
1. 违反语法规则
- 基本规则:类名是名词,方法名是动词或动宾短语
- 常见错误:
completedTranslate
应改为completeTranslation
- 解决方法:利用字典网站验证词性的正确性
- 要求:函数名必须符合动宾结构
2. 词汇选择不准确
- 问题根源:同样中文可能对应多个英文单词
- 典型案例:
audit
(审计)vsreview
(审核)的准确区分 - 解决方案:建立业务词汇表,包含中英文对照和特定场景含义
- 决策原则:使用集体智慧而非个体主观判断
3. 拼写错误
- 常见问题:
field
错拼为filed
导致理解困惑 - 工具支持:现代 IDE 提供拼写检查功能
- 反映问题:程序员英语感觉不够敏锐
解决策略
- 制定明确规范:规定不同语言元素的词性要求
- 建立词汇表:确保术语使用的一致性和准确性
- 利用工具:使用 IDE 拼写检查避免低级错误
- 团队约定:统一英语标准,避免个人偏好差异
核心原则
- 最低要求:写出来的代码要像在用英语表达
- 语法为先:严格遵循英语语法规则
- 准确第一:选择最准确的英语词汇,而非最复杂的
- 团队统一:建立团队级别的英语使用标准
重复代码:一个逻辑多处修改的根源
核心问题
重复代码导致简单需求需要多处修改,容易遗漏且易出错。
三种典型重复
1. 复制粘贴的代码
- CVS 式编程:Ctrl+C、Ctrl+V、Ctrl+S 的危害
- 问题:一个逻辑修改需要在多处同步更改
- 风险:容易遗漏修改点,留下潜在问题
- 正确做法:先提取函数,再在需要的地方调用
2. 重复的结构
- 表现:三个任务函数具有相同的 try-catch 结构
- 特点:业务逻辑不同,但处理模式相同
- 解决:通过提取 executeTask 函数消除结构重复
- 关键认知:动词不同不代表没有重复
3. if-else 语句的高度相似
- 问题:if 和 else 代码块几乎一模一样,只有参数不同
- 根因:if 判断的是参数而非动作
- 解决:提取变量或函数来表达真正的选择意图
- 目标:让 if 语句做真正的选择而非参数选择
DRY 原则应用
- 核心要求:每一处知识都必须有单一、明确、权威的表述
- 识别能力:需要对软件设计和分离关注点有更好的理解
- 主动发现:时刻保持对重复的敏感度
实践指南
- 避免复制粘贴:第一时间提取函数
- 识别结构重复:关注处理模式的相似性
- 重构相似分支:让条件判断表达真正的业务选择
- 持续重构:发现重复立即消除
长函数:函数长度容忍度决定代码质量
核心问题
对函数长度容忍度高是导致长函数产生的关键点。
长度标准与观察力
合理的函数长度标准
- 动态语言:5 行左右
- 静态语言:10 行以内
- 项目标准:不超过 20 行
- 工具强制:使用 CheckStyle 等工具执行长度限制
观察尺度的重要性
- 设计层面:高屋建瓴,把握整体架构
- 代码层面:细致入微,关注具体实现
- 核心能力:能够在不同尺度间自由切换
长函数产生的三大原因
1. 以性能为由
- 错误观念:性能优化是写代码的第一考量
- 正确认知:可维护性优先,性能问题出现时再优化
- 指导原则:先让代码工作,再让代码正确,最后让代码快速
2. 平铺直叙
- 问题表现:多个业务流程和不同层面细节混在一个函数
- 根本原因:“分离关注点"没有做好
- 解决方法:分离不同业务处理流程和不同层次处理细节
3. 一次加一点
- 累积效应:无意识的代码累积导致函数逐渐变长
- 团队问题:每个人都没做错但结果很糟糕
- 预防措施:遵循"童子军军规” - 让营地比来时更干净
解决方案
- 提取函数:最基本的重构手法
- 分离流程:区分不同的业务处理流程
- 分离层次:区分不同层次的处理细节
- 持续重构:每次修改都让代码变得更好
实践目标
- 把函数写短,越短越好
- 建立严格长度标准并工具化执行
- 培养细致入微的代码观察能力
大类:分而治之降低认知复杂度
核心问题
大类超过了一个人的理解范畴,导致顾此失彼,需要拆分成小类。
模块划分的本质
人类理解的局限性
- 认知限制:没有人能同时面对所有细节
- 分而治之:模块划分本质是降低认知复杂度
- 语言支持:文件、类、包、命名空间等都是模块划分方案
大类的根本问题
- 超出理解范畴:包含内容过多,无法全面掌控
- 影响决策:顾此失彼在所难免
大类产生的两大原因
1. 职责不单一
- 典型案例:User 类包含普通用户、作者、编辑三种角色信息
- 问题分析:不同角色的信息对其他角色没有意义
- 违反原则:单一职责原则,导致任何业务变动都要修改类
- 解决方案:拆分出 User、Author、Editor 三个独立的类
2. 字段未分组
- 典型案例:基本信息(userId、name)vs 联系方式(email、phoneNumber)
- 问题分析:同一个类中的字段不属于同一种类型的信息
- 变化频率:不同信息的稳定程度和变化频率不同
- 解决方案:引入 Contact 类封装联系方式信息
拆分策略
- 基于单一职责:分析不同内容的变动原因
- 识别变化维度:将不同变化原因的内容分离
- 引入新模型:为分离出的内容设计恰当的类
- 合理组织:利用语言特性管理拆分后的小类
核心原则
- 把类写小,越小越好
- 拆解本质是设计工作:需要深入理解业务
- 学好软件设计:是写好代码的基础
长参数列表:减少参数数量的三种策略
核心问题
人脑能够掌握的内容有限,长参数列表超出了人的把控能力。
三种解决策略
1. 聚沙成塔(封装成对象)
- 方法:将相关参数封装成一个参数类,如 NewBookParameters
- 增强行为:新模型应该有自己的行为,如 newBook()方法
- 稳定性:需求扩展时参数列表保持稳定
- 前提条件:参数属于一个类,有相同的变化原因
2. 动静分离
- 核心思想:识别动态数据(每次都变)和静态数据(不变的)
- 处理方式:静态数据成为类的字段,只传递变动的数据作为参数
- 典型案例:bookId(动态)vs httpClient 和 processor(静态)
- 根本问题:缺乏应有的软件结构导致静态部分以动态参数传递
3. 告别标记参数
- 问题识别:标记参数代表不同的处理流程
- 常见形式:布尔值、枚举值、字符串或整数形式的标记
- 解决方法:通过拆分函数将不同路径分离
- 重构手法:使用"移除标记参数"重构手法
设计原则
- 以行为为基础:模型的封装应该以行为为基础
- 分离关注点:不同变化频率的数据是不同的关注点
- 职责单一:标记参数往往意味着函数承担了多重职责
核心目标
- 编写"短小"的代码:通用的解决方案
- 减少参数数量:关键方向
- 提高表达性:让函数意图更清晰
控制语句滥用:简化分支降低复杂度
核心认知
控制语句是坏味道的高发地带,出现控制结构多半是错误的提示。
三大控制语句问题
1. 嵌套的代码
- 问题表现:多层缩进导致代码复杂度大幅增加
- 根本原因:平铺直叙写代码的问题体现
- 解决方案:提取单个元素操作,for 循环内容提取成函数
- 目标标准:函数至多有一层缩进
2. if 和 else 语句的问题
- 卫语句概念:Guard Clause 取代嵌套条件表达式
- else 的问题:else 也是一种坏味道,可通过提前返回消除
- 重构手法:以卫语句取代嵌套的条件表达式
- 实现方式:每个逻辑分支提前返回,避免 else 的使用
3. 重复的 Switch
- 问题识别:相同的 switch 逻辑在多处出现
- 本质问题:缺少了一个应有的模型
- 解决方案:以多态取代条件表达式
- 具体实现:引入 UserLevel 接口,用不同实现类替代 switch 分支
复杂度控制
圈复杂度概念
- 定义:衡量代码复杂度的重要标准
- 影响:分支过多会使复杂度大幅增加
- 控制:使用工具检查圈复杂度,设定合理阈值
核心原则
- 简化分支:循环和选择语句可能都是坏味道
- 降低嵌套:嵌套和 else 会使代码变得复杂
- 模型优先:用多态替代重复的条件判断逻辑
解决策略
- 提取函数:降低单个函数的复杂度
- 卫语句:用提前返回替代嵌套条件
- 多态替代:用继承和多态消除重复 switch
- 工具检查:使用静态分析工具控制复杂度
缺乏封装:构建真正的业务模型
核心问题
封装失败导致必须了解类的实现细节才能写出代码。
两大封装问题
1. 火车残骸(过长的消息链)
- 问题表现:一行代码中连续多个方法调用,如
book.getAuthor().getName()
- 根本问题:必须了解类的实现细节才能写代码,封装失败
- 解决方案:隐藏委托关系重构手法,如增加
getAuthorName()
方法 - 指导原则:迪米特法则(只与直接朋友交谈,不与陌生人交谈)
2. 基本类型偏执
- 问题实质:大量使用基本类型(如 double、String)而非领域模型
- 缺失模型:忽略了业务约束,缺少应有的模型
- 典型案例:Price 类型应该大于 0,需要特定的显示格式
- 解决方案:以对象取代基本类型重构手法
真正的封装
封装的本质
- 不是数据结构:封装不是简单的数据结构加算法
- 少暴露细节:要想摆脱初级程序员水平,从少暴露细节开始
- 基于行为:基于行为而非数据设计类
- 构建模型:封装是一个构建模型的过程
设计原则
- 迪米特法则:只与直接朋友交谈,减少对象间的耦合
- 行为优先:优先考虑行为而不是数据结构
- 业务模型:识别并封装业务概念,避免基本类型滥用
能力提升要点
- 模型意识:很多程序员写程序只是完成功能,缺乏构建模型的意识
- 差异识别:看到模型相同之处的同时,也要识别差异之处
- 设计学习:学习软件设计是成为更好程序员的必经之路
可变数据:限制数据变化,拥抱不变性
核心问题
比可变数据更可怕的是不可控的变化。
可变数据的三个层面
1. 满天飞的 Setter
- 封装破坏:setter 暴露了实现细节,破坏封装
- 危险操作:相比读数据,修改是更危险的操作
- 不可控变化:setter 带来的不可控变化比可变数据本身更可怕
- 解决方案:移除设值函数,用行为封装替代直接修改
2. 可变数据的新认知
- 行业进步:《重构》第二版新增,反映行业对编程的新理解
- 函数式思想:数据不变,需要更新时产生新副本
- 有效解决:不变性有效解决可变数据产生的各种问题
3. 不变类的设计
- 三要点:构造函数初始化、纯函数方法、返回新对象
- 完全挑战:完全消除可变数据很有挑战性
- 分类处理:区分实体对象和值对象,采用不同策略
实践策略
对象分类处理
- 实体对象:限制数据变化,控制修改范围
- 值对象:设计成不变类,创建后状态不可改变
- 现代趋势:语言引入值类型,变量成为次优选项
工具支持
- Lombok 使用:禁用@Setter 和@Data 注解
- IDE 工具:利用现代开发环境的支持
- 约束赋值:函数式编程对程序中赋值进行约束
设计原则
- 行为封装:通过行为而非直接修改来改变状态
- 不变优先:在可能的情况下设计不变类
- 分类管理:区分实体和值对象的不同处理方式
- 工具约束:使用工具强制执行不变性约束
变量初始化:一次性完成,避免业务混杂
核心问题
变量初始化与业务处理混杂,代码不清晰。
变量初始化的问题
声明与赋值分离
- 问题表现:变量声明时赋 null 值,真正的值在后续处理中才确定
- 代码混杂:变量初始化与业务处理混在一起
- 解决原则:变量一次性完成初始化
- 具体方法:提取函数,将转换逻辑封装起来
编程习惯的时代烙印
- 历史遗留:很多人的编程习惯留在了古老的年代(C 语言限制)
- 现代要求:现代编程应该追求更清晰的代码组织
- 风格进步:学习编程不仅要实现功能,编程风格也要与时俱进
不变性的深入应用
final 约束的使用
- 变量声明:使用 final 关键字限制变量的赋值
- 参数声明:方法参数尽量使用 final 约束
- 字段声明:类字段在可能的情况下使用 final
- 适用原则:在能够使用 final 的地方尽量使用 final
集合初始化的改进
- 传统方式:声明集合后逐个添加元素
- 现代方式:使用 List.of()或 ImmutableList.of()一次性创建
- 不变集合:创建后无法修改,更安全
- 性能考虑:不变集合在多数场景下性能足够
编程风格的转变
从命令式到声明式
- 命令式:描述"怎么做"的编程风格
- 声明式:描述"做什么"的编程风格
- 评判标准:用声明式的标准来看代码,是发现坏味道的重要参考
- 实践价值:声明式代码在表达性上要好得多
实践指南
- 一次性初始化:将变量初始化与业务逻辑分离
- final 约束:通过 final 约束提高代码安全性
- 现代特性:利用现代语言特性,采用声明式编程风格
- 工具支持:使用不变集合等现代库的支持
依赖混乱:代码应向稳定方向依赖
核心问题
依赖关系混乱会导致系统架构不稳定,代码难以维护。
两种典型依赖问题
1. 缺少防腐层
- 问题表现:Resource 层直接将请求对象传给 Service 层
- 层次混乱:NewBookRequest 既不适合属于 Resource 层也不适合 Service 层
- 解决方案:引入 NewBookParameter 作为 Service 层参数
- 防腐层作用:Resource 层是外部请求和核心业务间的防腐层
2. 业务代码里的具体实现
- 问题识别:业务代码中直接使用 feishuSender 等具体实现
- 违反原则:违反了依赖倒置原则,高层模块不应依赖低层模块
- 识别标准:业务代码中任何与业务无关的东西都是潜在坏味道
- 解决方案:引入接口抽象,通过依赖注入隔离具体实现
依赖方向原则
稳定性导向
- 核心原则:代码应该向着稳定的方向依赖
- 业务 vs 实现:业务的稳定性要比外部接口高
- 识别方法:问自己如果不用它,是否还有其它选择
- 反向依赖:反向依赖会带来系统不稳定
依赖倒置原则
- 高层模块:不应依赖于低层模块
- 抽象依赖:二者应依赖于抽象
- 接口隔离:通过接口将业务与具体实现分离
架构保障机制
工具自动化检查
- ArchUnit 工具:进行架构层面的单元测试
- 架构规则:定义代码层次间依赖关系的约束规则
- 自动化测试:通过自动化测试保证代码依赖关系的正确性
- 持续集成:将架构规则检查集成到 CI/CD 流程
实践指南
- 防腐层设计:将不同层次的关注点分离
- 依赖抽象:遵循依赖倒置原则,依赖抽象而非具体实现
- 工具检查:使用工具自动化检查架构规则的遵循情况
- 持续重构:及时发现和修正违反依赖规则的代码
代码一致性:降低团队认知成本的关键
核心问题
对于团队而言,一致性是非常重要的,即便不甚理想的标准也比百花齐放要好。
三个层面的不一致
1. 命名中的不一致
- 问题案例:DistributionChannel 枚举中 WEBSITE vs KINDLE_ONLY 的命名问题
- 基本原则:类似含义的代码应该有一致的名字
- 识别标准:不一致的名字通常表示不同的含义
- 解决方案:统一命名风格,考虑整体中的角色
2. 方案中的不一致
- 典型问题:新旧 Java 日期时间解决方案混用
- 产生原因:时间演化、程序员个人选择、类似程序库并存
- 影响范围:一个项目中对同一问题出现多个解决方案
- 解决策略:团队约定统一的技术选型和编码标准
3. 代码层次的不一致
- 问题表现:业务动作与实现细节混在同一函数中
- 根本原因:不同层次的代码写在一起
- 解决方法:通过提取方法分离不同层次的关注点
- 进一步优化:引入新模型(如 TranslationEngine)优化结构
一致性的价值
认知成本的降低
- 团队效率:一致性降低团队认知成本
- 标准优先:即便不甚理想的标准也比百花齐放要好
- 学习成本:减少团队成员学习和适应不同风格的时间
维护性的提升
- 可预测性:一致的代码风格提高代码的可预测性
- 协作效率:团队成员更容易理解和修改他人代码
分离关注点的核心作用
- 基本功:能够分清楚代码处于不同的层次,基本功还是分离关注点
- 设计问题:很多程序员纠结的技术问题,其实是一个软件设计问题
- 层次清晰:通过分离关注点保持代码层次的清晰
实践指南
- 建立标准:建立团队编码规范和技术选型标准
- 工具支持:使用工具强制执行一致性标准
- 分离关注点:通过分离关注点保持代码层次的清晰
- 设计思维:不要用技术手段解决设计问题
现代代码风格:拥抱新语言特性升级代码
核心问题
新的语言特性都是为了提高代码的表达性,减少犯错误的几率。
两大现代特性应用
1. Optional 解决空指针问题
- 历史问题:空指针被发明者称为"十亿美元错误"
- 解决方案:通过对象容器强制进行 null 检查
- 团队约定:所有可能为 null 的返回值都要返回 Optional
- 链式调用:提供更简洁的链式调用方式,如 map 和 orElse
2. 函数式编程的列表转换
- 观念转变:循环语句成为坏味道,函数式编程提供更好替代方案
- 核心操作:map、filter、reduce 等转换操作
- 表达方式:Stream API 将命令式代码转为声明式代码
- 表达性提升:描述"做什么"而非"怎么做"
编程风格的演进
从命令式到声明式
- 命令式编程:详细描述执行步骤的传统编程风格
- 声明式编程:描述"做什么"而非"怎么做"的现代风格
- 表达性对比:声明式代码在表达性上要好得多
- 实现要求:lambda 应该只有一行代码,复杂逻辑提取为函数
现代编程的优势
- 安全性:新语言特性用更安全的方式编写代码
- 表达性:代码表达性和可读性显著提升
- 维护性:避免不同时代编程风格并存造成理解困难
学习态度
- 持续学习:不断学习"新"的代码风格,不断改善代码
- 风格演进:学习编程不仅要实现功能,编程风格也要与时俱进
- 团队推广:在团队中推广现代编程特性和最佳实践
代码评审:沟通反馈过程的最佳实践
核心认知
代码评审的本质是沟通反馈的过程,不仅仅是发现问题。
代码评审的本质
沟通反馈过程
- 知识分享:将隐藏在个人头脑中的知识共享到团队
- 集体智慧:规避个体问题的主要方式是使用集体智慧
- 反馈原则:尽可能透明、尽可能及时
- 不止挑错:不仅仅是发现问题,更是团队知识传递
评审的三个维度
- 实现方案的正确性:设计评审无法覆盖的细节问题
- 算法的正确性:复杂度问题经常在工程中被忽略
- 代码的坏味道:很多团队容易忽略,标准过低的关键问题
频率与效果的关系
频率的重要性
- 建议频率:提升评审频率,比如每天评审一次
- 周期问题:周期过长导致累积问题增多,产生无力感
- 频率价值:每个周期代码有限,有心力修改
- 极致实践:结对编程(随时随地代码评审)
常见问题识别
- 正常 vs 异常:正常情况一切顺利,异常情况却考虑不足
- 算法复杂度:在面试中重视,在实际工作中常被忽略
- 坏味道识别:需要建立明确的代码质量标准
实践原则
- 尽可能多暴露问题:代码评审暴露的问题越多越好
- 尽可能高频率:频率越高越好,降低单次复杂度
- 知识共享优先:将代码评审视为团队学习过程
- 及时反馈:只有及时的沟通反馈,才有可能实现短小的原则
新需求应对:保持嗅觉,谨慎变动
核心问题
写代码时需要时时刻刻保持嗅觉,谨慎对待系统核心部分的变动。
两个实战案例分析
案例一:驳回功能的实现
- 需求背景:驳回审核通过的章节
- 现有基础:审核通过(PUT)和审核不通过(DELETE)接口
- 嗅觉敏感:对新增接口保持谨慎
- 解决思路:复用现有审核不通过接口,统一后续处理逻辑
案例二:定时提交功能的实现
- 需求背景:作品按设定时间自动提交(连续签到激励)
- 嗅觉警觉:对改动实体保持警惕
- 分析过程:调度信息与核心业务实体的职责分离
- 最终方案:引入 ChapterSchedule 模型 + 为 Chapter 增加 submittedAt 字段
核心设计原则
接口谨慎原则
- 基本认知:接口是系统暴露的能力,一旦提供就难以收回
- 谨慎态度:必须对新增接口保持谨慎
- 优先策略:优先考虑复用现有接口而非增加新接口
- 思考习惯:当想对外提供接口时,问自己真的要提供新接口吗?
实体稳定原则
- 核心地位:对于业务系统而言,实体是最核心部分
- 谨慎思考:对实体的改动必须有谨慎的思考
- 连锁影响:随意修改实体必然伴随其它部分调整
- 稳定要求:避免导致系统难以稳定
职责分离原则
- 分离标准:不同变化原因的信息应该分离到不同模型
- 职责清晰:深入理解单一职责原则对程序员非常必要
- 模型设计:通过引入新模型来分离不同的关注点
实践指南
- 保持嗅觉:时时刻刻对核心部分变动保持敏感
- 接口复用:优先考虑复用现有接口
- 职责分离:区分真正的业务属性和场景特定的信息
- 谨慎变动:对接口和实体的变动要格外小心
重构的本质:无脑模式匹配和刻意练习
核心认知
重构就是一个无脑模式匹配的事:识别坏味道 → 应用重构手法。
重构的本质理解
模式匹配过程
- 简单操作:重构不需要玄妙理论和含混美学,只需机械重复和模式匹配
- 明确标准:《重构》提供的 24 项坏味道形成了明确的代码质量检查清单
- 对应解决:坏味道提供具体症状,对应的重构手法提供解决方案
- 可操作性:机械重复和简单模式匹配,具备完全的可操作性
正确的使用方法
- 打开代码:任意一段代码(自己刚写的或即将修改的)
- 遍历坏味道:翻开《重构》第三章,遍历每个坏味道
- 识别问题:识别代码中是否存在坏味道
- 应用手法:如有则遵循对应重构手法进行重构
判断力的培养
刻意练习的重要性
- 练习环境:缺乏在受控环境下的刻意练习,很难通过工作中自然积累提升判断力
- 学习方法:如同古董店学徒先看真货再识假货
- 正确构造:正确的代码构造并非无穷无尽,几十个常见模式已几乎覆盖所有场景
工程实践支撑
- 对象健身操:Jeff Bay 提出的 9 项严格编码规则,强制以面向对象风格编程
- 极限编程:自动化测试、持续集成、集体代码所有制等实践
- 预防为主:从一开始就以合理方式编程,使坏味道不要出现
核心观点
- 标准明确:代码质量不应用个人好恶或含混的代码美学来判断
- 判断力培养:每位实践者必须培养判断力,学会判断类和函数的合适大小
- 预防优先:从一开始就以合理的方式编程才是负责任的程序员应该采取的工作方式
- 知行合一:一位"知行合一"的程序员最终会发现,极限编程是唯一合理且有效的软件开发方法
实践建议
- 建立标准:建立明确的代码质量标准,而非依赖个人审美
- 刻意练习:通过刻意练习和正确代码构造的学习来提升判断力
- 工程实践:采用极限编程等工程实践来预防坏味道的产生
- 系统应用:系统性地识别坏味道并应用对应的重构手法
真实代码评审:从实践中学习坏味道识别
核心价值
通过真实代码评审可以发现许多细微但重要的问题,这些问题需要通过系统性的重构来解决。
典型坏味道的发现
已知坏味道识别
- setter 问题:初始化过程中使用 setter,应用完整构造函数替代
- 命名问题:userIndex vs userId,ProcessItemservice 拼写错误等
- 缩写问题:ProcessTxtServiceImpl 中 Txt 造成理解障碍
- 过度设计:不必要的接口层次和 Impl 命名习惯
命名质量问题
- 得不偿失:仅仅省了一个字母,却造成了理解上更大的障碍
- 历史遗毒:早期编程习惯(如用 I 和 Impl 命名)应该避免
- 缩写避免:尽量不要用缩写,完整单词更有表达力
static 函数的重构演示
static 的本质问题
- 全局性质:从本质上说,static 函数是全局函数,static 变量是全局变量
- 测试困难:static 函数和变量不好测试
- 依赖注入困难:难以进行依赖管理和控制
安全重构步骤
- 去掉私有构造函数:移除阻止实例化的限制
- 调用点改为对象调用:将静态调用改为实例调用
- 引入字段保存对象:在使用类中引入字段
- 通过构造函数传入依赖:建立依赖注入
- 最终消除 static 关键字:完成 static 到实例的转换
重构的核心原则
安全重构理念
- 小步操作:重构不是大开大合的过程,而是细小而微的操作
- 每步可停:只有这样一点一点调整,代码足够安全,每一步都是能够停下来的
- 测试保障:真正能给予我们修改有效性回答的是单元测试
其他常见问题
- Singleton 模式:尽可能不用,处理方法类似 static 消除
- 文档问题:README 信息量不足或过度个人化
- 表达能力:程序员需要学会用代码和文字表达
实践指南
- 避免缩写:避免使用技术术语命名和不必要的缩写
- 安全重构:通过安全的重构步骤逐步消除问题代码
- 测试先行:重视单元测试作为重构有效性的保障
- 表达能力:重视文档和表达能力的培养