7.5 万行 Rust 的 Spec 工程实践:用大模型写 Rust 时,如何把「教程味」挡在仓库外

摘要 :大模型写 Rust 时常见 unwrap/expect 走捷径、忽略 crate 分层边界、以及「单测绿了但业务契约漂移」的局部最优。本文以开源项目 SkillLite (全仓 .rs 约 7.5 万行,不含 target/)为例,说明如何用 Spec 注入架构边界 spec任务制品(tasks)可机械验证的完成门槛,把工程约束「写进提示词之前」,让 AI 辅助开发更像「带护栏的结对编程」,而不是「会编译的草稿生成器」。

关键词:Rust;Spec 工程;大模型辅助编程;unwrap;crate 分层;局部最优;Clippy;thiserror


一、问题从哪来:训练语料偏「教程」,仓库偏「契约」

Rust 教程与示例为了可读性,大量使用:

  • unwrap() / expect("..."):把错误路径折叠掉,读者一眼看到主流程;
  • anyhow::Result + ?:快速原型里很顺手;
  • 单文件小例子:没有「跨 crate 依赖方向」「入口层路由」「沙箱不变量」这类组织级约束。

工程仓库恰恰相反:错误是类型系统里的一等公民,分层是编译期就应收束的架构,业务上还有「多端一致」「自进化 pending 父根」等平台契约。模型若只从统计模式上「像教程那样写」,就会在 PR 里反复制造三类技术债:

  1. 可恢复失败被伪装成 panic(unwrap 偷懒);
  2. 为了赶进度穿透 crate 边界(依赖图变成意大利面);
  3. 局部指标最优 (Clippy 零告警、单测全绿)但 全局契约劣化(文档、schema、运行时行为不一致)。

下面三节分别对应这三类问题,并给出 SkillLite 里可复制的 spec 对策;第四节补充若干「发散」方案;第五节为总结与反思。


二、unwrap / expect:不是语法问题,是「错误语义」被删掉

2.1 为什么模型爱用 unwrap

对模型而言,unwrap最短可执行路径 :少写枚举分支、少设计 Error 变体、少起名字。训练分布里它又高频出现,于是形成强先验:能编译 + 主路径演示跑通 ≈ 完成任务

对工程而言,这等价于把 「失败时系统该如何表现」 从 API 契约里删掉:调用方无法通过类型表达假设,运维无法区分「预期内的用户输入错误」与「应当告警的缺陷」。

2.2 SkillLite 的硬规则(节选)

spec/rust-conventions.md 中,项目把生产路径上的 unwrap 直接列为 Must Not (仅测试代码允许),并强制 crate 级 Error + Resultthiserror?.with_context() 的错误链习惯。

对照阅读 crates/skilllite-core/src/error.rs 可以看到「教程式 anyhow 一把梭」与「工程式分层错误」的差异:统一 Error 枚举、Validation 表达领域规则、Other(#[from] anyhow::Error) 兼顾渐进迁移。

rust 复制代码
// 教程/草稿里极常见:错误语义被吞掉,panic 边界外溢到运行时
fn load_config(path: &Path) -> Config {
    let raw = std::fs::read_to_string(path).expect("read config");
    serde_json::from_str(&raw).unwrap()
}

// 工程向:失败可传播、可分类、可在上层汇总展示
use crate::{Error, Result};

fn load_config(path: &Path) -> Result<Config> {
    let raw = std::fs::read_to_string(path)?;
    serde_json::from_str(&raw).map_err(Error::from)
}

Spec 的价值 在于:把「Must / Must Not」从口头 code review 变成 每次改 Rust 必注入的短规范 ,模型在写第一行业务代码前就被提醒:unwrap 不是风格偏好,是合入红线


三、严格的分层与 crate 依赖:没有「地图」就容易抄近道

Rust 的模块与 workspace 让依赖方向 非常具体:谁在 Cargo.tomldepend 谁,编译器会认真执行。大模型若没有「全仓分层地图」,常见失误包括:

  • 为了让某个函数「能调到」底层能力,反向让 core 依赖 agent
  • agent_loop 里不断堆 if tool_name == "xxx",而不是走扩展注册点;
  • 把平台细节(macOS/Linux)渗进上层业务 crate。

SkillLite 在 spec/architecture-boundaries.md 里用一句话钉死主依赖链(节选含义):

entry -> commands -> agent -> executor -> sandbox -> corecore 保持纯净、不得依赖上层。

这对 AI 辅助改动的意义是:在检索与推理之前先注入架构 spec,模型更少提出「在 core 里直接调 Tauri / MCP」这类结构上不可能或不该出现的方案;即便提出来,review 也有显式条文可引用。

flowchart TB subgraph entry["入口层"] CLI["skilllite CLI"] MCP["MCP / stdio"] UI["桌面助手 / Tauri"] end subgraph mid["编排与执行"] CMD["skilllite-commands"] AGT["skilllite-agent"] EXE["skilllite-executor"] end SBX["skilllite-sandbox"] CORE["skilllite-core\n(纯能力,不依赖上层)"] CLI --> CMD MCP --> CMD UI --> CMD CMD --> AGT AGT --> EXE EXE --> SBX SBX --> CORE

实践要点 :任何会动 workspace 布局、crate 依赖、入口路由的任务,在 spec/README.md 的映射里都会叠加 architecture-boundaries.md,并与 docs-sync.md 联动(中英文架构文档同步),避免「代码已改、文档仍画旧图」的二次迷路。


四、业务逻辑与局部最优:绿了 ≠ 对了

局部最优在 AI 辅助场景里特别隐蔽,因为模型极擅长优化 可立即度量的目标

  • cargo test 全绿;
  • cargo clippy -- -D warnings 无告警;
  • 某个 bug 的复现步骤被「绕开」。

但工程上真正关心的是 契约全集:用户可见行为、环境变量、命令行、schema、安全沙箱不变量、跨端一致(例如技能发现单点 SSOT)。若缺少「反幻觉」与「反假阳性测试」的规范,很容易出现:

  • 测试断言的是模型臆想的错误文案,而不是真实错误路径;
  • 为了通过测试放宽校验,根因未修;
  • 文档与代码漂移,review 难以一眼看出。

SkillLite 用 spec/verification-integrity.md 把完成定义改成:可独立验证的证据 优先于模型自述。并与 tasks/ 工作流结合:非琐碎改动要求 TASK.md 验收标准、PRD.md/CONTEXT.md 记录决策与边界、STATUS.md/REVIEW.md 留痕,从流程上抬高「宣布完成」的成本。

flowchart LR subgraph bad["局部最优陷阱"] A1["指标:测试绿"] A2["指标:Clippy 零告警"] A3["模型:声称已验证"] A1 --> MERGE1["合入"] A2 --> MERGE1 A3 --> MERGE1 end subgraph good["全局契约门槛"] B1["spec:verification-integrity"] B2["机械命令输出留证"] B3["反假阳性 / 反漂移检查项"] B4["tasks 制品与 board 同步"] B1 --> GATE["完成门槛"] B2 --> GATE B3 --> GATE B4 --> GATE GATE --> MERGE2["合入"] end

五、发散:还有哪些 Spec 化「护栏」值得做

除本文主线外,SkillLite 仓库里还有几条可推广的组合拳(具体条文见对应 spec/*.md):

  1. 按任务类型注入 specspec/README.md):architecturesecurityagent 等映射不同组合,避免「一条超长 AGENTS.md 没人读完」。
  2. structured-signal-first:核心行为优先结构化运行时信号,正则与文本规则只做兜底,减轻「模型爱写脆弱字符串匹配」的维护压力。
  3. docs-sync:行为、命令、环境变量变更强制中英文档对齐,把文档从「事后补写」变成合入条件。
  4. testing-policy :按变更类型要求最低测试集,和 verification-integrity 一起压制「假绿」。
  5. CI 与本地一致cargo fmt --checkclippy -D warningscargo test 作为共同语言;spec 里写清 Quick Verify,减少「我本地过了」的争议空间。

六、Spec 注入在研发流程中的位置(总览)

flowchart TD START["接到需求 / Bug"] ROUTE["spec/README.md\n判定任务类型"] INJ["组装 Injected Specs\n(含 verification-integrity)"] TASK["必要时 tasks/TASK-.../\nTASK / PRD / CONTEXT"] IMPL["实现 + 测试"] VERIFY["机械验证命令\n输出留证"] GATE["完成门槛:\nchecklist + 制品 + board"] START --> ROUTE --> INJ --> TASK --> IMPL --> VERIFY --> GATE

Cursor 侧通过 .cursor/rules/spec-injection-index.mdc 要求:凡改 Rust,必带 rust-conventionstesting-policy,从编辑器入口再次强化「规范不是文档角落里的摆设」。


七、总结与思考

  • unwrap/expect 的本质 是删掉错误语义;大模型因训练分布偏教程而高发;用 Must Not + CI + spec 注入 比事后 grep 更有效。
  • Rust crate 图 是严格的架构载体;architecture spec 相当于给模型一张「dependency DAG 说明书」,降低穿透分层、临时耦合的概率。
  • 局部最优 在 AI 场景下表现为「指标绿 + 自述完成」;用 verification-integrity任务制品 把「完成」定义成可审计证据链,才能保护业务契约。
  • 长期看,Spec 工程不是增加文档负担,而是 把重复的人类唠叨变成可组合、可路由、可机器引用的短规范,让大模型在仓库里的行为更稳定、可预期。

如果你也在维护中大型 Rust monorepo,不妨从三件事起步:一条 禁止生产 unwrap 的硬规则、一张 依赖方向图 、以及一条 「声称完成必须附命令输出」 的反幻觉门槛------它们成本不高,却能显著拉齐人与模型的「工程默认值」。

项目地址:github.com/EXboys/skil...

相关推荐
若水不如远方2 小时前
一文讲透单点登录原理(SSO):从同域共享到跨域票据
java·后端
武子康2 小时前
大数据-266 实时数仓-Canal + Kafka 实现 MySQL 数据库变更实时捕获
大数据·后端·kafka
丶西红柿丶2 小时前
python-带参数和不带参数的装饰器
后端
楼田莉子2 小时前
设计模式:构造器模式
开发语言·c++·后端·学习·设计模式
SimonKing3 小时前
大V说’AI替代不了你’,但现实是——用AI的人正在替代你
java·后端·程序员
IT_陈寒3 小时前
SpringBoot里的这个坑差点让我加班到天亮
前端·人工智能·后端
BingoGo3 小时前
Laravel13 + Vue3 的免费可商用 PHP 管理后台 CatchAdmin V5.2.0 发布
后端·php·laravel
rannn_1114 小时前
【Redis|高级篇1】分布式缓存|持久化(RDB、AOF)、主从集群、哨兵、分片集群
java·redis·分布式·后端·缓存
weixin_408099674 小时前
【实战教程】EasyClick 调用 OCR 文字识别 API(自动识别屏幕文字 + 完整示例代码)
前端·人工智能·后端·ocr·api·安卓·easyclick