めっちゃ遅れましたが計算研カレンダーの1日目の記事です。
1. はじめに
1.0. どういう記事か
大学サークル「計算研究会」で大学祭に向けて「GitbucketとGitを使ってゲームコンテンツをチーム開発をしてみた」ので、そのことについて書きたいと思います。
本記事は計算研で本年度行った開発方法を記事化して残し来年度に活かすという目的と我々と同じように大学サークルや趣味でチーム開発をやってみようと思っている方々に「こういうやり方である程度うまくいったよ」という知見を共有する目的で書かれています。
もう少し具体的に言うと「普段Git等は使っているけれどチームでは開発したことがない」という人の参考になるように書いています。(よってコマンドの説明等は最小限にしています)
「もっとこうした方がいい」や「ここの定義間違ってるよ」とかがあればコメントをお願いします。
1.1. 参加メンバーの雰囲気
今回紹介する方法を試したプロジェクトのメンバーは大体2種類ぐらいのグループに分けることができて
- 「Git触ったことがある」という人が1/3
- 「Git全く触ったことない」という人が2/3
という構成でした。プログラミングに関しては全く触ったことがないという人はいませんでした。
2. ツールとその他用語解説
どの様に開発したのかを述べる前にチーム開発で用いた際のツールやその他用語について述べたいと思います。
チーム開発をするにおいて使用するツールが一体どういうものなのかどんな目的で導入されるものなのかを知っておくことは重要です。目的がわかっていればよりツールを適切に使うことができるからです。したがって本記事ではツールの説明に関しては使い方の説明というよりも
- そもそもどういう立ち位置のツールなのか
- メリット
等の説明を行います。なので普段触っている人はおさらい程度に読んでください。
2.1. Git とは
Gitとはフリーオープンソースの分散バージョン管理システムです。(公式定義
バージョン管理システム自体はあるファイルが
- いつ
- 誰が
- どの様に
変更したのかを履歴として記録しておくものです。
Gitはリポジトリという単位でファイル群をまとめて管理します。またファイルの変更履歴はコミットという単位で管理されます。
またリポジトリも以下の2種類に分かれます。
- ローカルリポジトリ・・・ユーザ一人ひとりが利用するために、自分の手元のマシン上に配置するリポジトリ
- リモートリポジトリ・・・専用のサーバに配置して複数人で共有するためのリポジトリ
Git等のバージョン管理システムを利用するとさまざまなメリットを得られます。
まず最も大きいのが最新版がどれなのか・いつのバージョンと違いどのような変更が施されているかを簡単に追跡することが可能です。(最新版、final版、最新版2というファイル分け管理による地獄をみなくなります。)
誰が変更したかを確認することができるため、どうしてもコード上で変更の意図が確認できなかった際、どの様な意図で変更したのかを問い合わせることができたりします。
他にも
- バージョン巻き戻し機能 (reset、checkout) により修正機能が簡単で正確に
- 変更履歴の分岐・統合 (branch、merge) による並行作業が柔軟に
- プロジェクトの共有 (push、clone) が簡単に
など開発上でいいことづくめです。
2.2. Gitbucket とは
GitBucketとは、オープンソースにて開発されているGitをインターネット上で管理するプラットフォームである。
by wikipedia
感覚的にはGithubのようなネットでリポジトリを管理するシステムと同じようなものですが、相違点としてはGitbucketは自前でサーバーを設営することができ、使用の際に機能制限がなかったりプラグインを好きに導入できたり融通が利きます。
このシステムを使うことで簡単にリモートリポジトリを作成・管理・複製できます。Forkすることによりあるリモートリポジトリを他のリモートリポジトリとして複製することができます。この複製されたリモートリポジトリ同士でPull Requestというものを送り合って変更を柔軟に共有し合うことができます。
2.2.3. Fork とは
上記で若干解説しましたが、やったことを説明するためにもう少し詳しく説明します。
「フォーク」(Fork)はリポジトリのコピーです。 リポジトリをフォークすることにより、オリジナルのプロジェクトに影響を与えることなく変更を自由にテストできます。
とGithubの公式で言われています。これをもう少しかみ砕いて見ます。GithubやGitbucketではユーザーがリモートリポジトリに紐づいています。同じリポジトリを複数ユーザーで編集していくことは可能ですが、これには以下のような不便が伴います。
- 他のチームメンバーとブランチ名の衝突の可能性がある
- 途中結果をなにかの問題を議論するため一時的に共有する際他プログラマの作業を妨害してしまう可能性がある。または妨害になった場合、途中結果が消される可能性がある
そこでForkを使って自由に作業結果をPushできるリポジトリを複製してしまうことで、上記の不便をなくし柔軟に作業を行うことができます。
複数ユーザーに紐づいているリポジトリをユーザーごとに紐づいたリポジトリに分散させています。作業の途中結果をアップロードすることが容易になります。
2.2.4. Pull request とは
プルリクエストとは、コードの変更をレビュワーに通知し、マージを依頼する機能です。
このPull requestを使うとブランチ間で変更を共有依頼を出すことできます。これはForkしたリポジトリ間でも出すことができ、これによって先ほど分散させたリポジトリ間で作業成果を共有することができます。
これを用いて分担して作業を行っていたプログラマの間で成果を結合することができます。
2.3. Trello とは
Trelloはプロジェクトをボードで整理できる、コラボレーションツールです。Trelloを使えば、現在の作業対象、作業しているユーザー、進行中の領域を確認できます。
いわゆるTodoリスト管理ツールです。複数で一つの管理ボードを共有することが可能で、タスク一つに人を割り当てることができ、名前ではなくアイコンであるため見慣れてくるとすぐ誰がやっているのか?誰がやる予定なのか?がすぐ認識できるようになります。
GUIは図のような感じです。
3. 基本的な開発フローとリポジトリ構成
ツールの確認が終わったところで今回我々がやったことをを紹介したいと思います。
最初に開発フローの概観として我々が大学祭の開発で行った基本的な流れは
- 設計書及び仕様書から抽出したタスクをTrelloで発行
- コーディングに入る前や作業前に中央リポジトリ(リポジトリ構成で解説)の内容を取り込み
- 指定された
<機能名>-dev
ブランチを切ってコーディング開始 - (必要であれば)途中結果を自分のリモートリポジトリにPushし議論
- 作業が終わったと思ったら
中央リポジトリのdevelop
ブランチにPull request - リーダーや設計をした人がOKと感じたらMerge
- だめなら修正のコメントをPull requestに付与してリテイクしてもらう
という感じで、これを何度も繰り返してプロダクトを作成していきました。
3.0. リポジトリ構成
流れを確認したところでプロジェクト進行中でリモートリポジトリやブランチが大体どうなっていたかを紹介します。
Gitbucket側のリモートリポジトリの構成は
- 中央リポジトリ・・・ある程度できあがった作業結果がMergeされるリモートリポジトリ。このリポジトリをforkして作業用リポジトリになる。他の人の作業結果が共有されるときはこのリポジトリの内容をclone、fetch、pullして共有する。
- forkした作業用リポジトリ・・・作業員個々に割り当てられるリモートリポジトリ。作業途中な作業結果もpushできる。
という風になりました。リーダーや設計者の人が中央リポジトリのオーナーにし、その他プログラマやモデラ等の作業者がforkした作業用リポジトリのオーナーにしました。(オーナーとはそのリモートリポジトリに自由にPushできたり、送られたプルリクエストをマージできる人のこと)
また中央リポジトリのブランチは大体
- master・・・完成品、プロトタイプ
- develop・・・機能開発用ブランチ
という構成でした。
forkした作業用リモートリポジトリのブランチ構成は大体以下の構成でした。
- master・・・中央リポジトリのmasterを追随する感じのブランチ。
- develop・・・同じく中央リポジトリのdevelopを追随する感じのブランチ。
- feature-dev・・・機能開発用のブランチ。(これが中央リポジトリにはない)
です。
少し抽象的でわかりづらいとおもうので例を挙げて説明したいと思います。本プロジェクトでは僕 @kimarian がリーダーをしており、 @__DielsAlder__ 君などがプログラマやモデラを担当してくれていました。この前提を考えた場合、
- 中央リポジトリのオーナー: @kimarian
となりこのリモートリポジトリを@__DielsAlder__ 君などがforkし
- forkした作業用リポジトリのオーナー: @__DielsAlder__
となります。またここで注意が必要なのがforkした作業用リポジトリは作業者ごとに存在するということです。したがって作業者がN人いた場合N個の作業用リポジトリができます。
今回の運用では「@__DielsAlder__のリモートリポジトリ(forkした作業用リポジトリ)から@kimarianのリモートリポジトリ(中央リポジトリ)にPull requestを出し作業の結果をMergeする、そして他作業者がその作業結果に依存するときはfetch & mergeしてその結果をその他作業者のリポジトリに取り込んで作業を開始する。」といった雰囲気で開発をしていきます。
3.1. Trelloでタスク発行
もう少し詳しく開発フローを見ていきます。まず、タスクの発行ですが、以下のスクショに示したような設計書や仕様書(ざっくりとしたクラス構成やデータ、見た目等を共有するためのドキュメント)から現在作成できる部分を適宜抽出し
以下のようなチケットとしてTrelloに発行します。
これを作業者が読み、作業に入ります。作業者のGit活用能力に差があったので、最初のうちは「ここでfetch してmergeしてbranchしてcheckout」というようなコマンドをTrelloのチケットに書き込んだりしていました。
3.2. fetch & merge して branch
次に依存する他作業者の完了済み作業結果等をローカルに取り込むため以下のコマンドを用いて、中央リポジトリの情報をローカルリポジトリに取り込みます。
$ git fetch upstream
$ git checkout develop
$ git merge upstream/develop
※upstreamという名前で中央リポジトリを追加してあります。
その後、割り当てられたタスクの内容に従い作業用のbranchを切ります。
$ git branch mes-ind-dev
$ git checkout mes-ind-dev
その後チェックアウトを行い実際にコーディングを行います。
3.3. Pull request & Merge
ここからは作業者がまずプルリクエストをforkリポジトリの該当ブランチから中央リポジトリの該当ブランチにだします。
作業者がやること
指定された成果物ができあがったらプルリクエストを送ります。まず作業者がForkしたリポジトリに作業内容をPushします。
$ git commit
...適当にコミットメッセージ等を書く...
$ git push origin mes-ind-dev
次にgitbucketをブラウザで開き該当リポジトリのPullRequest一覧を開き、プルリクエストを発行します。
中央リポジトリオーナーがやること
プルリクエストがだされたら中央リポジトリのオーナーは発行されたPullRequestをレビューしOKそうかチェックします。大丈夫そうならマージ。そうすると中央リポジトリのdevelopやmasterに作業結果が取り込まれます。
4. やってみて思ったこと
セクション3で紹介した流れを実際のVRコンテンツ開発で実施してみて思ったことを述べたいと思います。
11月の大学祭に間に合ってある程度体験に耐えうるシステムをつくることができたので、おおむねうまくいったと思います。その要因や今後の改善点として特におもったことを述べます。
4.1. 作業者が自分専用のリモートリポジトリを持つことで中央リポジトリが壊れづらくなる
1つのリポジトリに全ての作業者を押し込んでしまうと、誤ってdevelopやmasterにpushしてしまう等が起こったとき開発全体に影響が及びます。(pushする際の権限設定等をきちんと設定すれば別ですが、gitbucketを用いてチーム開発を行うのは初めてであったため、より簡単でわかりやすくそのような事故を抑止する手段としてForkを用いました。今回はgitに不慣れなメンバーも参加していたため、そういった事態が容易に発生しうると思いました。)
自分専用のリモートリポジトリを持つことでもしそのようなことがあっても開発全体がとまることがありません。開発全体の作業結果を管理する中央リポジトリには大丈夫だと判断されたPull requestしかマージされないため、そのような事故の確率が格段に下がったと考えます。(レビュワーがガバったりした場面がありましたがね。)
今回はLFSを一時的に併用していましたが、LFS回り以外では中央リポジトリが壊れ開発に支障がでることはほとんどありませんでした。
4.2. タスク分割 & 割り当てに注意
タスク分割単位があまり適切でなく、コンフリクト率があがってしまった場面がありました。
同一のソースファイルに複数のメソッドが記述されており、そのメソッドの実装を複数人で分担した時のことです。以下のような原因でコンフリクトをおこしました。
- Visual Studioのコード成形機能を用いて無意識に自分の担当外の場所を修正していた
- 他の人が命名を変更したフィールドを前の人が前の命名のまま使ってしまった場面があった(効率化のため最低限のprivateメソッド、フィールドの命名を指定するというルールを敷いていたため)
同一のソースファイルをいじるタイプのタスク分配はできるだけ少人数に抑える必要があると感じました。また、可能であれば設計段階で機能を細かく分割する工夫が必要だと思います。そういった「クラスに対する機能が多すぎないか」等の設計を再考するフェーズをもう少しちゃんととればよかったかなと思います。
4.3. LFS
ゲームコンテンツ開発を行ったのでバイナリでリソースを扱う場面が開発当初で出てきたため、Git-LFSサーバーを用意し運用しました。しかし、運用が思った以上に難しく何度か開発がとまってしまうことがありました。
例を挙げればgit lfs init
してない作業者がgit clone
を叩いてしまい、pushできない等の現象が多発しました。
後半はLFSを切り離し、そのリソースを管理する際はUnitypackageとしてエクスポートした状態で各自の環境にインポートするという方法をとりました。
今後もしLFSを導入する流れになったら、その作業を行う前にきちんと調査や勉強会、運用方法の検討を行う必要があると思いました。
4.4. ブランチをもう少し工夫した方がいい気がした
中央リポジトリの管理はGitbucketとGit本体に関しては重大な問題は起きませんでしたが、作業者個々のリポジトリ管理の方法にはもう少し改善の余地があるように思いました。
例を挙げれば、個人のdevelopブランチにそのまま作業をコミットしてしまい、作業用のブランチが切られないという状況が結構あったように思います。(このあたりは個人のリポジトリ操作であるためある程度自由にやってもらってかまわないと思いますが...。)
作業用のブランチを切ると
- 最初に考えたときの実行方針が難しいと判断した場合のリカバリをしやすい
- ブランチを切っていればブランチを消してもう一度はやすだけで済む
- git reset等でさかのぼらなくてもよい
- 作業途中で別の修正等が入った場合、すぐにその作業に移ることができ、終わり次第すぐに復帰できる
- ブランチを切っていれば、developから修正ブランチを新たにはやすだけで済む
- 復帰の際はcheckoutのみで済む
- 余計な作業結果を含むPull requestはレビューが非効率的になる※
といった風に開発フローを柔軟にしたりやミスを防ぎやすくなると思います。今後はもう少し効率の良いGit運用ノウハウについて調査し、共有していきたいと思います。
※ブランチは他のブランチに変更をマージする形式で発行します。機能開発タスクと修正タスクの変更が同一ブランチにはいっているとPull requestをマージする際複数のトピックの変更についてレビューする必要があり、ややこしくなります。
4.5.レビューをもうすこしちゃんと
プルリクエストをマージする際、ガバガバなレビューを行い
- 余計なディレクトリをマージしてしまったり
- あるレビュワー的にはまずい変更をあるレビュワーが勝手にマージしてしまったり
等で余計な修正作業がはいってしまった場面がありました。これは複数人のレビューのApproveを受けて、初めてマージされるという機能がGithubにあるそうなので、次回の開発環境でこのような仕組みを用いることができたらぜひ使ってみたいと思います。
プルリクエストの必須レビューを有効にする
リポジトリの管理者は、必須レビューを施行し、プルリクエストのマージ前に特定の数の承認レビューが必要になるようにできます。
by プルリクエストの必須レビューを有効にする - GitHub ヘルプ
他にも規約としておそらく最低限のチェックリスト等をつくることはできると思いますので、導入を検討したいです。
4.6. 小ネタ
今回の開発で便利だなと感じたgitの機能としてaddコマンドの-pオプションがあります。
git add -p
同一のコミットに大量の変更があると、コミットメッセージのどこの内容がどのコード変更に対応してるのかを追うのが難しくなります。これを用いると「あ!w、コミット刻むのわすれてた!w」となっても、あとで変更を細かいコミットに刻むことができます。
これによってあとあと「ここからここのコミットをみればなに変更したかわかるから」といった報告が簡単で比較的低コストでできるようになりました。
詳細な使い方は 横着で神経質な私とあなたに贈るgit add -p - Qiita がわかりやすいです。
5. 来年に向けて
本格的な開発に入る前に、開発メンバー全員に簡単なGit + Gitbucket操作のチュートリアルを行ってもらいました。内容としては「clone、commit、add、push、pull」コマンドを使うものでした。しかしPull requestやfetch等の学習が漏れており、口頭で一部メンバーに慌てて学習を補完するというのをやったり、タスクチケットにコマンドを直接書き込む等を行いました。
これはさすがにスマートでないため、来年は今回漏れた部分も網羅できるような勉強会とチュートリアルを開発メンバーに行ってもらう予定です。
また他のwebサービス等の開発もバージョン管理システムを用いて行って行きたいです。その際にCD/CIツールの導入も行いたいです。
参考文献
- 池田尚史、藤倉和明、井上史彰(2014). 「チーム開発 実践入門」技術評論社
- サルでもわかるGit入門〜バージョン管理を使いこなそう〜【プロジェクト管理ツールBacklog】