🚀

PR作成時にTODOコメント、FIXMEコメントを自動検知する

2024/12/23に公開

はじめに

ソフトウェア開発において、ソースコード内に一時的なメモや将来的な修正指示を残すためにTODOコメントやFIXMEコメントを使用することは、多くの開発現場で広く浸透している手法だと思います。

// TODO: あとでイベントログの送信を追加する
// FIXME: あとで実際のAPIレスポンスを使用するように修正する

このようなコメントは、アジャイル開発など、スピード感を重視する開発現場で多く見られます。適切に管理されていれば良いですが、これらのコメントが放置されたままリリースに入り込んでしまうと、最悪の場合それが本番障害となったり、そこまでにならなくても技術的負債として積み上がり、将来的な開発の足かせとなるリスクがあります。

弊社のモバイルアプリ開発ではトランクベース開発を採用しており、PRが細かく分割されすぐにmainブランチにマージされるため、TODO/FIXMEコメントが混入しやすい状況にあります。実際に、TODOコメントの対応漏れといくつかの複合的な要因も絡み、本番障害へと発展したケースもあります。

本記事では、上記のような開発プロセスの中で不可避的に生まれるTODOコメントやFIXMEコメントを、GitHub Actionsを用いてPR作成時に自動的に検知し、開発チームに通知する仕組みについて紹介します。

サマリー

今回は、releaseブランチをmainブランチにマージするPRを作成したときに、以下のようなPR上のコメントを作成し、Slackにも通知するGitHub Actionsを作成しました。この通知内に含まれるTODOコメント、FIXMEコメントをそのままリリースしても問題ないかを、チーム内で相互に確認するステップをリリースプロセスのなかに組み込む運用としています。

現状の課題整理

まずは現状を整理し、解くべき課題を特定します。

トランクベース開発では、例えば1週間(5人日)かけて完成するような機能がある場合、これはおよそ5つのタスクに分解され、各タスクで4つ程度のPRが作成されるため、1つの機能が合計20個のPRに分割されてmainブランチにマージされることになります。そして、これが10名ほどのモバイルアプリエンジニアによって行われます。

そのため、弊社の開発プロセスではTODOコメントやFIXMEコメントが発生しやすくmainブランチに入り込みやすい状況である一方で、そのようなコメントの管理は非常に属人的な状況になっていました。つまり、コメントを記述した開発者自身が責任を持って後日対応するか、あるいは他の開発者がコードレビュー等で気付いて指摘する以外に、これらのコメントを確実に拾い上げる仕組みが存在しません。

このような状況を踏まえ、まず解決したいことは、リリース前に必ず対応すべき重要なTODOコメント及びFIXMEコメントを確実に検知し、適切に対処できる仕組みを構築することでした。本番障害に直結するようなクリティカルな問題や、セキュリティ上の懸念、データ損失のリスクがあるようなコメントを、リリース前に確実に拾い上げ対応できる状態を目指しました。

GitHub Actions

上記のような仕組みを構築するために、GitHub Actionsで以下のようなスクリプトを作成しました。

サンプルコード
name: detect new todo/fixme comment

on:
  pull_request:
    branches:
      - master
    types: [opened]

jobs:
  detect_todo_comment:
    if: startsWith(github.head_ref, 'release/')
    runs-on: ubuntu-latest
    env:
      GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
      SLACK_WEBHOOK_URL_FOR_TODO: ${{ secrets.SLACK_WEBHOOK_URL_FOR_TODO }}
    steps:
      - uses: actions/checkout@v4
        with:
          fetch-depth: 0

      - name: Get previous release commit
        id: get_previous_release_commit
        run: |
          previous_release_commit=$(git rev-list --tags --max-count=1)
          echo "previous_release_commit=${previous_release_commit}" >> $GITHUB_OUTPUT

      - name: Get diff from previous release
        id: get_diff_from_previous_release
        run: |
          git diff ${{ steps.get_previous_release_commit.outputs.previous_release_commit }} HEAD > diff.txt

      - name: Search for TODO and FIXME comments
        id: search_comments
        shell: bash
        run: |
          current_file=""
          new_line_num=0

          echo "| ソースコード | 行番号 | ファイル名 |" > results.md
          echo "|---|---|---|" >> results.md

          while IFS= read -r line; do
            # ファイル名
            if [[ "$line" =~ ^diff\ --git\ a/.+\ b/(.+) ]]; then
              current_file=${BASH_REMATCH[1]}
            # ハンクヘッダーの解析
            elif [[ "$line" =~ ^@@\ -[0-9]+(,[0-9]+)?\ \+([0-9]+)(,[0-9]+)?\ @@ ]]; then
              new_line_num=${BASH_REMATCH[2]}
            else
              # 行の種類を判定
              prefix=${line:0:1}
              content="${line:1}"

              if [[ "$prefix" == '+' && "$line" != '+++'* ]]; then
                # 追加行の処理
                if [[ "$content" =~ (TODO|FIXME) ]]; then
                  # 先頭の空白を削除
                  content_trimmed="$(echo "$content" | sed 's/^[[:space:]]*//')"
                  # Markdownの特殊文字をエスケープ
                  content_escaped=$(echo "$content_trimmed" | sed 's/|/\\|/g')
                  echo "| \`$content_escaped\` | \`$new_line_num\` | \`$current_file\` |" >> results.md
                fi
                ((new_line_num++))
              elif [[ "$prefix" == ' ' ]]; then
                # コンテキスト行の処理
                ((new_line_num++))
              fi
            fi
          done < diff.txt

          # 結果がない場合、results.mdを削除
          if [ $(wc -l < results.md) -le 2 ]; then
            rm results.md
            echo "found=false" >> $GITHUB_OUTPUT
          else
            echo "found=true" >> $GITHUB_OUTPUT
          fi

      - name: Create PR comment when found
        if: steps.search_comments.outputs.found == 'true'
        run: |
          cat << EOF > comment.md
          🚨 新たなTODOまたはFIXMEコメントが検出されました:
          $(cat results.md)
          EOF
          gh pr comment ${{ github.event.pull_request.number }} --body-file comment.md
          echo "$(cat comment.md)" >> $GITHUB_STEP_SUMMARY
      - name: Send Slack message when found
        if: steps.search_comments.outputs.found == 'true'
        run: |
          curl -X POST -H 'Content-type: application/json' --data "$(cat <<EOF
          {
            "text": "🚨 新たなTODOまたはFIXMEコメントが検出されました。GitHubのコメントを確認してください。\nPR: ${{ github.event.pull_request.html_url }}"
          }
          EOF
          )" $SLACK_WEBHOOK_URL_FOR_TODO

      - name: Create PR comment when not found
        if: steps.search_comments.outputs.found != 'true'
        run: |
          cat << EOF > comment.md
          ✅ 新たなTODOまたはFIXMEコメントは見つかりませんでした。
          EOF
          gh pr comment ${{ github.event.pull_request.number }} --body-file comment.md
          echo "$(cat comment.md)" >> $GITHUB_STEP_SUMMARY
      - name: Send Slack message when not found
        if: steps.search_comments.outputs.found != 'true'
        run: |
          curl -X POST -H 'Content-type: application/json' --data "$(cat <<EOF
          {
            "text": "✅ 新たなTODOまたはFIXMEコメントは見つかりませんでした。\nPR: ${{ github.event.pull_request.html_url }}"
          }
          EOF
          )" $SLACK_WEBHOOK_URL_FOR_TODO

このGitHub Actionsは、release/**ブランチからmasterへのPR作成時に、前回のリリースから新たに追加されたTODOコメントやFIXMEコメントを自動検知し、チームに通知します。

具体的には、前回のリリース時に作成されたタグから最新コミットまでの差分を取得し、1行1行正規表現でTODOまたはFIXMEが含まれていないかチェック、検出したものをまとめてPRコメントとSlackで通知します。また、検出結果を見やすくするために、該当コメント、ファイル名、行番号をMarkdownの表形式で表示しています。

おわり

このGitHub Actionsの導入により、リリース前にTODO/FIXMEコメントを自動で検知できるようになり、導入してからはコメントの見過ごしによる本番障害は発生していないため、一定の成果を上げています。

今後は、ファイル名をパーマリンク化させたり、コメント行の前後コンテキストを併記することで検出結果の確認精度を向上させたり、コメントの内容からそのリスク度を自動的に判別し、緊急度に応じて表示を変えるなどの工夫も考えられます。

また、放置されがちなコメントへの対策としては、定期的な棚卸しとGitHub Issueとして起票する作業の自動化、アノテーションコメントの運用ルールの策定なども必要に応じて考えられます。

これらの取り組みを通じて、見過ごされがちなTODO/FIXMEコメントをより適切に管理し、持続可能な開発プロセスを実現していくことが大事だと思います。

Thanks Message

GitHub ActionsのSlackに通知する部分の機能は、iOSチームメンバーの uetyo / 上ちょ さんに作成してもらいました。ありがとうございます!

dely Tech Blog

Discussion