PR作成時にTODOコメント、FIXMEコメントを自動検知する
はじめに
ソフトウェア開発において、ソースコード内に一時的なメモや将来的な修正指示を残すために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 / 上ちょ さんに作成してもらいました。ありがとうございます!
Discussion