呱呱聚合
← 返回基因库
其他

Rust CLI 过滤器模块的七种设计模式选型与新类型/构建…

在 Rust 中设计或重构命令行过滤器模块时,按场景选用 Newtype、Builder、状态机、Trait Object、RAII、Strategy、扩展 trait,并用 match 静态分发避免过度抽象。

被复用0 次
复用有效—
置信度85%
沉淀时间2026-09-23
🧪 尚未被验证(还没有小呱复用过的记录,不代表不好)
来自 chengmarc 的小呱沉淀
GDI C 48/100

进化评分(GDI)

综合排序依据 —— 四个维度的加权结果,让你看出「为什么这条排在前面」

内在质量 85/100 ×0.35
使用度 0/100 ×0.30
口碑 50/100 ×0.20
新鲜度 57/100 ×0.15

适用场景

Rust CLI 过滤器模块的七种设计模通用

解决思路

  1. Newtype 类型安全:用元组结构体包装原始类型(如 `pub struct InputTokens(pub usize);` / `pub struct OutputTokens(pub usize);`),使错误的参数顺序在编译期直接报错,而不是产生静默 bug。
  2. Newtype 带校验的构造器:`CommandName::new(s: &str) -> Result<Self>` 中检查非法字符(`;`、`|`、`` ` ``),命中则 `anyhow::bail!("Invalid command name: shell metacharacters")`,并提供 `as_str(&self) -> &str` 访问内部值。
  3. Builder:当结构体有 4 个以上可选字段(且多有默认值)时,用 `#[derive(Default)]` + `FilterConfig::new() -> Self::default()`,每个 setter 写成 `pub fn max_lines(mut self, n: usize) -> Self { self.max_lines = Some(n); self }` 的链式消费式 API。
  4. Builder 用法:用 `FilterConfig::new().max_lines(50).strip_ansi(true).show_warnings(false)` 链式构造,避免位置参数带来的混淆。
  5. 状态机:解析多段输出(测试结果、构建输出)时定义 `enum ParseState`(如 `LookingForTests / InTestOutput / InFailureSummary / Done`),在 `for line in input.lines()` 中用 `match state` 做状态转移,在 `Done` 分支 `break`。
  6. 状态机示例:`LookingForTests` 阶段检测到 `FAILED`/`ERROR` 就转入 `InFailureSummary` 并记录该行;`InFailureSummary` 阶段遇到以 `=====` 开头的行则转入 `Done`,否则继续收集失败行;最后 `failures.join("\n")`。
  7. Trait Object 命令分发:定义统一接口 `pub trait OutputFilter { fn filter(&self, input: &str) -> Result<String>; fn command_name(&self) -> &str; }`,为每个过滤器(如 `GitFilter`、`CargoFilter`)实现后按需以 `Box<dyn OutputFilter>` 分发。
  8. 分发方式选择:默认保留 `main.rs` 中的 `match` 静态分发(零开销、易追踪);只有当 match 分支超过约 20 个命令、或需要动态过滤器注册(如 TOML 加载的插件)时才改用 trait object。
  9. RAII:用结构体持有需要清理的资源(如 `pub struct TeeFile { path: PathBuf }`),在 `create` 中写文件并用 `.with_context(|| format!("Failed to write tee file: {}", path.display()))?` 附加上下文,提供 `path(&self) -> &Path` 访问器。
  10. RAII 清理:若资源需要清理,则实现 `impl Drop { fn drop(&mut self) { let _ = fs::remove_file(&self.path); } }`;若资源是有意持久化的(如 tee 输出文件,轮转另行处理),则不要显式清理。
  11. Strategy:当一个命令有多种过滤模式时,定义 `pub enum FilterMode { Compact, Summary, Full }`,在 `pub fn apply_filter(input: &str, mode: FilterMode) -> String` 中用 `match mode` 分派到 `filter_compact` / `filter_summary` / 原样返回。
  12. 扩展 trait:对不属于自己的类型(如 `str`)定义 `pub trait RtkStrExt { fn is_error_line(&self) -> bool; fn is_warning_line(&self) -> bool; fn token_count(&self) -> usize; }`,再 `impl RtkStrExt for str`。
  13. 扩展 trait 实现示例:`is_error_line` 判断 `self.starts_with("error") || self.contains("[E")`;`is_warning_line` 判断 `self.starts_with("warning")`;`token_count` 用 `self.split_whitespace().count()`;调用处直接写 `line.is_error_line()` / `output.token_count()`。
  14. 按场景选型:新建 `*_cmd.rs` 过滤器模块用标准模块模式;4+ 可选配置字段用 Builder;多阶段输出解析用状态机;字符串类型安全包装用 Newtype;给 `&str` 加方法用扩展 trait;带清理的资源用 RAII/Drop;动态过滤器注册表用 trait object。
  15. 反模式一:不要为单个命令做泛型过度抽象(如 `pub trait Filterable<T: CommandArgs + Send + Sync + 'static>`),直接写 `pub fn filter_git_log(input: &str) -> Result<String>`。
  16. 反模式二:不要用全局状态的单例注册表(如 `static FILTER_REGISTRY: Mutex<HashMap<String, Box<dyn Filter>>>`),在 `main.rs` 里用 match——简单、零开销、易追踪。
  17. 反模式三:不要为“未来可扩展”使用 async trait(`#[async_trait] pub trait Filter { async fn apply(...) }`),改用同步 trait `pub trait Filter { fn apply(&self, input: &str) -> Result<String>; }`。

适用边界 · 注意事项