継続的デリバリー読書会 14章
TRANSCRIPT
継続的デリバリー読書会 第14章 高度なバージョン管理
大崎的デリバリー @hidesuke
14.1 導入 • バージョン管理システムの目的 – アプリケーションに対して行われたすべての変更の完全な履歴を管理すること
– ソースコード/ドキュメント/ビルドスクリプト/テストも含める
• 本章の目的はバージョン管理システムを活用してチームの生産性をあげること
14.2 リビジョン管理システムの簡単な歴史
• SCCS – あらゆるバージョン管理システムのご先祖 – 1972年ベル研究所で生まれる – マーク・J・ロックカインドが作った
• その派生がいろいろいっぱいある
14.2.1 CVS • Concurrent Version System • 複数の開発者が同時に同じリポジトリ上で作業できる(という意味)
• クライアントサーバ型アーキテクチャ • 協力なブランチ、タグ機能 • 長い間よくつかわれた(他に自由に使えるものがなかったから)
• ファイル単位での変更追跡 • ただ、いろいろ問題もある(P.449参照)
14.2.2 Subversion • よりよいCVSを目指してつくられた • CVSの問題を修正。CVSユーザにとって使いやすいように作られた
• リビジョン単位でのバージョン管理 • リビジョン番号が各リポジトリ全体に適用される – 各ファイル単位ではない
• やっぱり問題点もいろいろある(P.451参照)
14.2.3 商用のバージョン管理システム
• Perfoce – パフォーマンス、スケーラビリティに優れる
• AccuRev • BitKeeper – 真の分散型バージョン管理システムとして最初に登場したもの
• TeamFoundationServer – VisualStudioつかっているなら使ってもいいんじゃない?
14.2.4 悲観的ロックをやめろ • 楽観的ロックに対応してるならそれを使えばよい – つまり、ローカルの作業コピーのファイルを編集しているときに、他のメンバーも各自の作業コピー上で同じファイルを編集できる
• 悲観的ロックの方がマージの際の衝突を防ぎやすいが、開発プロセスの効率を下げる – 悲観的ロック:排他ロックを取得しないと、そのファイルを編集できない
14.3 ブランチとマージ • ブランチの主な目的 – 複数の作業ストリームを同時に稼働させる – 互いが他のラインに影響を及ぼさないようにする
• 開発はメインライン、バグフィックスはリリースブランチで行うという方法がよくとられる
14.3 ブランチとマージ • (先ほどの例以外で)チームがコードのブランチを作る理由がいくつかある • 物理的な理由
– システムの物理的な構成によるブランチ – サブシステム単位でブランチを切っている
• 機能的な理由 – システムの機能的な構成によるブランチ – 論理的な変更、バグフィックス、機能拡張、デリバリー可能な機能単位
• 環境的な理由 – システムの運用環境によるブランチ – ビルド時や実行時のプラットフォームの違いなど
• 組織的な理由 – チームの開発活動によるブランチ – タスク、サブプロジェクト、グループ単位
• 手続き上の理由 – チームの作業の振る舞いによるブランチ – 運用ポリシーやプロセス、状態単位
14.3 ブランチとマージ • 一連の変更はひとつのブランチで行いそれを別のブランチにコピーする
• マージ
14.3 ブランチとマージ • ブランチはあとでマージしなければならない
• 各ブランチがデリバリープロセスにおいてどのような役割を果たすかポリシーを定義する必要がある
14.3.1 マージ • 各ブランチは完全に独立している、他のブランチの存在を全く無視して存在している
• しかし、ある時点でブランチへの変更内容を別のブランチに適用する時がくる – => とても時間のかかる作業
14.3.1 マージ • 真の問題が発生するケース • マージしたい二つのブランチでそれぞれ別の変更を行い、それらが衝突している場合 – 機能の実装方法が食い違っているような変更の場合、かなりの量のコードを書きなおさないといけない。
– これをマージする場合はコードを書いた人の意図をしらないと不可能
14.3.1 マージ • 意味的な衝突はバージョン管理システムでは検知できない
• 包括的な自動テストがなければ、実際に障害がおこるまで衝突があったことに気づかない
• マージするまでの期間が長くなればなるほど、作業する人がおおくなればなるほどマージに悩まされる
14.3.1 マージ • マージで悩まないためには……
• ブランチの数を増やし、ひとつのブランチで行われる変更の数を減らす
• ブランチの作成をなるべく控え、リリース毎に1つずつ程度にする
14.3.2 ブランチ、ストリーム、そして継続的インテグレーション • ブランチの利用と継続的インテグレーションは対立関係にある – それぞれ別のブランチで作業を行なっているということは継続的インテグレーションをおこなっていないということ
14.3.2 ブランチ、ストリーム、そして継続的インテグレーション • 継続的インテグレーションを行うプラクティス – 全員が少なくとも1日1度はメインラインにチェックインする
– メインラインへのマージを1日1回はやっているのならOK
14.3.2 ブランチ、ストリーム、そして継続的インテグレーション • より管理しやすいブランチ方針 – 寿命の長いブランチをリリース時にだけ作る(p.459 図14-2)
– 新しい作業は常にtrunkにコミット – マージされるのはリリースブランチに修正がはいったときだけ
– コードが常にリリース可能な状態
14.4 分散型バージョン管理システム
• Distributed Version Control System • DVCS
14.4.1 分散型バージョン管理システムとは?
• 各自が自己完結した一人前リポジトリを自分のコンピュータ上に持つ
• 変更はローカルリポジトリにコミット • 他のユーザの変更を直接取り込める • 更新を特定のユーザたちにだけ送ることが可能 • どのパッチを採用して、どのパッチを却下するのかを簡単にきめられる チェリーピック
• オフラインでもコミットできる • ローカルでのコミットを、手軽に変更したり、分岐したり、まとめたりといった作業をしてから他のリポジトリに送ることができる リベース
• 何かのアイディアをローカルリポジトリで試したいときに中央リポジトリにブランチを作らなくてよい
• 高可用性 • リポジトリのコピーが多数作られているので耐障害性が上がる
14.4.1 分散型バージョン管理システムとは?
• いわゆるメインラインはどこにも存在しない(P.461 図14-3)
• GitHub, BitBucket, GoogleCode – 既存リポジトリのコピーを作るの簡単 – 手元で変更した内容を他のユーザに公開してみてもらうのも簡単
– 本家のメンテナーもこれらの変更を確認でき、気に入ったものは自分のマスターリポジトリに取り込める
• 協調作業におけるパラダイムシフト • 新機能やバグフィックスのデリバリーが高速化
14.4.2 分散型バージョン管理システムの簡単な歴史
• Linuxの一部のセクションのメンテナーがBitKeeperを使い始めたのが普及のはじまり
• 現在は – Git – Mercurial – Bazaar – Darcs – Monotone – など
14.4.3 企業環境における分散型バージョン管理システム
• 本書執筆時点では営利企業でのDVCSの導入はあまり進んでいない
14.4.4 分散型バージョン管理システムを使う
SVN • svn up (最新のリビジョンの取得)
• 何らかのコードを書く • svn upで自分の変更と中央リポジトリの更新をマージ
• コミットビルドをローカルで実行
• svn ciで自分の変更をバージョン管理にチェックイン
hg (Mercurial) P.465 図14-4 • hg pull でリモートリポジトリの最新の更新をローカルリポジトリに取得
• hg co でローカルの作業コピーの内容をローカルリポジトリから更新
• 何らかのコードを書く • hg ci で変更をローカルリポジトリに保存
• hg pull リモートリポジトリの更新を取り込み
• hg merge ローカル作業コピーの内容をマージ結果で更新
• コミットビルドをローカルで実行 • hg ci マージ結果をローカルリポジトリにチェックイン
• hg push 更新をリモートリポジトリにプシュ
14.5 ストリームベースのバージョン管理システム
• IBMのClearCaseなど
14.5.1 ストリームベースのバージョン管理システムとは?
• 一連の変更を複数のブランチへ同時に適用出来る – マージの際の問題を解決するため
• ブランチが協力な概念であるストリーム置き換えられる
• ストリームが継承できる – 親ストリームに変更を適用すると、子孫ストリームにも変更が同時に適用される
14.5.1 ストリームベースのバージョン管理システムとは?
• バグフィックスをアプリケーションの複数のバージョンに適用する場合 – ストリームベースのツールじゃない場合は手動でマージ
– ストリームベースでは祖先ブランチに変更を適用するだけでよい
– 各ブランチの利用者は更新するだけで変更をうけいれ、修正を含む新しいビルドを作れる
14.5.1 ストリームベースのバージョン管理システムとは?
• サードパーティのライブラリをコードベースに追加する場合 – 共通の祖先に新しいバージョンをチェックインすればよい
14.5.1 ストリームベースのバージョン管理システムとは?
• あるストリームへの変更は、その変更を昇格させるまで他のストリームに何の影響も及ぼさない
• 昇格させるとそのストリームを継承するすべてのストリームから変更が視えるようになる
14.5.2 ストリームを使った開発モデル
• 特定の機能を実装するためのストリームを作る
• 機能の実装が終えたら、そのストリーム内でのすべての変更をチーム全体のストリームに昇格させる
• 継続的インテグレーションに投入する
• 中規模~大規模のチームでも複数の機能を同時に開発しつつお互いに影響を及ぼさないようにできる
14.5.2 ストリームを使った開発モデル
• 別々のチームが共有コードを異なる方法で変更した際に複雑なマージが発生
• ある新機能が依存する別の機能のコードがまだ昇格していない場合に、依存関係の管理に問題が発生する
• リリースストリーム上でのインテグレーションテストやリグレッションテストが新しい設定ではうまく動かないという場合に統合時の問題が発生する
14.5.2 ストリームを使った開発モデル
• チーム数やレイヤの数が増えるほど問題は悪化
• 影響はどんどん増殖 • チームレベル、ドメインレベル、アーキテクチャレベル、システムレベル、本番レベルの5つのストリームを作ることで解決
• ……しかし、ストリームを昇格させるたびに先に挙げたような問題が発生した
14.5.2 ストリームを使った開発モデル
• 上位ストリームへの昇格は可能な限り頻繁に行う
• 可能な限りの頻度で自動テストを実行する
14.5.3 静的ビューおよび動的ビュー
• ClearCaseの「動的ビュー」機能 – ファイルがストリームにマージされたときにそのストリームを継承するすべてのストリームを使う開発者のビューを更新する機能
– 各開発者が変更をこまめにチェックインしていれば、マージが楽になる
– 超低速 • 「静的ビュー」 – 開発者が自分でビューを更新しない限り変更は反映されない
14.5.4 ストリームベースのバージョン管理システムによる継続的
インテグレーション • 利点 – 開発者が自分だけのストリームで作業するのが容易
– マージも容易 • 変更が定期的に昇格されていれば継続的インテグレーションが可能 – これをやると、前述した利点もそれなりの制約を受けることになる
14.5.4 ストリームベースのバージョン管理システムによる継続的
インテグレーション • ClearCaseでは…… – 完全にサーバーベースのモデル – マージ、タグ付け、ファイルの削除まですべてがサーバ側で処理
– 親ストリームに変更を昇格させようとすると、兄弟ストリーム上で発生する衝突をこみったが解決しないといけない
– どんなサイズのリポジトリでも操作(チェックイン、ファイル削除、タグ付けなど)に恐ろしく時間がかかる
– 頻繁にチェックインしようとすると大きなコストになる
14.5.4 ストリームベースのバージョン管理システムによる継続的
インテグレーション • 変更セットの昇格機能について、継続的インテグレーションを組み合わせるときは問題がある
• バグフィックスをストリームの祖先に昇格すると、その子孫のストリームすべてに対してビルドを起動しなくてはならない – 無理
• 以下ではでは様々なパターンのブランチやマージについて、それぞれの利点・欠点とどんな場面で使えばいいかについて述べる
14.6 メインライン上での開発 • 開発者は常にメインラインにチェックインする • ブランを切ることはめったにない
• すべてのコードが継続的インテグレーションの対象
• 各開発者が他のメンバーによる変更をすぐに受け取れる
• プロジェクトの終盤になって「マージ地獄」や「インテグレーション地獄」に陥らずに済む
14.6 メインライン上での開発 • 通常の開発では開発者がメインライン上で作業を進める
• 少なくとも一日に一度はコードコミットする • 基本的にブランチは作らない • ひとつひとつの変更が充分に小さく、インクリメンタルなものになるように計画・実装し、各段階でテストを通し、既存の機能を壊さないことを確認しながら進めていく
14.6 メインライン上での開発 • ブランチを否定しているわけではない • すべての開発作業は、いつかひとつのコードラインに落ち着く
• ブランチを作るのは、メインラインへのマージが不要な場合のみ(リリースや、変更前のスパイク)
14.6 メインライン上での開発 • メインラインで開発を行うということは、メインラインが常にリリース可能な状態であるとは限らない
• 変更に対するフィードバックを素早く得られるという利点
• 完全に結合されたアプリケーションにどのように影響を及ぼすかすぐにわかるという利点
14.6.1 複雑な変更をブランチなしでおこなう
• 複雑な変更をブランチを切って行う場合 – メインブランチと大きくかけ離れる – マージで死ぬ – メインラインが安定するまでマージできない – リリースが大幅に遅れる – リファクタリングが困難になる
14.6.1 複雑な変更をブランチなしでおこなう
• コミットは常にtrunkに • 少なくとも一日一度はコミット • メインラインへのインクリメンタルな変更をこまめに進めていくのが重要
14.7 リリース用のブランチ • リリース直前は常にブランチを作ってもかまわないだろう
• リリースブランチをつくればコードフリーズを止めることができる – コードフリーズ:バージョン管理システムへのチェックインを数日から数週間にかかえて止めてしまうこと
• リリースブランチをきれば、開発者はメインラインで開発を進められる
14.7 リリース用のブランチ • 新規機能の開発は常にメインラインで行う • ブランチを作るのは、特定のリリース用に必要な機能が完全に揃った段階で、さらに新しい機能の開発を始めたくなったときだけ
• 致命的な欠陥の修正だけをブランチに適用し、それはメインラインにもただちに反映
• リリースが完了したら、そのブランチにタグを打つ
14.7 リリース用のブランチ • リリースブランチからさらに別のブランチを切ってはいけない
• リリース用のブランチはすべてメインラインからつくる
14.8 フィーチャによるブランチ • 大規模なチームが並行して機能を開発しながらメインラインもリリース可能な状態に保つ
• すべてのストーリー/フィーチャーは個別のブランチを用意して、そこで開発を進める
• trunkを常にリリース可能な状態にしておきたい場合に使う
14.8 フィーチャによるブランチ • メインライン上での変更は常にブランチにマージ • 一つのブランチの活動期間を短めに • ある時点でのアクティブなブランチ数がその時点で作業中のストーリー数を決して超えない
• 一つ前のストーリーのブランチがメインラインにマージされるまでは新しいブランチは作らない
• テスターによるストーリーの受け入れテストをしてからマージする • リファクタリングはすぐにマージして、マージの際の衝突を最小化する
• リーダーの役割の一つはtrunkを常にリリース可能な状態に保つことにあんる
• リーダーはすべてのマージをレビューしなければならない • リーダーはtrunkを壊す可能性のあるパッチを却下する権限を持つ
14.8 フィーチャによるブランチ • 長期にわたって使われつづけるブランチを作りすぎるのはよくない
• マージで死ぬ • ブランチを切るという行為は本質的に継続的インテグレーションに反する
• 真の継続的インテグレーションに近いのはCIシステムが全部ランチを一つの仮想trunkにマージすること – でもこれはたぶん失敗する
14.8 フィーチャによるブランチ • 分散型バージョン管理システムはこの手のパターンを想定して作られている
• メインラインからのマージ、最先端にに対するパッチの作成が簡単にできる
• オープンソースの世界ではうまくいく • 商用製品のプロジェクトでも、コア開発チームが少数精鋭であればうまくいくだろう
14.8 フィーチャによるブランチ • 大規模なプロジェクトでこのパターンでうまくいく場合 – コードベースがモジュール化されている – きちんとリファクタリング済み – デリバリー舞台が複数の小規模なチームにわかれている
– 経験豊富なリーダーが率いている – チーム全体がチェックインやメインラインへの統合を頻繁に行なっている
– デリバリーチームにリリースを迫るようなプレッシャーを与えないこと
14.9 チームによるブランチ • 大規模チームが複数の作業を並行で進めている時に、メインラインも常にリリース可能な状態にしたい
• trunkを常にリリース可能にしてしておく • チーム毎にブランチを切り、ブランチが安定した時点でtrunkにマージする
• あるブランチに何らかのマージをした場合、他のブランチにもすぐに同じ内容をマージしなければならない
14.9 チームによるブランチ • 小さめのチームを作り、チーム毎のブランチで作業を進める
• あるフィーチャ/ストーリーの実装が完了したら、ブランチを安定させてそれをtrunkにマージ
• trunkへの変更は常にすべてのブランチにマージする
• ユニットテストや受け入れテストは各ブランチでのチェックインのたびに実行する
• インテグレーションテストを含むすべてのテストは、各ブランチからtrunkへのマージがあるたびに実行する
14.9 チームによるブランチ • trunkへチェックインさせていると定期的なリリースを保証しにくくなる
• 複数のチームがそれぞれのストーリーに対応しているとtrunkはほぼ常に中途半端な作業が残った状態になる
• そのままではアプリケーションをリリースできなくなる
14.9 チームによるブランチ • 開発者たちはみな自分のチームのブランチにのみチェックインする
• このブランチをtrunkにマージするのは作業中のすべてのフィーチャーが完了したときのみ
14.9 チームによるブランチ • うまくいく場合 – 複数の小規模なチームが比較的独立して作業 – システム内で機能的にそれぞれ独立した部分を扱っている
• すべてのブランチはオーナーを任命し、オーナーにそのポリシーをまとめさせる必要がある
14.9 チームによるブランチ • trunkを常にリリース可能な状態に保つ • ブランチが安定するまでtrunkにマージできない – 安定:trunkにマージするときに自動テストを一切壊さない状態
14.9 チームによるブランチ • マージを頻繁に行わなければ、真の継続的インテグレーションが実現できなくなる – マージの際の衝突地獄
• すべてのチームはひとつのストーリーが完了した時点でtrunkにマージする
• trunkでの変更の取り込みも一日一度は行う
14.10 まとめ • 洗練されたモダンなバージョン管理システムの使いやすさは、チームでのソフトウェア開発において最も重要
• バージョン管理のパターンはデプロイメントパイプラインを設計する際に重要なポイント
• バージョン管理がうまくできてないと素早くローリスクなリリースをするのは難しい
• 使える機能を理解した上で正しいツールを選んで適切に使うことがプロジェクト成功の鍵
14.10 まとめ • 継続的インテグレーションを推進したいという思いと、ブランチを切りたいという思いは基本的に相反する
• CIを元にした開発を進めている時にブランチを切ろうとすると、なんらかの妥協が必要 – 完全にCIの立場で考えるとすべての変更はできるだけ早くtrunkにコミットするべき
– trunkは常に最新の完全な状態を保つ • あらゆるブランチの内容を一日一度以上の頻度でメインラインにマージするべし