AI 辅助编程的边界探索:7 月实验告诉我们 AI 能做什么不能做什么的结论
一、7 月实验设计:有边界地测试,而不是盲目依赖
实验设定很简单:7 月的每一次编程任务,先自己尝试,遇到卡点再用 AI,然后记录 AI 给出的答案是否可用、是否需要修改、修改了多久、最终效果如何。31 天里我记录了 127 次 AI 交互,按场景分成了四类。
最让我意外的是各个场景的成功率差异巨大:
代码解释 72% 的一次性正确率很高——AI 在"翻译"已有代码时表现优秀。代码生成 45% 的正确率——差不多一半能直接用。Bug 排查 31%——经常指错方向。架构设计只有 18%——几乎每次都要大规模调整。
二、AI 能做什么:三个经过验证的高效场景
经过 31 天的密集实验,我确认了 AI 在三个场景下确实能大幅提升效率:
场景一:代码翻译和解释。这是 AI 最强的领域。给我一段 Rust async 代码解释各部分的职责、给我解释这段 Cargo.toml 里的 features 配置是什么意思、把这段 Python 脚本翻译成 Rust——这些任务 AI 完成得又快又准。
// ============================================================ // 场景一示例:AI 帮我解释这段所有权代码 // 我写了这段,但希望确认自己的理解是否正确 // ============================================================ /// 统计一个字符串中某个子串的出现次数 /// /// 设计选择:使用 &str 而非 String,因为函数不需要拥有数据 /// 只需要临时读取——遵循"最小权限原则" fn count_occurrences(text: &str, pattern: &str) -> usize { // text.matches 返回一个迭代器,不会分配新内存 // count 消费迭代器,统计匹配次数 text.matches(pattern).count() } fn main() { let s = "hello rust hello world hello rust"; // AI 帮我确认:这里传 &str 是因为字符串字面量本身就是 &str // 如果是 String 变量,需要 &s 来传递引用 assert_eq!(count_occurrences(s, "hello"), 3); assert_eq!(count_occurrences(s, "rust"), 2); }场景二:代码生成(有模板的)。AI 在生成样板代码时特别高效。比如"给我写一个用 clap 解析命令行参数的基础模板"、"用 serde 定义一个包含这些字段的结构体"、"写一个 reqwest 的 POST 请求骨架"。这些有明确模式的东西,AI 几乎每次都产出能用的代码。
场景三:错误信息解读。Rust 编译器的错误信息已经非常详细了,但 AI 能把它们翻译成"人话"。比如 borrow checker 报的cannot borrow *self as mutable because it is also borrowed as immutable,AI 能逐行分析哪里发生了不可变借用、哪里发生了可变借用、它们的生命周期关系是什么。
// ============================================================ // 场景三示例:一段真实报错,AI 帮我分析根本原因 // 编译错误:cannot borrow self.data as mutable more than once at a time // ============================================================ struct Processor { data: Vec<String>, } impl Processor { // ❌ 错误写法 — 同时对 self.data 进行了两次可变引用 // fn bad_process(&mut self) { // let a = &mut self.data; // 第一次可变借用 // let b = &mut self.data; // 第二次可变借用,违反了"同一时间只有一个可变借用" // a.push("hello".to_string()); // } // ✅ 正确写法 — 把两次可变借用拆开,分作用域 fn good_process(&mut self) { { let a = &mut self.data; // 第一个可变借用,在块内 a.push("hello".to_string()); } // a 的生命周期在这里结束,释放借用 // 此时 self.data 不再被借用,可以再次获取可变引用 let b = &mut self.data; // 第二个可变借用,合法 b.push("world".to_string()); } }三、AI 不能做什么:四个已经被证实的盲区
反面教训比正面经验更值钱。以下四个场景,我验证了 AI 真的不行——或者至少不能依赖:
盲区一:零上下文的大型重构。我把一段 800 行的main.rs贴给 AI,让它"帮我拆成 5 个模块",出来的结果惨不忍睹。模块职责混乱、循环依赖、甚至引用了不存在的类型。AI 缺乏对整个项目上下文的理解,拆模块这种全局决策必须人工设计。
盲区二:异步代码的正确性。Tokio 的并发模型、spawn 和 block_on 的区别、哪些函数需要 Send + Sync——这些是 AI 频繁出错的领域。它经常生成"看起来像 async 代码"的东西,但实际运行会 deadlock 或 panic。异步编程的正确性必须靠人工理解和测试验证。
// ============================================================ // AI 容易写错的异步代码示例 // ============================================================ use tokio::sync::Mutex; use std::sync::Arc; /// 这是一个 AI 容易出错但看起来很合理的模式 /// 实际上如果持有锁的跨越 .await,可能导致死锁 struct SharedState { // 注意:用 tokio::sync::Mutex 而不是 std::sync::Mutex // tokio 的 Mutex 能在 .await 时安全释放锁 data: Arc<Mutex<Vec<String>>>, } impl SharedState { /// 原子地检查并插入数据 /// 必须确保 check 和 insert 在同一个锁保护区域内完成 async fn check_and_insert(&self, item: String) -> bool { let mut data = self.data.lock().await; if data.contains(&item) { false // 已存在,不插入 } else { data.push(item); true // 插入成功 } // MutexGuard 在这里自动释放 } }盲区三:性能优化建议。AI 经常建议"用 HashMap 替代 Vec"或"加个索引",但这些建议脱离实际数据量和访问模式。我问 AI 为什么慢,它说"可能是 I/O 阻塞",但实际是我的 Arc 拷贝太多。性能问题必须靠 profiling,不是靠 AI 猜。
盲区四:架构设计决策。这是正确率最低的领域(18%)。"我应该用 trait 还是 enum 做多态" "插件系统用宏注册还是手动注册" "场景管理和任务调度怎么分层"——这些问题 AI 的回答往往"不全错",但缺失关键约束和权衡。架构需要的是对项目全局的理解和取舍——这是人的领域。
四、AI 辅助编程的正确打法:一个三层模型
31 天下来,我对 AI 的使用策略收敛到了一个三层模型:
第一层(必须自己写):架构设计、性能关键路径、安全敏感代码。这些东西错了会引发连锁问题,而且 AI 缺乏足够的上下文做决策。
第二层(AI 辅助但必须验证):代码翻译、测试生成、错误解释。AI 在这些场景下能大幅提速,但必须逐行阅读和验证——它可能看起来都对,但细节有坑。
第三层(放心用 AI):样板代码、序列化结构体、CLI 参数定义、Cargo.toml 配置。这些有明确定型和重复模式的东西,AI 几乎不会出错。
五、总结
7 月的实验结论用一个比喻:AI 是一辆导航仪,不是自动驾驶。它能帮我找路、提醒我障碍、省掉走错路的时间——但方向盘始终要我自己握。用 AI 学 Rust,最危险的不是它犯错,而是我自己分辨不出来它什么时候在犯错。
三条实验结论:
- AI 在解释已有代码上最可靠(72%),在生成新逻辑上中等(45%),在架构决策上不可靠(18%)——用对场景很重要。
- 异步代码和性能优化是 AI 的重灾区——这些必须靠人工验证,不能盲信。
- 最好的姿势是三层模型:简单样板放心交给 AI,中等难度 AI 辅助但自己验证,核心逻辑必须自己写。
8 月我会继续在 AI 辅助上做实验,但方向会从"让 AI 帮我写代码"转向"让 AI 帮我 review 代码"——这个角色翻转可能会更有价值。