摘要:用 AI 做大范围结构调整时,对话顺、diff 干净,很容易一路合并下去。真正麻烦的是:错误不会在当下报错,只会在几周后的需求里结账。我在 skillLite 里踩过一次完整的「加抽象层 → Review 发现 ROI 不够 → 整段回撤」。
本文项目地址:SkillLite
1. 从哪开始出问题
复杂项目里动「结构」:模块边界、依赖方向、错误处理、生命周期,任何一处顺手优化都可能牵一串。把 AI 当高强度检索和样板生成器,收益很实在;一旦任务被说成「更清晰」「职责更单一」这种目标句,模型往往会给出语法正确、局部好看、和真实约束对不齐的方案。
这和常说的 Vibe Coding 是一回事:节奏跟着对话走,爽感来自马上能看到 diff,而不是先把红线写满。做小工具、改 UI,未必是坏事;上到架构层,代价会被放大——因为「方向错了」不会立刻红字报错。
2. 「好分层」长什么样,才值得你合并
不必逐案展开,共性就几条:
- 抽象超前:需求边界还没稳,先多一层,短期好看,长期改一次要动三处。
- 依赖放错地方:领域逻辑被拆散,基础设施细节却浮到不该出现的位置,隐性耦合变多。
- 和迁移计划打架:和灰度、兼容层、既定演进路径冲突的方案,单测照样能绿。
这些往往裹着「更模块化」「更易测试」的说法。单看 diff 不够,要站在「后面的人还能不能低成本改」上去审视项目架构。
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/;
回撤合入为 df1a49b(refactor: remove skilllite-services crate and update dependency management)。
动机:CLI、桌面等多入口,各自包一层领域逻辑容易漂移。
042 写 Phase 0 决策, 043 起空 crate
skilllite-services, 044 把WorkspaceService(工作区 skills 目录解析等)迁进去, 让skilllite-commands和src-tauri依赖它——每一步单看都像在做对的事。
Review 后的结论(见 045 的 TASK.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.md、TASK.md、REVIEW.md 和 Git 里,不靠聊天记录复述。想强调两件事:合并前卡住架构方向,和日常把 Review 当成默认动作,不矛盾;拍板和担责的始终是人
5. 日常怎么配合
- 任务里写红线:允许/禁止依赖、迁移阶段、明确「不做什么」,少给模型自由发挥的空间。
- 要选项不要独白:2~3 套方案带权衡,人选。
- 同一套 Review 清单:依赖方向、错误边界、可观测性、回滚路径,对人写的和 AI 辅助的一视同仁。
- 定期过线:可以轻,但不能缺——例如含大段生成的 PR 双人过、或每迭代抽一条核心链路走读。
- 大重构别和业务 PR 糊在一起:否则 Review 很难真起作用。
结语
AI 省掉很多重复劳动,这是事实。结构和架构这类杠杆高、纠错贵的事,一次核验错误可能比十次慢写更贵。工具能加快实现,不能替代你对系统的 ownership——该停就停,该 Review 就 Review,合并键在你手上。
本文为技术实践记录,观点来自具体场景,欢迎讨论。