ブランチ戦略について考える

自分のブログに載せてたけど、Qiitaでも反応が欲しかったので転記します。

---

# ブランチ戦略
前職では、ソース管理はSVNを使っておりました。  
現職に転職してからは、ソース管理にgitlabを使用しています。  
  
エンジニアの皆さんなら、githubを使用していて、gitの操作に慣れているものと思います。  
  
ただ、ブランチをどう切っていくか、という点については個人開発、チーム開発で大きく違うのではないでしょうか。  
  
  
現在のプロジェクトで、ブランチの切り方による事故がだいぶ発生してしまったので、自分の中で整理するために記事にします。  

## ブランチ戦略の肝
ブランチ戦略を選ぶにあたって、考慮すべき点は以下の点です。

- 変更管理
- コンフリクトの解消コスト

## ブランチ戦略の種類
三種類存在すると思ってます。  

- ブランチを切らない
- 開発ブランチを切る
- 開発ブランチを細かく切る
  
---
  
リリースブランチはデプロイ先の環境が複数ある場合は、必要だと思うので今回は記載しません。  
現在のプロジェクトでは、IT環境用の itブランチ、ST環境用の stブランチが存在します。  
もし、存在しない場合は、master = 本番環境と認識しています。  
  
## ブランチを切らない
### メリット
- シンプルで分かりやすい
- CI/CDとの相性が非常にいい
- リファクタリングも容易にできる
- 総合的なコンフリクトのコストが非常に低い
- リリーススピードが早い

### デメリット
- コミット = ほぼすぐ本番にリリースなので、何があっても事前に把握できない。  
- 変更管理をしっかりできないと原因究明がしづらい。  
    - gitログは残っているので、原因を追うのは可能
- 環境構築の手間が非常に高い
- コンフリクトは多発するので、開発が止まりやすい
    - 必要経費ではあります

### 説明
ブランチを一本も切らない、master一本で開発するやり方です。  
trunk base development  
と呼ばれています。  
  
個人開発だと、master一本で開発しているのが多いのではないでしょうか。  
良く知りませんが。
  
企業では、Googleがブランチ一本で開発しているらしいです。  
(gitを使っているというわけではないです)  
外資系のキラキラした話では、この話をよく聞く印象です。  
  
  
単純な真似も可能ではありますが、コミットがいきなり本番環境で動作することになるので、いかに本番環境にリリースされる前に不具合を検知できる仕組みを事前に作る必要になります。  
なので、CI/CDの相性は最高にいいですね。  
構築難易度だけが問題です。  
  
  
また、中途半端な状態でリリースされてしまうのを防ぐため、feature toggleを使って機能制限を明確にする仕組みも必要です。  
LINEさんの資料は非常に勉強になりました。  
[巨大なアプリ開発を支えるフラグ管理術 / Flag-based feature management](https://speakerdeck.com/line_developers/flag-based-feature-management)
  
  
コンフリクトも非常に起きやすいのですが、コンフリクトが起きるというのは必要経費であり、早期に互いの認識を高めるのを助けます。
  
  
また、基本的にロールバックはできないので、障害発生時は手動で問題のある箇所を取り除く、といった作業が必要になります。  

  
あと、この運用をするならソースを触るときはソロ禁止にしないと危険すぎますね。  
ペアやモブで常にお互いにチェックしないと、非常によろしくないものがコミットされてしまいます。

## 開発ブランチを切る
### メリット
- ブランチを切らないものと同じ
- 開発ブランチ = 検証環境 なのでワンクッション置ける。
- 変更管理がしやすい

### デメリット
- ブランチを切らないものと同じ
- ブランチを切らないものよりも時間がかかる
    - 仕組みはボタン一つでも、リリース判定会議等々の事務手続きは発生しがち

### 説明
開発ブランチ と masterブランチの差分がそのまま変更管理できますし、安全に検証できる環境を用意しての開発なので、安心感はあります。  
  
個人的には、一番推奨したいブランチ戦略です。  
  
その他の内容は、ブランチを切らない戦略と同じなので省略します。

## 開発ブランチを細かく切る
### メリット
- 開発中は安全に開発できる
    - チームが分かれていても安全に開発できる。  
    人事に関する機能はA部署、料金に関する機能はB部署…みたいな分け方もできる。

### デメリット
- 総合的なコンフリクトのコストが非常に高い
- リファクタリングがしづらい
- CIの考え方とは正反対の考え方


### 説明
git-flow, GitHub flow  
  
等々で一番なじみのあるブランチ戦略じゃないでしょうか。  
自分も、gitを習って最初のころは、ブランチを切りやすいのがgitのメリットだと思ったので、これで運用していました。  
  
ただ、ブランチを切る粒度が人によってさまざまになってしまって、ちゃんとチーム内で粒度を合意しないとブランチが乱立してしまいます。  
  
  
また、開発ブランチを細かく切ることのデメリットは、マージするときのコストが非常に大きくなってしまうことです。  
数行程度でも、ロジックの順番が違うことで大問題を起こしたりするのに、数百行の変更が入ったブランチをマージ掛けてコンフリクトを起こさないということは、ほぼありえません。  
  
コンフリクトを正しく修正できるならまだいいんですが…。  
大抵、誤ったマージでまた問題が発生して、それで工数を大きく取られるなんていうこともザラです。  
  
同様に、リファクタリング時もコンフリクトは大量発生します。  
  
  
CIと正反対の考え方、といったのは、CIは常時結合させて不具合を早く見つけるものなので、特定のブランチにはこの機能がある、という状態が既に常時結合していない、という意味です。  
CIサーバがあるなら、単純なコンフリクトはすぐにわかるので、猶更ブランチは分けないほうが今風の開発だと思います。

---

なので、自分たちのチームではこれを採用していません！  
…  
とはならなかったのが現状。  
  
現在のプロジェクトでは4チームが同時開発を行っています。  
うちのチーム**だけ**、ブランチ一本戦略と開発ブランチを細かく切るブランチの妥協案として、毎日ブランチを切って、毎日masterにマージするという運用をしようと思っていました。  
(feature/Ateam-20200209みたいなのを毎日切って、毎日masterにマージ)
  
そうなると、現在の運用を行いつつ、master一本戦略のメリットを享受して、影響が少なくて済むと思ってました。  
ただ、他チームがmasterにコンフリクトするコミットをマージしたタイミングで、Aチームのだれかがコンフリクト解消を行う必要があります。
  
  
自分はそのコンフリクト解消も必要経費だと認識していたのですが、自分のコミット以外で発生したコンフリクトを解消したくないとチームに猛反発を受けて、取りやめました。  
  
  
  
まぁ、1チームだけ別のブランチ戦略を行う、っていうのは確かにズレているので、次のプロジェクトでは、なんとしてもブランチ一本戦略に寄せた戦略は立てたいと思っています。

## その他
### 自動化と自働化
- 自動化(automation)
    - たとえ壊れても動き続ける。  
    CIサーバでテストが落ちても本番環境にリリースする…とか。
- 自働化(autonovation)
    - 壊れたら動かなくなる。人の手を介在させるが無駄が出ない。
  
自動化から自働化にするためには、失敗したら通知させる仕組みが必要。
トヨタは自働化を徹底しているそう。

### 認識しているブランチの意味合い
- master
    - 本番。
- feature branch
    - 実験ブランチにするときはこっち。  
    masterにマージしないときに。  
    処理が良く分からなくなって、相談したい時はこのブランチ切ってます。  
- pull request
    - 外部の人間、全く知らない人のソースをいじるときに対して切るもの。  
    OSS等はこうせざるを得ない。
- fork
    - 完全に元ソースとは別にするときに。  
    整合性はまったく取らないときに。

## まとめ
各自のチームに応じたブランチ戦略を立てようね！

### 参考資料
[DevOps 技術: トランクベース開発](https://cloud.google.com/solutions/devops/devops-tech-trunk-based-development?hl=ja)  
[巨大なアプリ開発を支えるフラグ管理術 / Flag-based feature management](https://speakerdeck.com/line_developers/flag-based-feature-management)