こんにちは!広告事業本部でリードアプリケーションエンジニアをしている高木です。
開発プロセスにおいて、コードレビューは品質を担保するために不可欠な工程です。多くのチームでは、プルリクエスト(以下、PR)が作成されると、レビュワーが自動でアサインされる仕組みを導入しているのではないでしょうか。
私たちのチームも同様の仕組みを持っていましたが、1つ課題がありました。それは、「レビューする時間がない人」がアサインされてしまうケースです。
今回は、その課題をGitHub ActionsとIssueを使って解決した方法についてご紹介します。
これまでの課題
私たちのチームでは、以前からPRが作成されるとレビュワーを自動でアサインする仕組みがありました。しかし、以下のような非効率な点が発生していました。
- 週の初めの定例会議で「今週は他の業務で忙しいので、レビュー対応が難しいです」と口頭で宣言するメンバーがいた。
- 自動アサインの仕組みは、その宣言を汲み取ってはくれないため、レビューが難しいメンバーにもお構いなしにアサインしてしまう。
- 結果として、PR作成者が手動で別の人にレビュワーを再アサインする必要があった。
この「レビュー厳しい宣言」も「手動での再アサイン」も、地味ながら双方にとって手間であり、小さなストレスの種でした。
解決策
この課題を解決するため、「レビューが難しい期間だけ、自動アサインの対象から自分を外せる」仕組みを導入することにしました。
当初、GitHubの機能やGitHub Actionsでレビュワーの有効/無効を切り替えるような仕組みは見つけられませんでした(もしかしたら今はもっと良い方法があるかもしれません)。そこで、GitHubのIssueを使ってこの状態を管理するアイデアを思いつきました。
具体的なフローは以下の通りです。
- 「レビュワー自動アサイン除外用」のIssueを1つ作成しておく。
- レビュー対応が難しいメンバーは、そのIssueに自分をAssignee(担当者)として登録する。
- PRが作成されるとGitHub Actionsが起動し、除外用IssueのAssigneeを除いたメンバーの中から、ランダムでレビュワーをアサインする。
- レビュー対応が可能になったら、IssueのAssigneeから自分を外す。
これにより、口頭での宣言や手動での再アサインといった手間をなくし、各自が都合の良いタイミングでアサイン対象から抜けたり戻ったりできるようになります。
実装の詳細
この仕組みは、GitHub Actionsのワークフローと、それを実行するためのRubyスクリプトで実現しました。
ワークフローファイル (.github/workflows/assign_reviewers.yml)
まず、PRが作成されたり、「Ready for review」になったタイミングで処理が実行されるようにワークフローを定義します。ドラフトPRの場合は実行されないようにif条件で制御しています。
name: Assign reviewers on: pull_request: types: [opened, ready_for_review] jobs: assign_reviewers: if: github.event.pull_request.draft == false name: Assign reviewers to PR runs-on: ubuntu-latest steps: - uses: actions/checkout@v4.2.2 - name: Assign reviewers to PR env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} PULL_REQUEST_NUMBER: ${{ github.event.pull_request.number }} run: ruby .github/workflows/assign_reviewers/assign_reviewers.rb ${PULL_REQUEST_NUMBER}
Rubyスクリプト (.github/workflows/assign_reviewers/assign_reviewers.rb)
こちらが、実際のレビュワー選定とアサインを行うスクリプトです。GitHub CLI (gh) を使って、GitHub APIを簡単に呼び出しています。
require 'json' ORGANIZATION_NAME = 'Organization名' TEAM_NAME = 'チーム名' REPOSITORY_NAME = 'リポジトリ名' EXCLUDED_REVIEWERS_ISSUE_NUMBER = レビュワー自動アサイン除外用のIssue番号 NUMBER_OF_REVIEWERS = 2 # タイトルに 'WIP' が含まれている場合は処理をスキップ def wip?(title) title.upcase.include?('WIP') end # PRの情報を取得(タイトルと作成者) def fetch_pull_request(pull_request_number) JSON.parse(`gh pr view #{pull_request_number} --json title,author`, symbolize_names: true) end # レビュワーを設定 def choice_reviewers(pull_request_author) target_reviewers = fetch_target_reviewers excluded_reviewers = fetch_excluded_reviewers(pull_request_author) (target_reviewers - excluded_reviewers).sample(NUMBER_OF_REVIEWERS) end # アサイン対象となるチームメンバー全員を取得 def fetch_target_reviewers JSON.parse(`gh api /orgs/#{ORGANIZATION_NAME}/teams/#{TEAM_NAME}/members`, symbolize_names: true).map { |member| member[:login] } end # 除外対象のレビュワー(Issueのアサイン者 + PR作成者)を取得 def fetch_excluded_reviewers(pull_request_author) assignees = JSON.parse(`gh issue view #{EXCLUDED_REVIEWERS_ISSUE_NUMBER} --json assignees`, symbolize_names: true)[:assignees] assignees.map { |assignee| assignee[:login] }.push(pull_request_author) end # 選定されたレビュワーをPRにアサイン def assign_reviewers(pull_request_number, reviewers) endpoint = "/repos/#{ORGANIZATION_NAME}/#{REPOSITORY_NAME}/pulls/#{pull_request_number}/requested_reviewers" system("gh api --method POST #{endpoint} #{reviewers.map { |reviewer| %(-f 'reviewers[]=#{reviewer}') }.join(' ')}") end # メイン処理 def main pull_request_number = ARGV[0] pull_request = fetch_pull_request(pull_request_number) exit if wip?(pull_request[:title]) reviewers = choice_reviewers(pull_request[:author][:login]) assign_reviewers(pull_request_number, reviewers) end main
導入後の効果
この仕組みを導入したことで、チームの開発プロセスはよりスムーズになりました。
- 「レビュー厳しい宣言」が不要に: メンバーは自分のタイミングでIssueのAssigneeになるだけでよくなりました。
- 手動での再アサインがゼロに: PR作成者は、アサインされたレビュワーが対応可能であると信頼できるため、再アサインの手間がなくなりました。
- 心理的な負担の軽減: レビューができないのにアサインされてしまう罪悪感や、再アサインを依頼する手間といった、小さなストレスから解放されました。
まとめ
今回は、GitHub ActionsとIssueを組み合わせることで、レビュワーのアサインをより柔軟に、そして効率的に行う仕組みをご紹介しました。
日々の開発業務における小さな手間や非効率は、少しの工夫で大きく改善できることがあります。もしあなたのチームでも同じような課題を抱えていたら、ぜひ参考にしてみてください。