Автоматизируйте код-ревью и ускорьте итерации

Код-ревью играет важную роль в поддержании качества кода при разработке программного обеспечения. 

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

Обратная сторона применения политики проверки кода

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

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

Возможные решения

Есть несколько обходных путей:

  • Разделение проекта в моно-репозитории на подпроекты с различными политиками проверки.
  • Настройка ветвей для менее критичных изменений.

Я пробовал оба.

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

Во втором варианте слишком много усилий уходит на синхронизацию между ветвями.

Кроме того, для обоих случаев будет не так-то просто перенести код из менее строгих областей в зоны действия политик.

Создайте решение получше

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

Отличное решение  —  приложение для автоматической проверки кода, которое позволяло бы владельцу репозитория определять безопасные и/или тривиальные пулл-реквесты и автоматически одобрять их, а остальное отдать на откуп протоколу защищенной ветки.

Например, приложение может обнаружить, что пулл-реквест изменяет файлы только в области, предназначенной исключительно для владельца  —  это может быть каталог с именем пользователя MyGitHub:

Такой запрос можно одобрить автоматически, экономя время другим разработчикам:

Звучит неплохо. Как сделать так, чтобы это заработало?

Поскольку приложение требует уведомлений для пулл-реквестов и должно взаимодействовать с GitHub, что согласуется с определением GitHub-приложения, я решил работать поверх инфраструктуры приложения GitHub с помощью Probot (фреймворк, который абстрагирует низкоуровневые детали API GitHub-приложения).

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

Каждый раз, когда открывается пулл-реквест, приложение получает уведомление и читает упомянутый выше конфигурационный файл из репозитория.

Затем приложение должно оставить отзыв об одобрении, если правила были применены.

Разработайте правила

Чтобы помочь разработчикам действовать одновременно, первый набросок конфигурации поддерживает вставку имен пользователей. Сначала это может показаться немного абстрактным, но рассмотрим ситуацию на примере.

Допустим, пользователь может определить следующую конфигурацию, чтобы разрешить пулл-реквесты, нацеленные на файлы внутри каталога с именем пользователя GitHub:

ownership_rules:
  directory_matching_rules:
    - name: personal projects in experimental
      path: playground/{{username}}/**/*
    - name: personal documentation
      path: docs/personal/{{username}}/**/*

Если по-прежнему есть неясность, давайте вставим в пример реальное имя пользователя.

Например, если я открою пулл-реквест на изменение plaground/tianhaoz95/README.md в моей учетной записи GitHub, tianhaoz95, с приведенной выше конфигурацией он будет автоматически одобрен.

Реализуйте приложение

Поскольку полная реализация может оказаться немного сложной, в этой статье я представлю только упрощенную версию.

Полный исходный код можно найти в репозитории.

1.Подпишитесь на действия пулл-реквестов

Probot  —  удобный фреймворк, который сопоставляет входящие webhook-события с функциями обработчика, поэтому нам не нужно делать ничего больше.

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

import { Application } from "probot";
import { maybeApproveChange } from "./core";

export = (app: Application): void => {
  app.on(
    [
      "pull_request.opened",
      "pull_request.reopened",
      "pull_request.synchronize",
    ],
    async (context) => {
      await maybeApproveChange(context);
    },
  );
};

Функция maybeApproveChange публикует отзыв об одобрении, если запросы на вытягивание удовлетворяют этим критериям. Более подробно это описано на шаге 5.

2. Получить список измененных файлов с событий пулл-реквестов

GitHub предоставляет удобный API для извлечения списка изменений файлов из пулл-реквестов. Доступ к API можно получить с помощью следующего кода:

export const getChangedFiles = async (context: Context): Promise<string[]> => {
  const changedFilesResponse = await context.github.pulls.listFiles(context.repo({
    "pull_number": context.payload.pull_request.number,
  }));
  const changedFiles: string[] = [];
  for (const changedFileData of changedFilesResponse.data) {
    changedFiles.push(changedFileData.filename);
  }
  return changedFiles;
};

3. Прочитать конфигурацию из хранилища

Благодаря абстракциям Probot чтение стандартных конфигурационных файлов GitHub (расположенных внутри каталога .github) не составляет труда. Мы можем просто вызвать метод config в контексте Probot, чтобы получить обработанную конфигурацию yml:

export const getConfig = async (context: Context): Promise<Config> => {
  const rawConfig = await context?.config("approveman.yml");
  return parseConfig(rawConfig);
};

Приведенный выше код считывает содержимое файла .github/approveman.yml, анализирует файл yaml и преобразует его в объект JavaScript.

4. Сравните список измененных файлов с конфигурацией

Чтобы убедиться, что пулл-реквест безопасен, все файлы в нем должны соответствовать хотя бы одному из правил:

const matchRule = (rule: DirectoryMatchingRule,
  filename: string, info: UserInfo, context: Context): boolean => {
  const renderedRule = render(rule.path, info);
  return minimatch(filename, renderedRule);
};

const matchOneOfRules = (rules: DirectoryMatchingRule[],
  filename: string, info: UserInfo, context: Context): boolean => {
  let matchOneOf = false;
  for (const rule of rules) {
    if (matchRule(rule, filename, info, context)) {
      matchOneOf = true;
    }
  }
  return matchOneOf;
};

export const ownsAllFiles = (rules: DirectoryMatchingRule[],
  filenames: string[], info: UserInfo, context: Context): boolean => {
  let ownsAll = true;
  for (const filename of filenames) {
    if (!matchOneOfRules(rules, filename, info, context)) {
      ownsAll = false;
    }
  }
  return ownsAll;
};

Примечание:

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

5. Возможно, одобрите пулл-реквест.

После проверки того, безопасен ли пулл-реквест, следующий шаг  —  утвердить его:

const approveChange = async (context: Context): Promise<void> => {
  const req = context.repo({
    "event": "APPROVE" as ReviewEvent,
    "pull_number": context.payload.pull_request.number,
  });
  await context.github.pulls.createReview(req);
};

export const maybeApproveChange = async (context: Context): Promise<void> => {
  try {
    const changedFiles = await getChangedFiles(context);
    const rules = await getConfig(context);
    const info = getUserInfo(context);
    if (ownsAllFiles(rules, changedFiles, info, context)) {
      await approveChange(context);
      await createPassingStatus(context);
    } else {
      await dismissAllApprovals(context);
    }
  } catch (err) {
    await createCrashStatus(context, err);
  }
};

Примечание:

  • Первая функция отправляет запрос к API GitHub для добавления одобрения пулл-реквеста.
  • Вторая функция  —  высокоуровневая оболочка, которая использует все, что мы описывали на шагах 1-4.

Готовые решения

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

Если вы задавались тем же вопросом, то попробуйте воспользоваться моим приложением ApproveMan, которое предназначено для повышения эффективности разработки.

Определение безопасных пулл-реквестов с помощью имени пользователя GitHub  —  это первая схема, которую я придумал, но определенно не единственная, которая может быть полезной. Если у вас есть какие-либо интересные правила конфигурации, ApproveMan  —  открытый проект, и ваша потрясающая идея  —  это пулл-реквест.

Улучшать рабочий процесс можно не только через код-ревью.

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

Спасибо за чтение!

Читайте также:

Читайте нас в Telegram, VK и Яндекс.Дзен


Перевод статьи: Jackson Zhou: “Iterate Faster With Automatic Code Review”