系统工具开发 7 月复盘:从编码到发布的完整 check list 和关键经验
一、系统工具开发的全流程:一张 check list 图
很多人(包括我最初)觉得工具开发就是"写代码 + cargo build"。但实际完整的流程比这长得多:
二、编码与构建阶段:从写代码到生成可发布二进制
编码阶段:不是在写 demo,是在做产品
编码阶段最容易犯的错是以"功能实现"作为唯一目标。但系统工具和普通函数的本质区别在于:系统工具的出入口是用户,而不是另一个函数。所以错误处理、日志、诊断信息和功能本身一样重要。
// ============================================================
// 编码阶段的关键:不是"能跑",是"任何情况下都有尊严"
// ============================================================
use clap::Parser;
use log::{info, error, debug};
/// AI CLI 工具的完整入口
#[derive(Parser)]
#[command(
name = "ai",
about = "终端 AI 助手 — 在命令行里直接对话 AI",
version = env!("CARGO_PKG_VERSION"),
// 为每个错误场景准备清晰的提示信息
after_help = "示例:\n ai \"这段代码怎么优化\"\n ai --model claude \"解释这个算法\""
)]
struct Cli {
#[arg(required = true, help = "向 AI 提出的问题")]
prompt: Vec<String>,
#[arg(short, long, default_value = "gpt-4o-mini", help = "使用的 AI 模型")]
model: String,
#[arg(long, default_value_t = false, help = "启用流式输出(打字机效果)")]
stream: bool,
#[arg(long, default_value_t = false, help = "显示详细调试信息")]
verbose: bool,
}
fn main() {
// 第一步:根据用户传入的 verbose 参数配置日志等级
// debug 模式下输出更多诊断信息,正常模式只输出关键信息
let cli = Cli::parse();
if cli.verbose {
env_logger::Builder::new()
.filter_level(log::LevelFilter::Debug)
.init();
} else {
env_logger::init();
}
// 第二步:在实际执行前做所有可检查的校验
if cli.model.trim().is_empty() {
error!("模型名不能为空,使用默认值可能会有问题");
std::process::exit(1);
}
info!("启动 AI CLI,模型: {}, 流式: {}", cli.model, cli.stream);
// ... 后续逻辑
}
7 月我在编码阶段踩的最大的坑就是没有从第一天就加入结构化日志。前两周的输出全靠 println! 和 eprintln!,出了问题根本没办法追溯。"明明执行了,为什么没输出?"——如果能看 debug 日志,这个问题两分钟就能定位。
构建阶段:release profile 不是"加个 --release 就行"
Rust 的 release 构建默认已经做了 LTO 之外的优化,但对于系统工具来说,还可以做得更多:
# ============================================================
# Cargo.toml — 发布用的 profile 配置
# 这些配置能在不损代码质量的情况下显著减小二进制体积
# ============================================================
[profile.release]
# 链接时优化(Link Time Optimization):跨 crate 做内联和死代码消除
lto = true
# 优化等级:3 = 最激进的优化(编译时间会增加 30~50%,但运行快 5~10%)
opt-level = 3
# 代码生成单元:1 = 单个单元,编译更慢但优化更充分
codegen-units = 1
# panic 策略:abort = 发生 panic 时直接终止,不展开栈
# 适合大部分系统工具,体积小、速度快
panic = "abort"
# strip 符号表:生成更小的二进制
strip = "symbols"
7 月我的 AI CLI 工具,不加这些配置时 release 二进制是 8.2MB,加完之后是 3.6MB。不是巨大的差别,但对于一个需要 cargo install 下载的工具来说,3.6MB 和 8.2MB 的下载体验是完全不同的。
跨平台编译是另一个花了我一整天的问题。Rust 理论上支持交叉编译,但实际上 openssl-sys 这样的 C 依赖在交叉编译时容易出问题。我的解决方案是:尽量使用纯 Rust 的实现(如 rustls 替代 openssl),然后在 CI 里用不同平台的 runner 做原生编译。
三、配置阶段:三种配置源的优先级设计
系统工具的配置通常有三个来源:命令行参数、环境变量、配置文件。这三个来源的优先级必须明确且一致。
优先级原则:命令行 > 环境变量 > 配置文件 > 默认值。这样用户临时切换模型(--model claude)不需要改配置,CI/CD 脚本可以用环境变量覆盖,日常使用保持配置文件。
// ============================================================
// 配置合并:命令行 → 环境变量 → 配置文件 → 默认值
// ============================================================
use serde::Deserialize;
/// 所有可能的配置来源合并后的最终配置
#[derive(Debug, Deserialize)]
struct AppConfig {
api_base_url: String,
api_key: String,
default_model: String,
timeout_secs: u64,
}
impl AppConfig {
/// 按优先级合并所有配置来源
fn merge(
cli_args: &Cli,
config_file: Option<ConfigFile>,
env_vars: &HashMap<String, String>,
) -> Self {
// 从低到高逐层覆盖
let mut config = AppConfig::default();
// 第一层:默认值(已经通过 Default trait 设置)
// 第二层:配置文件(比默认值高)
if let Some(file) = config_file {
if let Some(url) = file.api_base_url {
config.api_base_url = url;
}
if let Some(model) = file.default_model {
config.default_model = model;
}
}
// 第三层:环境变量(比配置文件高)
if let Some(key) = env_vars.get("AI_API_KEY") {
config.api_key = key.clone();
}
if let Some(url) = env_vars.get("AI_API_BASE_URL") {
config.api_base_url = url.clone();
}
// 第四层:命令行参数(最高优先级)
if !cli_args.model.is_empty() {
config.default_model = cli_args.model.clone();
}
config
}
}
四、发布和文档阶段:让用户能在 5 分钟内用起来
7 月我在发布上犯了两个大错。第一个是发了一个"没有 README 里写安装步骤"的版本——用户 cargo install 之后不知道怎么用。第二个是 CHANGELOG 拖了两周才补上。
一个系统工具的"可用性"标准是:一个完全不了解这个项目的人,能在 5 分钟内从零到成功运行第一次。这意味着 README 要包含:
- 一句话说清楚工具是做什么的
- 三行命令就能安装
- 第一个示例必须跑得通
- 常见报错的排查方法
cargo install 是最简单的安装方式,但对非 Rust 用户来说需要先装 Rust。为此可以考虑提供 cargo-binstall(预编译二进制)或 Homebrew formula 作为备选方案。
五、总结
7 月的系统工具开发让我意识到一件事:写代码只是系统工具开发的 30%。剩下 70% 是错误处理、日志、配置、测试、构建优化、文档、发布——这些东西不会让工具更快,但会让工具"能被别人用"。
三条月度 check list 精华:
- 编码阶段:错误处理 + 日志 + 版本号,缺一不可。不要等发布前才加日志——那时已经忘了哪里需要加。
- 构建阶段:lto + strip 把体积压下来,跨平台编译用 CI 而不是本地交叉编译。
- 发布阶段:README 的第一段 + 第一个示例 + 安装命令,这三样东西决定了 90% 的用户会不会用。
8 月的目标是走通 Homebrew 发布流程,让非 Rust 用户也能直接 brew install ai-cli。这会带来一批新的用户和使用反馈,那时候再迭代就更有方向了。
377



被折叠的 条评论
为什么被折叠?



