自建开源AI编程助手CodeCompanion:本地RAG+AST切片+VS Code深度集成
1. 项目概述为什么我们真需要一个“自己的Copilot”你有没有过这种体验写到一半的函数突然卡壳翻文档、查Stack Overflow、再切回编辑器十分钟过去只写了三行或者刚接手一个老项目光是理清模块依赖就花掉整个上午又或者团队里新人上手慢反复问同样基础的问题而资深工程师的时间被切成碎片。这些不是效率问题是信息流和认知负荷的结构性瓶颈。而市面上那些响当当的AI编程助手确实能缓解——但它们像租来的高级写字楼装修漂亮、设施齐全可你不能拆墙改格局不能把会议室改成实验室更没法在承重墙上钉满自己写的便签。这就是我决定动手从零搭一个完全自主可控的AI编程助手的起点。它不叫“Copilot”我管它叫CodeCompanion——一个真正属于你代码库、你工作流、你思维方式的搭档。核心关键词就三个免费、开源、可定制。免费不是指“试用30天”而是从模型权重、推理框架、前端界面到部署脚本全部躺在GitHub仓库里一行代码都不藏开源不是挂个MIT License就完事而是每个模块都经得起推敲向量数据库选什么、RAG怎么避免幻觉、本地模型如何平衡速度与精度、IDE插件怎么无缝注入上下文——这些决策背后都有明确的工程权衡可定制则意味着你可以把它塞进公司内网隔离环境可以给它喂食内部API文档和私有组件库甚至能用自己训练的微调模型替换默认的Llama-3-8B。这不是玩具项目而是我在给两个中型后端服务做重构时每天真实使用的工具。它不生成完美代码但会在我写错SQL JOIN条件时在右下角弹出一句“检测到user表和order表未通过外键关联当前ON子句可能返回笛卡尔积建议检查索引或添加WHERE约束”。这种精准、克制、可解释的辅助才是工程师真正需要的“搭档”而不是一个急于炫技的魔术师。2. 整体架构设计与技术选型逻辑2.1 为什么放弃“大模型简单封装”的偷懒路线最省事的方案是直接调用某个云厂商的API套个Web界面再加个聊天框。我试过两周就放弃了。问题不在功能而在失控感。比如某次它建议我用asyncio.gather()并发请求这本身没错但我们的服务运行在Python 3.8的旧容器里而gather的return_exceptions参数是3.9才加的——它没告诉你这个细节只给了“正确”代码。更麻烦的是调试当建议出错你是该怪模型怪提示词还是怪它读取的代码片段太短全黑盒。所以CodeCompanion的第一条铁律是所有环节必须可观测、可打断、可替换。这意味着架构必须分层清晰每一层都像乐高积木一样能独立升级。我们最终采用四层解耦设计数据层 → 检索层 → 推理层 → 交互层。这不是为了炫技而是为了解决三个现实痛点第一代码库更新频繁文档却常年不维护靠人工写提示词喂模型永远追不上代码变更速度第二工程师对“为什么这样建议”有强需求不能只给答案还要给依据第三不同项目技术栈差异巨大今天是PythonFastAPI明天可能是RustActix模型底座必须能无痛切换。2.2 数据层代码即知识但如何让它“可检索”代码不是自然语言直接扔进向量库效果极差。我对比了三种切片策略按文件、按函数、按AST节点。按文件太粗一个500行的Django视图文件向量表示会淹没关键逻辑按函数稍好但装饰器、类型注解、docstring这些高信息密度内容容易被平均掉。最终选定AST抽象语法树驱动的语义切片。具体做法是用tree-sitter解析Python代码提取function_definition、class_definition、if_statement等节点再对每个节点做两件事一是保留其完整上下文父类名、导入模块、所在文件路径二是用正则清洗掉无意义的空格和注释但刻意保留类型提示和docstring——因为工程师最信任的往往就是这两处。例如一个函数的docstring写着“Returns user profile with cached permissions, may raise PermissionError”这个信息比函数体里的return {...}重要十倍。清洗后的文本送入嵌入模型我们选nomic-embed-text-v1.5而非更火的all-MiniLM-L6-v2原因很实在前者在代码相关文本的MTEB基准测试中检索准确率高7.3%且单次嵌入耗时仅多0.8ms这对每秒要处理上百次查询的场景是值得的投资。数据层不存原始代码只存AST节点ID、嵌入向量、元数据文件路径、行号范围、修改时间戳。当代码库Git push后触发CI流水线自动增量更新向量库——不是全量重建而是比对git diff只处理变动文件实测20万行代码库增量更新平均耗时4.2秒。2.3 检索层RAG不是万能药得给它装上“刹车”RAG检索增强生成常被神化但实际落地时80%的“幻觉”来自检索环节。我们遇到最典型的失败案例用户问“如何验证JWT token”检索系统返回了authlib库的源码片段而项目里根本没用这个库用的是PyJWT。根源在于向量相似度只看字面匹配不看技术栈绑定。为此我们在标准RAG流程里加了三道“刹车”第一道是技术栈过滤器。在向量库元数据中为每个代码片段打上标签framework:fastapi、auth:pyjwt、db:postgresql。用户提问时前端自动提取技术栈关键词如从requirements.txt或pyproject.toml实时读取检索时强制AND条件匹配。第二道是上下文新鲜度衰减。给每个代码片段的向量相似度分数乘以一个衰减因子decay 1 / (1 days_since_last_commit)。一个三个月没动过的utils.py函数即使语义匹配度高也会被降权优先返回近期活跃模块的代码。第三道是双路召回融合。除了向量检索我们并行跑一个符号检索用ripgrep在代码库中快速grep关键词如“jwt”、“token”、“verify”返回精确匹配的行号。最后将向量结果与符号结果按权重合并向量70% 符号30%确保既懂语义也不漏硬编码。这套组合拳让检索准确率从基线的61%提升到89%最关键的是错误建议里“用错库”的比例从34%降到不足5%。2.4 推理层本地模型不是妥协而是掌控力的来源选本地模型不是因为“情怀”而是因为延迟、成本、隐私三重刚需。云API的RTT往返时延平均2.1秒而本地Llama-3-8B在RTX 4090上首token延迟压到380ms以内。算笔账一个工程师每天接受200次建议云服务按$0.01/千token计费月成本约$140本地部署显卡电费月均$8.5且无需担心API限流。模型选型上我们弃用了参数更大的Qwen2-72B尽管它在HumanEval上得分高3.7分。原因很骨感Qwen2-72B在4090上推理速度仅8 token/s而Llama-3-8B可达32 token/s且8B模型对提示词工程更友好——它不会因为少写一个“请”字就胡说八道。我们用llama.cpp量化到Q5_K_M4.5GB显存占用配合llama-server提供HTTP API。最关键的改造是提示词模板的工程化不是写死一段文字而是动态拼接。模板分三块角色定义“你是一个资深Python工程师专注FastAPI生态”、上下文约束“当前文件app/api/v1/users.py行号120-150”、检索证据“参考以下3段代码[code1]...[code2]...[code3]”。其中“检索证据”部分我们做了长度截断保护每段代码只取前120字符后80字符中间用TRUNCATED标记避免长代码挤爆上下文窗口。实测下来这个设计让模型在给出建议时引用证据的准确率从52%提升到81%工程师一眼就能看出“它到底看了哪几行代码”。3. 核心模块实现与实操细节3.1 向量数据库搭建从代码解析到实时索引向量库选型是场持久战。我们初期用过ChromaDB轻量易上手但当代码库突破5万行查询延迟开始飙升且不支持元数据过滤的AND逻辑。最终迁移到Qdrant不是因为它最火而是它原生支持payload字段的复杂过滤如{framework: {eq: fastapi}, auth: {eq: pyjwt}}且批量插入性能比Chroma高3.2倍。部署上我们没用Docker Compose搞一套独立服务而是直接集成进主应用Qdrant以qdrant_clientPython SDK嵌入数据目录设为./data/qdrant这样整个系统只需一个uvicorn进程启动运维复杂度归零。代码解析管道的核心是tree-sitter的Python绑定。这里有个坑官方Python binding编译麻烦我们改用预编译的tree_sitter_languages包它内置了Python、JavaScript、Rust等主流语言的parser。解析函数如下from tree_sitter import Language, Parser from tree_sitter_languages import get_language, get_parser def parse_python_file(file_path: str) - List[Dict]: language get_language(python) parser get_parser(python) with open(file_path, rb) as f: source_code f.read() tree parser.parse(source_code) root_node tree.root_node # 提取所有function_definition节点 functions [] for node in root_node.descendants_by_type(function_definition): # 获取函数名、起始行、结束行 name_node node.child_by_field_name(name) if not name_node: continue func_name source_code[name_node.start_byte:name_node.end_byte].decode() start_line node.start_point[0] end_line node.end_point[0] # 提取函数体不含def行和docstring body_start node.child_by_field_name(body).start_point[0] if node.child_by_field_name(body) else start_line 1 body_text \n.join( source_code.decode().split(\n)[body_start:end_line1] ) # 清洗移除空行和纯空格行但保留docstring cleaned_body \n.join([ line for line in body_text.split(\n) if line.strip() or ]) functions.append({ id: f{file_path}#{func_name}, content: cleaned_body, file_path: file_path, line_range: [start_line, end_line], metadata: { framework: fastapi, auth: pyjwt } }) return functions这段代码的关键在于descendants_by_type的精准使用——它比正则匹配可靠得多能准确识别嵌套函数、装饰器包裹的函数等边界情况。清洗时保留的判断是为了不误删多行docstring。实测一个1200行的users.py解析清洗向量化耗时1.7秒完全在可接受范围。3.2 RAG检索引擎如何让AI“看懂”你的代码意图检索引擎的难点不在技术而在理解工程师的真实意图。用户输入“怎么处理并发请求”可能想问“如何用asyncio并发调用API”也可能想问“如何用Redis锁防止库存超卖”。单纯靠向量相似度两者都会召回concurrent.futures的示例但后者完全跑偏。我们的解法是意图分类前置。在用户提问进入检索前先过一个轻量级分类器用sklearn训练一个TF-IDF LogisticRegression模型区分7类常见意图error_debugging、api_usage、performance_optimization、security_best_practice、testing_strategy、refactoring_suggestion、deployment_issue。训练数据来自内部Jira的1200个已关闭工单标题标注准确率92.4%。分类后检索策略动态调整若为error_debugging优先召回tests/目录下的单元测试代码和logs/目录的错误日志解析片段若为security_best_practice强制增加security标签过滤并提高pydantic、cryptography等安全相关库的权重若为refactoring_suggestion则启用“跨文件关联检索”先找到当前函数再通过AST分析其调用的其他函数一并召回。这个分类器只有1.2MB用joblib序列化后加载耗时18ms却让后续检索的相关性提升显著。我们用一个简单指标验证随机抽100个真实提问人工评估“首条检索结果是否与问题强相关”基线无分类为63%加入意图分类后升至87%。更重要的是它让工程师建立了信任——当看到AI建议旁标注着“基于您payment_service.py第89行的process_payment函数和tests/test_payment.py的并发测试用例”就知道这不是瞎猜。3.3 本地推理服务Llama-3-8B的精细化调优Llama-3-8B的默认配置在代码任务上表现平平。我们做了三项关键调优第一温度temperature动态控制。固定temperature0.1太死板会导致建议过于保守设为0.7又容易天马行空。我们改为根据问题类型动态设置error_debugging类问题用0.05追求确定性refactoring_suggestion类用0.3允许适度创新api_usage类用0.15平衡准确与灵活性。这个值由前端在请求头中传递服务端直接读取。第二top_p核采样阈值优化。默认0.9但我们发现对于代码补全0.85更佳——它能排除明显错误的token如把return错成retunr又不至于过度限制创造性。第三停止词stop tokens精准注入。模型常在生成代码时输出# TODO:或# FIXME:后不停止。我们在提示词末尾显式添加停止词数组[\n\n, # TODO:, # FIXME:, ]并确保llama.cpp的stop参数正确接收。实测这使代码生成的完整性生成完整函数而非半截从71%提升到94%。部署时我们没用复杂的Kubernetes而是用systemd管理llama-server进程。配置文件/etc/systemd/system/codecompanion-llm.service关键内容如下[Unit] DescriptionCodeCompanion LLM Server Afternetwork.target [Service] Typesimple Usercodecomp WorkingDirectory/opt/codecompanion ExecStart/usr/bin/llama-server \ --model /opt/codecompanion/models/Llama-3-8B-Q5_K_M.gguf \ --port 8080 \ --ctx-size 4096 \ --n-gpu-layers 45 \ --no-mmap \ --verbose-prompt Restartalways RestartSec10 EnvironmentLLAMA_NUM_THREADS8 [Install] WantedBymulti-user.target其中--n-gpu-layers 45是关键——Llama-3-8B总共有32层设45是确保全部offload到GPU实测显存占用稳定在7.2GB4090显存24GBCPU占用低于15%。--no-mmap禁用内存映射避免大模型加载时触发OOM Killer。这套配置让服务7x24小时稳定过去三个月零宕机。3.4 VS Code插件开发让AI真正融入你的手指肌肉记忆插件不是简单的“调API显示弹窗”而是要成为编辑器的一部分。我们用VS Code的Webview API构建前端核心挑战是上下文感知。传统插件只能获取当前光标位置但CodeCompanion需要知道“用户正在编辑的函数叫什么它的参数类型是什么上一行是不是async def”。解决方案是插件后台启动一个轻量Python子进程持续监听VS Code的textDocument/didChange事件用jedi库实时解析当前文件AST。当用户按下快捷键默认CtrlShiftC子进程立即执行获取光标所在行号向上扫描找到最近的def或async def关键字确定函数边界解析该函数的parameters节点提取参数名和类型注解向下扫描收集光标后5行的代码预测用户想补全的内容将以上结构化数据连同当前文件路径、git分支名打包成JSON发给后端。这个设计让AI建议的精准度质变。例如当光标停在def create_user(name: str, email: EmailStr) - User:的-后面插件会明确告诉后端“用户正在定义create_user函数的返回类型当前已有类型注解EmailStr请推荐合适的返回类型”。后端据此生成提示词“你是一个FastAPI专家当前函数create_user接收name和email需返回一个User模型实例。请给出完整的UserPydantic模型定义包含id: int、name: str、email: EmailStr、created_at: datetime字段并添加Config指定orm_modeTrue”。这种深度上下文是任何通用聊天界面做不到的。插件发布在VS Code Marketplace安装量已超2300用户反馈最集中的好评是“它终于不再给我推荐flask的代码了连我pyproject.toml里写的[tool.poetry.dependencies]都认得”。4. 实操过程与部署全流程4.1 环境准备从零开始的15分钟初始化部署不是终点而是起点。我们设计了一键初始化脚本目标是新同事拉下代码15分钟内拥有完整可用的CodeCompanion。脚本setup.sh核心逻辑分三步第一步硬件探测与依赖安装。脚本首先运行nvidia-smi -L检测GPU若存在则安装CUDA Toolkit 12.2和nvidia-cudnn-cu12若无GPU则自动切换到CPU模式安装openblas和libomp。接着用pip install -r requirements.txt但requirements.txt做了环境变量分组# requirements.txt # 通用依赖 requests2.31.0 pydantic2.6.4 # GPU模式专属 # gpu-deps torch2.2.0cu121; platform_system Linux and python_version 3.9 llama-cpp-python0.2.73; platform_system Linux and python_version 3.9 # CPU模式专属 # cpu-deps llama-cpp-python0.2.73; platform_system Linux and python_version 3.9 and not (platform_machine x86_64 and NVIDIA in os.environ.get(GPU_VENDOR, ))这样pip install -r requirements.txt会自动忽略不匹配的依赖无需手动切换。第二步模型与向量库下载。脚本调用huggingface-hub库从Hugging Face Hub下载预量化模型Llama-3-8B-Q5_K_M.gguf4.5GB和初始向量库快照qdrant-initial-snapshot.tar.gz1.2GB。为防网络中断下载逻辑带断点续传用curl -C -命令失败后自动重试3次。第三步服务启动与健康检查。脚本依次启动Qdrantqdrant --config ./config.yaml、llama-serversystemctl start codecompanion-llm、主应用uvicorn app.main:app --host 0.0.0.0 --port 8000最后用curl http://localhost:8000/health轮询直到返回{status:healthy}。整个过程我们实测在一台16GB内存、RTX 3060的开发机上耗时13分42秒。脚本还生成一份QUICKSTART.md里面是3个真实场景的快速测试用例比如“打开app/main.py在app.get(/)函数里按CtrlShiftC输入‘如何添加JWT认证’”确保新用户第一次使用就有正向反馈。4.2 代码库接入让AI读懂你的项目只需3个配置项接入新项目绝不是“把整个代码库扔进去”那么简单。我们提炼出三个必填配置项藏在项目根目录的.codecompanion.yaml中# .codecompanion.yaml project_name: payment-service tech_stack: - framework: fastapi - auth: pyjwt - db: postgresql - cache: redis code_dirs: - app/ - models/ - schemas/ exclude_patterns: - **/__pycache__/** - **/migrations/** - **/tests/** - **/venv/**这三部分直击要害project_name用于生成提示词中的角色定义“你是一个payment-service项目的资深工程师”tech_stack是检索时的硬性过滤器确保不推荐Django的models.Modelcode_dirs和exclude_patterns则精准控制向量库的摄入范围——我们刻意排除tests/目录因为测试代码的写法如mock、assert会污染模型对“生产代码风格”的学习。接入流程自动化CI流水线检测到.codecompanion.yaml更新自动触发codecompanion ingest --config .codecompanion.yaml命令该命令会用git ls-files列出所有匹配code_dirs且不匹配exclude_patterns的文件对每个文件执行AST解析如3.1节所述批量upsert到Qdrant同时更新last_ingested_commit元数据。整个过程对开发者透明他们只需维护好这个YAML文件代码库的“可被AI理解度”就自动同步。4.3 日常运维监控、告警与迭代闭环一个没人维护的AI助手三个月后就会变成“鸡肋”。我们建立了轻量但有效的运维闭环监控层面只埋3个核心指标llm_request_latency_msP95延迟阈值设为1200ms超时则告警retrieval_precision_rate每100次请求随机抽5次人工评估首条检索结果相关性低于85%触发告警plugin_active_users每日唯一VS Code插件激活数连续3天下降超20%则需调查。这些指标通过Prometheus暴露Grafana看板只有1个页面、4个图表避免信息过载。告警层面我们不用企业微信/钉钉机器人那种“刷屏式”告警。而是设计了一个/alert端点当retrieval_precision_rate告警系统自动生成一个诊断报告包含告警时段内检索准确率最低的5个提问每个提问对应的检索结果ID、匹配分数、元数据标签自动对比这些提问的共性如是否都含“redis”关键词是否都来自cache/目录。工程师收到的不是“指标异常”而是一份可行动的报告“过去24小时所有关于‘redis’的提问检索准确率均低于60%原因是cache/目录代码未打cache:redis标签请执行codecompanion tag --dir cache/ --tag cache:redis”。迭代层面我们坚持“小步快跑”。每周五下午团队用15分钟开站会每人分享1个本周CodeCompanion帮ta解决的真实问题如“它帮我发现了datetime.utcnow()在Docker容器里的时区bug”然后投票选出1个最值得沉淀为新功能的点。过去8周由此诞生了“跨文件调用链检索”、“SQL查询性能建议”、“Pydantic模型自动生成”三个高价值特性。这种源自真实战场的迭代比任何OKR都管用。5. 常见问题与实战排障指南5.1 “AI建议总是重复或者答非所问”——检索环节的5个致命陷阱这是最高频的投诉90%源于检索配置失误。我们整理了一份“检索健康检查清单”工程师自查5分钟即可定位检查项问题现象检查方法修复方案元数据标签缺失所有建议都用错技术栈如推荐Flask代码运行qdrant_client.count(collection_namecode, filter{framework: {eq: fastapi}})返回0则说明标签未写入检查.codecompanion.yaml的tech_stack是否正确确认ingest命令是否成功执行查看日志是否有Inserted X nodes代码切片过粗建议内容空泛如“请使用try-except”不提具体函数查看Qdrant中任意一个function_definition节点的content字段若长度500字符说明未按AST切片修改解析脚本确保body_text只取函数体不包含def行和docstring之外的无关代码向量模型不匹配相似代码检索不到如get_user_by_id和fetch_user无法关联用curl -X POST http://localhost:6333/collections/code/points/search传入{vector: [0.1,0.2,...], limit: 3}看返回结果是否合理切换嵌入模型为nomic-embed-text-v1.5重新运行ingestfreshness衰减过猛新写的代码完全不被检索到检查days_since_last_commit计算逻辑确认git commit时间获取是否正确git log -1 --format%at在ingest脚本中打印每个文件的commit_timestamp和计算出的decay值验证是否为负数或过大双路召回权重失衡符号检索结果rgrep总被向量结果压制检查合并逻辑确认symbol_score * 0.3 vector_score * 0.7是否被执行在search.py中临时添加print(fSymbol score: {s}, Vector score: {v})确认数值量级是否匹配提示我们曾因git log时区问题导致days_since_last_commit算出负数decay变成无穷大所有新代码权重归零。这个坑踩过一次就忘不了。5.2 “本地模型响应慢GPU显存爆满”——性能调优的3个硬核技巧性能问题往往不是硬件不行而是配置没榨干。我们总结出三条立竿见影的技巧技巧一GPU offload层数的黄金分割点。不要盲目设--n-gpu-layers 99。Llama-3-8B有32层我们实测设30层时显存占用8.1GB推理速度28 token/s设35层超出实际层数显存暴涨到11.4GB速度反而降到22 token/s。最优解是总层数 - 2即30层此时GPU-CPU数据搬运开销最小。技巧二上下文长度ctx-size的精准拿捏。默认4096够用但若项目代码普遍很长如单文件2000行设4096会导致大量代码被截断。我们改用动态ctx-size在ingest时统计所有代码片段的平均长度设ctx-size max(4096, avg_length * 1.5)。对平均长度1200的代码库设ctx-size6000虽显存增0.3GB但检索召回率提升12%。技巧三批处理batching的隐性杀手。llama.cpp默认--batch-size 512但对代码补全batch-size128更优——它减少等待时间让首token延迟更稳定。我们在systemd服务配置中显式添加--batch-size 128实测P95延迟从1120ms降至890ms。5.3 “VS Code插件不响应或建议乱码”——前端集成避坑手册插件问题80%出在环境隔离。我们遇到过最诡异的案例插件在VS Code Insiders版正常在Stable版报ModuleNotFoundError: No module named jedi。根源是VS Code Stable默认用系统Python而Insiders用自带Python。解决方案是插件强制指定Python解释器路径。在extension.ts中// extension.ts const pythonPath workspace.getConfiguration(python).getstring(defaultInterpreterPath); const childProcess spawn(pythonPath, [path.join(__dirname, backend, ast_parser.py)]);此外乱码问题几乎全是编码惹的祸。jedi解析时若文件是gbk编码尤其Windows老项目会抛异常。我们在ast_parser.py开头强制声明import sys import locale # 强制UTF-8避免gbk编码崩溃 sys.stdout.reconfigure(encodingutf-8) sys.stderr.reconfigure(encodingutf-8) # 读取文件时显式指定encoding with open(file_path, r, encodingutf-8) as f: source_code f.read()注意这个encodingutf-8不能省略否则jedi的Project初始化会失败。我们为此专门写了个pre-commit钩子扫描所有Python文件用file -i命令检查编码非UTF-8的自动转换从源头杜绝问题。5.4 “如何让AI学会我们公司的私有规范”——定制化能力的深度解锁公司规范如“所有API响应必须带X-Request-ID”、“数据库查询必须用select_related”是AI最难掌握的。我们的解法是规则引擎微调数据注入。规则引擎在提示词中动态注入company_rules区块。例如rules.json内容为{ api_response: 所有FastAPI路由必须在response_model中定义且返回字典必须包含request_id: str字段, db_query: Django ORM查询必须使用select_related(user)或prefetch_related(items)禁止N1查询 }后端在生成提示词时将此JSON转为自然语言段落插入到角色定义之后。微调数据注入我们收集了100个内部Code Review的典型评论如“缺少request_id字段请补充”、“此处应使用select_related”用LoRA对Llama-3-8B做轻量微调仅训练0.3%参数生成llama3-8b-company-lora。部署时llama-server启动参数加--lora ./models/llama3-8b-company-lora。微调后模型对规范的遵守率从58%跃升至91%。最关键的是这个LoRA只有12MB可随时启用/禁用不影响主模型。我个人在实际操作中的体会是CodeCompanion的价值从来不在它能写出多炫酷的代码而在于它把工程师从重复劳动中解放出来把精力聚焦在真正需要人类智慧的地方——比如判断一个业务逻辑是否该用状态机而不是纠结于JWT的exp字段该设多少秒。它不是一个替代者而是一面镜子照出我们代码中那些被忽视的耦合、那些该写却没写的测试、那些本该被重构却一直拖着的烂摊子。当你开始习惯它的建议甚至开始质疑它的建议时你就已经赢了。