Меня зовут Савва Лебедев, и я менеджер наставников в Яндекс Практикуме на линейке курсов по C++: слежу за работой наставников, участвую в найме, решаю организационные моменты. Сейчас я наставник и ревьюер курса «Разработчик C++» , а до этого сам прошёл путь от обычного до старшего студента. На курсе есть задание на пир-ревью, и эта практика здорово прокачивает и хард-, и софтскилы. Именно поэтому хочу рассказать о пир-ревью и с позиции учащегося, и с позиции человека, который отвечает за качество обучения и профессиональное развитие студентов.

Зачем нужно пир-ревью

В программирование, как и в любую профессию, люди зачастую приходят без опыта. Если студент учится на курсе с нуля, вероятно, он мог и не видеть другой код, кроме своего, и не знает, как бы другие на курсе решили бы ту же задачу. Ему особенно полезно посмотреть на код, написанный другими учащимися. Может быть, какие-то моменты он упустил или и вовсе никогда не знал. Сталкиваясь с чужим кодом, другими практиками, он сможет заполнить эти пробелы. 

Процесс ревью полезен обеим сторонам. Человек, чью работу проверяют, получает оценку написанному, рекомендации, как можно сделать лучше. Ревьюер же анализирует, как пишут другие люди, тренируется искать ошибки и предлагать улучшения — от архитектуры до код-стайла.

Слово peer («пир») можно перевести как «равный». И проверяющий, и тот, кого проверяют в этом случае, — равны. Это могут быть коллеги или студенты из одного потока. 

Пир-ревью распространено и в коммерческих компаниях. Неважно, кто над какими фичами работает внутри отдела/службы/команды, они должны хотя бы примерно представлять, что делают другие, и уметь читать код и контекст. На каждый коммит должен посмотреть хотя бы один «сосед».

Пир-ревью может делать любой разработчик в команде, кому есть что сказать. Можно, например, не знать специфики проекта, но хорошо владеть языком и указать на best practices в коде. Если проект завязан на бизнес-логику, то делать ревью должен один из ближайших «соседей», а если речь идёт про очевидное логирование — то тот, кто готов разделить с вами ответственность.

Если обобщить, серьёзные изменения в коде могут подтвердить только соседи по отделу. Люди, не знающие бизнес-логики, лишь сделают море ошибок.

Как реализованы задания для пир-ревью

У нас на курсе C++ есть не только тренажёр, в котором студенты выполняют задания, но и пир-ревью — отдельная практическая задача:

Так студенты не только практикуются в написании и анализе кода, но и развивают софтскилы — умение работать в команде, навык коммуникации, способность корректно донести свою мысль, вести дискуссию, отстаивать своё мнение, давать обратную связь и адекватно реагировать на критику.

На пир-ревью даётся три задания. Пары студентов, которые будут взаимно проверять работы, определяет специальный бот. В результате один студент проверяет несколько работ разных соучеников.

Как это работает: нужно зарегистрироваться через бота, который подключит вас к GitHub. Там специально для peer-review создаётся приватный репозиторий, в который первый студент выкладывает код в виде Pull Request, а второму сообщается, что нужно провести ревью этого кода. Это очень удобно делать в GitHub, потому что можно прокомментировать каждую строчку кода, а в придачу оставить общий комментарий. Можно обсудить комментарии в виде дискуссии под заданием, а потом закрыть обсуждение, в ходе которого вы пришли к общему знаменателю. Это тоже учит студентов тому, как всё будет происходить на работе: ты ничего не добавляешь в проект сразу, сначала создаёшь Pull Request, в котором работаешь. Его ревьюят, комментируют и аппрувят, то есть подтверждают. Только после этого код отправляется в продакшн. 

Раньше всё было организовано через LMS Eduflow. В этом сервисе есть встроенный инструмент, который так и называется: peer review. Им пользуются в том числе студенты Стэнфорда. Схема работы похожая: можно загрузить выполненную задачу, и система автоматически назначит того, кто её проверит. Но впоследствии мы отказались от этого зарубежного инструмента и сделали свой — импортозаместили ботом, подключенным к GitHub :) Мне эта форма кажется более удобной, потому что у студента даже после обучения остаётся репозиторий со всеми загруженными в него фрагментами кода.

Мы составили для студентов инструкцию, чтобы они следовали ей при проверке. 

Получив комментарий к своему коду, студент не обязан внести все предложенные коррективы. Есть возможность вступить в дискуссию: почему предложено это решение, а не другое? Ревьюер — такой же новичок, поэтому между участниками процесса происходит полезный обмен знаниями. Каждый из них чего-то не знал, а теперь запомнит новые практики. После того как ревью получено, нужно оставить обратную связь. Выполнение всех этих шагов — условие перехода к следующей части курса.

Как давать конструктивное пир-ревью

Получив ссылку на репозиторий одногруппника, студенты могут оставлять комментарии к коду. Нужно убедиться, что код отвечает требованиям задания и стилю кода, принятому в Практикуме. Писать комментарий стоит по таким правилам:

  • Ревью — ответственная задача. Представьте себя на месте другого человека и подумайте, какая обратная связь будет наиболее полезна.

  • Используйте мягкие формулировки. Постарайтесь не использовать слово «нужно» (альтернатива — «лучше») и повелительное наклонение («сделай»). Лучше не перекладывать работу кода на его автора — «этот код делает» вместо «ты делаешь».

  • Используйте развёрнутые объяснения.

  • Обосновывайте необходимость другого решения.

  • Делайте встречные предложения — как сделать лучше.

  • Добавляйте поясняющие ссылки на статьи и обсуждения.

Делая ревью, важно не переходить на личности, не прибегать к сарказму. Плохо писать: «А нормально нельзя было сделать?», хорошо — «Мы используем такой подход, можно глянуть тут или почитать вот это». 

В русскоязычной культуре допустимо (но зачем?) ругать код, а вот в англоязычной или международной нынче царит идея, что код — это продукт человека, и ругань кода = опосредованная ругань на человека. А это уже не вписывается в базовые приличия.

Заключение

Как поясняет наш редактор курсов по C++ Ульяна Баженова, умение читать чужой код и оценивать его плюсы и минусы — большая часть работы программиста. Когда приходишь в новую компанию, нужно уметь разбираться с кодом, который до тебя написали другие. А иногда нужно разбирать собственный код, написанный давно, когда ты «был другим человеком». Справиться со всеми этими вызовами и помогает пир-ревью:

  • Учит давать конструктивную и сбалансированную обратную связь. Перед пир-ревью в Яндекс Практикуме мы рассказываем студентам, как давать фидбек, на что обратить внимание, чтобы обратная связь была полезной и нетравмирующей. А ещё напоминаем, что нужно обязательно хвалить удачные решения.

  • Тренирует навык работы с обратной связью: прислушиваться к чужой точке зрения и отстаивать свою, возвращаться к коду, который казался уже прекрасным, и улучшать его, не воспринимать замечания как личные оскорбления.

  • Находить проблемы в любом коде. Когда умеешь находить проблемы в чужом коде, начнешь чаще замечать и исправлять их в своём.


Предлагаем вам попробовать! Вот задача от автора  курса «Разработчик C++ Георгия Осипова: разработайте функцию Wrap, которая разобьёт текст на строки заданной ширины. При этом используйте один из четырёх видов выравнивания текста.

#include <iostream>
#include <string>
#include <vector>
#include <list>

enum Align {
    SLEVA, PO_CENTRU, SPRAVA, JUSTIFY
};

std::string justify(std::string text, int width) {
    int spaces = std::count(text.begin(), text.end(), ' ');
    int rest = width - ((int)text.size() - spaces);

    std::string res;
    int prev_pos = 0;

    for (int pos = text.find(' '); pos < text.size(); prev_pos = pos + 1, pos = text.find(' ', pos + 1)) {
        res += text.substr(prev_pos, pos - prev_pos);

        int spaces_count = rest / spaces;
        if (rest % spaces > spaces / 2) spaces_count++;
        rest -= spaces_count;
        spaces -= 1;
        res += std::string(spaces_count, ' ');
    }

    res += text.substr(prev_pos);

    return res;
}

std::list<std::string> Wrap(std::string text, int width, Align align) {
    std::string ccc;
    std::list<std::string> res;

    auto push = [&res, width, align](std::string l, bool is_last) {
        int rest = width - l.size();
        switch (align) {
        case Align::SLEVA:
            res.insert(res.end(), l + std::string(rest, ' '));
            break;
        case Align::SPRAVA:
            res.insert(res.end(), l + std::string(rest, ' '));
            break;
        case Align::PO_CENTRU:
            res.insert(res.end(), std::string(rest / 2, ' ') + l + std::string(rest - rest / 2, ' '));
            break;
        case Align::JUSTIFY:
            res.insert(res.end(), is_last ? l : justify(l, width));
            break;
        default:;
        }
    };

    // int off;
    // int space;

    int off = 0;
    int probel = 0;
    while (off < text.size()) {
        int next_off = text.find(' ', probel);
        if (next_off == std::string::npos)
            next_off = text.size();

        int next_space = text.find_first_not_of(" ", next_off);

        if (next_space == std::string::npos)
            next_space = text.size();

        if (ccc.size() + next_off - off <= width) {
            ccc += text.substr(off, next_off - off);
            off = next_off;
            probel = next_space;
            continue;
        }
        else if (ccc.empty()) {
            ccc = text.substr(off, width);
            probel = off + width;
        }

        push(ccc, false);
        ccc = "";
        off = probel;
    }

    push(ccc, true);
    return res;
}

int main() {
    // для ""s
    using namespace std::literals;

    std::string text = "Lorem ipsum  dolor sit amet, consectetur        adipiscing elit, sed do eiusmod tempoooooooooooooooooooooooooooooooooooooooooooooooooooooooooooor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur."s;
    //Wrap(text, 40, std::cout);

    for (auto& str : Wrap(text, 40, Align::JUSTIFY)) {
        std::cout << str << "\n";
    }
}

В решении есть недочёты это сделано нарочно, ведь так пир-ревью будет содержательнее. 

Оставляйте пир-ревью в комментариях! Не забывайте о правилах: используйте мягкие формулировки и развёрнутые объяснения. 

Комментарии (5)


  1. APXEOLOG
    29.06.2023 09:25
    +7

    Что такое пир-ревью

    Обзор на еду?


  1. panzerfaust
    29.06.2023 09:25
    +2

    Есть хоть 1 причина шатать старика Оккама и переименовывать обычное код-ревью?


    1. code_panik
      29.06.2023 09:25
      +2

      По-моему, в статье речь о ревью именно в рамках образовательного процесса. На курсе в качестве задания предлагается перекрестное ревью кода студентами для формирования базовой культуры работы с кодом в команде.


  1. konshyn
    29.06.2023 09:25
    +3

    Нужна пояснительная бригада: это какой-то троллинг над PR review?


    1. GerrAlt
      29.06.2023 09:25
      +1

      Да нет же, это когда медийного блогера кормят-поят-развлекают на вечеринке, а потом он уже пилит пир-ревью - отзыв достаточно ли все было хорошо. В статье еще список застольных тем предлагают - вероятно полезный.