はじめに
前回記事では、CGF(Clean-history GitFlow)のブランチ戦略について、その全体像や考え方を中心に紹介しました。
そこで、今回の記事では「要件確定〜本番リリース」までを、実務に沿った運用フローとしてイメージできるよう、
実際の運用をベースに、より具体的な観点で整理していきたいと思います。
なお、実例として紹介させていただく弊社の開発運用方針は、以下の通りです。
- 実装タスクを課題で管理する(チケット駆動)
- feature ブランチは squash マージが前提(履歴の綺麗さを最大限保つ)
- 全員が実装者であり、レビュアーである
- チケット管理ツールとは別に、全員が利用するチャットツールがある
- 使用する検証環境はひとつ
シリーズ記事
1. 要件が確定した後にやること
要件が確定したら、まずは「公開単位」を定義するためにチケットを作成します。
前回記事でもお話した通り、CGF では
- 親タスク = develop ブランチ
- 子タスク = feature ブランチ
となるため、ここで作成したチケットがそのままブランチ設計として扱われます。
では実際の流れを、より具体的な考え方とともに見ていきましょう。
1-1. フェーズ分割が必要かを判断する
当たり前の話ではありますが、要件が確定するということは「何をリリースするのか」が決まることでもあります。
この時点で考えておかなければならないのが、
「この要件は、単一のリリースとして成立するのか」ということです。
要件のリリースを分けたいケースとしては、以下のようなものが挙げられます。
- 要件の規模がそもそも大きい(※)
- 仕様が段階的に決まる
- モノレポ構成で、UIとAPIを別フェーズに切り分けたい
- リスクが高いため、段階的に公開したい
このような場合は要件をフェーズに分割し、それぞれを別の親タスク(develop)として扱います。
逆に、単一リリースで問題ないのであれば、「1要件 = 1親タスク」となります。
重要なのは、実装の都合ではなく「公開単位」として成立するかどうか、で判断すること
※ ここで言う「規模」は単純な作業量だけに留まりません(詳細は6章)
1-2. タスク単位でチケットを作成する
リリース単位が決まったら、まずは親タスク単位でチケットを作成します。
これがそのまま develop ブランチの単位となるわけですが、ここで意識しておきたいのが、
「親タスク(develop)は "リリース可能な単位" として扱う」という点です。
develop は単なる「作業の束」ではなく、「この単位で本番に出す」意思決定の単位
親タスク用のチケットが作成できたら、次は子タスク用のチケットを作成します。
たとえば、新規テーブルの作成、登録APIの作成、更新APIの作成...といったような形ですね。
子タスク(feature)は実装単位で分割していきますが、
「develop にマージされた時点で、リリース可能な状態が保たれている」ことが前提となります。
そのため、「〇〇API作成という単位を更に分割したい」というニーズがあった場合、
「インターフェイス実装」「ビジネスロジック実装」「テストケースの追加」のような形で細分化していきます。
各 feature が単体で破綻しない構造を保つように分割・設計する
重要なのは、実装単位(feature)と公開単位(develop)を明確に分離することです。
CGF では、feature 単位で公開可否を判断する運用は行いません。
公開単位を develop に揃えることで「何をリリースしたのか」が明確になり、
切り戻し単位が常に説明可能な状態となります。
2. 実装の基本方針
親子タスクの作成で公開・実装単位の設計ができたら、タスク粒度に従って実装していきます。
※ 基本設計/詳細設計 といった実装方針は既に決まっているものとし、ここでは扱いません
CGF では、実装はすべて feature ブランチ上で行います。
develop ブランチに直接コミットすることはありません。
なお、弊社では develop ブランチの作成担当は特に決めていません。
最初に feature ブランチを切る人など、都度コミュニケーションを取りながら進めています。
大きめのプロジェクトであれば、「親タスク作成者が develop ブランチ作成までを担当する」など
運用ルールとして組み込んでしまった方がシンプルかもしれませんね。
2-1. feature ブランチの扱い
feature ブランチは実装者(チケット担当者)が develop ブランチから切ります。
ここはあくまでも「実装タスクを完了させるための作業空間」であるため、
弊社では作業の進め方自体に大きな制約事項は設けていません。
■ feature -> feature の扱い
タスクの実施順に前後関係があること自体に問題はないため、
feature ブランチから feature ブランチを切ることを禁止していません。
ただし、未マージの変更に依存したまま並行実装が進む状態は、事故の温床になりやすいため注意が必要です。
そのため、扱いには注意が必要であることを共通認識として持ち、
「並行で存在する feature 同士には、極力依存関係を持たせない」という方針をとっています。
2-2. コミット運用
コミット粒度について、弊社では厳密なルールは設けていません。
意識できるメンバーには
「実装のまとまり単位でコミットできるとよい」という程度に共有しています。
一方で、履歴の追跡性を担保するため、
「コミットメッセージにはチケットIDをプレフィックスとして付与する」というルールを設けています。
// コミットメッセージは「{子タスクID} {コミットメッセージ}」の形式で統一
// コミットメッセージ例
git commit -m "qte4bFy バリデーション追加"
2-3. コンフリクトの扱い
feature -> develop でコンフリクトが発生した場合、
弊社では feature 実装者がコンフリクト解消を行います。
対応方法は、PRの状態によって以下のように使い分けています。
- PRレビュー依頼前:rebase 対応も可
- PRレビュー依頼後:逆マージ(develop -> feature)のみ
前回記事でも触れていますが、このように使い分けの幅を持たせているのは、
「実装者の開発体験を維持しつつ、レビュアーに不要な負荷をかけない」ためです。
2-4. develop に入る基準
原則として、develop に入るのは「その feature が担う要件の範囲が完成された状態」です。
「production 直前のPRで気づくだろう」という考え方は取りません。
CGF の思想として、develop -> production のPRで確認するのは
「feature ブランチで行った対応が、期待通り反映されているか」であり、
原則として「大きなレビューバックが発生する設計ではない」
そのため、
- 実装者は「このfeatureは完成している」と言い切れる状態で出す
- 追加対応、切り分けたい対応が出てきた場合は、新たにチケットを作成して別 feature として対応する
- レビュアーも同じ前提で確認する
という共通認識を持ちます。
■ feature 段階での検証
CGF では、feature の段階でも検証環境を利用できます。
そのため、develop での網羅的な確認が必要な場合を除き、
「feature の責任範囲」として検証まで行うようにしています。
2-5. 弊社で行なっている補助的な取り組み
■ チケットに「完了条件」を明記
以下を共通認識として明確にするため、各チケットには必ず完了条件を記載しています。
- 何を満たせば完了なのか
- どこまでが責任範囲なのか
■ PRテンプレートの運用
PRにはブランチ種別に応じたフォーマットを用意し、
各項目を埋めた状態でレビュー依頼することを原則としています。
以下はその例です。
- feature PR(feature -> develop / feature -> hotfix)
- タスク:該当チケットへのリンク
- (不具合事象:事象・再現手順(不具合対応時のみ記載))
- やったこと:実装・修正内容
- 動作確認:確認方法やスクリーンショットなど
- その他:レビュー観点や注意事項(★:注意事項については「特になし」でも可)
- develop PR(develop -> production)
- タスク:該当チケットへのリンク
- 機能概要:機能の目的
- 統合済み機能:マージ済みPRの一覧
- その他:注意事項(★)
- hotfix PR(hotfix -> production)
- タスク:該当チケットへのリンク
- 不具合概要:不具合事象の大まかな内容
- 統合済み機能:マージ済みPRの一覧
- その他:注意事項(★)
これらは「ルールを増やすため」ではなく、
メンバー間の認識齟齬を減らすための補助線として運用しています。
チームによっては、このような対応内容・補足事項は
「PR テンプレートではなく、チケット側に集約して管理した方がわかりやすい」
というニーズもあるかもしれませんね。
3. 検証フェーズの考え方
CGF が位置付けるブランチごとの役割から、
弊社では検証の目的をブランチ種別によって以下の二層で分けています。
- feature 単位の検証
- develop 単位の検証
また、それぞれの責任範囲が異なるため、確認観点についても明確に区別しています。
3-1. feature 単位の検証
feature の段階で行う検証は、「その feature が担う責任範囲を果たしていること」の確認です。
feature 単位での検証の目的は「その feature の責任範囲を閉じること」です。
そのため、ここでは以下の内容を確認します。
- 仕様通り動くこと
- その feature 対応で差分の出たファイルとその周辺の整合性
これは、一般的なテスト区分で言えば「機能単位での確認」に近い位置付けです。
また、先にも述べた通り、feature の実装は
「この feature が担うべき要件は完成している」と言い切れる状態がゴールです。
そのため、feature の検証では、以下の内容を前提としています。
■ 検証は「最新 develop を取り込んだ状態」で行う
feature の検証は、常に最新の develop を取り込んだ状態で実施します。
ここで重要なのは、
「目標とする "歴史" を再現すること」です。
古い develop を基準として検証した後に差分を取り込むと、
「検証済みの内容」と「実際にマージされる状態」がズレる可能性があるため、
必ず最新化した状態で確認します。
3-2. develop 単位の検証
develop の段階で行うのは、機能単体の再検証ではありません。
各 feature が責任範囲内で完結していることを前提とし、「全体として破綻していないこと」の確認を行います。
develop の検証の目的は、
- 機能同士の整合性の確認
- 統合時の副作用がないことの確認
- 横断的な挙動・影響の確認
であり、これは一般的なテスト区分で言えば「システム観点」に近い位置付けになります。
ただし、CGF の思想に照らした場合、
テスト工程というよりは、「責任単位の分離」として捉えた方がわかりやすいかもしれません。
feature での検証
- 本質:「その feature が担う責任範囲を果たしていること」の確認
- テスト区分: ≒ 機能テスト
develop での検証
- 本質:「全体として破綻していないこと」の確認
- テスト区分: ≒ システムテスト、リグレッションテスト
このように、検証観点についてもブランチ種別で責任範囲を区別することで、
- feature で完結させる
- develop で統合観点を確認する
という役割分担が成り立ちます。
3-3. 検証環境における再現性
CGF では、ブランチの履歴に依存せず検証環境を利用できる構造を取っています。
そのため、どのブランチにいても同じ前提で検証を行うことが可能です。
そしてそれは、「検証環境は再現性が重要である」ということでもあります。
とはいえ、保証するべきは「feature/develop の責任範囲を、誰でも同じ状態で再現できること」なので、
検証環境におけるDBレコードの再現性を担保する手段は問いません。
弊社では、再現性を担保する手段として seeder を選択肢に挙げることが多いです。
その理由としては、
- 自動実行が可能である
- バージョン管理ができる
- migrationと整合する
- CIに組み込める
ことから、「再現性を低コストで維持できる」と考えているためです。
また、大量データが必要なケースでは、
別リポジトリでテストデータを管理し、必要に応じて投入する運用も検討します。
4. リリース前後の PR
develop での検証が完了したら、対応内容を production に反映していきます。
CGF には本番環境へのデプロイ方法自体に制約はないため、ここでは
- develop PR(develop -> production)
- hotfix PR(hotfix -> production)
に焦点を当て、弊社の運用フローを紹介したいと思います。
4-1. develop PR
■ PR 作成者は固定化しない
develop PR の作成を行うにあたり、特定の担当者は決めていません。
- 「リリース担当」を固定しない
- 状況に応じて、コミュニケーションで決める
という形で運用しています。
これは、「チームで扱う前提とすることで、属人化を避ける」ことを目的としています。
■ レビュアーは固定化せず、人数を増やす
レビュアーについても、特定の役割とはしていません。
ただし、feature PR 時よりもレビュアーの人数を増やしています。
弊社では、基本的に feature PR のレビュアーは1名としているため、
develop PR は2名以上、と位置付けています。
これは、リリースを「個人の判断」にしないための運用方針です。
4-2. hotfix PR
CGF では、本番環境で見つかった不具合の緊急対応についても、
hotfix をプレフィックスとする親タスクとして扱うため、構成としては develop と同じになります。
これは、前回記事で紹介した
- 履歴上でわかりやすくするため
- コミュニケーション時の認識齟齬を防ぐため
という理由のほか、
「ロールバック時に develop と同じ扱いの方がわかりやすい」という利点もあると考えています。
「hotfix ブランチに直接修正対応をコミットしていく方式よりスピード感が落ちるのでは?」
という懸念もあるかと思いますが、弊社では以下のように運用を工夫しています。
■ feature PR と hotfix PR は同じレビュアーを指名する
develop PRでは、feature PR 時よりもレビュアー人数を増やす運用としていますが、
hotfix PR のレビュアーは1名、feature PR のレビューを行った人を指名する、としています。
理由としては単純で、「hotfix 対応はスピード感も重要だから」です。
ひとつの hotfix に紐づく feature が複数存在する場合は、レビューを行った人の中から1名を指名します。
指名するのは、hotfix PR 作成者です。
あくまでも体感ですが、この運用と「hotfix ブランチに直接コミットしていく対応」で
対応のスピードにそこまで差があると感じたことはありません。
リリース前後であっても対応フロー自体は変えず、運用ルールでフォローすることで
- 追跡性の維持
- イレギュラー対応の回避
いった観点を守ることができ、
結果として「ブランチを含む運用全体の安定性」につながります。
5. この運用を成立させるための情報共有設計
CGF では、検証環境は特定のブランチや個人に紐づくものではなく、
「チームで共有して利用すること」を前提としています。
しかし、共有を前提とするのは検証環境だけではありません。
develop もまた、チーム全体で共有して扱うことが前提のブランチです。
こうした運用を安定して成立させるためには、
「誰かが気をつける」ではなく、情報共有を「仕組み」として設計する必要があります。
属人運用には再現性がなく、「暗黙知」は人数が増えるたびに破綻していく原因になります。
5-1. なぜ情報共有を「仕組み化」するのか
「チームで共有するもの」に対して行う操作は、個々の判断が他メンバーの作業に影響します。
たとえば、
- 検証環境の利用
- develop ブランチの rebase 操作
- production への反映
など。いずれも、対応者以外にも影響する操作です。
このとき「声をかけるようにしよう」だけでは、運用として安定しないことが多いです。
属人的になり、タイミングによっては衝突が発生したりします。
そのため、弊社では
情報共有を「個人の行動」ではなく、「運用上のフロー」として仕組み化するよう設計しています。
「気づいた人が共有する」のではなく、「共有することが前提」の状態を作る、という考え方です。
5-2. チケット駆動とリアルタイム情報の役割分担
弊社では、共有するべき情報はその性質から2種類に分類できると考えています。
■ チケットベースの情報(非同期・恒久的)
- 何を誰が対応をするのか
- いつまでに対応するのか
- その対応の状態(ステータス)
- 調査内容やその対応に関連するやりとり
これらは「履歴として残したい情報」です。
後から経緯や状況が追跡できることが重要になります。
そのため、こうした情報はチケット管理ツールに情報を集約し、
チケット内で必要な情報共有が完結できるようにしています。
■ リアルタイムな情報(同期・一時的)
- 今、検証環境を使っている人はいるのか
- develop を rebase したい
- これからリリース作業を開始する
これらは「その瞬間の衝突」を防ぐための情報です。
履歴よりも「タイミング」が重要になります。
そのため、これらの情報はチャットツールで共有するようにしています。
これにより、確認する側は通知に対して
「チケット管理ツール < チャットツール」という優先度がつけやすくなります。
通知確認の優先度が明確に設計されていれば、
- チャットの通知はすぐに確認
- チケットの通知は自分のタイミングで確認
という判断が自動的に行えます。
こうすることで、すべての通知に対して「まずは確認して、即時の対応要否を判断する」必要はなくなります。
メンバーはそれぞれタスクを抱えているため、
設計によって、「人間の不要なコンテキストスイッチを減らしていく」ことも観点のひとつと考えています。
両者は代替関係にはありません。
役割が異なるため、明確に分ける設計としています。
5-3. 運用例:検証環境利用時の共有方法
検証環境は共有が前提である以上、利用状況の可視化は必須となります。
弊社では、チャットツールに専用のチャンネルを用意し、
- 利用開始
- 利用終了
- 占有が必要な場合の宣言
を全メンバーに向けて投稿する運用としています。
これにより、意図しない上書きや「使っていると思わなかった」などの衝突を防ぎます。
重要なのは「共有がデフォルト」という状態を、仕組みとして持つことです。
ただし、検証環境を複数用意していれば、そもそも衝突することがないため
こうした懸念点を根本から解消することができます。
検証環境での競合が増えてきた際は、複数用意することを検討しても良いかもしれませんね。
5-4. 運用例:develop での rebase 実施時の共有方法
develop での rebase もまた、リアルタイムで共有が必要な操作のひとつと考えています。
develop はチームで共有しているブランチであり、
各メンバーの作業ブランチの起点にもなります。
そのため、rebase のように履歴を書き換える操作は、
範囲が個人に留まらない、影響の広い対応と位置付けています。
弊社では、チャットツールのプロジェクト用のメインチャンネルに
- develop を rebase する前に、実装に関わったメンバー全員にメンションで実施可否を確認
- メンションされた全メンバーからの承認を得た後、rebase 操作を実施
というフローとしています。
承認できないメンバーは、その理由と承認条件を rebase 実施者に共有します。
rebase 操作についても担当者を設定しているわけでないので、対応そのものを引き取ることもあります。
ただし、その場合も実施前には改めて実施可否の確認が必要です。
共有ブランチに対する履歴操作は「共有操作」であり、個人の判断で変更しないよう、仕組みでフォローしています。
5-5. この仕組みのメリット・デメリット
■ メリット
メリットについては、この章でこれまで述べた通りで
- 衝突の予防
- 追跡性の維持
- コンテキストスイッチの削減
- 運用全体の安定性向上
が挙げられます。
詳細については前述しているため、ここでは割愛させていただきます。
■ デメリット
前回記事でも取り上げましたが、リアルタイムな情報として確認が必要な場合、
即時にコミュニケーションが取れる相手ばかりではないチーム構成では、
「確認待ち」がそのまま開発の停滞につながる可能性があります。
この点については、
- 回答期限としてどのくらい待つのか
- 反応がない場合の対応
といった基準をあらかじめ決めておくことで、ある程度解消できると考えています。
弊社では、確認の時点で
「〇日△時までにご回答いただけない場合は、xxxとして対応します。」
といったような形で期限と対応方針を共有することもあります。
チームメンバーの状況として、レスポンスにラグが生じることが事前にわかっている場合には
運用ルールとして決めておいた方が、都度基準を設定する必要がなくなるため、よりスムーズかもしれませんね。
共有設計の目的は、「迷いなく判断するために前提を揃えること」です。
「この人は言わなくてもやってくれるのに」というような状況は、仕組みで解消するべきだと考えています。
6. タスク粒度と規模に関する補足
これまで、公開単位と構造についても話をしてきましたが、ここでひとつ補足です。
CGF は構造を定義しますが、公開単位の「大きさ」は可変です。
develop を
- 要件単位
- リリース単位
- フェーズ単位
どれで切っても思想から逸脱しません。
守るべきは「公開単位が明確であること」です。
6-1. 規模 = 量 ではない
CGF で捉える要件の規模は、単純な作業量の話だけではありません。
たとえば、
- コンフリクトの頻発
- rebase 操作の頻発
- develop の滞留
なども含めて「規模」と考えます。
このような状態が続く場合、要件の重さそのものよりも、
現在の運用条件とタスク単位・粒度の相性がズレている可能性があります。
一度、develop の粒度を見直してみましょう。
規模が大きいのであれば、粒度を再設計する必要があります。
チームの性質や人数、並行開発数や開発のスピード感、デプロイ頻度など
さまざまな要素によって、最適なタスク粒度は変わります。
6-2. 大きな要件を扱う場合
「そうは言っても、要件自体が大きい場合 develop を大きくせざるを得ないのでは?」
などといった疑問もあるかと思います。
要件そのものが大きい、規模が大きいと感じた場合、
- フェーズで分割する
- ユーザーに見えない形で、段階的に本番に乗せる
- フィーチャーフラグを活用する
といった方法で、公開単位を分割することもできます。
CGF は、常に最小粒度で設定することを強制する戦略ではありません。
「運用に無理が出ていないか」を観測しながら、粒度を調整していく形をとります。
タスクの粒度は固定ではなく、
構造を守りながら、チームに合った単位を選ぶことができます。
おわりに
いかがでしたでしょうか。
今回は、実際の運用フローをベースに、より具体的な観点で CGF の思想を整理してみました。
お気づきかもしれませんが、CGF では「最初のタスク分割」が重要になってきます。
もちろん、タスク分割に失敗したからといって、運用が回らなくなるわけではありません。
チームとして動きやすくするための要素として、「タスク分割が鍵となる」といったところでしょうか。
ただし、最初から理想の単位で切ることは難しいと思うので、
実際に回してみて、チームに合った粒度に調整していくのが良いかなと考えています。
個人的には、feature のテスト観点に悩んだら、チケットの切り方を考え直す目安としています。
次回は、フェールセーフとしての CGF 運用の自動化について、取り上げていきたいと思います。
以上です。最後まで閲覧いただきありがとうございます。