Four Keys 変更リードタイム向上のための Tips

この記事は、[エンジニア組織の開発生産性・開発者体験向上の取り組みをシェアしよう！ by Findy Advent Calendar 2023](https://qiita.com/advent-calendar/2023/developer-productivity) 13日目の記事です。
本記事では、Four Keys の変更リードタイム向上を目指して実施した活動を紹介していきます。

## 背景
今年の5月頃から Four Keys を使った開発生産性の可視化、向上を目的に [Findy Team+](https://findy-team.io/service_introduction?gclid=CjwKCAiAg9urBhB_EiwAgw88mUkWUeSUuKTMdE-PnQbNbyfd22JZmmTFB3WesI95gnrDXj3P4-zUbxoC64oQAvD_BwE#about) の利用を始めて、10月の [Findy Team+ Award 2023](https://award.findy-team.io/) の Small Division 部門で表彰していただくに至るまでにやったこと (やりたかったことも含めて) 、取り組んだ内容を私自身の備忘録も兼ねて振り返りながら綴っていきます。
利用開始当初の計測結果では「変更リードタイム」が一番伸びしろがあったので、まずは「変更リードタイム」を改善していくことを目標に活動していました。
Four Keys を活用した開発生産性可視化に至るまでの道のりについては、記事にしていただいていますので、興味ある方は是非ご一読ください。

https://blog.findy-team.io/posts/intereview_saisoninformationsystems/

## 変更リードタイム向上のための Tips
- ファーストコミットの自動化
- プルリクエストのサイズを小さくする
- ラベル運用
- ファーストレビューの自動化 (やりたかったけど導入にはいたらず)

## ファーストコミットの自動化
ファーストコミットが変更リードタイムの計測起点になります。
正しく計測することでボトルネックを適切に分析できます。
ファーストコミットの自動化にはチェックアウト後に処理を差し込めるフック ([post-checkout](https://git-scm.com/docs/githooks#_post_checkout)) を使って以下のようなことをやってます。
- ブランチを切ったら自動で空コミットを作成する
- コミットメッセージは "Work has started on the ${BRANCH_NAME}" とする

`.githook/post-checkout` に以下のようなスクリプトを配置しておきます。
実行権限の付与も忘れずに。

```sh:post-checkout
#!/bin/bash

readonly PREV_HEAD=$1
readonly CURRENT_HEAD=$2
readonly CHECKOUT_TYPE=$3

IGNORED=("develop" "master" "main")
IGNORED_PREFIX=("release" "support")
BRANCH_NAME=$(git rev-parse --abbrev-ref HEAD)

containsElement () {
    local e
    for e in "${@:2}"; do [[ "$e" == "$1" ]] && return 0; done
    return 1
}
startWith () {
    local e
    for e in "${@:2}"; do [[ "$1" == "$e"* ]] && return 0; done
    return 1
}

if [ ${CHECKOUT_TYPE} -ne 1 ];
then
    echo "Not a branch checkout"
    exit 0 ;
fi

if [ "${PREV_HEAD}" == "null-ref" ];
then
    echo "Clone repository"
    exit 0 ;
fi

if containsElement "${BRANCH_NAME}" "${IGNORED[@]}";
then
    echo "Checked out an ignored branch"
    exit 0 ;
fi

if startWith "${BRANCH_NAME}" "${IGNORED_PREFIX[@]}";
then
    echo "Checked out an ignored prefix branch"
    exit 0 ;
fi

NUM_CHECKOUTS=$(git reflog --date=local | grep -o "${BRANCH_NAME}" | wc -l)

# 同じ名前のブランチ名が以前に作られていた場合は新規ブランチとみなされずに空コミットはされません
if [ "${PREV_HEAD}" == "${CURRENT_HEAD}" ] && [ "${NUM_CHECKOUTS}" -eq 1 ]; then
    git commit --allow-empty -m "Work has started on the ${BRANCH_NAME}"
    echo "first commit on new branch ${BRANCH_NAME}"
fi
```
これにより、新規ブランチ作成時に自動的に空コミットが作成されるようになり計測の起点ができます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/286805/514cbdd6-3e8d-f898-6bf6-241cdd603f6e.png)

## プルリクエストのサイズを小さくする
プルリクエストを小さくする利点としては以下が挙げられます。
- 影響範囲の局所化
- レビュー負荷の軽減

大きな変更は影響範囲も大きくなりますし、問題を埋め込むリスクは高まり、手戻りリスクが上がります。
また、大きなプルリクエストはレビューする側の人の負荷も高く、レビューに時間と労力がかかります。当然レビューを受ける側の人へのフィードバックは遅くなるため、コミュニケーションが非効率になりやすいです。

### プルリクエストのサイズ目安
プルリクエストのサイズとしては、変更行数 500 行を基準としていました。
Findy Team+ の通知機能を有効にすることで変更行数 500 行を超えるプルリクエストを検知してくれるので、レトロスペクティブで振り返りリファインメントでタスク粒度の調整などを実施するのが良いと思います。

## ラベル運用
もともとラベル運用を始めたきっかけは、分析対象のデータに調査タスクなどのノイズが混じっていたので、これを除外する目的でラベル運用を開始しました。
やってみて思ったのは、プルリクエストにラベルを貼ることで分析時にノイズを除去できる様になるだけでなく、目的毎にラベルを分類することで目的別にピンポイントで分析が可能になります。
このことから、ラベルのついていないプルリクエストはマージしないように Github Actions を使ってチェックするようにしています。

```yaml
name: Label Checker

on:
    pull_request:
        types:
            - synchronize
            - labeled
            - unlabeled
            - opened
            - reopened

jobs:
    ready-deploy:
        runs-on: datacatalog
        name: Label Check
        steps:
            - name: Pull request has labels?
              continue-on-error: true
              id: has_labels
              uses: actions/github-script@v5
              with:
                  github-token: ${{ secrets.GITHUB_TOKEN }}
                  script: |
                      const response = await github.rest.issues.listLabelsOnIssue({
                        owner: context.repo.owner,
                        repo: context.repo.repo,
                        issue_number: context.payload.pull_request.number
                      });
                      console.log(response);

                      if (response.data.length > 0) {
                        return true;
                      } else {
                        return false;
                      }

            - name: Set status check to success
              if: steps.has_labels.outputs.result == 'true' # outputs の値は文字列型のみとなるので注意
              run: |
                  curl --request POST \
                    --url https://api.github.com/repos/${{github.repository}}/statuses/${{github.event.pull_request.head.sha}} \
                    --header 'authorization: Bearer ${{secrets.GITHUB_TOKEN}}' \
                    --header 'content-type: application/json' \
                    --data '{
                      "context": "Label Check",
                      "state": "success",
                      "description": "Can be merged"
                    }'

            - name: Set status check to pending
              if: steps.has_labels.outputs.result == 'false'  # outputs の値は文字列型のみとなるので注意
              run: |
                  curl --request POST \
                    --url https://api.github.com/repos/${{github.repository}}/statuses/${{github.event.pull_request.head.sha}} \
                    --header 'authorization: Bearer ${{secrets.GITHUB_TOKEN}}' \
                    --header 'content-type: application/json' \
                    --data '{
                      "context": "Label Check",
                      "state": "pending",
                      "description": "Can be merged by assigning labels"
                    }'
```

## ファーストレビューの自動化
こちらは検証で止まっており、導入には至っておりませんが紹介します。
ファーストレビューを自動化することで変更リードタイム向上に大きく貢献すると考えました。
レビュー着手までの時間が短縮されるとともに、プルリクエストがオープンされてからマージされるまでの全体時間も短縮される期待が持てます。
ファーストレビューの自動化には [PR-Agent](https://github.com/Codium-ai/pr-agent) という、Codium AI によってオープンソースで開発されている ChatGPT を使ったプルリクエストを便利にするための AI ツールを利用します。プルリクエストに関する様々な作業を自動化することができ、現時点で以下のような機能を持っています。
- プルリクエストの説明自動生成
- コードレビュー
- コードの提案
- コードコメントの自動生成
- CHANGELOG の自動生成
- カスタムラベルの生成

### PR-Agent の使用方法
ローカルでの実行、リモートリポジトリ上での実行どちらも可能です。
実行に必要なものは Open AI の API キーのみなので CI に簡単に導入できます。
今回は Github Actions を利用して実行していきます。
詳しい使い方は [こちら](https://github.com/Codium-ai/pr-agent/blob/main/INSTALL.md) を参照してみてください。

### 設定
まず `.github/workflows/pr-agent.yaml` に以下のようなワークフローを作成します。


```pr-agent.yaml
name: pr-agent

on:
  pull_request:
    types: [opened, reopened, synchronize]
  issue_comment:
    types: [created, edited]

permissions:
  pull-requests: write
  issues: write

jobs:
  pr_agent:
    runs-on: ubuntu-latest
    name: Run PR Agent
    if: ${{ github.event.sender.type != 'Bot' }}
    steps:
      - id: pr-agent
        uses: Codium-ai/pr-agent@main
        env:
          OPENAI_KEY: ${{ secrets.OPENAI_KEY }}
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
          PR_REVIEWER.EXTRA_INSTRUCTIONS: 'Please use Japanese in descriptions.'
          PR_DESCRIPTION.EXTRA_INSTRUCTIONS: 'Please use Japanese in descriptions. Titles should have prefix of commitlint pattern such as `feat:`, `fix:`, `perf:`, `refactor:`, `test:`, `chore:`, `ci:`, `docs:` etc'
```

PR-Agent は GitHub Actions のワークフローに環境変数を指定することでレビュー内容を日本語にしたり、プルリクエストのタイトルを commitlint 形式にしたりできます。env セクションに環境変数を追加することで設定値を調整できるようになっています。
各種機能の詳しい設定は [こちら](https://github.com/Codium-ai/pr-agent/tree/main/docs) をご確認ください。

次に GitHub リポジトリの Actions Secret に OPENAI_KEY を追加し、 OpenAI のAPIキーを登録します。
`Settings > Secrets and variables > Actions > New repository secret > Add secret`
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/286805/f2a22b5e-cb06-c916-6138-d99516b599e8.png)
設定は以上です。それでは PR-Agent 様にレビューしていただきましょう。

以下のような雑なプルリクエストを作っていきます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/286805/6bb682e7-61d6-dc96-d079-49a596d67824.png)

変更内容はこんな感じです。
変更の意図としては、引用符の間に埋め込まれたユーザー提供のデータ自体に予期しないデータが含まれていないことをチェックするようにします。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/286805/fbf75faf-394b-ceb3-6e0d-5b9dc8c93fae.png)

プルリクエストを作成すると、すぐさま以下のようなレビューを実施してくれます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/286805/eef12681-9c6a-10a6-6d9e-d9de2ba4e5f4.png)
プルリクエストの分析を行ってくれるとともに、フィードバックとして以下2点のコメントをいただきました。
1. テストを追加しろ
1. エラー原因を握りつぶさずに呼び元にちゃんとエラーを返せ

至極全うでごもっともなご意見です。
このレベルでファーストレビューしてもらえると、その後のレビューは捗るのではないでしょうか。

次に雑に作ったプルリクエストなので説明が空っぽ、タイトルも雑なので、タイトルと説明を生成してもらいます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/286805/609c0012-5382-1867-0618-89d222b5af82.png)

なんということでしょう、タイトルと説明を丁寧に記載してくれただけでなくラベルも設定してくれています。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/286805/4977c7bc-2ad2-b4fb-336f-d32efd77603c.png)

Markers template という機能を使うことで、オリジナルの説明に PR-Agent が自動生成したコンテンツを簡単に統合できます。
プルリクエストをテンプレート化している場合には、以下のキーワードをテンプレートに入れておくと自動で置き換えてくれます。
- pr_agent:summary
- pr_agent:walkthrough

Markers template を有効にするための設定
```yaml
        env:
          PR_DESCRIPTION.USE_DESCRIPTION_MARKERS: 'true'
```

残念ながら、当社では実運用には至っておりませんが、リードタイム向上にはかなり強力なツールではないでしょうか。

## 最後に
本記事では、変更リードタイム向上を目指して実施した活動を紹介しました。どなたかのお役に立てると幸いです。
まだ知見も少ない中、手探りではありますが、パッケージソフトウェアの開発における生産性可視化のデファクトをつくっていきたい、という気持ちでこれからも日々開発に取り組んでいきます。

## 参考記事
[PR-Agent を使って Pull Request をAIレビューしてみた。（日本語対応もしてみた）](https://tech.layerx.co.jp/entry/2023/09/01/102612)