搞了半年AI编码工具,聊聊它到底改变了我什么?
搞了半年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的建议经常是错的,甚至连编译都过不了。这个领域目前还是得靠自己。
我的实际工作流变化
半年下来,我的日常开发流程大致变成了这样:
- 需求分析——这一步完全没变,还是得自己想清楚要做什么
- 设计阶段——模块划分、接口定义还是手动做,但会让AI帮我Review设计方案,有时候它能提出一些我没想到的边界情况
- 编码阶段——框架代码让AI生成,核心逻辑自己写,然后让AI帮忙做代码审查
- 测试阶段——测试骨架让AI生成,自己补充业务相关的边界用例
- 调试阶段——遇到奇怪的Bug先丢给AI看看,有时候能省很多时间
整体算下来,同样的开发任务,耗时大约缩短了40%-60%。其中最大的收益不在编码本身,而在测试和调试环节。

给还没用AI工具的开发者几句话
第一,别排斥。 我知道很多老开发者对AI工具有本能的抵触,觉得"这不就是个高级自动补全吗"。说实话我一开始也是这么想的。但真用起来之后你会发现,它的能力远不止补全——代码审查、Bug分析、测试生成,这些才是真正节省时间的地方。
第二,别依赖。 AI生成的代码一定要自己过一遍。我见过有人直接把AI输出的代码提交上去,结果出了线上事故。AI不理解你的业务,它只是在做模式匹配。最终的质量把关永远是你自己的责任。
第三,把省下来的时间花在刀刃上。 AI帮你省了写样板代码的时间,那就把这些时间花在架构设计、性能优化、代码审查上。这些才是真正体现开发者价值的地方,也是AI目前还做不好的地方。
写在最后
有人说AI会取代程序员,我觉得短期内不会。但AI一定会淘汰那些拒绝使用AI工具的程序员——就像当年IDE淘汰了坚持用记事本写代码的人一样。
工具在进化,我们也得跟着进化。
至于AI编码工具最终能发展到什么程度,说实话我不知道。但至少在当下,它已经实实在在地改变了我的工作方式。与其站在岸上观望,不如先跳下去试试——水温没你想的那么凉。
本文为个人使用体验分享,不构成任何工具推荐。文中提及的工具名称均为其各自公司的注册商标。
AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。
更多推荐
所有评论(0)