重构前必须写测试,否则如同蒙眼拆弹;应先运行现有测试、补关键路径单元测试、用deprecationwarning标记旧函数、添加类型提示并用mypy检查、采用依赖注入而非继承、用libcst等ast工具安全批量重构。

重构前不写测试,等于蒙眼拆炸弹
Python 动态特性让改代码看起来很轻松,但没测试覆盖的 refactor 实际上是在靠人脑模拟所有执行路径——这不可靠。尤其当涉及 __getattr__、__getattribute__、装饰器链或 monkey patch 时,表面没报错,运行时才暴露逻辑断裂。
实操建议:
- 先用
pytest --collect-only确认当前测试能跑通,再补关键路径的单元测试(哪怕只有 3 行断言) - 对被重构函数,至少覆盖:正常输入、边界值、
None或空容器、异常触发路径 - 避免直接删旧函数——先用
DeprecationWarning标记,等日志里确认无调用再移除
类型提示不是装饰品,是重构的契约
加了 typing 注解后,mypy 能提前发现参数类型错位、返回值误用、属性访问越界等问题。没类型提示的模块,重构时连“这个变量到底该是什么类型”都要靠猜。
常见错误现象:
立即学习“Python免费学习笔记(深入)”;
-
AttributeError: 'str' object has no attribute 'append'—— 实际上游传入了字符串而非列表,但函数签名没约束 - 重构后
dict变成TypedDict,但调用方仍用字符串键硬取,mypy会立刻报Key "x" not present in TypedDict
实操建议:
- 用
mypy --disallow-untyped-defs强制函数必须有类型注解 - 对已有弱类型代码,优先给函数签名加注解,再逐步补
Union和Optional,别一上来就重写整个类的类型体系
依赖注入改法比继承更安全
想把硬编码的 requests.get 换成可 mock 的客户端?或者把数据库连接从模块级全局变量抽出来?直接上继承或抽象基类容易引入新耦合,而依赖注入(DI)让变化点更可控。
使用场景:
- 替换外部服务调用(如用
httpx.AsyncClient替代requests.Session) - 切换不同实现(如本地缓存用
dict,生产用redis.Redis)
实操建议:
- 把依赖作为函数参数或构造函数参数传入,而不是在函数体内直接 import 或调用
get_db_connection() - 避免用
__init__.py里的全局单例;改用工厂函数或contextvars.ContextVar管理请求级实例 - 如果用
pytest,配合monkeypatch.setattr替换依赖比 patch 模块更精准
AST 分析比正则更靠谱,但别自己写解析器
批量改 print() 为 logging.info()?把 datetime.now() 统一替换成 timezone.now()?正则替换会误伤字符串、注释和格式化表达式,比如把 f"Time: {datetime.now()}" 里的 datetime.now() 错误替换了。
实操建议:
- 用
lib2to3或更现代的libcst做 AST 级重构——它只匹配真实语法节点,不碰字符串内容 - 小范围改动优先手改 +
git grep验证;大范围用libcst写 transformer,但务必先跑cst.parse_module(code).has_errors确保源码可解析 - 别自己写 AST 遍历逻辑来判断“这里是不是函数调用”,直接用
cst.Call+cst.Name匹配更稳
复杂点在于 Python 版本兼容性:3.8+ 的 AST 结构和旧版有差异,libcst 虽然做了适配,但如果你的代码要同时支持 3.7 和 3.11,得在 transformer 里加版本分支判断——这点很容易被忽略。










