超説明・開発フロー
このページについて
共同開発で決められる「ルール」には、世の中のプロジェクトで広く一般的なものや、プロジェクトごとに特殊なものがあります。前者は暗黙の了解や不文律になっていることが多く、検索もしにくいことが多いです。共同での開発経験の少ない人にとっては不文律があると辛いので、このページではそれらを明示的に書き下します。
Brain Hackers では、基本的にここに書いてある流れで開発を進めます。ただし、Linux など他の文化からやってきたものについてはこの限りではありません。
第〇章 基本的な概念
このページ全体に共通している概念について説明します。
0.1. 困ったら人に聞こう
このページは概説的なので、多種多様なケースに網羅的に対応することはできません。開発についてよく知っている人に聞くのが結局のところ最速です。理解に苦しんだらすぐ会話してください。
0.2. 合意があればなんでもいい
開発フローの作り方はケースバイケースです。絶対的に正しい or 誤っているというものはなく、合意があればどんなルールでも有り得ます。
0.3. このページを完全に知識がない状態で読むのは難しい
特に Git についてはこのページの内容だけで全容を把握するのは無理です。末尾のリンク集にあるサル先生のGit入門を読めばわかりやすく学べます。手を動かしてリポジトリをぶち壊して…を繰り返す過程でこのページを読むと次第に意味がわかるでしょう。このページの内容が身についていると共同開発がしやすいことは間違いないので、たまに読み返してみてください。 第一章 道具
開発にはいろいろなツールを使います。本章では、共同開発で使う道具に焦点を当てて説明します。
1.1. Git
ソースコードの差分と歴史を管理する「バージョン管理システム」の1つです。手元の歴史(ローカルリポジトリ)とインターネット上の歴史(リモートリポジトリ)が区別され、開発者間で必ずしも同じ歴史を見ていない可能性があるという特徴を持つシステムです。
1.2. Git クライアント
Git のリモートリポジトリに操作を伝えたり、ローカルリポジトリを育てたりするツールです。git というコマンド名で起動できる公式クライアントは、黒い画面でコマンドによりリポジトリを操作します。他にも Sourcetree のようなグラフィカルなクライアントもあります。どれを使うべきかネットではいつも意見が分かれていますが、グラフィカルなものは GUI で Git の概念を抽象化してしまい本来の意味で Git を理解できない可能性があるため、おすすめしません。
1.3. GitHub
Git リポジトリの置く場所を提供するサービスです。事実上世界最大の Git による開発プラットフォームです。Git が提供する共同開発の仕組みに加えて、Pull Request(変更を提出・承認・却下する GitHub 独自のフロー)や、Issue(問題のトピックを提出し議論する機能)、Wiki(情報をコードではなく自然言語で書き溜めておく場所)などの機能があります。
1.4. フォーマッター (formatter)
コードの見た目を統一するツールです。スタイルを統一することで、コードの見た目の個人差が減り、保守しやすくなります。
フォーマッターはコードのシンタックス(文法)を統一するだけで、セマンティクス(意味)については考えません。コードの意味まで見て悪い箇所を指摘してくれるツールには、後述のリンターが対応します。
ここでは C を例に解説します:
code:c1.c
void foo(int* i) {
printf("i = %p, *i = %d\n", i, *i);
}
int main(void) {
int i;
for(i = 0; i < 3; i++) {
foo(&i);
}
}
code:c2.c
void foo ( int *i )
{
printf( "i = %p, *i = %d\n", i, *i );
}
int main ( void )
{
int i;
for( i = 0 ; i < 3 ; i++ )
{
foo( &i );
}
}
以上の2つのコードスニペットを見てください。各々のコードの持つ意味(セマンティクス)は完全に等価ですが、文法(シンタックス)が違います。スペースや中括弧の置き場所など、こういったシンタックスの差をなくしてスタイルを統一するのがフォーマッターの役目です。どういうフォーマッターを使うかやどういうスタイルにするかは言語や界隈によって主流が存在し、通常はそれをそのまま採用します。
1.5. リンター (linter)
コードの持つ意味や構造をチェックし、バグに繋がりやすい典型的な間違いや品質の悪い記述を指摘します。フォーマッターはシンタックス(文法)をチェックし、リンターはセマンティクス(意味や構造)をチェックするという違いがあります。
第二章 用語
道具と同じように用語も星の数ほどあります。コード提出の流れを解説する前に用語を並べておきます。今すべてを理解する必要はありません。
2.1. リポジトリ (repository)
ソースコードの置き場所です。通常のフォルダ(ディレクトリ)に置く事との違いは、リポジトリが「ファイルの変更の歴史を保持していること」にあります。いつ・誰が・どの箇所を・どのように変更したという 5W1H が克明に記録されています。
リモートリポジトリ (remote -)とローカルリポジトリ (local -)があり、開発者はまず手元のローカルリポジトリを育てた上でリモートリポジトリへとプッシュします。
2.2. コミット (commit)
リポジトリにおける変更の単位です。1コミットにつき、
1つのコミットメッセージ (commit message)
1人の作者 (author)
1人のコミッター (committer)
M個の親コミット (parent)
N箇所の変更 (diff)
後に解説するマージではマージコミット (merge -)という特殊なコミットが生成されます。 通常のコミットでは親は1つですが、マージの場合親は2つ以上あります。
2.3. ブランチ (branch)
コミットの連なりです。1つのブランチにつき1本の歴史を記録しています。別なブランチからコミットを取り込むことがありますが、これらの操作単位もやはり歴史の中の1コミットでしかありません。歴史をたどっていくと、他のブランチと共通の祖先に必ずたどり着きます。
デフォルトブランチは、リポジトリの中で1つだけある特別なブランチです。多くの場合、master か main と名付けられています。特にブランチを指定せずにプルすると、ローカルリポジトリ内のツリーはこのデフォルトブランチの最新状態になります。つまり後に解説する HEAD は標準でこのブランチになります。そのリポジトリの内容をどこかへデプロイする(ビルドして実際に稼働させる)ときは大抵デフォルトブランチの内容が使われます。つまり、デフォルトブランチには安定した実装があることが普通望まれます。
ところで実は、「ブランチ = コミットの連なり」という表現はあまり正しくありません。なぜならブランチとはただ1つのコミットを指し示す名前だからです。1つのコミットを指し示しているのになぜ branch = 枝と呼ぶのかというと、そのコミットには親コミットがあり、さらにその親コミットがあり、…とたどることでどのコミットからでも必ずひとつの共通祖先に辿れるから、というのが理由です。
2.4. プッシュ (push)・プル (pull)
プッシュはローカルリポジトリからリモートリポジトリへと変更を反映する操作、プルはリモートリポジトリからローカルリポジトリへ変更を取り込む操作です。厳密に言うと、プル = フェッチ + マージ です。
2.5. HEAD
Git で管理されているすべての歴史のうち、自分がいま閲覧しているコミットのことを別名で HEAD と呼びます。大抵の場合、HEAD は何らかのブランチと同じ箇所を指しています。この状態でコミットをすると、HEAD が指し示すコミットに新たなコミットがぶら下げられ、HEAD とブランチはその新しいコミットに移動します。
HEAD がどのブランチも指していないコミットへ移動すると、その状態は Deatched HEAD と呼ばれます。(ブランチはコミットの連なり全体を指すのではなく、ある1つのコミットのみを指すことに注意)
2.6. フォーク (fork)
あるリポジトリの歴史をそっくりそのままコピーすることです。食器のフォークのように枝分かれするさまからこの名前が付いていて、この名前があるからにはコピー以上の意味があります。それは、コピー元と全く同じ歴史を共有していることで、あとから自分が加えた変更を元のリポジトリに合流させてもらえるという意味です。
フォークを行うのは、所有権がなくプッシュ権限を持たないリポジトリに変更を加えたい時です。逆に言うと、たとえば GitHub の Brain Hackers Organization のメンバーになっていて、Brain Hackers が管轄するリポジトリに変更を加えたい時はフォークをする必要はありません。詳しくは3章で述べます。
第三章 変更を提出する流れ
あるリポジトリの内容の変更は、本質的に言えば「自分が書いた変更差分を歴史に組み込む」が起こると達成します。これから説明する以下のような操作は、流れは異なりますがすべて「自分が書いた変更差分を歴史に組み込む」が生じる点では同じです。
リポジトリのデフォルトブランチに直接コミットしてプッシュする
リポジトリのデフォルトブランチから別なブランチを生やし、自分のコミットをプッシュして pull request を出す
リポジトリをフォークしてコミットを追加して pull request を出す
3.1. デフォルトブランチに直接コミットしてプッシュ
PR を提出したいリポジトリに書き込み権限がある場合、有効な方法です。デフォルトブランチは全員に共通であり、誰かの一存でコミットしたり、歴史を改変したりするべき存在ではないため、共同開発向きのやり方ではありません。デフォルトブランチへ自分の変更を取り込むには、pull request を使うべきです。
リポジトリの管理者が特にレビューの必要がない変更を加える場合は、これを省略してコミットすることもよくあります。その場合、「デフォルトブランチにコミットしてプッシュしたから、各自プルしてください」とちゃんとメンバーに連絡する必要があります。
3.2. 別なブランチを生やして pull request を出す
PR を提出したいリポジトリに書き込み権限がある場合、有効な方法です。リポジトリのオーナーかコラボレーターであれば書き込み権限があります。
1. main (master) から git checkout -b {何らかのブランチ名} で新しいブランチを作る
2. 変更をコミットする
3. git push origin --set-upstream {ブランチ名} でオリジンにブランチをプッシュする
4. GitHub のリポジトリのページを開くと、直近でプッシュされたブランチで pull request を作るか聞かれるので "Compare & pull request" を押す
https://gyazo.com/849ec4cb4214abcbffe63f482ab8615e
5. 具体的なタイトルを記入する
例: ◯◯が特定条件下で実行されないバグを修正
例: 処理△△を実装
6. 具体的な説明を書く
例:「事象」「変更内容」「注意事項」などのセクションを # で設ける
例: コードスニペットにバグを再現できるコードを記入する
7. Reviewers にある歯車をクリックしてリポジトリのコードがわかる人をレビュアーを設定する
https://gyazo.com/4948ec6a9b928000c2ad5032d2fd669f
8. "Create pull request" をクリックして提出する
9. 変更が承認 (approve) されるまで議論や追加の変更を提出する
10. 変更が承認されマージされたら、冒頭でプッシュしたブランチを消す(自動で消させる設定も可能)
3.3. フォークして pull request を出す
PR を提出したいリポジトリに書き込み権限がない場合に使う方法です。
1. リポジトリ右上の "Fork" をクリックする
https://gyazo.com/24fbd2a9fbe6c41005380e84a4261134
2. 自分がオーナーとなったフォーク先に自動で移動するので、移動先にてリポジトリを手元の PC へクローンする
3. 「別なブランチを生やして pull request を出す」と同じステップで、ブランチ作成からマージまで進める
4. 今後 pull request を出す予定がない場合、fork したリポジトリは消す
第4章 コミュニケーション
開発はリポジトリ上だけで行われるわけではなく、よく Slack, Discord, IRC, メールその他で会話しながら行います。どの会話を GitHub でやってどの会話を会話ツールですればよいのかをケース別で紹介します。会話ツールはここでは Discord とします。
4.1. Pull requestを提出し変更について議論する
以下の流れが一般的です。Pull request の内容について議論する場合は GitHub 上で会話を行い、Pull request の提出自体やその他の運用について相談する場合は Discord で行います。
1. PR を提出する
タイトル、ブランチ名、PR 説明文を具体的に記述する
2. レビュアーを PR に追加する
3. Discord でレビュアーに PR を提出した旨とそのリンクを送る
レビュアーに追加されると普通メール通知が来るが、メールの確認頻度は人により違うので手動通知が望ましい
PR のページにサッと飛べるとレビュアーの手間が省けて嬉しいのもある
4. レビュアーはレビューを行い、変更要請 (change request) や承認 (approve) を行う
5. レビュー内容に応じて提出者は PR をマージするか、修正を行う
5. 1. デフォルトブランチに入れるコミットに一定のルールが有る場合、それに沿うようコミットを amend / squash / commit + force-push する
5. 2. 特に決まったルールがない場合、そのままマージする
6. (変更要請があった場合)修正を push する
7. 下図の矢印で示されたボタンを押し再レビューを依頼する + 再レビューを依頼したことを Discord でレビュアーに伝える
https://gyazo.com/5a20c1050b18415363251d265dbcb454
7. レビュアーが4から繰り返す
第5章 共同開発に臨む姿勢と考え方
共同開発は複数の人が集まって作業します。その「腕」や知識レベルには必ず差があります。非常に典型的なのは、その中でも技術が低い方の人が負い目を感じたり、チームに悪影響を与えていると妄想してしまうことです。一方、技術が高い方の人も問題のある行動をすることがあります。例えば技術の低いメンバーに対して高圧的に接したり、などです。いずれも、この章にある考え方を学ぶことで改善できます。
5.1. まずは書いてみて後から改善しよう
最初から完全無欠なコードを生み出せる人はいません。だから、自分の技術に自信があるかとか、人に自分のコードを見せるのは恥ずかしいとか、そういう気持ちはまず捨ててとりあえず書いてみましょう。それを Pull Request で共有して、もっと良くするにはどうすれば良いかを話し合いましょう。
5.2. 悪いのは低品質なコードではなく、それを改めない姿勢である
これは 0.4. を補強する概念です。仮にバグがあるコードや美しくないコードを書いたとしても、それはまったく悪いことではありません。なぜならこれから良くしていけばいいからです。本当に悪いのは、それを受け止めずにはねのけたり、次に活かさないことです。これではみんなの気分が下がるし、コードの質も上がりません。今誠実に対話し学ぶことで、コードも、チームも、そしてあなた自信の技術力ももっと良くなります。
5.3. レビューは文句を言う場所ではなく、より良いコードへと前進する対話の場である
コードレビューでは、コードをもっと良くするにはどうすればよいかを議論します。だから、レビュアーは提出者に対して怒っているわけでも、イジッているわけでも、はたまた蔑みたいわけでもありません。ここはレビュアーも提出者も勘違いしてはいけません。提出者は前向きな気持ちでこれを提出し、指摘を恐れないでください。またレビュアーも、極力ナイスな言葉遣いを意識し、前向きな表現になるよう心がけてください。絵文字を積極的に使うとか、語尾を工夫するとかして表現を柔和にするのもよいでしょう。もし万が一レビュアーが明らかに高圧的あるいは敵対的な場合は、「嫌な思いをした」と声を上げるのも一つの手ですが、そのプロジェクトには関わらず立ち去るのがベターです。
5.4. ナイスに振る舞う以上にチームを良くする行動はない
「ナイス (nice) な人」とは、誠実で、心が開かれていて、いつも笑顔にあふれた人のことです。自己啓発本でも読まされてるのかと思うかもしれませんが、結局の所、こう振る舞う以上に良いものはないです。常に自分の発言や行動を第三者的に振り返って改善しましょう。Be nice!
最終章 便利なリンク
サル先生のGit入門 非常にわかりやすい Git 解説サイト。このページにある用語はもちろん、それ以外の概念についても図解で解説されている。