みなさん、エンジニア楽しんでいますか?
最近、Issue駆動開発という言葉をよく見かけます。
AIエージェントにIssueを渡して、PRまで作ってもらう使い方が増えてきたからだと思います。
私は実務でIssueをあまり使ったことがありません。
AIに任せるにしても、どんなIssueを書けばいいのか、出てきたPRのどこを見ればいいのかが分からないと使いこなせません。
なので、AIに任せる前にIssue駆動開発の流れを理解したいと思いました。
この記事は、これから自分の個人開発(Goで作っている在庫管理のAPI)でIssue駆動開発を始めるにあたって、調べたことと決めたルールをまとめた勉強メモです。
流れは、Enchan1207さんのgist「Issue駆動開発とは」を参考にしています。
Issue駆動開発とは
Issue駆動開発は、機能の追加やバグの修正を、まずIssueとして登録してから始める進め方です。
課題駆動開発やチケット駆動開発と呼ばれることもあります。
Issueは、GitHubのリポジトリごとに作れるチケットのようなものです。
BacklogやJiraのチケットを、リポジトリの中で管理しているイメージです。
タイトルと本文があり、作ると #12 のような番号が振られます。
流れはシンプルで、1つのIssueに対して1つのブランチを切り、作業が終わったらPRを出してマージします。
コミットやPRにIssueの番号を書いておくと、あとから「このコードは何のために書いたのか」をIssueまでたどれます。
役割の分け方としては、Issueに「何を、なぜやるのか」を書き、PRに「どうやったのか」を書きます。
AIとの相性がいい理由
AIエージェントにIssueを渡す使い方では、Issueがそのまま「AIへの指示書」になります。
GitHubの公式ブログでもIssueには問題の説明や再現手順、試したことを書くように勧めていて、「Issueが明確であるほど、PRの質も上がる」と書かれています。
つまり、AIに任せるほど、人間の仕事は「Issueを書くこと」と「出てきたPRを読むこと」に寄っていきます。
流れ
ここからは手順です。
例として、在庫管理のAPIで「同時に注文が入ると在庫がマイナスになる(在庫数1コが-1コ)」というバグを直す場合で考えます。
ghとgitコマンドを使います。
1. Issueを立てる
gh issue create --title "在庫が同時注文でマイナスになる" --body-file issue.md
--body-file には、本文を書いたファイルを渡しています。
本文は、背景、やること、完了の条件を書くようにしました。
## 背景
在庫が1個の商品に、2つの注文がほぼ同時に入ると、両方とも成功して在庫が -1 になる。
## やること
在庫を減らす処理を、同時に実行されても在庫がマイナスにならないようにする。
## 完了の条件
- 在庫が1個のときに2つの注文を同時に送ると、片方だけが成功する
- 失敗した方には在庫不足のエラーが返る
作成すると、#12 のような番号が振られます。
以降は、この番号で作業を紐づけていきます。
2. ブランチを切る
git switch main
git pull
git switch -c 12-fix-stock-race
ブランチ名の先頭にIssueの番号を入れています。
ブランチ名だけで、どのIssueの作業かが分かるようにするためです。
3. コミットする
git commit -m "fix: 同時注文で在庫がマイナスにならないようにする (#12)"
コミットメッセージにも #12 を入れます。
GitHubの画面では、この番号がIssueへのリンクになります。
4. PRを出す
git push -u origin 12-fix-stock-race
gh pr create --title "fix: 同時注文で在庫がマイナスにならないようにする" --body-file pr.md
PRの本文には、一番上に Closes #12 と書きます。
Closes #12
## 何をしたか
在庫を減らすUPDATEに、「在庫が注文数以上あること」という条件を付けた。
## なぜこうしたか
在庫をSELECTで確認してからUPDATEすると、その間に別の注文が割り込んで、在庫がマイナスになる。
条件付きのUPDATEにすると、確認と更新が1つのSQLで終わるので、割り込まれない。
Closes は、GitHubが決めているキーワードです。
PRの本文にこれを書いておくと、PRがデフォルトブランチにマージされたときに、Issueが自動で閉じられます。
Closes のほかに、Fixes や Resolves など、close・fix・resolve の活用形が全部で9つ使えます。
ただし、PRのマージ先がデフォルトブランチ以外だと、このキーワードは無視されます。
本文の「なぜこうしたか」は、自分で足したものです。
コードを読めば「何をしたか」は分かりますが、「なぜこの書き方にしたのか」は残りません。
半年後の自分や、ポートフォリオを見てくれる人に、判断の理由を残しておきたいと思っています。
5. マージして片付ける
gh pr merge --rebase --delete-branch
git switch main
git pull
--rebase は、ブランチのコミットを、mainの先頭に1つずつ積み直してマージします。
マージコミットが作られないので、mainの履歴が一本道のまま残ります。
--delete-branch を付けると、マージしたあとに、手元とGitHub上のブランチを両方消してくれます。
マージされると、Closes #12 によってIssueも閉じられ、1つの作業が終わります。
自分で決めたルール
個人開発で回すにあたって、ルールは次の5つにしました。
- 1つのIssueに対して、ブランチもPRも1つにする
- mainには直接pushしない
- マージはrebaseで行う
- PRの本文に、なぜそう実装したかを書く
1人で開発しているので、PRを出してもレビューしてくれる人はいません。
それでもPRを通すのは、変更の理由を残す場所がほしいと思ったからです。
逆に、ラベル、GitHub Projects、ブランチの保護設定、Issueのテンプレートは、今は入れていません。
調べると色々出てきますが、1人でIssueを数本回すだけなら無くても困らないので、いろいろ慣れてきたら足すつもりです。
おわりに
Issue駆動開発は、Issueを立ててからブランチを切り、PRでIssueを閉じる、という流れ自体はシンプルでした。
大事なのは、Issueに「何をなぜやるのか」を、PRに「どうしてそうしたのか」を書くことだと思っています。
これは、AIにIssueを渡すときにもそのまま必要になる部分です。
これから個人開発で実際に回してみて、分かったことがあればまた記事にします。