KINTO Tech Blog
Development

残り続けるPRを減らす!GitHub Actionsワークフローのすすめ

masahi
masahi
Cover Image for 残り続けるPRを減らす!GitHub Actionsワークフローのすすめ

こんにちは!

KINTO ONE開発部 新車サブスク開発グループ バックエンドチーム所属の朝日です。
弊グループではKINTO ONEサービスを提供するためのシステムの運用開発をしています。
2023年8月に大規模なシステムリニューアルのリリースを行いました。
長期間にわたるシステム開発の中でチーム一丸となり切磋琢磨・試行錯誤を繰り返してきたわけですが、その中でチームで導入したプルリクエスト(以下PR)レビューを促進するGitHub Actionsのワークフローがとてもお気に入りなので紹介します。

ワークフロー概要
処理詳細は後述

後回しにされてしまうコードレビュー

みなさん、コーディングとコードレビュー、どちらがお好きですか。私は圧倒的にコーディングです。
コードレビューがシステム品質を保つ上でとでも重要な工程ということは重々承知しているものの、タスクの期限が迫っていると自身のコーディングを優先させてしまい、ついついレビューを後回しにしてしまうこともあるのではないでしょうか。
システムリニューアルプロジェクトで実装に着手し始めた当初も、コーディングは終わっているのにレビューがされずOpen状態のPRが多く残ってしまうことがありました。
その結果、以下のような事象が多く起こりチームの課題となっていました。

  • タスクを完了にすることができずチームとしての進捗状況が把握しにくい
  • 依存関係のあるタスクに着手できない or 影響を与えてしまう
  • mergeの際にコンフリクトが発生しやすくなる

これらの事象を防ぐために、今回紹介するPRレビュー促進するGitHub Actionsのワークフローを導入しました。

GitHub Actionsとは

弊社ではソース管理ツールにGitHubを使用しています。
GitHub ActionsとはGitHub上で利用できる自動化ツールで、自動化したい処理をワークフローに記述、リポジトリ内に配置しておくことで任意のタイミングで処理を実行することができます。
GitHub上の機能ということもあり、GitHubのイベントと親和性が高い特徴があります。

自動レビュワー設定ワークフロー

ワークフロー実行後のPR
本題です。
今回紹介するのはPRを作成したタイミングでレビュワーをランダムで自動設定、Slack通知するGitHub Actionsのワークフローです。
ついでにPR作成者の自動アサインとPR上にコメントも残します。

ワークフロー処理詳細

ワークフロー概要

  1. PRが作成されたタイミングでワークフローが実行されます。
  2. ワークフローからAWS Lambda(以下Lambda)[1]にリクエストします。
  3. Lambda処理の中にチームのメンバー情報を事前に定義しておきます。その際に"仕様担当"と"技術担当"の属性を持たせています。自分以外のチームメンバーの中から仕様レビュー担当と技術レビュー担当のレビュワーを一人ずつランダムで選択します。計二人を選択しているのはチームのルール(PRマージには二人以上のレビュー必須)にも由来しています。
  4. Lambdaから取得した二名のメンバー情報をPRのReviewersに、自身の情報をAssigneesに自動設定、レビュワーにメンション付きのコメントを残します。
  5. 設定されたレビュワーに指定したSlackチャンネルでお知らせします。

Slack通知
Slack Webhookに連携

おすすめポイント

レビュワー設定を最小人数に絞る

レビュワーをメンバー全員でなく最小人数を絞ることで「自分がレビューしなくても誰かが見てくれるだろう…」という他力本願を排除し、レビューの後回しを抑制します。

チームメンバーに"仕様担当"と"技術担当"の属性を持たせる

レビュワー人数を絞るとアサインされたメンバーの得意不得意によってレビューの品質が左右されてしまうことが懸念されました。
そのため、メンバーそれぞれに"仕様担当"と"技術担当"の属性を事前に設定し、ドメイン知識を得意としているメンバーには仕様中心に、技術が得意なメンバーや新規参画したばかりで仕様を勉強中のメンバーには技術面を中心に見てもらうようにしました。

リポジトリを跨いだチームメンバー情報の共有

複数のリポジトリで同じ処理を実行するには、各リポジトリに同じワークフローを配置する必要があります。
メンバー情報の定義をワークフロー内に記述した場合、メンバー新規参画などでメンバー情報を更新したい際に設定している全てのワークフローを修正しなければなりません。
そのためレビュワーランダム選択処理をLambdaで実装し、各リポジトリから参照できるように共通化にしました。
メンバー情報を更新の際はLambda処理の修正のみで完結するようにしています。

Slack通知で即確認可能

ワークフロー内でSlack通知処理を行うことによって、設定されたレビュワーに即時に確認してもらえます。
GitHubもメールでの通知をしてくれますが、利用するコミュニケーションツールを統一することによって通知の見落としを防ぐ効果があります。

自動化による"手間"の削減

GitHub ActionsはCICD自動化ツールとして注目されることが多い機能です。しかしそれ以外にも、今回のワークフローのようなちょっとした手間を自動化することもできます。
元々はPRレビューの促進を目的としたツールでしたが、実際使ってみることにより自動化のメリットを実感することができました。
もし今回のワークフローの処理を自身で行うとなると、チームメンバーの状態によってレビューの依頼に忖度してしまったり、別途Slackでメッセージを送る必要があったりします。
それぞれ小さな手間ではありますが、私のチームでは2年以上今回のワークフローを利用しており、"削減した手間"は相当数に上ります。今ではなくてはならないツールです。

紹介したワークフローのサンプルコード

今回紹介したワークフローのサンプル(Lambda不要ver.)を紹介します。
カスタマイズして使ってみてください。

auto-assigning-reviewers.yml
name: "Auto Assigning Reviewers"

on:
  pull_request:
    types: [opened, ready_for_review, reopened]

jobs:
  assigning-reviwers:
    runs-on: ubuntu-latest
    if: |
      github.event.pull_request.draft == false
    steps:
      - uses: actions/checkout@v3
      - uses: actions/setup-node@v3
        with:
          node-version: 16
      - run: npm install @slack/webhook
      - uses: actions/github-script@v6
        with:
          github-token: ${{ secrets.GITHUB_TOKEN }}
          script: |
            const { IncomingWebhook } = require('@slack/webhook')

            // メンバー情報を定義
            const MEMBER_INFO = [
              { slackName: "@仕様把握子",  speciality: 'spec' ,slackMemberId: "XXXXXX", githubName: "XXXXXX"},
              { slackName: "@仕様得夫",  speciality: 'spec' ,slackMemberId: "XXXXXX", githubName: "XXXXXX"},
              { slackName: "@仕様得得",  speciality: 'spec' ,slackMemberId: "XXXXXX", githubName: "XXXXXX"},
              { slackName: "@技術剛",  speciality: 'skill' ,slackMemberId: "XXXXXX", githubName: "XXXXXX"},
              { slackName: "@技術任路",  speciality: 'skill' ,slackMemberId: "XXXXXX", githubName: "XXXXXX"},
              { slackName: "@新参者",  speciality: 'skill' ,slackMemberId: "XXXXXX", githubName: "XXXXXX"},
            ]

            // PR作成者を取得
            const user = context.payload.sender.login;
            const author = MEMBER_INFO.find(member => member.githubName === user);

            // レビュワーをランダムで二人選出
            const skills = MEMBER_INFO
                            .filter(member => member.githubName !== author.githubName)
                            .filter(member => member.speciality === 'skill');

            const specs = MEMBER_INFO
                            .filter(member => member.githubName !== author.githubName)
                            .filter(member => member.speciality === 'spec');

            let getRandomNumber = (min,max) => Math.floor(Math.random() * (max - min + 1)) + min;
            const chosenSkillMember = skills[getRandomNumber(0, skills.length - 1)];
            const chosenSpecMember = specs[getRandomNumber(0, specs.length - 1)];

            const issue_number = context.issue.number
            const pull_number = context.payload.pull_request.number
            const { repo, owner } = context.repo

            // PR Reviewersの設定
            await github.rest.pulls.requestReviewers({
              owner,
              repo,
              pull_number,
              reviewers: [chosenSkillMember.githubName, chosenSpecMember.githubName]
            });

            // PR Assigneesの設定
            github.rest.issues.addAssignees({
              owner,
              repo,
              issue_number,
              assignees: [user]
            });

            // Slack通知
            const title = context.payload.pull_request.title
            const html_url = context.payload.pull_request._links.html.href

            const message = `オッス! <@${chosenSkillMember.slackMemberId}> <@${chosenSpecMember.slackMemberId}> \n
            <@${author.slackId}> の新しいPRのレビュアーに選定されました 🥳 \n
            <${html_url}|${title}>`

            // 通知したいSlackチャンネルのwebhook URLを指定
            const webhook = new IncomingWebhook('https://hooks.slack.com/xxxxxxxxx')

            await webhook.send({
              text: message,
            })

            // PRにコメント
            await github.rest.issues.createComment({
              owner,
              repo,
              issue_number,
              body: `@${chosenSkillMember.githubName},@${chosenSpecMember.githubName} レビュアーに選定されました 🚀`
            })

最後に

今回我が物顔で紹介しましたが、いつも便利ツールを導入してくれる同じチームの丁さん、素敵なツールとアイデアをありがとうございます。
スーパー丁さんの記事はこちらで公開されているので、合わせてどうぞ。
みなさま、GitHub Actionsで素敵な自動化ライフをお過ごしください!
https://blog.kinto-technologies.com/posts/2023-12-04-BE-Grafana-Observability/

脚注
  1. AWS Lambdaは関数として定義した処理をサーバーレスで実行できるAWSサービスの一つです。 ↩︎

Facebook

関連記事 | Related Posts

We are hiring!

【フロントエンドエンジニア】新車サブスク開発G/東京・大阪

新車サブスク開発グループについてTOYOTAのクルマのサブスクリプションサービスである『 KINTO ONE 』のWebサイトの開発、運用をしています。​業務内容トヨタグループの金融、モビリティサービスの内製開発組織である同社にて、自社サービスである、TOYOTAのクルマのサブスクリプションサービス『KINTO ONE』のWebサイトの開発、運用を行っていただきます。

【フロントエンドエンジニア(コンテンツ開発)】新車サブスク開発G/東京・大阪

新車サブスク開発グループについてTOYOTAのクルマのサブスクリプションサービスである『 KINTO ONE 』のWebサイトの開発、運用をしています。​業務内容トヨタグループの金融、モビリティサービスの内製開発組織である同社にて、自社サービスである、TOYOTAのクルマのサブスクリプションサービス『KINTO ONE』のWebサイトの開発、運用を行っていただきます。