
一、一段高度相关的代码来自一个已经删除的模块你让 Agent 处理一个任务给订单取消流程加幂等处理——同一个订单重复取消第二次要返回已经取消的结果而不是报错。为了避免它到处乱翻你打开了代码检索工具让它先检索相关实现。它检索回来五条片段其中一条看起来非常对口一个名为CancelOrderHandler的类里面有完整的幂等判断逻辑甚至有注释写着重复请求返回上一次结果。它基于这条片段给出了方案在CancelOrderHandler里补一个分支复用已有的幂等键逻辑。问题是这个类在半年前的架构调整里已经被删掉了替代它的是order_service.py里的一个函数。检索工具不知道这件事因为它的索引建立在那之前的某个时间点而且从此没有再更新过更细一点说索引里存的是当时那份代码的快照而代码库早就往前走了。你的评审如果不够仔细就会进入一个尴尬的局面一个基于不存在的类写出来的方案看起来完整、命名一致、逻辑自洽却无法落地。修改它的成本甚至比从零开始更高——你要先解释为什么这个类不存在再重新讨论方案。这一篇处理的就是这一类问题检索系统告诉你这段代码和问题相关但它不会告诉你这段代码还是当前版本。相关内容与正确内容是两件事中间需要一层版本信息来连接。把这件事放到日常节奏里看它出现的频率比想象的高新同事入职时看的项目文档、上个季度整理的技术方案、从其他团队复制过来的示例代码——它们都带着曾经正确的属性而使用它们的人常常默认现在也正确。代码检索只是把这个问题工程化、规模化地呈现出来。处理方式也一致给材料带上来源和时间在使用前校验一次。二、先把几个词讲明白检索从一大堆材料里找出和问题相关的一小部分。你搜关键字是检索向量搜索也是检索代码检索工具是检索RAG 这个说法里的 R 也是检索。RAG先检索、再把检索到的内容交给模型生成答案的做法。它的好处是模型不必记住所有代码坏处是它会对检索回来的内容照单全收——包括过期的内容。相关性分数检索结果和查询的接近程度的数值。分数高说明像不说明对。生活里的类比是图书馆找书目录系统能告诉你这本书和你的题目接近但不能告诉你这本书里写的是旧版规范。索引为了让检索变快而建立的数据结构。对于代码来说索引是一份快照——某个时刻的代码被切块、编号、存储下来。索引不会自动跟随代码更新。切片chunk把长文件切成一段一段方便检索和放入上下文。切片的问题是上下文会被截断一个函数的前后条件、调用者、被调用者都可能被切开剩下的片段看起来仍然相关。符号全名函数、类、方法在代码里的完整名字比如OrderService.cancel_order。它是判断这段片段是否还有效最直接的线索——符号改名或者删除检索结果里的引用就失效了。元数据附加在材料上的信息来自哪个仓库、哪条分支、哪个提交、什么时候索引、文件内容哈希。元数据不参与相关性打分但它决定了你能不能判断这份材料的时效。失效stale材料曾经正确、现在不再描述现实。索引失效是最常见的失效形式代码变了索引还停在过去。引用头放在片段前面的一小段说明写清来源、版本和用途。它不参与检索但决定了这段材料进入上下文时是事实还是历史参考。三、为什么相关不能保证正确3.1 检索的目标是相似度不是真实性检索系统的设计目标是从大量内容里找出最有可能有帮助的一小部分。它对有可能有帮助的度量方式是相似度词重合、语义接近、结构相仿。这个度量对时效性是完全无感的——一段三个月前的代码和今天的问题可能比当前代码更像因为旧代码里可能恰好有一段更贴近你描述的注释。于是出现一个反直觉的现象越具体的旧片段越容易被检索系统排到前面。一段包含详细的幂等处理、注释完整、命名清晰的旧代码在相似度上往往胜过当前版本里那段写得朴实、几乎没有注释的替代实现。检索没有错它忠实执行了它的规则错的是把它返回的结果当作了当前事实。3.2 代码有三个容易出错的维度具体到代码检索失效集中在三个维度上第一是位置。文件被移动、重命名、拆分让片段里的路径和行号不再指向原来的内容。位置失效最容易被发现因为你打开那个路径会发现不存在或者内容对不上。第二是版本。同一条分支上的代码前后不同不同分支上的同一路径内容也可能不同。检索索引如果建立在旧提交上或者在多个分支之间混着建返回的片段就和你手上的工作区不一致。版本失效最隐蔽因为路径存在、内容也合理只是它属于过去。第三是符号。函数被重命名、类被删除、方法被移动片段里的符号在新代码里不再存在。符号失效会以无法解析的形式暴露但前提是你去验证。三个维度可以同时出现也可以各自独立。判断时需要逐个查路径对不对、版本对不对、符号在不在。还有一个补充的观察三个维度里位置和符号可以靠一次搜索发现版本不能。路径存在、符号存在、代码看上去也合理但它是旧版本的——这种失效在表面上没有任何异常。所以在三种校验里版本校验的优先级最高也最容易被跳过因为它不像文件不存在那样会主动把你的注意力拉过来。3.3 索引是快照代码是活的索引的构建过程通常是扫描代码、切块、计算表示、写入存储。整个过程发生在某个时间点之后索引就静止了除非有人再次构建。代码则在持续变化合并、回滚、重构、删除。两条时间线一旦分开索引就开始积累失效内容而且它积累的速度和团队的提交速度相关。这里有一个容易忽略的后果索引的失效不会自我暴露。一条索引记录不会过期变红它只是在被使用时给出一个过去的答案。发现失效的唯一办法是在使用的那一刻去校验它——这就是为什么元数据和校验流程是这套机制里不可替代的部分。换个角度说索引把时间这个维度藏起来了它保存了内容却没有保存这份内容属于哪个时刻的意识。使用者的工作就是把时间维度补回去——补的方式就是元数据和校验。理解这一点之后为什么要多存几个字段就不再是额外的负担而是让检索结果可用的前提条件。3.4 用元数据把相关性升级成可用性让检索结果变得可用的方法很朴素给每条结果附带足够的元数据让使用者在用之前能自己判断。一个最小集合是六项仓库 路径 片段来自哪里 分支 它在哪条分支上有效 提交commit 索引时刻对应的提交 符号全名 片段里主要符号的完整名字 行号范围 片段在文件里的位置 索引时间 哈希 何时索引、内容指纹六项的作用各不相同路径和符号回答它是什么分支和提交回答它是什么时候的行号和哈希回答它还是不是原样。后两项是校验依据——把片段对应的文件当前哈希算出来和记录里的哈希比一比就能知道这一块内容有没有变过。有了元数据使用规则也可以固定下来使用前两步校验任何一步不通过就重新读取当前文件不要继续用缓存片段。第一步校验版本索引记录的提交是不是当前工作区的版本或者至少在同一个分支且没有涉及该文件的改动第二步校验内容当前文件的哈希和记录里的是否一致两步都通过可以放心使用有一步不通过就以当前文件为准并把索引记录标记为需要重建。3.5 引用格式让我看到的是哪一版写清楚校验之外还有一个成本极低的习惯给每一条进入上下文的代码片段加上引用头。格式可以用三行来源src/orders/services/order_service.py#L41-L58 版本main9f3c1ab2026-09-29 校验哈希一致内容与索引时相同三行的作用是让我看到的是哪一版从脑子里的一团印象变成纸面上的事实。它同时服务于三件事评审时能核对任务结束后能追溯下次任务能直接复用如果版本没变这三行就是现成的。还有一个细节当片段来自历史参考失效但可借鉴的内容时引用头要显示不同的措辞来源src/orders/handlers/cancel_handler.py模块已删除 版本feature/old-arch8d0a1f22026-03-11 用途仅参考幂等设计思路实现勿参照用途这一行是给模型看的指令它把这段内容和现状无关这件事明确说出来了。没有这一行历史片段和当前片段在上下文里的地位是一样的。四、完整例子五条检索结果逐个校验4.1 任务与检索任务虚构示例shop订单服务要改进取消流程的幂等性——同一个订单重复调用取消第二次返回已取消的结果而不是报错审计事件不能因为重复请求而多写。你允许 Agent 使用代码检索工具要求它把检索结果的原样元数据一并写出。它返回了五条片段整理成一张表#来源路径符号分支提交索引时间判定1src/orders/handlers/cancel_handler.pyCancelOrderHandlerfeature/old-arch8d0a1f22026-03-11失效模块已删除2src/orders/services/order_service.pyOrderService.cancel_ordermain9f3c1ab2026-09-28可用3src/orders/generated/order_api_client.pyOrderApiClient.cancelmain9f3c1ab2026-09-28排除生成代码4src/orders/repositories/order_repo_old.pyOrderRepoOld.savemain4c2e77a2026-06-02失效备份文件5src/orders/domain/order.pyensure_cancellablemain9f3c1ab2026-09-28可用示例数据。五条结果里真正可用的只有两条一条来自旧分支、一条属于生成代码、一条属于备份文件。如果不做校验直接把这五条一起交给模型它有很大概率会把第一条当成主线实现来参考——因为它在语义上最对口。4.2 校验过程对每一条做两步校验过程写下来# 第一步版本校验索引提交 vs 当前 HEAD $ git rev-parse HEAD 9f3c1ab... # 当前 main 的提交 # 索引提交 9f3c1ab 的片段通过版本校验 # 索引提交为 8d0a1f2、4c2e77a 的片段需要进一步检查文件是否存在。 # 第二步内容校验文件哈希 vs 索引哈希 $ git ls-files -s src/orders/services/order_service.py 100644 7be2... 0 src/orders/services/order_service.py # 与索引记录里的哈希一致 - 该片段与索引时内容相同可以直接使用示例输出。校验里有两个细节值得注意。第一个是版本校验不通过但内容校验通过的情况比如索引提交较旧但该文件此后没有变过——这种情况下片段依然可用只要把它标注为内容一致、版本较旧。第二个是文件不存在的情况路径src/orders/handlers/cancel_handler.py在当前分支查不到这条结果直接判失效不需要再做内容校验。4.3 校验之后的使用方式校验完把结果分成三类交给模型时的处理方式也不同类别片段处理方式可直接使用#2、#5原样给出附带元数据排除#3生成代码不进入上下文说明原因失效参考#1、#4可作为历史背景必须标注模块已删除勿参照注意第三类不是删掉不用。旧架构里的幂等设计可能包含有价值的思路幂等键怎么生成、重复请求怎么识别、审计事件怎么去重。这些思路可以借鉴但借鉴的前提是明确知道它不是现状——所以标注勿参照实现、仅参考思路比直接丢弃更合理。4.4 模型在这两种输入下的差别同一个任务跑两遍示例记录一遍把五条片段不加标注地给出去一遍用上面校验之后的输入。第一遍的方案是围绕CancelOrderHandler展开的它建议在处理器里加幂等分支、复用CancelOrderHandler的键生成逻辑还建议把新字段加进这个类的构造函数。方案的每一句都建立在一个不存在的类上评审时需要从头解释。第二遍的方案落在order_service.py和domain/order.py上它先确认了当前是函数式编排然后给出在取消用例中读取已有审计事件的幂等判断并把重复请求不新增审计事件写进了改动说明。这个方案和当前代码结构一致验收条件也直接可测。两次的差别不在于模型的判断能力而在于输入是否描述了一个真实存在的世界。给过期材料就是在请它对一个不存在的世界做设计。这也解释了为什么校验不该被视为对模型的不信任。它和给人类新同事准备材料是同一件事你会告诉新同事这份文档是去年的接口已经改了以代码为准。模型需要的正是同一句话因为它的处境和新同事一样——没有历史只能从材料里判断现状。4.5 把校验做成流程里的一个动作校验不该依赖人的细心。把它做成一个固定动作两种做法都可以第一种在检索工具的输出里自动附带元数据路径、分支、提交、索引时间、哈希并在使用规则里写明元数据缺失的片段不得使用。成本在于工具改造收益是全覆盖。第二种用一个很小的脚本做批量校验读入检索结果列表对每一条查git rev-parse和文件哈希输出可用 / 版本较旧 / 失效 / 缺失四类。脚本不需要复杂二十行左右就够它把我知道要校验变成每次都会校验。两种做法都需要一个前提索引本身要带元数据。如果索引只存了文本和分数那么无论用什么流程你都无法判断时效。这件事在引入检索工具时就该问清楚它记录什么、更新频率如何、支持不支持按分支索引。4.6 索引怎么维护才不容易失效维护索引有三条可用的策略按成本从低到高第一条是定期重建。每天或者每次合并之后重跑一次索引构建。它的好处是简单坏处是两次构建之间仍然存在窗口期的失效对变化快的仓库窗口期可能正好覆盖你这次任务。第二条是按分支分别建索引并只使用当前分支的索引做检索。这能解决同一条路径在不同分支内容不同的问题代价是索引数量和构建频率上升。第三条是按需校验也就是前面说的使用前两步校验。它不依赖索引是否最新代价是每次使用多两个查询。实践中最稳的组合是第二条加第三条分支独立使用前再校验一次内容。无论选哪种都建议把索引版本写进使用记录这次任务用的索引对应哪个提交、构建时间是什么。出现检索给的片段和现状不符时第一时间能定位到索引的年龄。4.7 结果进上下文之前的整理动作校验完成之后、把材料交给模型之前还有一个三十秒的整理动作顺序固定1. 去掉重复同一符号的多个切片只保留最完整的一段 2. 补上边界被切片截断的函数补回签名和关键注解 3. 加上引用头来源、版本、校验结论三行 4. 标注用途可用 / 仅参考思路 / 排除排除项不放进上下文 5. 记录清单本次用了哪几条、来自哪个提交第一步和第二步对应切片的固有缺陷检索按块工作块边界常常切在函数中间删掉了签名或者参数说明。补回边界比多给几段更有效——多给几段会重新引入噪音补边界只补齐理解所必需的部分。第五步的清单建议随手写进任务的记录文件。它的作用在下次任务才完全显现同一个模块的任务反复出现时你能看到哪些片段每次都要用这些片段就值得从检索结果升级成第一层材料。4.8 那个二十行脚本长什么样上一节说二十行左右就够下面把它写出来。脚本读一份检索结果清单逐条对比当前工作区文件还在不在、内容哈希和索引时是否一致。# tools/check_snippets.py逐条校验检索结果是否仍然可用# 用法python tools/check_snippets.py snippets.tsv# 清单每行格式相对路径TAB索引时记录的哈希前缀importhashlibimportpathlibimportsysforlineinpathlib.Path(sys.argv[1]).read_text(encodingutf-8).splitlines():ifnotline.strip():continuepath,_,recordedline.rpartition(\t)ppathlib.Path(path)ifnotp.exists():print(f失效{path}文件已不存在)continuenowhashlib.sha256(p.read_bytes()).hexdigest()[:12]ifnotrecorded:print(f待补{path}结果里没有版本元数据)elifnowrecorded:print(f可用{path}哈希一致{now})else:print(f较旧{path}索引{recorded}- 现在{now})运行结果示例$ python tools/check_snippets.py snippets.tsv 可用 src/orders/services/order_service.py 哈希一致 4a91c0f7d2e8 较旧 src/orders/repositories/order_repo.py 索引 88b0e5c1ff02 - 现在 1c73ba90dd41 失效 src/orders/legacy/cancel_v1.py 文件已不存在 待补 docs/design/error-codes.md 结果里没有版本元数据四类输出对应四种处理动作。可用的片段带引用头直接用较旧的先重读当前文件再决定去留因为内容变了不等于不相关可能只改了注释或者格式失效的直接从材料里删掉同时记一笔索引里还留着已删除的文件这是索引该维护的信号待补的最有用——它提醒你这条结果来自一个不记录版本的来源单独看它时没有任何时效保障只能作为线索不能作为现状依据。还有一条边界要写清楚哈希一致只说明文件本身没变不代表这段代码在架构里还有效——举例来说order_repo.py一个月没改过但调用它的服务层已经换了一套事务边界那段片段作为现状依然会误导人。所以哈希校验解决的是文件变没变剩下的判断仍然靠引用头里的两行这段片段说明了什么、这次打算怎么用它。怎么检查脚本真的在起作用故意做一次破坏性试验。把清单里某一行的哈希改掉一个字符重跑脚本应该把这条标成较旧。如果它仍然输出可用说明哈希那一段没生效常见原因是脚本读的是一个缓存副本而不是当前文件。这个试验值得在接入检索工具的头一周做一次它能证明你的校验链条真的连着工作区。五、反例与代价四种让检索误导人的做法5.1 反例一看分数不看元数据做法按相关性分数从高到低取前几条直接放进上下文。它为什么看起来能行分数是检索系统给出的唯一量化指标用它排序是最自然的做法。最后的代价是高分过期被优先使用。前面说过旧片段常常因为注释更完整、描述更具体而获得更高分。分数高的片段反而更容易是失效的——这个反直觉现象解释了为什么很多团队一开始用检索时觉得效果很好索引新鲜过了一段时间就开始出现引用了不存在的东西索引变旧。值得补一句的是这个问题不会自然好转。索引越用越旧除非有人重建团队提交越快索引和现状的差距越大。把一个使用前校验的动作加进流程是这类问题的唯一系统性解法——它不是一次性修复而是每次使用都要经历的一道门。5.2 反例二索引不记录版本做法索引里只存文本和向量不记录提交、分支和时间。它为什么看起来能行存储更省、构建更快检索出来照样能用文本做对比。最后的代价是失效不可判断。使用者无法区分这段代码还是原样和这段代码三个版本前就改了工具无法提示这条结果来自已删除的文件。更麻烦的是责任模糊出问题时你会归咎于模型乱说而实际原因是系统没有提供判断依据。修复的代价也会高——往往需要重建整套索引结构。5.3 反例三把缓存片段当现状使用从不重读做法第一次检索之后把片段缓存起来后续任务直接复用不再读文件。它为什么看起来能行省钱省时间尤其是多次迭代同一个任务的场景——每次都重读文件既慢又啰嗦。最后的代价是缓存和代码分叉。同一个任务的多次迭代之间代码可能已经被改动包括被模型自己改的继续用旧片段做判断就会出现它对着一小时前的代码讨论现在的修改。实用的折中方案是给缓存设一个短的有效期并在做关键判断改代码、定方案之前重读涉及的文件——缓存用于探索重读用于决策。5.4 反例四把生成代码、备份文件、测试夹具都纳入索引做法索引整个仓库不做过滤。它为什么看起来能行覆盖全面谁知道哪段代码有用呢过滤规则本身也需要维护。最后的代价是噪音进入结果。生成代码协议客户端、脚手架、备份文件、测试夹具、示例配置在语义上和主线实现高度相似很容易排到结果前列。检索系统不可能替你判断这段是生成的不要改它只知道内容像。过滤应该发生在索引构建阶段通过路径规则排除生成目录和备份文件或者至少给它们打上类型标签让使用规则能区分对待。有一点需要澄清这四种反例的共同特征是看起来更省事。不做校验省一次查询不记录版本省几个字段缓存复用省一次读文件不过滤省一次配置。它们每一个单独看都很划算代价却统一落在判断依据不可靠上——而依据不可靠时省下来的时间会以返工的形式加倍还回去。六、落地步骤让检索结果带着版本使用七步里前三步是了解你的索引中间三步是使用前校验最后一步是把结果变成经验。如果团队刚开始使用检索工具可以先做第 2、3、4、5 步——它们直接决定结果能不能用第 1 步和第 7 步属于长期维护可以稍后补齐。第一步盘点索引覆盖范围。写清楚索引包含哪些仓库、哪些分支、哪些路径被排除。为什么先做这一步因为索引里有什么决定了你能检索到什么索引里混着什么决定了结果会有哪些噪音。怎么检查随机抽三条结果核对它们是否都在当初声明的范围内。第二步确认每条结果带齐元数据。六项路径、分支、提交、符号、行号、索引时间与哈希。为什么元数据必须检查因为它是后面所有判断的依据缺一项就有一条校验做不了。怎么检查把三条结果的元数据摊开看字段是否齐全、是否可解析。第三步做版本校验。拿索引提交和当前工作区对比一致就通过不一致就查涉及的路径有没有变化。为什么以路径为粒度因为你不需要整仓库一致只需要这个文件没变。怎么检查脚本能输出通过或者需进一步检查的明确结论。第四步做内容校验。计算当前文件哈希和索引记录比对不一致就重新读取当前文件。为什么哈希比时间戳可靠因为时间戳可能因为检出、格式化等原因变化而内容哈希只对内容敏感。怎么检查故意修改一个文件后跑校验确认结果从通过变成不一致。第五步按类别处理结果。可用元数据齐全、两项校验通过、版本较旧但内容一致标注后可用、失效文件不存在或者已被替换只作历史参考、排除生成代码、备份文件、夹具。为什么分类而不是丢弃因为历史参考仍然有价值只要标注清楚。怎么检查每一类是否都有对应的处置说明。第六步把校验写进流程。用脚本或者工具规则固化检索结果先过校验再进入上下文。为什么必须固化成流程因为人的注意力会被看起来对的内容带走这一步靠自觉维持不了。怎么检查把校验脚本故意跳过一次看看交付质量有没有变化。第七步记录并复盘。每次任务记录用了几条结果、几条失效、失效的类型分布。为什么值得记因为失效分布直接提示索引策略该往哪调整——如果失效集中在某个目录可能是那个目录变化太快需要更频繁的构建如果集中在某条分支可能是分支索引策略需要改。怎么检查下一轮任务里失效比例有没有下降。可复制的使用清单[ ] 检索结果六项元数据齐全路径/分支/提交/符号/行号/索引时间哈希 [ ] 版本校验索引提交与当前工作区一致或涉及文件未变 [ ] 内容校验文件哈希一致 [ ] 失效结果已分类可用 / 较旧可用 / 仅历史参考 / 排除 [ ] 所有进入上下文的结果都带来源标注 [ ] 索引构建时间与覆盖范围已记录 [ ] 本次任务的失效统计已回填七、常见问题问检索分数高是不是就代表更应该采纳不是。分数衡量的是像不像不是对不对。在代码检索里分数高的片段常常是描述详细、注释完整的旧代码——它们因为信息密度高而更容易和你的问题匹配。正确的采纳顺序是先校验元数据路径、分支、提交、哈希再按校验结果分类最后才考虑相关性。把分数当排序依据可以把它当质量依据不行。问怎么知道索引是什么时候建的、覆盖哪些分支问工具或者看记录两个问题的具体形式是“上次构建时间和支持按分支过滤吗”。如果工具没有提供要在使用规范里写清楚元数据缺失的检索结果不得直接使用并把这件事当成选型时的评估项。索引的构建时间是这套机制里最重要的一个数字它决定了所有结果的新鲜度上限。问旧代码里的设计思路确实有用怎么用才安全把思路和实现分开使用。做法是对失效片段做一次提炼把可借鉴的部分写成两三句原则比如重复请求通过唯一键识别、审计按事件键去重标注来源和已废弃的说明把实现细节整体排除。这样它进入上下文时是一段历史经验而不是一段可以照抄的代码。提炼还有一个附带好处写原则的过程会逼你自己确认这条思路在当前架构下还成立吗。问索引重建的成本高做不到很频繁怎么办两条路可以并行。第一条是分层构建变化频繁的目录业务代码高频重建变化少的目录基础设施、脚本低频重建各自记录构建时间。第二条是把校验作为兜底无论索引多旧使用前都做版本和内容校验失效的当场剔除。分层解决尽量新鲜校验解决万一不新鲜也不出错。两者结合索引频率就不再是单点风险。问团队同时在多个分支上工作索引该怎么建优先保证当前分支可用。三种做法按复杂度排列只索引默认分支使用时把结果当作默认分支的参考并且必须校验按分支分别建索引检索时只用当前分支的索引按需构建谁需要谁触发。无论哪种都要在使用记录里写明本次结果来自哪条分支的索引否则评审时无法判断结论的适用范围。问生成代码应该纳入索引吗默认不纳入。生成代码协议客户端、序列化类、脚手架有两个问题内容多、和主线实现相似度高挤占结果位置改动它通常没有意义下次生成会覆盖。如果确实需要检索生成产物比如确认某个字段名把它单独标记为一类使用时明确这是生成代码只读取不做修改。测试夹具、示例配置同理可以用但要能被识别出来。问检索和直接读文件哪个更好它们分工不同理想流程是检索定位、读文件确认。检索的作用是从大范围里找到候选成本低、覆盖广读文件的作用是拿到权威内容成本略高、结果可信。把两者连起来的方式是前面说的校验检索给出候选和元数据校验判断时效最后读文件确认内容。跳过读文件直接使用片段就等于把候选当成了结论。问模型自己带检索能力时怎么约束它的输出用格式约束。要求它在引用代码时使用固定格式路径 符号 提交或当前工作区没有这三项的引用不采信。这条要求有两个作用迫使它区分检索到的和确认过的让你在评审时一眼看出哪些结论有依据。如果它无法提供提交信息比如工具不支持就要求它把引用范围限制在这次提供的材料里超出范围的内容必须明确标注未验证。问怎么尽早发现看起来很对但其实过期的结论三个信号值得记住。第一提到的符号在当前代码里搜不到——直接判定失效。第二路径对不上文件不存在或者内容完全不符——同样判定失效。第三描述的行为和当前测试矛盾——这时以测试为准回头检查材料。三个信号都能用很低的成本检查搜一次符号、打开一次路径、跑一次测试。把这三步固定成引用检查就能拦下大部分过期结论。问索引里的内容被删除了怎么办删除有两种含义处理方式不同。一种是文件被整体删除重构、功能下线索引记录要保留但标记为已删除检索时默认不返回需要时作为历史参考。另一种是文件还在、内容被改写这时索引的切片会指向一段已经不存在的内容——这类记录在内容校验时会失败按重新读取处理。两种情况的共同点是不要因为校验失败就静默丢弃记录而是把它标记出来因为失效记录本身是有用的数据——它能告诉你索引哪一部分在快速变化。问怎么让校验不影响正常的检索速度把校验分成两档。快档只做版本校验里的一个动作比较索引提交和当前提交是否相同——相同就直接通过不做任何文件级检查只有在提交不同时才对涉及的少量文件做哈希校验。这样绝大多数请求只多一次很轻的比较成本几乎可以忽略。慢档用于关键判断改动代码之前、写进方案之前对涉及的文件逐个开哈希校验。快档覆盖日常慢档保关键路径——分级之后校验太慢就不再是跳过它的理由。问如果团队还没有代码检索工具这一篇还有用吗有用因为检索的范围比你想象的宽。除专门的工具之外还有三类常见的检索在聊天里搜索以前贴过的代码片段这类片段最容易过期、在文档里搜索接口说明文档比代码更容易过期、以及从别处复制过来的示例代码来源往往不明。这三类都适用同样的规则带着来源和版本使用使用前校验。哪怕只是养成一个习惯——把来源、时间和是否仍有效三行写在每段材料前面——也能解决大部分问题。问校验失败之后要不要立刻重建索引分情况。如果失败的是少数几个文件大多数结果校验通过不需要立刻重建——用使用前重新读取兜住就够了重建可以按原计划进行。如果失败比例明显偏高比如五条里三条失效说明索引已经整体落后这时候重建的收益大于成本值得立刻做一次。判断依据最好来自记录把每次任务的失效条数和总数记下来比例的趋势比单次结果更有信息量——连续几次比例上升就是重建或者调整策略的信号。问怎么把这一篇的规则讲给不用命令行同事听用一个生活化版本。把检索结果比作图书馆里的旧剪报剪报说的事可能曾经是真的但报纸出版的时间和现在的现实是两回事。使用前先看三样东西——出处哪份报纸、日期什么时候的、以及和手头资料对不对得上有没有新版。对不上就去查最新的原文而不是直接照剪报办事。这套说法不需要任何术语覆盖的规则和元数据校验完全一致来源、时间、内容比对。问我的项目没有检索工具这些还有用吗有用只是形态不同。“检索结果不只来自检索系统编辑器里搜出来的片段、聊天记录里粘贴过的代码、同事口头描述的我们那边是这么写的”都是同一种东西时效风险一模一样甚至更高——它们连相似度分数都没有你连看起来有多相关都判断不了。落到动作上还是三件事给每个片段的来源标一条路径和一个时间用它之前重读当前文件把只是参考思路和可以照着写分开标注。这三件事不需要任何工具而且一旦养成习惯将来接入检索工具时你会自然地把同一套规则套上去。工具的作用是把人工动作变成自动动作它不会替你决定什么是能用的证据。八、动手练习与小结练习给你的检索结果加一层校验如果你正在使用任何代码检索工具或者只是习惯用关键词搜索加复制片段按下面四步做一次。第一步挑一次最近的任务把当时用到的片段找出来。没有留档的话用检索工具重跑一次同样的查询把前五条结果导出。为每一条补齐六项元数据路径、分支、提交、符号、行号、索引时间与哈希缺的字段先留空空缺本身就是发现。第二步做两步校验。用git rev-parse HEAD确认当前提交用当前分支的文件内容计算哈希和索引记录比对。把每条结果标成可用 / 较旧可用 / 失效 / 排除。第三步重做一次那个任务或者至少重做方案部分这次只给校验后的结果并且每条都带来源标注。记录方案的差异它这次落在哪些文件上、有没有出现引用了不存在的东西。第四步把校验固化。写一个二十行左右的脚本批量做校验或者在使用规范里加一条检索结果先过校验把本次的失效统计几条失效、原因是什么记录下来作为下一次调整索引策略的依据。做完的产出是一份校验清单、一段校验脚本或规范条文、以及一份失效统计。第三次做同样的事情时你会发现校验变成自动的了。小结这一篇讲的是检索结果的时效问题。核心结论有三条。第一检索按相似度工作相关性不提供版本证据越具体的旧片段可能得到越高的分数所以看起来最对口反而需要额外警惕。第二代码失效集中在三个维度分支、版本、位置含符号判断时要逐个检查索引是快照、代码是活的失效不会自我暴露。第三解决方式是把元数据和校验固定进流程六项元数据路径、分支、提交、符号、行号、时间与哈希、两步校验版本、内容、四类处置可用 / 较旧可用 / 仅历史参考 / 排除。三个使用习惯值得带走所有进入上下文的结果都带来源标注历史材料标注勿参照实现、仅参考思路把失效统计回填用它调整索引策略而不是凭感觉。到这里材料怎么给这条线走完了一个完整段落上一篇讲一次修改的影响面怎么找——决定给哪些材料这一篇讲检索回来的材料怎么校验——决定材料能不能用。两篇合起来是一套输入侧的卫生习惯范围要准材料要新。再往前接的是更早的两篇——材料按任务挑选代码按接口与实现分层给。从下一篇开始主题转到输出侧的另一半检查本身可靠不可靠。我们会从最常见的一句话开始——测试通过这四个字到底意味着什么以及怎样把这句话变成可以被检查的证据。补充三条最容易执行的纪律如果这一篇只能带走三件事建议是下面三条它们都能在十分钟内落地。第一条凡是用到检索结果先看它的元数据是否齐全不齐的先补齐补不齐的就不要当作事实使用。这一条能拦下最常见的一类问题——把过去的代码当成现在的代码。第二条凡是要改代码或者写方案涉及的文件必须重新读取一次检索结果只用来定位不用来确认。这一条把缓存和决策依据分开避免对着旧版本讨论新问题。第三条凡是引用历史材料写清仅参考思路或者勿参照实现。这一条让历史材料保持价值同时不污染当前判断。三条纪律都不需要新工具只需要在流程里加几个动作。它们的共同目标是同一件事让每一次判断都建立在这一版代码上而不是某一次看过的某一段代码上。补充一张图记住整套机制把这一篇的内容压成一条链方便随时回想检索相似度 → 元数据它是哪一版 → 校验版本 内容 → 分类可用/较旧/历史/排除 → 引用头来源 版本 用途 → 进入上下文 → 记录用了什么、失效多少链条上有两个位置最容易被跳过也正是问题的高发点。一个是元数据——它决定了后面所有判断能不能做另一个是引用头——它决定了模型能不能分辨事实和历史。把这两个位置补上整条链就闭环了缺任何一个检索结果就会以看起来对的形式进入上下文。还有一句提醒值得记下链条的目的不是让检索变慢而是让检索结果变得敢用。带着版本使用的结果你可以直接拿去做方案、写改动、交给评审不带版本的结果你只能在心里给每条打一个问号而这个问号会一直带到评审桌上。补充把这套习惯和已有流程接上最后说怎么落地不新增流程也能接上接在任务单的材料一节写清本次检索使用了几条结果、来自哪个提交、校验结论如何。接在评审清单里加一条引用是否带来源与版本。接在任务记录的回填里加一行失效条数 / 总条数用于观察索引健康度。接在入职文档里把使用前校验、引用带来源写成新人的默认习惯而不是口口相传的默契。四处接入点的共同点是它们都是已经存在的动作只是多写了一两行信息。这类顺手就做的改法比新增一个独立流程更容易长期维持——流程会被人绕过习惯不会。