首页/开发/代码质量与评审速查/配套实践:优先不可变(const/final 先行)
重构手法62 条

配套实践:优先不可变(const/final 先行)

把能不变的变量都声明为不可变:缩小可变状态范围,审查时一眼分清「会变的地方」;与 Split Variable、Replace Temp with Query 协同使用,副作用面持续收窄。

优先不可变是什么

把能不变的变量都声明为不可变:缩小可变状态的范围,审查时一眼分出哪些值会变。它与拆分变量、以查询取代临时变量协同使用,配合编译器的只读检查,副作用面会持续收窄,推理代码时需要考虑的状态组合也随之减少。

基本信息

常用手段const / final / val / readonly
推进顺序局部变量 → 字段 → 集合内容
收益变更点集中,代码推理更简单
相关手法拆分变量、以查询取代临时变量
语言差异Rust 绑定默认不可变,Java 需要显式修饰

典型使用场景

  • 局部计算结果只在初始化时赋值一次
  • 构造函数注入的依赖在对象生命周期内不变
  • 配置对象在启动加载完成后只读
  • 常量集合对外只读暴露
  • 函数参数按只读方式使用,避免意外回写
  • 审查时先标出会变的状态,其余按不可变处理

三种语言对照

三种语言对照
// JavaScript:优先 const,避免中途重新赋值
const total = items.reduce((sum, item) => sum + item.price, 0);

// Java:局部变量与字段尽量 final
final BigDecimal total = computeTotal(items);

// Rust:绑定默认不可变,需要修改才写 mut
let total = items.iter().map(|i| i.price).sum();

不可变的三个层次

层次做法说明
声明加不可变修饰符局部变量先行,成本最低
字段构造后不再赋值需要显式设计,避免依赖懒初始化
集合内容只读视图或不可变集合防止元素被外部修改
不可变不等于性能差:多数语言对不可变绑定有优化。真正需要评估的是把大对象复制成新实例,而不是禁止重新赋值。
集合声明为不可变只是浅不可变:集合元素本身仍可能被修改。共享出去之前要确认返回的是只读视图还是副本。
先把不可变写足再谈优化:编译期能确定的不变量交给类型系统承担,运行期需要判断的状态就会变少,测试要覆盖的组合数量也随之下降。

易错点

  • 集合声明不可变但元素可变:浅不可变的边界没讲清,共享数据被意外修改
  • 为迁就懒初始化到处保留可变字段:改用显式初始化或惰性访问器
  • 一律改成不可变导致频繁整对象复制:大对象场景应按热点评估
  • 只在局部变量加不可变:字段与集合内容仍是可变入口,收益有限

数据来源与参考

  • 《重构》第 2 版(不可变数据相关手法)与各语言风格指南
​‌‌​​‌​​​‌‌​‌‌‌‌​‌‌​​​‌‌​‌‌‌​​‌‌​​‌​‌‌‌​​‌‌‌​​​‌​‌‌​‌​​‌​‌‌‌​​​‌​‌‌​‌​​‌​‌‌​​​‌‌​‌‌‌​‌​​​‌‌​‌‌‌‌​​‌​‌‌‌​​‌‌​​​‌‌​‌‌​‌‌‌‌​‌‌​‌‌​‌