30秒速览
- 别指望AI能直接吞掉2000行烂代码然后吐出一个完美版本,那只会得到一堆新bug。
- 我的秘诀是先当“导游”,写一份模块“地图”给AI,告诉它核心目标、阶段和哪些雷区不能碰。
- 拆解巨型函数时,AI是手术刀,但你要明确指令“优化这里的循环缓存”,而不是笼统地说“优化它”。
- 差点翻车在业务规则重构上,AI不理解隐晦的状态依赖和执行顺序,这块必须自己牢牢把关。
- 最终效果:模块耗时从3.2秒降到1.5秒,代码行数减少38%,测试覆盖率从28%干到85%,但我的头发也掉了不少。
接手一个“祖传”的推荐系统评分模块,我差点没背过气去
上周,我们那个日活大概15万的内容推荐平台出了点小状况。用户反馈说推荐的相关性时好时坏,尤其是新上线的“深度兴趣”评分功能,表现得很不稳定。负责维护这个模块的同事刚离职,这口锅,不偏不倚,正好落在了我头上。我打开那个传说中的 user_scoring_v2.py,一个专门负责计算用户对内容潜在兴趣分的模块。好家伙,足足2000多行,塞在一个文件里。
第一印象就是“五彩斑斓的黑”。全局变量和魔法数字满天飞,函数长得能当面条吃,一个 calculate_final_score 函数竟然有400多行,里面嵌套了七八层 if-else。注释要么是上古时期留下的“TODO: 这里需要优化”,要么就是完全过时的描述。最要命的是,业务逻辑被硬编码的数据处理和临时补丁打得千疮百孔。我尝试理清一个评分分支的逻辑,花了半小时,最后发现因为一个边界条件写反了,导致整整一类用户的“社交因子”权重被错误地设为了0。
我硬着头皮跑了一遍单元测试(谢天谢地还有测试,虽然覆盖率不到30%),果然有几个关于新功能的测试是失败的。更让我头皮发麻的是模块的响应时间——在模拟生产数据量的情况下,处理一批用户平均要3秒多,而SLA要求是1.5秒内。这代码已经不是“屎山”了,这简直是“屎山”发生了泥石流,把所有的逻辑通路都堵死了。直接在上面修bug?我怕是会引入更多bug。重写?老板只给了我两周时间,还得兼顾其他任务。就在我盯着屏幕,感觉职业生涯要出现一个污点时,我想起了团队刚采购的Claude Code。行,死马当活马医,这次就让AI当我的副驾,一起闯闯这摊烂泥潭。
别让AI直接重写,你得先当个“导游”和“架构师”
我犯的第一个错误,就是急性子。我直接选中整个文件,丢给Claude Code,prompt写:“重构这个模块,让它更清晰、高效。” 结果它生成了一堆看似合理、实则完全跑不通的代码。它会把一些隐式依赖的外部工具函数当成标准库,或者自作主张地改变了一些核心数据结构的格式,导致下游调用全部崩溃。我意识到,让AI面对2000行混乱的代码,就像让一个初来乍到的游客在没有地图的情况下整理一个杂乱无章的巨大仓库——它只能看到局部,看不到全局联系。
我停了下来,决定换个策略:我先当“导游”,再让它当“工人”。
首先,我写了一个详细的“模块导览”文档,作为给AI的上下文。这个文档不是代码,而是纯文本,我用Markdown格式写在了编辑器的一个独立分页里:
# 模块:user_scoring_v2.py 重构指南
## 核心目标
计算用户U对内容I的最终兴趣分数 Score(U, I)。用于推荐系统排序。
## 输入/输出
- 输入: `user_profile` (dict), `item_features` (dict), `context` (dict,包含时间、位置等)
- 输出: `final_score` (float), 以及可选的 `score_breakdown` (dict,用于调试)
## 主要计算阶段(当前混乱地交织在一起)
1. **基础画像分**:基于用户历史点击/阅读品类匹配。
2. **实时兴趣分**:基于用户最近1小时、24小时的行为序列,用简单衰减函数计算。
3. **社交影响力分**:考虑用户的粉丝数、互动率,以及内容作者的权威度。
4. **冷启动处理**:对新用户或新内容,使用基于流行度/属性的兜底策略。
5. **业务规则校正**:一堆硬编码的规则,如“禁止向未成年用户推荐某些类别”、“周末夜间娱乐内容权重+0.1”。
## 已知问题(必须修复)
- `calculate_final_score` 函数过长,逻辑分支过多。
- 全局变量 `GLOBAL_WEIGHTS` 和 `MAGIC_THRESHOLDS` 难以维护。
- 数据验证缺失,经常因输入字段缺失导致KeyError。
- “深度兴趣”子函数 `_deep_interest_model_v3` 性能极差,有重复计算。
- 单元测试覆盖率低,且部分测试已失效。
## 绝对不可更改的合约
- 函数入口名称和参数签名:`def calculate_score(user_profile, item_features, context):`
- 返回的 `score_breakdown` 字典中,键 `'base'`, `'realtime'`, `'social'` 必须存在。
- 与 `data_fetcher.get_user_vector()` 和 `logger.log_scoring_event()` 的外部调用方式。
有了这份“地图”,我开始了渐进式重构。我没有让它一次性重写所有,而是分步骤给指令。
第一步:提取数据验证层。 我选中所有出现`user_profile[‘xxx’]`的地方,告诉AI:“识别所有直接访问输入字典的代码,它们缺乏健壮性。请为我生成一个独立的 `DataValidator` 类,提供安全获取数据的方法,并支持默认值。” 这次的效果立竿见影:
class ScoringDataValidator:
"""安全地访问评分模块输入数据,避免KeyError并处理类型转换。"""
@staticmethod
def get_user_category_pref(profile, category, default=0.0):
"""从用户画像中获取指定品类的偏好分。"""
# 原代码直接写:pref = user_profile['preferences'][category]
# 现在安全多了
preferences = profile.get('preferences', {})
# 尝试float转换,失败则返回默认值
try:
return float(preferences.get(category, default))
except (ValueError, TypeError):
return default
@staticmethod
def get_item_feature(features, key, default=None, expected_type=str):
"""获取内容特征,并可选择进行类型检查。"""
value = features.get(key, default)
if expected_type and value is not None and not isinstance(value, expected_type):
# 记录日志,并尝试转换或返回保守默认值
logger.warning(f"Item feature {key} type mismatch: {type(value)}")
try:
value = expected_type(value)
except:
value = default
return value
# 使用示例替换原有危险代码
# 旧代码:weight = user_profile['weights']['social']
# 新代码:weight = ScoringDataValidator.get_user_category_pref(user_profile, 'social', default=0.5)
这个类虽然简单,但立刻让核心逻辑里少了十几处潜在的 `KeyError` 崩溃点。AI还主动建议为数值型字段增加类型转换和边界检查,这比我自己想得周到。
AI拆解巨型函数:它像外科手术刀,但你需要握着它的手
接下来是重头戏:对付那个400行的巨无霸函数。我让AI先帮我做一个“静态分析”,把函数里所有的逻辑块标注出来。我给了它函数代码,prompt是:“将此函数内的逻辑划分为独立的功能阶段,并为每个阶段拟一个描述性的函数名。”
AI返回了一个列表:
- 输入数据预处理与校验
- 计算基础画像匹配分
- 计算实时兴趣衰减分
- 调用深度兴趣模型(性能热点)
- 计算社交影响力分
- 应用业务规则校正
- 分数归一化与合成
- 组装调试信息并返回
这个划分基本靠谱。我决定让它从最独立、最复杂的“深度兴趣模型”下手。我选中了那大约150行的 `_deep_interest_model_v3` 函数,它的主要问题是里面有个三层嵌套循环,对用户行为序列和内容标签进行全量匹配计算,复杂度是O(N*M)。
我的提示词必须非常具体:“重构这个函数,重点关注性能优化。我注意到它在计算用户行为序列 `seq` 中每个行为与内容标签 `tags` 的相似度时,使用了三重循环。请思考:1. 是否有预计算的可能?2. 循环内的 `calculate_similarity` 调用是否重复?3. 能否使用向量化操作或更高效的数据结构?”
AI的第一次尝试只是把循环结构整理了一下,换了个更“优雅”的写法,但复杂度没变。我有点失望,于是给了更直接的指令:“我怀疑 `calculate_similarity` 函数在相同输入下被重复调用了。请分析代码,识别出可以缓存的 `(behavior, tag)` 对,并引入一个LRU缓存(使用 `functools.lru_cache`)。同时,将最内层对 `seq` 和 `tags` 的循环,尝试改为使用 `itertools.product` 生成组合,并在循环开始前预计算所有 `tag` 的归一化向量。” 这次它给出了让我眼前一亮的代码:
from functools import lru_cache
import itertools
def _deep_interest_model_v3(user_profile, item_features, context):
# ... 获取 seq 和 tags ...
# 优化1: 预计算并缓存 tag 的向量表示,避免在循环中重复获取和计算基础量
tag_vectors = {}
for tag in tags:
# 假设 get_tag_vector 是比较耗时的操作
tag_vectors[tag] = get_tag_vector(tag)
# 优化2: 为相似度计算添加缓存,因为 (行为, 标签) 组合可能在不同用户/内容间重复
@lru_cache(maxsize=1024)
def cached_similarity(behavior_id, tag):
# 现在可以直接用预计算的向量了
behav_vec = get_behavior_vector(behavior_id) # 这个可能也需要缓存,但先不管
tag_vec = tag_vectors[tag]
return cosine_similarity(behav_vec, tag_vec)
total_score = 0.0
# 优化3: 使用 product 明确组合关系,结构更清晰
for behavior, tag in itertools.product(seq, tags):
# 衰减因子只与行为相关,可以提到 tag 循环外进一步优化,但先保持逻辑清晰
decay = _get_time_decay(behavior['timestamp'], context['current_time'])
similarity = cached_similarity(behavior['id'], tag)
total_score += decay * similarity * tag['weight']
# ... 后续处理 ...
return total_score
我拿着这段代码去跑性能测试,单次调用从平均~120ms降到了~45ms!效果显著。这个过程中我学到的是:你不能只告诉AI“优化它”,你得告诉它“我怀疑问题出在XX地方,你可以试试YY方向”。你得把自己的经验和直觉作为导航输入给它。
差点翻车:AI引入的“智能” bug 和逻辑一致性危机
重构进行得很顺利,直到我决定让AI帮我“整理”那些散落在各处的业务规则校正代码。这部分代码像膏药一样贴在主逻辑的不同位置,比如:
# 规则片段A,在计算完基础分之后
if user_profile.get('age', 0) = 22 and item_features.get('category') == 'comedy':
final_score += 0.05 # 深夜喜剧内容加分
# 规则片段C,在另一个完全不同的函数里
if user_profile.get('region') == 'EU' and 'personalized_ads' not in user_profile.get('consent', []):
final_score = 0 # GDPR合规要求
我对AI说:“将这些分散的、硬编码的业务规则提取出来,集中到一个 `BusinessRuleEngine` 类中。每个规则应该是一个独立的、可配置的函数。注意规则的应用顺序可能很重要。” 这个想法本身没错。AI很快生成了一个看起来很美的类:
class BusinessRuleEngine:
def __init__(self):
self.rules = []
def add_rule(self, rule_func, priority=10):
self.rules.append((priority, rule_func))
self.rules.sort(key=lambda x: x[0]) # 按优先级排序
def apply_all(self, score, score_breakdown, user_profile, item_features, context):
current_score = score
for _, rule in self.rules:
current_score = rule(current_score, score_breakdown, user_profile, item_features, context)
return current_score
# 示例规则定义
def rule_minor_safety(current_score, breakdown, user, item, ctx):
if user.get('age', 0) = 22 and item.get('category') == 'comedy':
return current_score + 0.05
return current_score
我把所有规则都迁移了过去,跑了一遍测试。大部分都过了,但有两个关于分数边界(确保分数在0-1之间)的测试失败了。我调试了半天,发现一个致命的陷阱:规则的应用顺序和副作用被改变了。
在原来的“膏药”代码里,规则A(未成年人保护)是先执行的,它把分数乘以0.1。然后规则B(深夜喜剧)是在这个被降低的分数基础上加0.05。但在AI生成的引擎里,虽然我设定了优先级,但每个规则函数接收的 `current_score` 是上一个规则处理后的结果。这看起来没问题,对吗?问题出在那些不返回修改后分数,而是直接修改 `score_breakdown` 字典的规则。原来的代码里,有一条规则是直接 `score_breakdown[‘social’] = 0`。在旧的、交织的逻辑中,这个修改会影响后续某些依赖于 `score_breakdown` 值的计算。但在新的、线性的规则引擎中,除非我把 `score_breakdown` 也作为可变对象在每个规则间传递和修改,否则后续规则看到的 `score_breakdown` 就不是最新的状态。
更糟糕的是,AI在提取规则时,把一条原本是“如果XXX,则直接返回0分并终止后续所有计算”的规则,也写成了 `return current_score * 0`。这看起来结果一样,但实际上,在旧逻辑中,`return 0` 之后代码就跳出了,后面的规则根本不会执行。而在新引擎里,即使这条规则返回了0,引擎还是会继续应用列表中的下一条规则!这可能导致0分又被别的规则改掉了,完全违背了业务意图。
这个坑让我折腾了一整个下午。最终,我没有完全采用那个“优雅”的规则引擎。我做了折衷:
- 对于有严格顺序和依赖关系的核心校正规则,我保留了它们在一个有明确步骤的函数 `_apply_core_corrections` 中,使用传统的if-else控制流,确保逻辑清晰且执行顺序固定。
- 对于那些独立的、纯函数式的、只基于输入计算一个修正系数的规则(比如“周末系数”),我才把它们放到一个简单的 `_apply_independent_factors` 列表里循环执行,它们只做乘法或加法,不互相干扰。
- 对于“一票否决”规则,我把它提到了所有规则的最前面,一旦触发,直接返回最终分数,不再进行任何后续计算。
这次踩坑让我明白:AI擅长整理模式,但对复杂、隐晦的状态依赖和控制流语义缺乏深刻理解。在重构涉及状态变更和顺序敏感的逻辑时,你必须像审查新人代码一样,极度仔细地审查AI的产出。
性能、可测试性与最终的“缝合”艺术
经过一周的拆解、重构、测试和填坑,各个部件已经焕然一新。现在是时候把它们组装回去了。组装本身,我反而更多地亲自动手了。因为AI很难把握我心中最终的“接口美学”和模块划分。我创建了新的模块结构:
user_scoring/
├── __init__.py
├── core.py # 主流程协调器
├── calculators/ # 各种分数计算器
│ ├── base.py
│ ├── realtime.py
│ └── social.py
├── models/ # “重型”模型,如深度兴趣模型
│ └── deep_interest.py
├── correction/ # 校正规则
│ ├── core.py
│ └── factors.py
├── validation.py # 数据验证器
└── constants.py # 配置和常量(替换全局变量)
主流程 `core.py` 里的 `calculate_score` 函数现在清爽得像一首诗:
def calculate_score(user_profile, item_features, context):
"""计算用户对内容的兴趣分数(重构版)。"""
# 1. 数据验证与准备
validator = ScoringDataValidator()
safe_profile = validator.prepare_user_profile(user_profile)
safe_item = validator.prepare_item_features(item_features)
safe_ctx = validator.prepare_context(context)
# 2. 初始化分数分解字典
breakdown = {'base': 0.0, 'realtime': 0.0, 'social': 0.0, 'deep': 0.0}
# 3. 并行计算可独立计算的分数(概念上,实际可能仍是顺序)
breakdown['base'] = BaseScoreCalculator.calculate(safe_profile, safe_item)
breakdown['realtime'] = RealtimeScoreCalculator.calculate(safe_profile, safe_item, safe_ctx)
breakdown['social'] = SocialScoreCalculator.calculate(safe_profile, safe_item)
# 4. 计算“深度兴趣”分(较耗时)
breakdown['deep'] = DeepInterestModelV3.compute(safe_profile, safe_item, safe_ctx)
# 5. 初步合成
raw_score = _synthesize_score(breakdown, config.STAGE_WEIGHTS)
# 6. 应用核心业务规则校正(顺序敏感)
corrected_score, breakdown = CoreCorrection.apply(raw_score, breakdown, safe_profile, safe_item, safe_ctx)
# 7. 如果未被核心规则否决,应用独立调整因子
if corrected_score > 0:
corrected_score = IndependentFactors.apply(corrected_score, safe_profile, safe_item, safe_ctx)
# 8. 最终边界处理
final_score = max(0.0, min(1.0, corrected_score))
# 9. 记录日志(采样,避免性能冲击)
if random.random() < config.LOG_SAMPLING_RATE:
logger.log_scoring_event(user_id=safe_profile['id'], item_id=safe_item['id'],
final_score=final_score, breakdown=breakdown)
return final_score, breakdown
性能怎么样?优化前后的对比数据让我长舒一口气:
| 指标 | 重构前 | 重构后 | 提升 |
|---|---|---|---|
| 平均单次评分耗时 | ~3200 ms | ~1450 ms | 约 55% |
| “深度兴趣”子函数耗时 | ~120 ms | ~45 ms | 约 62% |
| 代码行数 (逻辑代码) | ~2100 行 | ~1300 行 | 约 38% |
| 单元测试覆盖率 | 28% | 85% | +57个百分点 |
| 圈复杂度 (平均函数) | 15.2 | 6.8 | 降低 55% |
更重要的是,当我需要调整“社交影响力”的权重时,我再也不用在2000行里大海捞针,只需要修改 `constants.py` 里的一个配置字典,或者去 `calculators/social.py` 里找到对应的方法。新加的单元测试也让我的信心大增,至少我知道修改不会无声无息地破坏其他功能。
AI重构后,我成了“人肉编译器”和“逻辑警察”
项目按时上线了,线上监控显示评分模块的延迟和错误率都有明显下降,那个飘忽不定的“深度兴趣”功能也稳定了下来。老板挺满意,但我自己心里清楚,这场胜利一半归功于Claude Code这个强大的“副驾”,另一半则归功于我这个时刻紧绷的“主驾”和“质检员”。
我总结了这次用AI辅助重构大型遗留代码的几点真实体会:
- AI是优秀的“代码理发师”和“模式识别器”:它能快速整理格式、重命名变量、提取重复代码块、将长函数按明显功能切开。对于有明确模式的任务(如添加缓存、引入校验层),只要你指令清晰,它做得又快又好。
- AI是糟糕的“系统分析师”和“业务逻辑守护神”:它不理解你代码中那些隐含的状态机、顺序依赖和特殊边界条件。把涉及复杂控制流和状态变更的重构完全交给它,等于给自己挖坑。
- 重构策略必须是渐进和分层的:不要想着一口吃成胖子。我的成功路径是:1) 建立全局上下文(导览文档);2) 从外围、低风险、高收益处下手(数据验证、工具函数);3) 集中火力攻击性能热点和复杂度爆点(巨型函数、嵌套循环);4) 谨慎处理业务逻辑密集区,亲自把控最终结构和关键控制流。
- 测试是你的救命稻草,也是AI的试金石:如果没有那部分残存的单元测试,我可能根本无法及时发现规则引擎引入的逻辑错误。在整个重构过程中,我要求AI为每一个新提取的函数或类生成对应的单元测试,这极大地保障了重构的安全性。
- 你的角色从“编码者”变成了“架构指令官”和“逻辑审计员”:你需要花更多时间设计重构步骤、编写精确的提示词、审查AI生成的代码逻辑是否与原始意图一致。你的核心价值不再是打字,而是对系统的深刻理解和决策。
这次经历后,我的血压确实随着代码质量的提升而“飙升”过——在踩坑的时候。但最终,看到一个可维护、可测试、性能更优的新模块,那种成就感是实实在在的。AI编程助手不是银弹,它是一把极其锋利的“链锯”。用得好,你可以快速砍倒一片混乱的“丛林”;用不好,或者不戴护具(测试、审查),下一个被锯伤的很可能就是你自己。下次再面对干行“祖传代码”,我还会叫上AI,但我会更清楚,我才是那个握着方向盘、看着地图、决定在哪条路上行驶的人。