Введение: почему ваше ревью превращается в поле боя?
Вы отправили Merge Request. Вроде всё чисто, логика верная, код компилируется. Через час приходит ответ от сеньора: «-10 баллов Гриффиндору» и 35 комментариев к 200 строкам кода. Знакомо?
Ревью кода на C++ — это не просто проверка синтаксиса. Это искусство, которое разделяет junior-ов и senior-ов. C++ даёт разработчику огромную власть над памятью и производительностью, но с великой властью приходит великая ответственность за неопределённое поведение и утечки ресурсов.
В этой статье мы заглянем в голову опытного ревьюера. Я собрал реальные замечания, которые senior-ы оставляют на code review в продакшн-проектах. Если вы пишете на C++ и хотите, чтобы ваши PR принимали с первого раза — читайте внимательно. А если вы тимлид — используйте этот чек-лист для онбординга новичков.
1. Выстрел в ногу: ручное управление памятью
Самый частый холивар на ревью — "почему ты не используешь умные указатели?". Senior-ы ненавидят голые new и delete в современном C++ (C++11 и новее).
Плохой пример
class DataProcessor {
int* buffer;
size_t size;
public:
DataProcessor(size_t s) : size(s) {
buffer = new int[s]; // Кто будет удалять?
}
~DataProcessor() {
delete[] buffer; // А если исключение в конструкторе?
}
};
Комментарий senior-а: "Нарушение RAII. Если конструктор выбросит исключение после выделения памяти — деструктор не вызовется. Утечка памяти. Используй std::vector или std::unique_ptr."
Правильный пример
class DataProcessor {
std::vector<int> buffer;
public:
explicit DataProcessor(size_t s) : buffer(s) {}
};
Сеньоры обожают RAII (Resource Acquisition Is Initialization). Если вы пишете new в коде, будьте готовы объяснить, почему std::make_unique или std::vector вам не подходят. В 95% случаев — подходят.
2. Const-correctness: поставьте себе «запятую»
Пропущенный const — это красная тряпка для любого senior-разработчика на C++. Это не просто стиль, это контракт с читателем кода и компилятором.
Что бесит на ревью
std::string getName() { // Почему не const?
return name;
}
void printData(std::vector<int>& data) { // Копия? Ссылка? Изменяет?
for (auto& item : data) {
std::cout << item;
}
}
Комментарий senior-а: "Метод getName() не изменяет объект — сделай его const. Параметр data не изменяется — передавай const std::vector<int>&. Иначе вызывающий код не знает, изменишь ты его данные или нет."
Исправляем
std::string getName() const { // const метод
return name;
}
void printData(const std::vector<int>& data) { // const ссылка
for (const auto& item : data) {
std::cout << item;
}
}
Правило простое: если метод не меняет состояние объекта — const. Если функция не меняет аргумент — const T&. Const correctness в C++ — это не опция, это стандарт.
3. Move-семантика: копировать — не наша религия
Современный C++ — это про перемещение, а не копирование. Senior-ы сразу видят, где вы создаёте лишние копии и убиваете производительность.
Плохой паттерн
std::vector<int> processData(std::vector<int> data) { // Копия на входе
// ... много логики ...
return data; // Копия на выходе? Или NRVO?
}
void caller() {
std::vector<int> bigData = {1, 2, 3, ... 1000000};
auto result = processData(bigData); // Ещё одна копия!
}
Комментарий senior-а: "Две лишних копии вектора по 4 МБ. Передавай по значению только если ты всё равно собираешься сделать копию внутри. Иначе — const T&. А для возврата используй std::move или полагайся на RVO."
Оптимальный вариант
std::vector<int> processData(const std::vector<int>& data) {
std::vector<int> result;
result.reserve(data.size());
// ... заполняем result ...
return result; // RVO сработает, копии не будет
}
Ещё один частый косяк: передача std::unique_ptr по константной ссылке. Если функция забирает владение — принимайте по значению и используйте std::move. Если только смотрит — const T& или сырой указатель (но с умом!).
4. Неопределённое поведение: тихий убийца
Senior-разработчики имеют радар на undefined behavior. Код может работать на вашей машине, но упасть на сервере. Вот классика.
Пример: переполнение signed int
int calculateSum(int a, int b) {
return a + b; // Если a и b — большие числа, переполнение — UB!
}
Комментарий senior-а: "Стандарт C++ говорит: signed integer overflow — это undefined behavior. Компилятор может оптимизировать так, что твой код сделает что угодно. Используй std::optional или проверяй границы."
Пример: висячие ссылки
std::string& getLocalString() {
std::string local = "hello";
return local; // UB! local уничтожится при выходе из функции
}
Такие ошибки сложно отловить тестами. Они проявляются только под нагрузкой или при смене компилятора. Senior-ы читают код не как набор инструкций, а как набор контрактов с памятью.
5. Имена переменных: код пишется для людей
Да, это банально. Но именно на ревью C++ кода senior-ы чаще всего пишут: "Что значит эта переменная?".
Что раздражает
int a, b, c;— магические буквыauto it = vec.begin();— в 2024 году используйте range-based for или алгоритмыint tmp;— для чего tmp?