AI 辅助 Rust 代码结构化:用模型把 500 行的 main 拆成干净模块

AI 辅助 Rust 代码结构化:用模型把 500 行的 main 拆成干净模块

一、500 行的 main.rs:一个真实的反面教材

不是没意识到问题,而是"今天先加一个功能、明天再加一个特性",每个功能单独看都不大,但积少成多,等我想整理的时候发现已经是一团乱麻了。

结构大概是这样的:

// main.rs —— 527 行的大杂烩
// 前面 50 行:一堆 use 语句,有些甚至没用到(IDE 没提示到)
// 中间 200 行:各种函数混在一起:文件读写、数据转换、API 调用
// 后面 100 行:main 函数 80 行 + CLI 参数解析 20 行
// 最后:几个测试函数也堆在同一个文件里

如果这就是个"写完就扔"的脚本,也就算了。但这个工具我打算长期维护,这种结构会让改一行代码的风险和改 500 行一样大——因为你根本不确定耦合了哪些逻辑。

于是我想试试:能不能用 AI 模型帮我分析依赖关系,再提出模块拆分方案?

flowchart LR
    A["527 行 main.rs"] --> B["AI 分析\n提取函数签名\n识别依赖关系"]
    B --> C["生成拆分方案\n按职责分组"]
    C --> D["人工审核\n调整边界"]
    D --> E["执行拆分\n逐模块迁移"]
    E --> F["编译验证\n测试回归"]
    F --> G["干净的多模块结构"]

二、实际操作流程:三步走

第一步:让 AI 生成依赖分析图

我把 main.rs 的全部代码发给 AI,prompt 是:

"请分析这个 Rust 文件的函数依赖关系,按功能职责(I/O、数据处理、API通信、CLI)分组,画出 ASCII 依赖图。"

AI 返回的分析结果帮助我建立了全局视角。基于它的分析,我自己画了更清晰的 Mermaid 图:

flowchart TD
    subgraph cli["CLI 层"]
        main["main()"]
        build_cli["build_cli()"]
    end

    subgraph io["I/O 层"]
        read_file["read_file()"]
        write_output["write_output()"]
        scan_dir["scan_directory()"]
    end

    subgraph core["核心逻辑层"]
        process["process_data()"]
        extract["extract_metadata()"]
        transform["transform_output()"]
    end

    subgraph api["API 通信层"]
        call_api["call_api()"]
        parse_response["parse_response()"]
    end

    main --> build_cli
    main --> process
    process --> read_file
    process --> extract
    process --> call_api
    call_api --> parse_response
    extract --> transform
    transform --> write_output

第二步:根据分析结果设计模块

基于依赖图,我设计了 6 个模块:

模块文件职责
CLIcli.rs命令行参数解析
文件 I/Oio.rs文件读写、目录扫描
元数据metadata.rs提取文件元信息
格式转换format.rsJSON/YAML/Table 输出
APIapi.rsHTTP 请求 + SSE 解析
错误error.rs自定义错误类型

第三步:逐模块迁移,每次迁移后验证

这句话最关键:不是一次性全部拆分完再编译,而是拆一个模块就 cargo check 一次。 这样可以第一时间发现循环依赖、缺少 pub 声明、use 路径错误等问题。

// 迁移步骤示例:先把文件 I/O 相关函数抽到 io.rs

// === src/io.rs(新文件)===
use std::fs;
use std::path::{Path, PathBuf};
use crate::error::AppError; // 引用同项目的错误模块

/// 读取文件内容,自动检测编码
pub fn read_file_content(path: &Path) -> Result<String, AppError> {
    if !path.exists() {
        return Err(AppError::FileNotFound {
            path: path.to_path_buf(),
        });
    }
    // 先尝试 UTF-8 读取
    match fs::read_to_string(path) {
        Ok(content) => Ok(content),
        Err(_) => {
            // 降级:按字节读,尝试 GBK 解码
            let bytes = fs::read(path)?;
            String::from_utf8(bytes)
                .map_err(|_| AppError::EncodingError {
                    path: path.to_path_buf(),
                })
        }
    }
}

/// 递归扫描目录,收集所有文件路径
pub fn scan_directory(dir: &Path, recursive: bool) -> Result<Vec<PathBuf>, AppError> {
    let mut files = Vec::new();
    if !dir.is_dir() {
        return Ok(vec![dir.to_path_buf()]); // 单个文件直接返回
    }
    for entry in fs::read_dir(dir)? {
        let entry = entry?;
        let path = entry.path();
        if path.is_dir() && recursive {
            // 递归扫描子目录
            files.extend(scan_directory(&path, true)?);
        } else if path.is_file() {
            files.push(path);
        }
    }
    Ok(files)
}

// === src/main.rs(修改后)===
mod io; // 声明新模块

use crate::io::read_file_content; // 使用模块内的函数
// main 函数现在非常简洁
fn main() -> Result<(), AppError> {
    let cli = Cli::parse();
    let files = io::scan_directory(Path::new(&cli.path), cli.recursive)?;
    for file in files {
        let content = read_file_content(&file)?;
        // ... 处理逻辑
    }
    Ok(())
}

三、AI 辅助的优势和局限

优势

mindmap
  root((AI 辅助重构))
    优势
      快速建立全局依赖视图
      发现人工容易忽略的耦合
      自动检查 pub 声明缺失
      生成模块间关系图
    局限
      不理解业务语义
      可能过度拆分
      命名不一定准确
      需要人工最终决策

局限

AI 只能做"形式上的分析"——哪个函数调用了哪个函数、哪些类型放在一起。但为什么这三个函数应该在一个模块里?这个问题 AI 回答不好,需要你对业务的理解。

比如我的项目中,"文件元数据提取"和"文件格式检测"从函数依赖上看是独立的,但从业务语义上它们都属于"文件分析"流程。AI 会建议拆成两个模块,但我选择合并在一个 metadata.rs 里——因为修改时几乎总是一起改。

核心原则:AI 做分析 + 建议,人类做决策。

四、拆分后的收益和一套可重复的流程

拆分完成后,最直观的收益是:

  1. 单文件都不超过 150 行:每次修改只需看 1-2 个文件
  2. 编译速度变快:Rust 的增量编译机制——只改一个模块时只重新编译这个模块
  3. 测试更容易写:每个模块可以独立测试,不需要 mock 整个流程
  4. 新同事看代码时间从 2 小时降到 30 分钟

我总结了一套可重复的流程:

flowchart LR
    S1["1. 把代码发给 AI\n要求分析依赖"] --> S2["2. 根据分析结果\n手动画模块边界图"]
    S2 --> S3["3. 创建新文件\n迁移一个函数"]
    S3 --> S4["4. cargo check\n确认编译通过"]
    S4 --> S5["5. 调整 pub 声明\n和 use 路径"]
    S5 --> S6["6. 运行现有测试\n确认行为不变"]
    S6 --> S7{"还有函数\n没迁移?"}
    S7 -->|是| S3
    S7 -->|否| S8["7. 清理原文件\n删除已迁移的代码"]

五、总结

用 AI 辅助重构这件事,我的心得是"三个不要":

  1. 不要让 AI 直接帮你写模块代码——它可能引入你不熟悉的抽象
  2. 不要一次性迁移所有模块——逐模块迁移 + 逐模块验证,每次只动一个维度
  3. 不要让 AI 替你做命名决策——模块名、函数名、类型名要你自己想清楚,因为以后你要天天面对这些名字

刚开始写 Rust 的时候,我总觉得"代码能跑就行"。现在越来越体会到:代码结构是你送给未来自己的一份礼物。你今天多花 1 小时整理模块,未来每次改代码都能省 30 分钟。这个 ROI,值得投资。

下一篇文章我会分享 Cargo 工作区的重构实践——把单体 crate 拆成多个子 crate 的决策过程。


欢迎在评论区分享你的代码重构经验。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值