diff --git a/CODE_ANALYSIS_2026-04-01.md b/CODE_ANALYSIS_2026-04-01.md new file mode 100644 index 0000000..b3fd4ff --- /dev/null +++ b/CODE_ANALYSIS_2026-04-01.md @@ -0,0 +1,408 @@ +# 当前代码分析与改进建议 + +**日期**: 2026-04-01 +**范围**: `src/vlm/` 主流程代码、CLI 入口、测试与工程配置 +**验证方式**: 本地执行全量测试,结果为 **483 passed / 10.73s** + +--- + +## 1. 结论摘要 + +当前仓库整体处于**中上水平**:架构主线清晰、测试覆盖较完整、执行链路明显偏向“安全优先”。项目并不是一个简单的批量重命名脚本,而是一个**分阶段、可审查、可回滚**的媒体库整理流水线。CLI 入口负责初始化配置与日志,随后将控制权分发到 `scan / parse / enrich / analyze / plan / execute` 等阶段模块。`pyproject.toml:18-19` `src/vlm/cli.py:79-155` `src/vlm/context.py:8-16` + +项目最强的地方在于: + +- 执行默认是 dry-run,真实修改必须显式确认。`src/vlm/cli.py:548-603` `src/vlm/executor.py:74-83` +- 执行前有 plan,可人工复核;执行后有 rollback log,可逆操作。`src/vlm/commands/plan.py:68-104` `src/vlm/commands/execute.py:163-177` `src/vlm/executor.py:162-184` +- 重复项优先进入 quarantine,而不是直接删除。`src/vlm/planner.py:114-158` `src/vlm/executor.py:234-262` + +但也存在一些明显的工程问题: + +1. `cli.py` 已经偏大,错误处理模式重复。`src/vlm/cli.py:158-603` +2. 阶段间仍以大量 dict/JSON 作为契约,类型边界偏弱。`src/vlm/commands/parse.py:32-155` `src/vlm/enrichment.py:24-156` `src/vlm/io.py:85-249` +3. 分析阶段对 identity 与 file 的顺序有隐式耦合。`src/vlm/commands/analyze.py:49-55` `src/vlm/io.py:181-249` +4. 相同业务规则在多个模块重复实现。`src/vlm/planner.py:34-40` `src/vlm/duplicate_resolve.py:11-16` `src/vlm/plan_review.py:12-16` +5. 部分 plan 结果依赖生成时的真实文件系统状态,降低了 plan 的可复现性。`src/vlm/planner.py:43-76` `src/vlm/planner.py:354-358` `src/vlm/planner.py:473-477` + +--- + +## 2. 当前架构概览 + +### 2.1 总体分层 + +项目大致可以分为四层: + +1. **CLI 与命令分发层**:负责参数解析、配置初始化、错误输出。`src/vlm/cli.py:79-155` +2. **命令编排层**:负责组织每个阶段的输入、输出和控制台反馈。`src/vlm/commands/scan.py:14-96` `src/vlm/commands/parse.py:16-165` `src/vlm/commands/analyze.py:19-114` `src/vlm/commands/plan.py:14-109` `src/vlm/commands/execute.py:36-257` +3. **领域核心层**:承载扫描、解析、增强、分析、规划、执行逻辑。`src/vlm/scanner.py:31-133` `src/vlm/parser.py:80-246` `src/vlm/enrichment.py:24-156` `src/vlm/analysis.py:12-123` `src/vlm/planner.py:79-190` `src/vlm/executor.py:26-184` +4. **共享模型/基础设施层**:承载 `Config`、数据模型、I/O、状态、缓存等。`src/vlm/config.py:13-148` `src/vlm/models.py:13-185` `src/vlm/io.py:29-82` `src/vlm/state.py:22-205` + +### 2.2 设计风格 + +当前实现采用的是**artifact-driven pipeline**:每个阶段把结果写入中间产物文件,而不是只在内存里串行传递。这种设计的直接收益是: + +- 阶段可以单独重跑; +- 中间结果可人工检查或编辑; +- 出错定位更容易; +- 更适合高风险文件整理任务。`src/vlm/scanner.py:527-593` `src/vlm/commands/parse.py:141-157` `src/vlm/io.py:60-82` `src/vlm/planner.py:597-677` + +--- + +## 3. 核心流程分析 + +### 3.1 Scan + +扫描阶段负责发现视频文件、分类、提取基础元数据,并可选复用旧 inventory 中的元数据缓存。目录遍历优先使用系统 `find`,不可用时回退到 Python 递归扫描。`src/vlm/commands/scan.py:47-75` `src/vlm/scanner.py:136-183` + +当前分类逻辑完全依赖 `library_root` 下的**顶层目录名**。如果目录不在配置映射中,就会被归入 `other`。这意味着目录规范直接决定了后续 parse/plan 的可用性。`src/vlm/scanner.py:369-412` `src/vlm/config.py:26-30` + +### 3.2 Parse + +解析阶段按 `movie / series / anime / other` 分支工作: + +- 电影:提取标题与年份;`src/vlm/parser.py:80-151` +- 剧集:提取标题、季、集,支持多种命名模式;`src/vlm/parser.py:154-246` +- anime/other:当前 v1 不自动组织,仅标记说明。`src/vlm/commands/parse.py:101-117` + +该阶段还支持把 inventory 中的 `size / resolution / codec / bitrate` 嵌入 identity 记录,为后续重复项质量比较做准备。`src/vlm/commands/parse.py:23-29` `src/vlm/commands/parse.py:50-100` + +### 3.3 Enrich + +增强阶段会根据 title/year 调用 provider,并把结果缓存到 SQLite。增强内容包括: + +- 中文/英文标题; +- 外部评分与票数; +- `needs_review` 决策; +- `display_title`。`src/vlm/enrichment.py:24-156` `src/vlm/enrichment.py:251-356` + +这里有一个非常关键的点:`plan` 阶段构建 identity 时优先读取 `display_title`。因此,**enrich 的结果会直接影响最终目录与文件命名**。`src/vlm/io.py:114-178` + +### 3.4 Analyze + +分析阶段做两件核心工作: + +- 对剧集做缺集启发式检测;`src/vlm/analysis.py:12-67` +- 对电影/剧集做重复项检测与质量比较。`src/vlm/analysis.py:70-123` + +这里的完整性分析不是“官方元数据级别的完整性判断”,只是依据已有文件范围 `[min_episode, max_episode]` 找缺口。`src/vlm/analysis.py:13-21` `src/vlm/analysis.py:50-67` + +### 3.5 Plan / Execute + +`plan` 会把 `(VideoFile, Identity)` 转成 `FileOperation`,并结合 analysis 和状态信息做重复项保留、ignored 文件过滤、目录保留提示等。`src/vlm/commands/plan.py:36-66` `src/vlm/planner.py:79-190` + +`execute` 负责真正落盘执行,并在 execute 模式下维护 transaction log、rollback log 和 state。`src/vlm/commands/execute.py:70-132` `src/vlm/executor.py:92-179` `src/vlm/state.py:105-205` + +--- + +## 4. 优点总结 + +### 4.1 安全优先,适合真实文件整理 + +这是当前代码最成熟的能力: + +- 默认 dry-run;`src/vlm/cli.py:555-559` `src/vlm/executor.py:78-83` +- 支持回滚;`src/vlm/commands/execute.py:163-177` `src/vlm/executor.py:162-184` +- 重复项优先 quarantine;`src/vlm/planner.py:148-157` `src/vlm/executor.py:234-262` +- state 持久化写入采用临时文件 + `os.replace`,有原子替换保护。`src/vlm/state.py:82-102` + +### 4.2 模块职责相对清晰 + +`scanner / parser / enrichment / analysis / planner / executor` 的分工符合业务阶段,也方便编写针对性测试。`src/vlm/scanner.py:31-133` `src/vlm/parser.py:80-246` `src/vlm/enrichment.py:24-156` `src/vlm/analysis.py:12-123` `src/vlm/planner.py:79-190` `src/vlm/executor.py:26-184` + +### 4.3 数据模型可读性较好 + +`VideoFile`、`MovieIdentity`、`SeriesIdentity`、`FileOperation`、`ExecutionPlan`、`RollbackLog` 等模型把主流程概念表达得比较完整。`src/vlm/models.py:13-185` + +### 4.4 测试覆盖明显比较扎实 + +测试目录包含 29 个测试文件,覆盖 parser、scanner、planner、executor、quarantine、CLI、state、reports 等模块;其中还包含 Hypothesis 属性测试。`pyproject.toml:28-35` `tests/test_scanner.py:27-260` `tests/test_enrichment.py:36-260` `tests/test_cli_state.py:58-240` `tests/test_executor.py:18-120` `tests/test_quarantine.py:13-120` `tests/test_planner.py:18-120` `tests/test_analysis_properties.py:1-80` + +--- + +## 5. 发现的问题与解决方案 + +## 问题 1:`cli.py` 体积偏大,异常处理模式重复 + +### 现象 + +CLI 入口同时承担了: + +- 配置加载; +- 日志初始化; +- 旧路径兼容; +- 多个主命令注册; +- 多处重复的 `try/except + click.echo + logger + sys.exit`。`src/vlm/cli.py:49-76` `src/vlm/cli.py:79-155` `src/vlm/cli.py:158-603` + +### 影响 + +- 新增命令时容易继续复制粘贴错误处理; +- 入口文件持续膨胀; +- 行为一致性难以长期保证。 + +### 解决方案 + +**建议短期方案**:抽取统一的命令异常包装器,例如: + +- 一个通用 `run_command()`; +- 或一个 Click 装饰器,用于统一处理 `FileNotFoundError / JSONDecodeError / ValueError / OSError`; +- CLI 仅保留参数定义和分发。 + +**建议中期方案**:继续下沉命令定义,把 `review-plan / quarantine / rollback / report` 这类非主链命令拆到独立模块中,再由 CLI 统一注册。`src/vlm/cli.py:452-620` + +### 优先级 + +**高**。这是典型的结构性技术债,越晚处理,越容易固化。 + +--- + +## 问题 2:阶段间契约大量依赖裸 dict,类型边界偏弱 + +### 现象 + +尽管项目有 dataclass 模型,但在 `parse -> enrich -> analyze/plan` 之间,很多地方仍是 dict 直接读写,例如 parse 直接拼 JSON record,enrich 直接修改 record,I/O 层再把 record 组装回 dataclass。`src/vlm/commands/parse.py:66-155` `src/vlm/enrichment.py:66-156` `src/vlm/io.py:85-178` `src/vlm/io.py:181-249` + +### 影响 + +- schema 漂移风险较高; +- 某字段是否存在依赖运行时约定; +- 对静态检查和重构不友好。 + +### 解决方案 + +**建议短期方案**:为 `identities.json`、`analysis.json`、`plan.json` 定义更明确的 schema 约束,并在 load/save 时做结构校验。`src/vlm/io.py:29-82` `src/vlm/planner.py:597-677` + +**建议中期方案**:引入中间层 dataclass 或 `TypedDict`: + +- `IdentityRecordMovie` +- `IdentityRecordSeries` +- `AnalysisDuplicateRecord` +- `PlanRecord` + +让 parse/enrich/analyze/plan 在边界上操作显式类型,而不是开放 dict。 + +### 优先级 + +**高**。这是影响长期可维护性的核心问题。 + +--- + +## 问题 3:分析阶段对输入顺序有隐式耦合 + +### 现象 + +`analyze_cmd()` 先拿到 `movie_identities / series_identities / video_files`,再通过切片和 `zip()` 组合成 `identity_file_pairs`。这要求 `io.identities_to_analysis_input()` 输出的 `video_files` 与两个 identity 列表保持严格顺序一致。`src/vlm/commands/analyze.py:41-55` `src/vlm/io.py:181-249` + +### 影响 + +- 现在能工作,但逻辑脆弱; +- 只要 `io.py` 内部重构顺序,重复项分析就可能悄悄出错; +- 问题一旦发生,通常不是异常,而是“结果不对”。 + +### 解决方案 + +**建议直接改造**:让 `identities_to_analysis_input()` 直接返回 `list[tuple[Identity, VideoFile]]`,避免调用侧再切片配对。 +或者统一按 `path` 建索引后 join,而不是靠列表位置。`src/vlm/io.py:181-249` + +### 优先级 + +**高**。这属于“看起来没问题,但很脆”的隐式约定。 + +--- + +## 问题 4:重复规则散落,`sample` 识别逻辑重复实现 + +### 现象 + +`sample` 文件识别规则在以下位置重复存在: + +- `planner.py`; `src/vlm/planner.py:34-40` +- `duplicate_resolve.py`; `src/vlm/duplicate_resolve.py:11-16` +- `plan_review.py`; `src/vlm/plan_review.py:12-16` + +### 影响 + +- 后续如果要更新 sample 识别规则,必须改三处; +- 一旦其中一处漏改,会出现行为不一致; +- 测试也会被迫覆盖重复逻辑。 + +### 解决方案 + +提取单一公共函数,例如放到 `utils.py` 或新增 `media_rules.py`: + +- `is_sample_path(path: Path) -> bool` +- 所有模块统一调用; +- 增加一组针对 sample 识别的集中测试。 + +### 优先级 + +**中高**。改动不大,但收益稳定。 + +--- + +## 问题 5:plan 结果依赖实时文件系统状态,降低可复现性 + +### 现象 + +plan 阶段会: + +- 分析哪些目录会被搬空;`src/vlm/planner.py:43-76` +- 检查目标文件是否已存在;`src/vlm/planner.py:354-358` `src/vlm/planner.py:473-477` +- 将这些事实写入 operation 和 metadata。`src/vlm/planner.py:159-190` + +### 影响 + +- 同一份输入 identities,在不同时间点可能生成不同 plan; +- plan 更像“当下环境快照”,而不是纯粹的逻辑结果; +- 对复现、比对和离线审查不够友好。 + +### 解决方案 + +**建议短期方案**:保留当前行为,但把“实时文件系统检查结果”明确标记为环境信息,例如: + +- `metadata.runtime_conflict_snapshot` +- `metadata.directory_impact_snapshot` + +**建议中期方案**:将 plan 拆成两步: + +1. **logical plan**:只表达目标路径和操作意图; +2. **validation pass**:在 execute 前单独检查冲突和目录影响。 + +这样 plan 更可复现,execute 前验证也更明确。`src/vlm/commands/execute.py:16-33` + +### 优先级 + +**中高**。不是 bug,但会限制系统演进。 + +--- + +## 问题 6:配置语义有轻微串味 + +### 现象 + +扫描阶段并发控制使用的是 `config.enrichment_max_concurrency`。`src/vlm/scanner.py:76-77` `src/vlm/config.py:32-40` + +### 影响 + +- 配置名称与用途不完全一致; +- 用户理解成本增加; +- 后面如果 enrich 和 scan 的并发需求不同,不易扩展。 + +### 解决方案 + +新增独立配置字段,例如: + +- `scan_max_concurrency` +- `enrichment_max_concurrency` + +并保留兼容逻辑:若未设置 `scan_max_concurrency`,则回退到 `enrichment_max_concurrency`。 + +### 优先级 + +**中**。 + +--- + +## 问题 7:CLI 帮助文本与当前真实流程存在轻微偏差 + +### 现象 + +`main()` 顶部展示的 common workflow 没有包含 `enrich`,但实际项目已将 enrich 纳入正式流程。`src/vlm/cli.py:100-107` `src/vlm/cli.py:266-319` + +### 影响 + +- 新用户可能低估 enrich 的作用; +- 尤其在当前实现里,enrich 会影响最终命名路径。`src/vlm/io.py:114-178` + +### 解决方案 + +更新 CLI 顶部说明,把推荐流程改为: + +`scan -> parse -> enrich -> analyze -> plan -> execute` + +### 优先级 + +**低到中**。实现很简单,但用户感知收益明显。 + +--- + +## 问题 8:review 状态模型存在语义重叠 + +### 现象 + +`MovieIdentity` 与 `SeriesIdentity` 注释已经明确指出 `needs_review` 与 `review_status` 存在语义交叉。`src/vlm/models.py:46-53` `src/vlm/models.py:79-85` + +### 影响 + +- 状态转移规则不够清晰; +- 后续手工审核、自动审批、拒绝逻辑继续增加时,复杂度会快速上升。 + +### 解决方案 + +建议把两者整理为更明确的状态机: + +- `review_status`: `pending / approved / rejected` +- `review_reason`: `low_confidence / missing_year / low_reputation / manual` +- `needs_review` 作为派生字段,不持久化或不作为主状态源 + +### 优先级 + +**中**。建议在下一轮涉及审核工作流时一起处理。 + +--- + +## 6. 推荐改进顺序 + +### 第一优先级(建议优先处理) + +1. **收缩 `cli.py`,统一异常处理**。`src/vlm/cli.py:158-603` +2. **加强阶段间类型契约,减少裸 dict 传播**。`src/vlm/commands/parse.py:66-155` `src/vlm/io.py:85-249` +3. **消除 analyze 阶段的顺序耦合**。`src/vlm/commands/analyze.py:49-55` + +### 第二优先级(建议随后处理) + +4. **提取统一的 sample/path 规则函数**。`src/vlm/planner.py:34-40` `src/vlm/duplicate_resolve.py:11-16` `src/vlm/plan_review.py:12-16` +5. **把 plan 的逻辑结果与环境验证拆分**。`src/vlm/planner.py:43-76` `src/vlm/commands/execute.py:16-33` +6. **拆分 scan/enrich 并发配置**。`src/vlm/scanner.py:76-77` `src/vlm/config.py:32-40` + +### 第三优先级(体验与语义优化) + +7. **修正 CLI workflow 文案**。`src/vlm/cli.py:100-107` `src/vlm/cli.py:266-319` +8. **收敛 review 状态模型**。`src/vlm/models.py:46-53` `src/vlm/models.py:79-85` + +--- + +## 7. 测试与工程状态 + +### 7.1 测试状态 + +当前测试状态良好,已覆盖: + +- scanner;`tests/test_scanner.py:27-260` +- enrichment;`tests/test_enrichment.py:36-260` +- planner;`tests/test_planner.py:18-120` +- executor;`tests/test_executor.py:18-120` +- quarantine;`tests/test_quarantine.py:13-120` +- CLI state;`tests/test_cli_state.py:58-240` +- analysis property-based tests。`tests/test_analysis_properties.py:1-80` + +### 7.2 工程配置 + +项目要求 Python 3.10+,显式运行依赖较少,当前主要依赖 `click` 与 `pyyaml`,测试框架使用 `pytest` 与 `hypothesis`。`pyproject.toml:1-19` `pyproject.toml:28-35` + +这说明项目偏“标准库驱动 + 轻依赖”。优点是可维护性和部署都比较轻;不足是静态质量门禁配置(如 lint/type check)目前没有明显体现。`pyproject.toml:28-35` + +--- + +## 8. 最终判断 + +这个仓库当前最值得肯定的是:它已经具备了一个**真实可用文件整理工具**应有的安全骨架。它的主要问题不是“代码不可用”,而是“随着功能继续增长,结构性技术债会逐步放大”。 + +因此,最合适的策略不是大规模推倒重来,而是: + +- 继续保留当前 pipeline 主线; +- 优先处理 CLI 入口膨胀、阶段契约脆弱、规则重复这些结构性问题; +- 在不破坏安全模型的前提下,提高代码边界清晰度与可演进性。 + +如果按一句话总结: + +> 当前代码已经有不错的产品化基础,下一步应从“功能正确”转向“边界稳固、结构可持续演进”。