Understand-Anything 基准测试中的结果语义设计:如何区分「分析不支持」与「执行失败」
2026/9/7 17:20:58 网站建设 项目流程

Understand-Anything 基准测试中的结果语义设计:如何区分「分析不支持」与「执行失败」

【免费下载链接】Understand-AnythingGraphs that teach > graphs that impress. Turn any code into an interactive knowledge graph you can explore, search, and ask questions about. Works with Claude Code, Codex, Cursor, Copilot, Gemini CLI, and more.项目地址: https://gitcode.com/GitHub_Trending/un/Understand-Anything

本篇围绕 Understand-Anything 仓库中大型仓库基准测试(large-repo benchmark)的一项设计文档展开:当某个文件没有任何已注册的分析器、或某个解析器缺少可选的调用图(call-graph)能力时,基准报告应当把它记为「跳过」(skipped)而不是「失败」(failed)。读完后,你将理解该仓库如何在不改动报告 schema 1.0.0 的前提下,把「能力缺失」从完整性故障中剥离出来,使报告状态从failed回归degraded(退出码 0),并能沿着PluginRegistryanalyzeFileWithOutcomes→ 完整性汇总这条调用链核对源码级实现与测试证据。

问题背景:一次「假失败」的复现

设计文档 benchmark-unsupported-outcomes-design.md 描述的起点是一个具体的故障现象:对 Understand-Anything 仓库自身做全量基准复现时,报告被判定为failed,尽管所有被报告的「分析失败」本质上都是——

  • 文件类型没有任何注册的分析器(unsupported file);
  • 或已选中的解析器不具备可选的调用图能力(unavailable optional call-graph capability)。

根因在于一次「契约返回值」被误标:PluginRegistrygetPluginForFile(path)在契约上就允许返回null(表示该路径没有注册插件),但旧的结果映射器把这个契约性的null折叠成了运行时失败。而基准的完整性检查(integrity check)本身工作正常——它忠实地把被错误标记的failed计入了结构失败数,进而把整份报告拖入failed状态。设计文档对此的表述很直白:这是 mapper 的错,而不是 integrity 检查的错。

文档给出了复现运行的定量证据:

指标数值说明
扫描文件数457全仓库运行
扫描行数126,169全仓库运行
结构「失败」数20全部 20 个均因没有注册分析器
调用图「失败」数18全部 18 个均因没有调用图能力
已注册解析器真实失败0没有任何一个注册过的解析器真的报错

「零真实失败」这个结论是整个语义重设计的依据:既然失败全部源于能力缺失,那么正确的表达应当是「跳过」,而不是「失败」。

三个候选方案与取舍

设计文档评估了三种方案,这种「列出备选 + 说明否决理由」的结构值得在设计文档写作中借鉴。

方案一:复用现有 skipped 路径,但加入能力感知(选定)

核心思路:在分析之前先查询注册表支持情况(capability-aware)。

  • 文件若没有注册分析器,走已存在且已测试filesSkipped字段记账,报告因此是degraded、退出码 0;
  • 代码/脚本类解析器如果只有结构能力、没有可选的调用图 API,则记录一次「跳过的调用图」;
  • 一旦解析器被选中后抛出异常、返回非法最终值或产生损坏输出,则仍然判定为 failed——即能力一经宣告,失败就是真失败。

该方案的收益在设计文档中明确列出:保持报告 schema 1.0.0 不变、保持仓库规模总量(scanner totals)完整、复用已有测试覆盖的 degraded 报告路径。

方案二:在 schema 新版本中加入显式 unsupported 计数器(否决)

设想是新增structureUnsupported与能力级计数器。表达力更强,但代价是把一个本应局部的补丁扩大成 schema 版本化 + 迁移工作——而既有的 skipped 字段已经足以建模所需行为。从文档的取舍逻辑看,这是一个典型的「现有数据模型够用就不加版本」的保守决策。

方案三:扫描阶段排除不支持的扩展名(否决)

设想是让 scanner 直接忽略所有不支持的扩展名。文档列出三条否决理由:

  1. 隐藏了有用的仓库规模信息(scanner totals 会变小);
  2. 需要维护一份脆弱的重复 allowlist(与语言注册表形成双份事实来源);
  3. 根本解决不了「已支持解析器上的可选能力探测」问题——调用图能力的缺失与扩展名无关。

选定行为(Selected behavior)逐条解读

设计文档用六条行为规范锁定语义,这里逐条结合仓库实现解读:

1. 扫描总量继续包含所有发现的文件。扫描器对文件的支持判定与结构分析的支持判定解耦:.xyz这类无分析器的文件照常进入文件数与行数统计,保证基准的「规模证据」属性不被能力缺口侵蚀。

2.getPluginForFile(path)返回null时,该路径计入filesSkipped,不产出结构结果。这一条的契约源头在 registry.ts:

getPluginForFile(filePath: string): AnalyzerPlugin | null { const langConfig = this.languageRegistry.getForFile(filePath); if (!langConfig) return null; return this.getPluginForLanguage(langConfig.id); }

即两层都可能返回null:语言注册表不认识该扩展名,或语言认识但没有插件注册到它。设计文档要求的就是:这个契约性的null必须在基准语义中被显式消费,而不是被下游当异常处理。

3. 结构能力与调用图能力分别记账。若选中的解析器支持结构但不支持调用图提取,结构结果记succeeded,调用图记skipped。这条对应的实现细节见下节的analyzeFileWithOutcomes

4. 能力一经宣告,失败即真失败。「Once a parser advertises a capability, an exception, missing final value, or malformed output is a real failure.」这是防作弊条款:不能因为引入了 skipped 语义,就顺手把解析器抛异常、返回undefined、返回结构体缺字段这类真故障也吞成 skipped。

5. 只含「不支持跳过」的运行是degraded而非failed,退出码 0。这一条最终落到了基准运行器的状态判定逻辑中,large-repo-benchmark.mjs 中的顺序是:

if (hasFailedIntegrity(report.integrity)) { report.status = 'failed'; report.error = 'One or more deterministic integrity checks failed'; exitCode = 1; } else if ( report.warnings.length > 0 || report.integrity.filesSkipped > 0 ) { report.status = 'degraded'; exitCode = 0; } else { report.status = 'ok'; exitCode = 0; }

注意filesSkipped > 0只触发degraded分支;而hasFailedIntegrity(large-repo-benchmark.mjs)判定的故障项里只有structureFailures > 0callGraphFailures > 0、批次缺失/重复/畸形等硬故障——filesSkipped根本不在失败判定列表中。这正是「跳过不触发完整性失败」在汇总层的落点。

6. Markdown 与 JSON 报告保持相同的 schema 版本与成对完整性规则。即不引入新字段,报告 schema 1.0.0(large-repo-report-1.0.0.schema.json)原样保留,pairId、digest、锁定的成对写入机制(deliverBenchmarkReports)均不受影响。

源码级实现:三个文件如何落地这套语义

1. 能力感知的结果映射analyzeFileWithOutcomes

纯映射逻辑独立在 extract-structure-result.mjs 中(注释写明它是从 CLI 入口拆出来的,便于单测不 import shebang 脚本)。关键路径在 L132-L151:

export function analyzeFileWithOutcomes(registry, file, content) { const wantsCallGraph = file.fileCategory === 'code' || file.fileCategory === 'script'; const selectedPlugin = typeof registry.getPluginForFile === 'function' ? registry.getPluginForFile(file.path) : registry; if (selectedPlugin === null) { return { analysis: null, callGraph: null, structureOutcome: 'skipped', callGraphOutcome: 'skipped', }; } const supportsFullAnalysis = typeof selectedPlugin?.analyzeFileFull === 'function'; const supportsSeparateCallGraph = typeof selectedPlugin?.extractCallGraph === 'function'; // ...

三个要点与设计文档一一对应:

  • 先查注册表getPluginForFile返回null时两个 outcome 都是skipped,对应行为条款 2;
  • 调用图是「想要才要」wantsCallGraph只对code/script类别为真,docs/config 类文件即使解析器宣告了analyzeFileFull也不会走 full 路径(测试用例「uses separate structure analysis for non-code files even when full analysis is advertised」专门验证fullCalls === 0);
  • 能力探测决定调用图命运supportsSeparateCallGraph为假时,调用图 outcome 初始化为skipped而非failed,对应行为条款 3。

而「真失败」防线同样落在这一函数内:analyzeFileFull抛异常、返回null/undefinedstructure不通过isValidStructuralAnalysis(必需字段functions/classes/imports/exports缺失或条目畸形)、callGraph不通过isValidCallGraph,均分别映射为structureOutcome: 'failed'callGraphOutcome: 'failed',且两者独立判定(full 结果里结构非法但调用图合法时,会出现structureOutcome: 'failed'+callGraphOutcome: 'succeeded'的混合结果)。

2. CLI 入口把 skipped 归入filesSkipped

extract-structure.mjs 的主循环(L110-L119):

const { analysis, callGraph, structureOutcome, callGraphOutcome } = analyzeFileWithOutcomes(registry, file, content); if (structureOutcome === 'skipped') { filesSkipped.push(file.path); continue; } analysisOutcomes.structure[structureOutcome] += 1; analysisOutcomes.callGraph[callGraphOutcome] += 1;

注意被跳过的路径不产生 result、不计入任何 outcome 计数,所以输出中filesAnalyzed === results.length的不变量(基准侧summarizeStructureOutput会校验这一点)天然成立;而filesSkipped里的每个路径仍会被基准的pathCounts统计,保证「每个被扫描文件在结构阶段都有交代」的完整性检查(missing/duplicate/unexpected structure paths)不因跳过而误报。

输出契约(脚本头部注释与 L127-L133 一致):

{ "scriptCompleted": true, "filesAnalyzed": 0, "filesSkipped": [], "analysisOutcomes": { "structure": { "succeeded": 0, "failed": 0 }, "callGraph": { "succeeded": 0, "failed": 0, "skipped": 0 } }, "results": [] }

3. 基准侧的汇总与状态判定

基准运行器 large-repo-benchmark.mjs 中summarizeStructureOutput(L1257 起)把每个批次的输出折算成filesAnalyzed / filesSkipped / structureSucceeded / structureFailed / callGraphSucceeded / callGraphFailed / callGraphSkipped,其中complete的判定条件是「无畸形 且structureFailed === 0callGraphFailed === 0且路径账目全平」——callGraphSkipped不参与 complete 判定,这与设计文档「missing optional call-graph capability is also skipped」完全一致。随后buildBenchmarkIntegrity(L1423 起)把各批次汇总为integrity块,structureCoverage只按「成功 /(成功+失败)」计算,skipped 不计入分母。

验证标准:red/green 测试与全量基准的验收口径

设计文档的 Verification 一节给出了可执行的验收标准,仓库中的测试文件 test_extract_structure_outcomes.test.mjs 已按此落地:

  • RED→GREEN 用例 1:注册表getPluginForFile()返回null,期望两个 outcome 均为skipped(L333-L352);
  • RED→GREEN 用例 2:选中的解析器只有analyzeFile(无analyzeFileFull也无extractCallGraph),期望结构succeeded、调用图skipped(L354-L382);
  • 既有异常测试继续有效:「records advertised full-analysis exceptions as failures」等用例证明抛异常、返回undefined、畸形条目(测试覆盖 30+ 种畸形结构条目与 6 种畸形调用图条目)仍映射为failed——即行为条款 4 的回归防线。

除此之外,配套测试还包括 test_large_repo_report_schema.test.mjs 与 test_large_repo_benchmark.test.mjs,后者覆盖 normal、empty、degraded、partial-failed 四类报告对 schema 1.0.0 的符合性。

全量基准的最终验收目标(来自设计文档):对干净的 Understand Anything worktree运行,产出的成对报告须满足——

  • 结构失败 0;
  • 调用图失败 0;
  • 失败批次 0;
  • 状态非failed(预期为degraded,因为filesSkipped > 0)。

发布流程与修复后的真实报告

设计文档的 Publication 一节约定:修复提交并推送后,把生成的 Markdown 与 JSON 报告贴到 PR #587,且评论必须说明「TensorFlow 未在本地运行,因为其仓库规模与资源需求超出本次本地验证的实际范围」。仓库内已存在该次发布的实证样本 understand-anything-pr587-full-sample.md(配套 JSON 见 understand-anything-pr587-full-sample.json),修复后报告的头部指标是:

MetricValue
Statusdegraded
Files / Lines459 / 127,155
Structure coverage1
Files skipped20
Failed batches0
Schema version1.0.0

与修复前复现运行(457 文件 / 126,169 行)相比,文件数与行数略有差异属正常——两次运行对应的 worktree 提交点不同;关键在于状态从「被 38 个能力缺失型失败拖入failed」变为「20 个文件记入filesSkipped、状态degraded、退出码 0」。这正对应 large-monorepo.md 中对外文档的同步更新:「没有注册结构解析器的文件会计入filesSkipped并产生 degraded 报告;缺少可选调用图能力同样记为跳过;解析器宣告能力之后的异常或非法结果仍是完整性失败」。

关键语义速查

结合 large-monorepo.md 的退出码表与本次设计,完整决策链可以压缩成一张表:

情形structureOutcomecallGraphOutcome报告影响
getPluginForFile返回null(无注册分析器)skippedskipped计入filesSkipped,无 result;状态degraded,退出码 0
code/script 文件,解析器无analyzeFileFull/extractCallGraphanalyzeFile结果判定skipped调用图不计失败
解析器抛异常 / 返回非法值 / 输出畸形failedfailed(独立判定)触发hasFailedIntegrity,状态failed,退出码 1
全部成功且无跳过状态ok,退出码 0

从这次设计可以看到 Understand-Anything 基准体系的一条工程原则:完整性检查宁严勿松,但对「能力缺失」必须给出独立的、可审计的出口——把null的契约语义在调用点显式消费掉,而不是让下游用失败来猜测它的含义。相关文件清单供继续深入:设计文档 2026-07-17-benchmark-unsupported-outcomes-design.md、实施计划 2026-07-17-benchmark-unsupported-outcomes.md、CLI 入口 benchmark-large-repo.mjs、基准运行器 large-repo-benchmark.mjs、映射层 extract-structure-result.mjs、CLI 与结果记账 extract-structure.mjs、注册表 registry.ts。

【免费下载链接】Understand-AnythingGraphs that teach > graphs that impress. Turn any code into an interactive knowledge graph you can explore, search, and ask questions about. Works with Claude Code, Codex, Cursor, Copilot, Gemini CLI, and more.项目地址: https://gitcode.com/GitHub_Trending/un/Understand-Anything

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

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

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

立即咨询