引言:为什么你的代码审查变成了战场?
\n\n你提交了合并请求。看起来一切干净,逻辑正确,代码也能编译。一小时后,高级开发者回复了:“格兰芬多扣10分”,并附上了对200行代码的35条评论。熟悉吗?
\n\nC++代码审查不仅仅是语法检查。这是一门区分初级和高级开发者的艺术。C++赋予了开发者对内存和性能的巨大控制权,但能力越大,责任越大,要警惕未定义行为和资源泄漏。
\n\n在本文中,我们将深入经验丰富的审查者的思维。我收集了高级开发者在生产项目的代码审查中留下的真实意见。如果你正在编写C++代码,并希望你的拉取请求能一次通过——请仔细阅读。如果你是团队负责人,请将此清单用于新成员入职培训。
\n\n1. 自找麻烦:手动内存管理
\n\n审查中最常见的争论点是——“你为什么不用智能指针?”。高级开发者讨厌在现代C++(C++11及更高版本)中使用裸的new和delete。
糟糕的示例
\nclass DataProcessor {\n int* buffer;\n size_t size;\npublic:\n DataProcessor(size_t s) : size(s) {\n buffer = new int[s]; // 谁来删除?\n }\n\n ~DataProcessor() {\n delete[] buffer; // 如果构造函数抛出异常怎么办?\n }\n};\n\n高级开发者的评论:“违反RAII原则。如果构造函数在分配内存后抛出异常——析构函数不会被调用。内存泄漏。请使用std::vector或std::unique_ptr。”
正确的示例
\nclass DataProcessor {\n std::vector<int> buffer;\npublic:\n explicit DataProcessor(size_t s) : buffer(s) {}\n};\n\n高级开发者热爱RAII(资源获取即初始化)。如果你在代码中写了new,请准备好解释为什么std::make_unique或std::vector不适合你。在95%的情况下,它们是合适的。
2. 常量正确性:给自己加上“逗号”
\n\n遗漏const是任何C++高级开发者的眼中钉。这不仅仅是风格问题,而是与代码阅读者和编译器之间的契约。
审查中令人恼火的地方
\nstd::string getName() { // 为什么不是const?\n return name;\n}\n\nvoid printData(std::vector<int>& data) { // 拷贝?引用?会修改吗?\n for (auto& item : data) {\n std::cout << item;\n }\n}\n\n高级开发者的评论:“getName()方法不修改对象——请将其设为const。参数data不会被修改——请传递const std::vector<int>&。否则调用者不知道你是否会修改其数据。”
修正后
\nstd::string getName() const { // const方法\n return name;\n}\n\nvoid printData(const std::vector<int>& data) { // const引用\n for (const auto& item : data) {\n std::cout << item;\n }\n}\n\n规则很简单:如果方法不改变对象状态——加const。如果函数不修改参数——用const T&。常量正确性在C++中不是可选项,而是标准。
3. 移动语义:拷贝不是我们的信仰
\n\n现代C++是关于移动而非拷贝。高级开发者一眼就能看出你在哪里创建了多余的拷贝并拖累了性能。
\n\n糟糕的模式
\nstd::vector<int> processData(std::vector<int> data) { // 输入时拷贝\n // ... 大量逻辑 ...\n return data; // 输出时拷贝?还是NRVO?\n}\n\nvoid caller() {\n std::vector<int> bigData = {1, 2, 3, ... 1000000};\n auto result = processData(bigData); // 又一次拷贝!\n}\n\n评论