搞了半年AI编码工具,聊聊它到底改变了我什么

本文不是广告,不是教程合集,就是一个写了六年C++的普通开发者,用了半年AI编码工具之后的真实感受。有吹的地方,也有骂的地方。

写在前面

去年年底组里来了个实习生,我让他写一个日志模块的单例封装。他打开Cursor,噼里啪啦敲了几句自然语言描述,三十秒不到,一个带线程安全、支持多级别输出的Logger类就出来了。

说实话,那一瞬间我是有点恍惚的。

我当年写第一个线程安全的单例,光是研究std::call_once和双检锁的区别就花了一下午。不是说实习生不该学这些底层原理——而是这个效率差距,确实让我重新审视了自己的工作方式。

从那之后我开始认真用AI编码工具。GitHub Copilot、Claude、Cursor,轮着试了个遍。半年下来,我的结论是:AI工具不会替代你思考,但它能替你干掉大量"已经想清楚但还得手动敲"的活。

哪些场景是真的香

1. 模板化代码生成

C++开发最烦的是什么?样板代码。

一个类你得写头文件声明、源文件实现、构造析构、拷贝移动语义、操作符重载……逻辑可能就几行,但脚手架代码能写一屏。

比如我最近在做一个轻量级的配置管理器,需求很简单:读取INI格式配置文件,支持分组和键值对查询。以前我得从零开始搭架子,现在我跟AI说清楚需求,它直接给我一个能用的起点:

configmanager.h

#ifndef CONFIG_MANAGER_H  
#define CONFIG_MANAGER_H

/// @file configmanager.h
/// @brief 轻量级INI配置管理器 v1.0
/// @details 支持分组读取、键值对查询、默认值回退
/// @usage
///   ConfigManager cfg;
///   cfg.load("app.ini");
///   auto val = cfg.getValue("database", "port", "3306");

#include <string>
#include <unordered_map>
#include <fstream>
#include <sstream>

class ConfigManager
{
public:
    /// @brief 加载INI配置文件
    bool load(const std::string& filePath);       // 从指定路径加载配置

    /// @brief 获取配置值,支持默认值回退
    std::string getValue(                          // 按组名+键名查询
        const std::string& section,                // 分组名称
        const std::string& key,                    // 键名
        const std::string& defaultValue = ""       // 查询失败时的回退值
    ) const;

    /// @brief 检查指定分组是否存在
    bool hasSection(const std::string& section) const;  // 判断分组是否已加载

    /// @brief 检查指定键是否存在
    bool hasKey(                                   // 判断某个键是否存在
        const std::string& section,                // 所属分组
        const std::string& key                     // 键名
    ) const;

private:
    /// section -> (key -> value) 的二级映射
    using SectionMap = std::unordered_map<std::string, std::string>;
    std::unordered_map<std::string, SectionMap> dataMap_;  // 全部配置数据
};

#endif // CONFIG_MANAGER_H

configmanager.cpp

#include "configmanager.h"

bool ConfigManager::load(const std::string& filePath)
{
    std::ifstream file(filePath);                  // 打开配置文件
    if (!file.is_open())                           // 文件打开失败直接返回
        return false;

    std::string line;                              // 逐行读取缓冲
    std::string currentSection;                    // 当前解析到的分组名

    while (std::getline(file, line))               // 逐行遍历文件内容
    {
        // 去除首尾空白
        size_t start = line.find_first_not_of(" \t");  // 定位第一个非空白字符
        if (start == std::string::npos)            // 空行直接跳过
            continue;
        line = line.substr(start);                 // 裁剪前导空白

        if (line.empty() || line[0] == ';' || line[0] == '#')  // 跳过注释行
            continue;

        if (line.front() == '[' && line.back() == ']')         // 解析分组标记
        {
            currentSection = line.substr(1, line.size() - 2);  // 提取方括号内的组名
            continue;
        }

        size_t eqPos = line.find('=');             // 查找键值分隔符
        if (eqPos != std::string::npos)            // 确认是合法的键值对
        {
            std::string key = line.substr(0, eqPos);    // 等号左侧为键
            std::string value = line.substr(eqPos + 1);  // 等号右侧为值

            // 去除键尾部空白
            size_t keyEnd = key.find_last_not_of(" \t");       // 定位键的有效末尾
            if (keyEnd != std::string::npos)                    // 安全裁剪
                key = key.substr(0, keyEnd + 1);

            // 去除值前导空白
            size_t valStart = value.find_first_not_of(" \t");  // 定位值的有效起始
            if (valStart != std::string::npos)                  // 安全裁剪
                value = value.substr(valStart);

            dataMap_[currentSection][key] = value;  // 存入二级映射
        }
    }

    return true;                                   // 加载完成
}

std::string ConfigManager::getValue(
    const std::string& section,
    const std::string& key,
    const std::string& defaultValue
) const
{
    auto secIt = dataMap_.find(section);            // 查找目标分组
    if (secIt == dataMap_.end())                    // 分组不存在则返回默认值
        return defaultValue;

    auto keyIt = secIt->second.find(key);           // 在分组内查找目标键
    if (keyIt == secIt->second.end())               // 键不存在则返回默认值
        return defaultValue;

    return keyIt->second;                           // 返回实际配置值
}

bool ConfigManager::hasSection(const std::string& section) const
{
    return dataMap_.count(section) > 0;             // 分组存在则返回true
}

bool ConfigManager::hasKey(const std::string& section, const std::string& key) const
{
    auto secIt = dataMap_.find(section);            // 先定位分组
    if (secIt == dataMap_.end())                    // 分组不存在直接返回false
        return false;
    return secIt->second.count(key) > 0;            // 再判断键是否存在
}

使用示例:

#include "configmanager.h"
#include <iostream>

int main()
{
    ConfigManager cfg;                              // 创建配置管理器实例
    cfg.load("app.ini");                            // 加载配置文件

    // 读取数据库端口,找不到就用默认值3306
    std::string port = cfg.getValue("database", "port", "3306");
    std::cout << "DB Port: " << port << std::endl;  // 输出查询结果

    return 0;
}

这段代码AI大概花了十几秒生成,我在上面改了一些细节(比如注释风格、命名规范),前后不到五分钟就搞定了一个可用的模块。换做以前,光是写完整个load函数的边界处理,我怎么也得磨个半小时。

但我要强调一点:AI给的代码不是拿来就用的。 上面这个版本我至少改了三处——原始生成的代码没有处理BOM头、没考虑值里面带等号的情况、注释风格也不符合我们组的规范。AI出的是70分的半成品,剩下30分还得你自己补。

请添加图片描述

2. 单元测试生成

写测试可能是所有C++开发者最不愿意干的事。不是不知道重要,是真的枯燥。

现在我会把写好的头文件直接丢给AI:"帮我生成这个类的单元测试,用GoogleTest框架,覆盖正常路径和边界情况。"出来的测试用例虽然不可能覆盖所有场景,但至少能帮我搭好80%的测试骨架,我再往里面补几个业务相关的边界用例就行。

这个改变带来的结果是:我们组的测试覆盖率从以前的40%出头,涨到了现在的75%左右。不是因为大家突然变勤快了,而是写测试的成本变低了。

3. Bug定位和代码审查

这是我觉得AI最被低估的能力。

前阵子我遇到一个多线程环境下的偶发崩溃,coredump里的调用栈指向一个看起来完全正常的std::vector操作。我把相关代码片段丢给Claude,它两句话就点出了问题:在另一个线程里有一个没加锁的push_back,导致迭代器失效。

这个Bug我自己盯了一下午没盯出来。不是我水平不行,是人盯多线程代码真的容易"灯下黑",你太熟悉自己的逻辑了,反而会跳过一些隐含的前提假设。AI没有这个包袱,它就是逐行看,反而能看出你看不见的东西。

请添加图片描述

哪些场景还是不行

公平地说,AI编码工具目前还有不少硬伤。

复杂的架构设计做不了。 你让AI帮你写一个函数、实现一个类,没问题。但你让它帮你设计一套微服务的通信架构、规划模块之间的依赖关系,它给出来的方案往往很"教科书"——正确但不实用。真实项目里的技术选型,要考虑团队能力、历史债务、运维成本,这些AI理解不了。

对项目上下文的理解很有限。 现在的AI工具基本都是"看一个文件说一个文件",它不了解你整个项目的代码风格、架构约定、业务规则。所以它生成的代码经常在局部看起来没问题,放到项目里就各种格格不入。

C++的模板元编程基本靠不住。 如果你做的是SFINAE、Concepts这类高级模板技巧,AI的建议经常是错的,甚至连编译都过不了。这个领域目前还是得靠自己。

我的实际工作流变化

半年下来,我的日常开发流程大致变成了这样:

  1. 需求分析——这一步完全没变,还是得自己想清楚要做什么
  2. 设计阶段——模块划分、接口定义还是手动做,但会让AI帮我Review设计方案,有时候它能提出一些我没想到的边界情况
  3. 编码阶段——框架代码让AI生成,核心逻辑自己写,然后让AI帮忙做代码审查
  4. 测试阶段——测试骨架让AI生成,自己补充业务相关的边界用例
  5. 调试阶段——遇到奇怪的Bug先丢给AI看看,有时候能省很多时间

整体算下来,同样的开发任务,耗时大约缩短了40%-60%。其中最大的收益不在编码本身,而在测试和调试环节。

请添加图片描述

给还没用AI工具的开发者几句话

第一,别排斥。 我知道很多老开发者对AI工具有本能的抵触,觉得"这不就是个高级自动补全吗"。说实话我一开始也是这么想的。但真用起来之后你会发现,它的能力远不止补全——代码审查、Bug分析、测试生成,这些才是真正节省时间的地方。

第二,别依赖。 AI生成的代码一定要自己过一遍。我见过有人直接把AI输出的代码提交上去,结果出了线上事故。AI不理解你的业务,它只是在做模式匹配。最终的质量把关永远是你自己的责任。

第三,把省下来的时间花在刀刃上。 AI帮你省了写样板代码的时间,那就把这些时间花在架构设计、性能优化、代码审查上。这些才是真正体现开发者价值的地方,也是AI目前还做不好的地方。

写在最后

有人说AI会取代程序员,我觉得短期内不会。但AI一定会淘汰那些拒绝使用AI工具的程序员——就像当年IDE淘汰了坚持用记事本写代码的人一样。

工具在进化,我们也得跟着进化。

至于AI编码工具最终能发展到什么程度,说实话我不知道。但至少在当下,它已经实实在在地改变了我的工作方式。与其站在岸上观望,不如先跳下去试试——水温没你想的那么凉。


本文为个人使用体验分享,不构成任何工具推荐。文中提及的工具名称均为其各自公司的注册商标。

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐