全局数据(Global Data):坏味道识别与重构实战指南
全局数据(Global Data):坏味道识别与重构实战指南
24种代码坏味道系列 · 第5篇
1. 开篇场景
你是否遇到过这样的代码:多个函数都在使用全局变量 globalUserName、globalUserCount,当你修改一个函数时,不知道会不会影响其他函数,就像在一个共享的记事本上写字,不知道其他人是否也在同时修改?
1 | std::string globalUserName; |
这就是全局数据的典型症状。全局变量可以在任何地方被访问和修改,导致代码之间的隐式依赖关系,就像房间里有一个所有人都能随意使用的开关,你不知道什么时候会被谁打开或关闭。
当你需要测试某个函数时,必须确保全局变量处于正确的状态。当你修改全局变量时,很难确定会影响哪些函数。更糟糕的是,多线程环境下,全局变量会导致竞态条件。
2. 坏味道定义
全局数据是指使用全局变量存储状态,导致代码之间产生隐式依赖,难以测试和维护。
就像一个没有锁的共享储物柜,任何人都可以随意存取物品,你不知道物品什么时候会被拿走或替换。
核心问题:全局数据破坏了封装性,使得代码之间的依赖关系不明确,增加了理解和维护的难度。
3. 识别特征
🔍 代码表现:
- 特征1:在文件作用域定义的变量(非
const) - 特征2:多个函数访问同一个全局变量
- 特征3:函数的行为依赖于全局变量的状态
- 特征4:测试时需要设置全局变量的初始状态
- 特征5:使用
extern关键字在多个文件间共享变量
🎯 出现场景:
- 场景1:快速原型开发时,使用全局变量传递数据
- 场景2:从C语言迁移到C++时,保留了全局变量的习惯
- 场景3:单例模式使用不当,变成了全局变量
- 场景4:配置信息使用全局变量存储
💡 快速自检:
- 问自己:这个变量是否真的需要在全局作用域?
- 问自己:如果删除这个全局变量,需要修改多少函数?
- 工具提示:使用静态分析工具检测全局变量的使用情况
4. 危害分析
🚨 维护成本:修改全局变量需要检查所有使用处,时间成本增加60%
⚠️ 缺陷风险:全局状态导致不可预测的行为,bug风险增加80%
🧱 扩展障碍:添加新功能时需要考虑全局状态的影响
🤯 认知负担:需要理解全局状态才能理解函数行为,心理负担重
5. 重构实战
步骤1:安全准备
- ✅ 确保有完整的单元测试覆盖
- ✅ 创建重构分支:
git checkout -b refactor/encapsulate-global-data - ✅ 使用版本控制,便于回滚
步骤2:逐步重构
重构前(问题代码)
1 | // 坏味道:全局变量 |
问题分析:
- 全局变量可以在任何地方被修改,难以追踪
- 函数的行为依赖于全局状态,难以测试
- 多线程环境下不安全
重构后(清洁版本)
1 | // ✅ 使用类封装状态 |
关键变化点:
封装全局变量(Encapsulate Variable):
- 将全局变量封装到
UserSession类中 - 通过方法控制对状态的访问
- 将全局变量封装到
提高可测试性:
- 每个
GoodExample实例有自己的session - 测试时可以创建独立的实例,互不影响
- 每个
提高线程安全性:
- 每个实例有自己的状态,减少共享状态
步骤3:重构技巧总结
使用的重构手法:
- 封装变量(Encapsulate Variable):将全局变量封装到类中
- 用类替换数据值(Replace Data Value with Object):将数据组合成对象
注意事项:
- ⚠️ 如果全局变量是配置信息,考虑使用配置类或依赖注入
- ⚠️ 如果全局变量是常量,可以使用
const或constexpr - ⚠️ 重构后要更新所有使用全局变量的地方
6. 预防策略
🛡️ 编码时:
即时检查:
- 避免在文件作用域定义非
const变量 - 使用类封装相关状态
- 优先使用局部变量和参数传递
- 避免在文件作用域定义非
小步提交:
- 发现全局变量时,立即封装
- 使用”封装变量”重构,保持状态局部化
🔍 Code Review清单:
重点检查:
- 是否有全局变量(非
const) - 全局变量是否可以被封装
- 函数是否过度依赖全局状态
- 是否有全局变量(非
拒绝标准:
- 非
const的全局变量 - 多个函数共享的可变全局状态
- 测试时需要设置全局变量的代码
- 非
⚙️ 自动化防护:
IDE配置:
- 启用全局变量警告
- 使用静态分析工具检测全局变量使用
CI/CD集成:
- 在CI流水线中集成静态分析工具
- 检测全局变量使用,生成警告报告
下一篇预告:可变数据(Mutable Data)- 如何保护数据不被意外修改
本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 青羽川!
评论
