Vibe Coding很爽,架构却不能赌:一次 Review 把方向拉回来

1 阅读5分钟

摘要:用 AI 做大范围结构调整时,对话顺、diff 干净,很容易一路合并下去。真正麻烦的是:错误不会在当下报错,只会在几周后的需求里结账。我在 skillLite 里踩过一次完整的「加抽象层 → Review 发现 ROI 不够 → 整段回撤」。


本文项目地址:SkillLite

1. 从哪开始出问题

复杂项目里动「结构」:模块边界、依赖方向、错误处理、生命周期,任何一处顺手优化都可能牵一串。把 AI 当高强度检索和样板生成器,收益很实在;一旦任务被说成「更清晰」「职责更单一」这种目标句,模型往往会给出语法正确、局部好看、和真实约束对不齐的方案。

这和常说的 Vibe Coding 是一回事:节奏跟着对话走,爽感来自马上能看到 diff,而不是先把红线写满。做小工具、改 UI,未必是坏事;上到架构层,代价会被放大——因为「方向错了」不会立刻红字报错。

2. 「好分层」长什么样,才值得你合并

不必逐案展开,共性就几条:

  • 抽象超前:需求边界还没稳,先多一层,短期好看,长期改一次要动三处。
  • 依赖放错地方:领域逻辑被拆散,基础设施细节却浮到不该出现的位置,隐性耦合变多。
  • 和迁移计划打架:和灰度、兼容层、既定演进路径冲突的方案,单测照样能绿。

这些往往裹着「更模块化」「更易测试」的说法。单看 diff 不够,要站在「后面的人还能不能低成本改」上去审视项目架构。

3. 合并前我实际在核对什么

那次如果不是在合并前停一步,后面大概率是补丁式纠偏:

  1. 对照现有分层:允许依赖、禁止依赖、模块归属,是不是和改动一致,而不是只看新代码漂不漂亮。
  2. 盯删改多于盯新增:模型擅长加层;删错边界、改错默认行为的成本,要人工逐项过。
  3. 能拆就拆:大重构拆 PR;能先写决策再动代码的,先写清约束。

目的不是否定 AI,而是把「方向」从概率输出,拉回和真实系统对齐的决策

4. 案例:skilllite-services 从引入到回撤

任务目录与提交可在仓库核对: tasks/TASK-2026-042-services-phase0-decisions/ 043-services-bootstrap-crate/044-services-phase1a-workspace/045-services-rollback-phase1a/; 回撤合入为 df1a49brefactor: remove skilllite-services crate and update dependency management)。

动机:CLI、桌面等多入口,各自包一层领域逻辑容易漂移。

042 写 Phase 0 决策, 043 起空 crate skilllite-services, 044 把 WorkspaceService(工作区 skills 目录解析等)迁进去, 让 skilllite-commandssrc-tauri 依赖它——每一步单看都像在做对的事

Review 后的结论(见 045TASK.md): 问题不是「代码写坏了」,而是这层抽象不划算——调用方被迫多写 Result 处理样板,总行数反而上去; 用 grep 对后续 Phase 的「共享」前提复核,有的能力 CLI 侧零调用、只有桌面用;再往下和 skilllite-core 的边界重叠偏薄,多一层服务壳性价比不高。CONTEXT.md 里有一句硬判断:一次提取若让调用方 LOC 净增,不是在立范式,是在把差的范式钉进结构里。继续堆 Phase 只会变成「为了给这一层找存在理由」而堆层。

决策:045 选 回滚 Phase 1A,保留 Phase 0 里仍有价值的 CI、文档、deny 基线;043、044 标 superseded,审计链留在仓库里。回撤和合并一样,都是要说清理由的交付物。

回撤后的调用方式skilllite-services 已从当前树移除,入口重新直连 skilllite_core::skill::discovery

桌面桥里解析工作区 skills 根:

pub(crate) fn resolve_workspace_skills_root(workspace: &str) -> PathBuf {
    let root = find_project_root(workspace);
    resolve_skills_dir_with_legacy_fallback(&root, "skills").effective_path
}

CLI 侧对应逻辑:

pub fn resolve_skills_dir(skills_dir: &str) -> PathBuf {
    let workspace = std::env::current_dir().unwrap_or_else(|_| PathBuf::from("."));
    let resolution = skilllite_core::skill::discovery::resolve_skills_dir_with_legacy_fallback(
        &workspace, skills_dir,
    );
    if let Some(warning) = resolution.conflict_warning() {
        eprintln!("{}", warning);
    }
    resolution.effective_path
}

两段都是:工作区路径 → resolve_skills_dir_with_legacy_fallback → 取 effective_path,中间不再经过独立 crate 包一层。若要对照「曾经有过的服务层」,本地可 git show ab1b250:crates/skilllite-services/df1a49b 的删除 diff 并排看。

045/REVIEW.md 里记了 cargo check / cargo test --workspace / 双 manifest 的 clippy 与 cargo deny check bans / validate_tasks.py 等——这是比私聊截图稳的操作日志。提交说明里也写明了删除原因:跨入口重复度不足以支撑该 crate 存在insufficient cross-entry duplication to justify its existence)。

skilllite-services 从落地到删除(TASK-2026-045,提交 df1a49b)。证据链在仓库的 CONTEXT.mdTASK.mdREVIEW.md 和 Git 里,不靠聊天记录复述。想强调两件事:合并前卡住架构方向,和日常把 Review 当成默认动作,不矛盾;拍板和担责的始终是人

5. 日常怎么配合

  • 任务里写红线:允许/禁止依赖、迁移阶段、明确「不做什么」,少给模型自由发挥的空间。
  • 要选项不要独白:2~3 套方案带权衡,人选。
  • 同一套 Review 清单:依赖方向、错误边界、可观测性、回滚路径,对人写的和 AI 辅助的一视同仁。
  • 定期过线:可以轻,但不能缺——例如含大段生成的 PR 双人过、或每迭代抽一条核心链路走读。
  • 大重构别和业务 PR 糊在一起:否则 Review 很难真起作用。

结语

AI 省掉很多重复劳动,这是事实。结构和架构这类杠杆高、纠错贵的事,一次核验错误可能比十次慢写更贵。工具能加快实现,不能替代你对系统的 ownership——该停就停,该 Review 就 Review,合并键在你手上。

本文为技术实践记录,观点来自具体场景,欢迎讨论。