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 个模块:
| 模块 | 文件 | 职责 |
|---|---|---|
| CLI | cli.rs | 命令行参数解析 |
| 文件 I/O | io.rs | 文件读写、目录扫描 |
| 元数据 | metadata.rs | 提取文件元信息 |
| 格式转换 | format.rs | JSON/YAML/Table 输出 |
| API | api.rs | HTTP 请求 + 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 做分析 + 建议,人类做决策。
四、拆分后的收益和一套可重复的流程
拆分完成后,最直观的收益是:
- 单文件都不超过 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 的决策过程。
欢迎在评论区分享你的代码重构经验。


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



