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

📅 2026/7/12 15:46:46
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的全部代码发给 AIprompt 是请分析这个 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) - ResultString, 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) - ResultVecPathBuf, 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 做分析 建议人类做决策。四、拆分后的收益和一套可重复的流程拆分完成后最直观的收益是单文件都不超过 150 行每次修改只需看 1-2 个文件编译速度变快Rust 的增量编译机制——只改一个模块时只重新编译这个模块测试更容易写每个模块可以独立测试不需要 mock 整个流程新同事看代码时间从 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 辅助重构这件事我的心得是三个不要不要让 AI 直接帮你写模块代码——它可能引入你不熟悉的抽象不要一次性迁移所有模块——逐模块迁移 逐模块验证每次只动一个维度不要让 AI 替你做命名决策——模块名、函数名、类型名要你自己想清楚因为以后你要天天面对这些名字刚开始写 Rust 的时候我总觉得代码能跑就行。现在越来越体会到代码结构是你送给未来自己的一份礼物。你今天多花 1 小时整理模块未来每次改代码都能省 30 分钟。这个 ROI值得投资。下一篇文章我会分享 Cargo 工作区的重构实践——把单体 crate 拆成多个子 crate 的决策过程。欢迎在评论区分享你的代码重构经验。