Claude Code 重构 467 行遗留代码实录
2026/5/14 17:30:43 网站建设 项目流程

Claude Code 重构 467 行遗留代码实录


前几篇用 Claude Code 搞的都是小任务——加个 flag、导个 CSV。那些场景你手写也花不了多少时间,AI 只是让你少打几个字。

这一篇来真的。

手头有个订单分析脚本,两年前写的,从 50 行一路长到快 500 行。file I/O、数据清洗、统计计算、报表生成全揉在一个文件里。没有测试,改一行怕炸一片。

我要让 Claude Code 把它拆成模块化结构,补上测试,而且——重构过程中所有原有功能一个都不能坏。


先看看这个烂摊子

# analyzer.py — 467 行,单文件,零测试importcsvimportjsonimportsysimportosfromdatetimeimportdatetimefromcollectionsimportdefaultdictimportredefload_data(filepath):"""加载 CSV 订单文件"""ifnotos.path.exists(filepath):print(f"错误:文件{filepath}不存在")sys.exit(1)withopen(filepath,'r',encoding='utf-8')asf:reader=csv.DictReader(f)rows=[]forrowinreader:# 清洗金额字段——去掉货币符号和逗号if'amount'inrow:row['amount']=float(row['amount'].replace('$','').replace(',',''))if'date'inrow:# 统一日期格式forfmtin['%Y-%m-%d','%m/%d/%Y','%d-%m-%Y']:try:row['date_parsed']=datetime.strptime(row['date'],fmt)breakexceptValueError:continuerows.append(row)returnrowsdefvalidate(rows):"""验证数据完整性"""errors=[]fori,rowinenumerate(rows):if'order_id'notinrowornotrow['order_id']:errors.append(f"行{i}: 缺少 order_id")if'amount'inrowandrow['amount']<0:errors.append(f"行{i}: 金额为负数{row['amount']}")returnerrorsdefanalyze(rows,group_by='region',metric='amount'):"""统计分析"""groups=defaultdict(float)forrowinrows:key=row.get(group_by,'unknown')ifmetricinrow:groups[key]+=row[metric]returndict(groups)defgenerate_report(rows,output_format='text'):"""生成报表"""valid_rows=[rforrinrowsifr.get('amount',0)>0]total=sum(r.get('amount',0)forrinvalid_rows)by_region=analyze(valid_rows,'region','amount')by_month=defaultdict(float)forrinvalid_rows:if'date_parsed'inr:month_key=r['date_parsed'].strftime('%Y-%m')by_month[month_key]+=r['amount']ifoutput_format=='json':report={'total_revenue':total,'total_orders':len(valid_rows),'by_region':by_region,'by_month':dict(by_month),'generated_at':datetime.now().isoformat()}returnjson.dumps(report,indent=2,ensure_ascii=False)else:# 默认文本格式lines=[]lines.append("="*50)lines.append("订单分析报告")lines.append("="*50)lines.append(f"总订单数:{len(valid_rows)}")lines.append(f"总收入:${total:,.2f}")lines.append(f"生成时间:{datetime.now().strftime('%Y-%m-%d %H:%M')}")lines.append("-"*50)lines.append("按区域统计:")forregion,amtinsorted(by_region.items(),key=lambdax:x[1],reverse=True):lines.append(f"{region}: ${amt:,.2f}")lines.append("-"*50)lines.append("按月统计:")formonthinsorted(by_month.keys()):lines.append(f"{month}: ${by_month[month]:,.2f}")return'\n'.join(lines)if__name__=='__main__':iflen(sys.argv)<2:print("用法: python analyzer.py <文件路径> [--json]")sys.exit(1)filepath=sys.argv[1]output_format='json'if'--json'insys.argvelse'text'rows=load_data(filepath)errors=validate(rows)iferrors:print("数据验证发现问题:")foreinerrors:print(f" -{e}")sys.exit(1)report=generate_report(rows,output_format)print(report)

问题清单:

  • load_data里耦合了文件 I/O、CSV 解析、金额清洗、日期解析
  • validate只用 print 输出错误,没法在其他地方复用
  • analyze硬编码了groupby+sum,换一种聚合方式就得重写
  • generate_report混了数据计算和格式化输出——by_month 的逻辑嵌在报表函数里
  • sys.exit(1)散落各处,脚本能跑,但不能作为库被调用
  • 零测试。所有重构的前提是有测试保底

第一步:给重构上保险——先让 AI 写测试

启动 Claude Code,让它先分析现状:

$ claude

分析 analyzer.py 的结构和所有可测试的单元,然后给每个可测试的函数写测试。先不要动生产代码。

• Reading analyzer.py (467 lines) • Analyzing structure: - load_data(): file I/O + CSV parse + data cleaning (3 concerns mixed) - validate(): data validation with hardcoded checks - analyze(): aggregation logic (OK, single concern) - generate_report(): computation + formatting (2 concerns mixed) - main(): CLI entry point → Will test: load_data with mock files, validate, analyze, generate_report → Creating test_analyzer.py with pytest fixtures [+] tests/test_analyzer.py (82 lines) - test_load_data_with_valid_csv — creates temp CSV, verifies parsing - test_load_data_currency_cleaning — $1,234.56 → 1234.56 - test_load_data_date_parsing — 3 date formats - test_validate_missing_order_id — detects missing IDs - test_validate_negative_amount — detects negative amounts - test_analyze_basic — group by region, sum amounts - test_generate_report_text — text format output - test_generate_report_json — JSON format output

Claude Code 不仅写了测试,还写了对load_data的测试——这个函数耦合了文件 I/O,它用tmp_pathfixture 创建临时 CSV,验证清洗逻辑。比我手工写还细致。

→ Running tests: 8 passed in 0.31s

重构之前,8 个测试全部通过。这是关键——有了测试,后面怎么改都有人兜底。


第二步:开始拆代码

有了测试保底,开始重构:

把 analyzer.py 拆成模块化结构:

  1. loader.py — 负责文件读取和 CSV 解析
  2. cleaner.py — 负责数据清洗(金额、日期)
  3. validator.py — 负责数据验证
  4. analyzer.py — 保持 analyze 和 report 逻辑
  5. cli.py — 命令行入口

loader 和 cleaner 分开是因为清洗逻辑可能在别的地方也需要用。
每拆一个模块就跑一次测试,保证不破坏已有功能。

Claude Code 的拆分策略:

→ Step 1: Extract cleaner.py • Date parsing logic → clean_date() • Amount cleaning logic → clean_amount() [+] cleaner.py (24 lines) [~] analyzer.py — import from cleaner, remove inline cleaning → pytest: 8 passed → Step 2: Extract loader.py • File I/O → load_csv() • CSV parsing → parse_orders() [+] loader.py (18 lines) [~] analyzer.py — import from loader → pytest: 8 passed → Step 3: Extract validator.py • validate() → validator.py • Changed: return ValidationResult dataclass instead of printing [+] validator.py (32 lines) [~] analyzer.py — use ValidationResult → pytest: 7 passed, 1 failed → Fix: test_validate was checking string output, now returns structured result. Updating test... → pytest: 8 passed

注意第三步。拆validator.py时,Claude Code 把原来的print输出改成了返回一个ValidationResultdataclass——这让验证结果可以在调用方做任何处理。但这导致测试挂了——原有测试检查的是 print 字符串,现在返回结构化对象。

有意思的是 Claude Code 自己发现测试挂了,自己分析原因,自己修了测试。我没插手。


第三步:处理 CI 跑不过的问题

模块化重构完成了,结构变成:

analytics/ ├── loader.py (18 lines, 文件 I/O + CSV) ├── cleaner.py (24 lines, 金额/日期清洗) ├── validator.py (32 lines, 数据验证) ├── analyzer.py (85 lines, 分析 + 报告) ├── cli.py (15 lines, CLI 入口) └── tests/ └── test_analyzer.py (106 lines)

测试全绿。但改了模块结构后,cli.py里原来的 import 全变了。CI 跑的话需要确保包路径正确。

我问 Claude Code:

检查一下包之间的 import 是否正确,确认安装到 PYTHONPATH 后
所有模块能互相引用。

• Checking imports across modules: cli.py → from analytics.loader import load_csv ✓ cli.py → from analytics.analyzer import generate_report ✓ analyzer.py → from analytics.cleaner import clean_amount ✓ validator.py → from analytics.cleaner import * ✗ → Issue: validator.py does wildcard import from cleaner, but cleaner.py only has clean_date and clean_amount. Wildcard won't cause error here but is fragile. Fixed. • Creating pyproject.toml for package installation [+] pyproject.toml (minimal build config) • Running pytest with installed package: → 8 passed

它还顺手补了个pyproject.toml,让团队其他人 clone 下来后可以pip install -e .把包装到开发环境。


重构前后对比

维度重构前重构后
文件数1 个7 个(5 模块 + pyproject.toml + tests)
单文件最大行数467106(tests)
循环依赖无(因为全在一个文件里)无(分层清晰)
测试覆盖08 个用例,涵盖 parser/cleaner/validator/analyzer/report
可复用性只能在 CLI 跑每个模块可独立 import
错误处理sys.exit(1)散落各处返回结果对象,由 CLI 层决定如何处理

最关键的变化不太容易量化——现在改代码不会心悸了。加一个聚合方式、换一种输出格式,改对应模块就行,测试告诉你有没有坏。


为什么重构这个场景特别适合 Claude Code

这趟下来,有三件事让它特别适合干重构:

能看见全局。467 行的单文件,它能看到里面混了哪些职责,给每个函数归类到正确的模块。人工做这件事需要先通读代码、梳理依赖,Claude Code 上来就干了。

边拆边测,谁挂谁修。每拆一个模块跑一次测试,挂了立刻定位。这本来是最佳实践,但人做起来累——拆一个模块、跑测试、修、再跑。Claude Code 把这三个动作串成一个循环,你不必盯着。

不会偷工减料。人做重构,拆到第三个模块就开始嫌麻烦——“就这样吧,剩下的不动了”。Claude Code 按你给的指令一步一步执行完,不省略、不走捷径。


踩到的坑

1. 给它明确的模块边界

别只是说"重构这个文件"。精确描述每个模块的职责、为什么这么划分。我告诉它"loader 和 cleaner 分开是因为清洗逻辑可能在别的地方也需要用",它就理解了设计意图。

2. 重构前必须先写测试

没有测试保底,Claude Code 也会做重构,但你可能发现不了哪里坏了。它重写完测试全绿——前提是测试覆盖了关键路径。

3. 让它自己做跑测试→修→再跑的循环

测试挂了你不需要自己去改。Claude Code 看到报错输出,会分析原因、修复、重跑。有时候要循环两三次才全绿,但整个过程你只需要看着终端滚动。


下一篇

下一篇换个角度——如果烂摊子是别人留下的呢?用 Codex 的沙箱来理解一个你没见过的代码库,本地环境零风险。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询