AI 辅助编程的 7 个误区:把模型当高级搜索引擎是对它的最大浪费

AI 辅助编程的 7 个误区:把模型当高级搜索引擎是对它的最大浪费

一、当 AI 说"这里加一个 Mutex 就好"的时候

去年十二月,我在给 dayuan 做并发缓存层。ChatGPT 很自信地建议:"在 HashMap 外层包一个 Arc<Mutex<>>,多线程访问就安全了。"

我照做了。然后发现高并发场景下吞吐量从 5000 QPS 跌到了 300 QPS——因为所有读操作都在争同一个锁。最后我用 dashmap + 分片锁的方案才把性能拉回来。

这事让我明白一个道理:AI 给你的方案永远只是"能编译通过"的方案,不是"正确"的方案,更不是"好"的方案。

作为自学编程的人,我天然依赖 AI 来弥补知识短板。但一年多下来,我发现很多人在用 AI 编程时,陷入了 7 个典型的认知误区。

二、误区全景


三、从"使用工具"到"被工具使用"

误区 1:把 AI 当高级搜索引擎

这是最常见的用法——也是最浪费的用法。

用户:tokio::spawn 怎么用?
AI:  这是文档……

你在浪费模型的推理能力。你不是在用一个代码助手,你是在用一个美化版的 grep docs.rs

正确的用法

用户:我在写一个 WebSocket 服务,需要在 accept 新连接时
      同时维护一个全局的在线用户列表,支持广播。
      现在我用 Arc<Mutex<Vec<u64>>> 存储在线用户 ID。
      每次广播都要遍历一遍,5000 并发时 Mutex 变成瓶颈了。
      
      不用给我写全代码,帮我分析三种可能的优化方向,
      以及每种在什么场景下适合。

AI:  好,我来分析:
      
      1. dashmap + 连接级别锁:
         适用:读写比例均衡,需要强一致性
         代价:内存占用比 Vec 高 30%
      
      2. 分片路由 + 无锁广播:
         适用:写少读多(在线状态变更少,广播多)
         代价:实现复杂度高,需要设计分片策略
      
      3. 事件总线 + 订阅模式:
         适用:广播是主要操作,不需要存储状态快照
         代价:需要引入消息中间件(如 tokio::broadcast)

看到了吗?你不是在问"怎么用",你是在把自己的设计困境描述给 AI,让它帮你做 trade-off 分析

误区 2:无脑接受 AI 的代码

/// ❌ AI 写的代码,看起来没问题,直到你在生产环境看到 OOM
async fn process_all_users(user_ids: Vec<u64>) -> Vec<UserInfo> {
    let mut tasks = Vec::new();
    
    for id in user_ids {
        // AI 不知道你的 user_ids 可能有 10 万个!
        tasks.push(tokio::spawn(async move {
            fetch_user_info(id).await
        }));
    }
    
    // 10 万个 task 同时运行 → 连接池耗尽 → OOM
    let mut results = Vec::new();
    for task in tasks {
        results.push(task.await.unwrap());
    }
    results
}

每次收到 AI 代码,带着这五个问题审一遍

  1. 边界条件:如果输入为空会怎样?如果输入有 10 万条会怎样?
  2. 错误处理:每个 .await 点都可能出错,处理了吗?
  3. 性能假设:这个操作是 O(1) 还是 O(n)?瓶颈在哪里?
  4. 并发安全:多线程/多协程同时运行时,有数据竞争吗?
  5. 资源泄露:文件句柄、网络连接、内存有没有正确释放?

误区 3:跳过理解,直接复制粘贴

这是我刚开始用 ChatGPT 时最大的毛病。看到代码跑通了就以为学会了,一周后再遇到同样的问题——还是不会。

我的实践:AI 辅助学习的正确姿势

/// 场景:AI 生成了这段代码帮你解决问题
/// 但你没有直接粘贴,而是逐行加注释来确保自己理解了

use std::collections::HashMap;
use std::sync::Arc;
use tokio::sync::RwLock;

/// 线程安全的缓存实现
/// 追问 AI 的问题:
/// Q: 为什么用 RwLock 而不是 Mutex?
/// A: 因为缓存的读操作远多于写操作,RwLock 允许多个读并发
/// Q: 为什么外面要包 Arc?
/// A: 因为 RwLock 需要被多个 task 共享,Arc 提供多所有权
pub struct Cache {
    inner: Arc<RwLock<HashMap<String, String>>>,
    //     ^^^ 多线程共享需要引用计数
    //          ^^^^^^ 读写锁:多个读者可以同时读,写者独占
    //                          ^^^^^^^^^^^^^^^^^^^ 实际存储
}

impl Cache {
    pub fn new() -> Self {
        Self {
            inner: Arc::new(RwLock::new(HashMap::new())),
        }
    }

    /// 读取缓存(可以多个 task 同时读)
    pub async fn get(&self, key: &str) -> Option<String> {
        let guard = self.inner.read().await;
        //                 ^^^^^^ 获取读锁,不阻塞其他读者
        guard.get(key).cloned()
    }
    // guard 在这里被 drop → 读锁释放

    /// 写入缓存(独占访问)
    pub async fn set(&self, key: String, value: String) {
        let mut guard = self.inner.write().await;
        //                   ^^^^^^^ 获取写锁,阻塞所有读者和写者
        guard.insert(key, value);
    }
}

我的法则:AI 生成的代码,凡是不能逐行解释的,先弄懂再用。

误区 4:觉得 AI 能替代系统学习

AI 能告诉你"Tokio 的 runtime 有 block_in_place 这个函数"。但它不能告诉你什么时候不该用它

/// ❌ AI 告诉你的:
/// 在 async 函数里调用同步代码,用 block_in_place 就行
async fn my_async_fn() {
    let result = tokio::task::block_in_place(|| {
        // 同步阻塞代码
        std::thread::sleep(Duration::from_secs(5));
        42
    });
}

/// 但 AI 不会告诉你的是:
/// 1. block_in_place 会把当前 task 移出 worker 线程
/// 2. 如果大量 task 都用 block_in_place,worker 线程池会"漏光"
/// 3. block_in_place 只应在 CPU 密集计算中使用
/// 4. 对于 IO 操作,应该用 spawn_blocking 或专门的异步 IO

系统学习 > AI 问答。对于一个新概念,我的学习路径是:

  1. 读官方文档的 Overview(至少前 3 页)
  2. 用 AI 回答"这个概念解决了什么问题"(不是"怎么用")
  3. 写一个最小可运行的 demo
  4. 故意写一个反例,看看会出什么问题
  5. 最后才把 AI 生成的代码放进项目

误区 5:用 AI 生成安全关键代码

/// ❌ AI 写的用户认证代码 —— 永远不要这样用!
#[derive(Deserialize)]
pub struct LoginRequest {
    pub username: String,
    pub password: String,
}

async fn login_handler(req: LoginRequest) -> Result<impl Reply> {
    // AI 的建议:直接拼 SQL
    let query = format!(
        "SELECT * FROM users WHERE username = '{}' AND password = '{}'",
        req.username, req.password  // ← SQL 注入!
    );
    // 用 req.username = "admin' --" 就能绕过密码验证
    
    sqlx::query(&query).fetch_one(&pool).await?;
    Ok("登录成功")
}

安全敏感的代码,永远不要让 AI 替你写。包括但不限于:

类别为什么不能信 AI
SQL 查询拼接AI 不一定会用参数化查询
密码哈希AI 可能建议用 MD5/SHA1
JWT 验证AI 可能跳过签名校验
权限检查AI 可能把授权逻辑写在客户端
加密实现永远不要自己实现加密算法

误区 6:高估 AI 的上下文理解能力

// 你问 AI:
// "这个函数有什么问题?帮我修复"

pub async fn process_data(config: &Config, data: &Data) -> Result<Output> {
    // AI 能看到这段代码,但它不知道:
    // 1. Config 的字段中哪些是热更新的
    // 2. Data 在什么生命周期内有效
    // 3. 调用方的并发模型是什么样的
    // 4. 这个函数在整体架构中的角色
    
    let enriched = enrich(data)?;
    let transformed = transform(&config.rules, &enriched).await?;
    Ok(transformed)
}

给 AI 提供上下文的最佳实践

用户:我在维护一个多租户 SaaS 的数据处理管线。这个 process_data
      函数是管线的第二步,前一步是数据清洗,后一步是存储。
      当前的问题是:当租户数量从 10 增长到 1000 时,
      这个函数从 50ms 变成了 3s。
      
      [粘贴函数代码]
      
      我在怀疑 enrich 阶段的 HashMap 在租户维度做了 O(n²) 操作。
      帮我分析是否存在这个问题,以及如何修复。

误区 7:用 AI 做架构决策

AI 不能替你选数据库,不能替你决定微服务拆分,不能替你评估技术债。因为架构决策的核心不是技术优劣,而是你今天有多大的团队、明天要支持多少用户、三个月后的需求是什么——而这些信息都不在模型的训练数据里。


四、我现在的 AI 编程工作流

具体来说:

  1. 需求阶段:把需求描述给 AI,让 AI 用三种不同方案各写 5 行伪代码。比较思路,不比较代码细节。
  2. 设计阶段:自己画出数据流图或状态机图,让 AI 挑漏洞。
  3. 实现阶段:自己写核心逻辑。AI 帮忙生成测试用例、文档注释、CLI 参数定义。
  4. 审查阶段:把自己的代码给 AI,限定问题"这段代码有哪些我没考虑的边界情况?"
  5. 重构阶段:功能验证通过后,让 AI 提出重构建议——这时候它已经有了完整上下文。

五、总结

AI 辅助编程一年多,我对 AI 的定位经历了三次迭代:

  • 第一阶段:AI 是代码生成器——"帮我写一个……"
  • 第二阶段:AI 是结对编程伙伴——"这里有问题,帮我看看……"
  • 第三阶段(现在):AI 是思维延伸——"我在做 X,有三种方案,帮我分析每种在 Y 场景下的 trade-off……"

关键转变是:从"让 AI 替我想"变成了"我思考,AI 检查"。的我之所以能靠自学转行,很大程度上受益于 AI。但我也看到太多人陷入"AI 写了代码 → 跑通了 → 以为自己学会了"的循环里。这不是在学习,这是在制造技术债——只不过债主是你未来的自己

记住两件事:

  1. AI 写代码只节省了你 20% 的时间,但如果你不理解它写的代码,未来修 bug 会多花 200% 的时间。
  2. 好的开发者不是能快速写出代码的人,而是能判断"这段代码不该这么写"的人。AI 不能帮你做这个判断——只有你自己的理解能。

下一篇预告:Rust 错误处理的反面教材,半年里见过的最糟糕的错误处理代码分析。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值