読者です 読者をやめる 読者になる 読者になる

ForkからPull Requestまでの大まかな流れ

先日、初めてPull Request(以下、PR)をしてMergeされる機会があったので、流れをまとめておく。
この話題については既にたくさんの記事が存在するが、備忘録として、書いておく。

ホスティングサービスはGitHubを使っている前提で書いているが、他のホスティングサービスでも問題ないはず。

GitHub Flowで作業していく。トピックブランチで作業を行い、それをmasterにマージする。

Forkから作業を終えるまで

PRするためには当然ながら、自分のリポジトリにおいて、コードの追加なり変更なりを行う必要がある。
まず、そこまでを完了させる。

1 Forkする
2 $ git clone Forkしてきたリポジトリのパス

Forkは、GitHub上の簡単なボタン操作で可能。
これで、自分のアカウント上に、Forkしてきたリポジトリが出来る。
それをクローンすれば、ローカルで作業出来るようにする。

3 $ git checkout -b topic-branch
4 作業とコミットを繰り返す
5 $ git push origin topic-branch

作業用のブランチ、今回の場合はtopic-branchを作成し、そこで作業とコミットを繰り返す。
作業が終わったらまず、リモート上の自分のリポジトリにpushする。
自分のリポジトリはデフォルトでoriginという名前で登録されているはずなので、5のコマンドで、pushできる。

これで、自分のリポジトリ上での作業は終わった。

Fork元のリポジトリの変更を取り込む

複数人数で作業している場合、自分のリポジトリで作業している間に、Fork元のリポジトリの内容が更新されている場合がある。
その場合は、自分のリポジトリにその差分を取り込み、Fork元のリポジトリの最新の状態に合わせる必要がある。

6 $ git remote add upstream Fork元のリポジトリのパス

Fork元のリポジトリを、リモートリポジトリとして登録する。ここでは、upstreamという名前にした。
これで、originupstreamという2つのリモートリポジトリが登録されている状態になった。
前者は自分のリポジトリで、後者はFork元のリポジトリ

7 $ git checkout master
8 $ git pull upstream master
9 $ git push origin master

Fork元のリポジトリmasterブランチからpullしてくる。
これで、ローカルリポジトリmasterブランチは、最新の状態になった。
そして9で、originmasterブランチにもその変更を反映させる。

10 git checkout topic-branch
11 git rebase master
12 git push -f origin topic-branch

topic-branchでrebaseすることで、自分の作業結果は残しつつ、最新のmasterの状態を取り込める。
それをpushすることで、originが、Fork元のリポジトリの最新の状態を反映しつつ自分の作業も終えた状態になる。

これで、PRが出来る状態になった。

PRからMergeまで

13 Pull Request
14 コードレビュー
15 (修正が必要な場合)$ git push origin topic-branch

PRは、GitHub上の操作で行える。baseに対してcompareをPRするよう、リポジトリやブランチを選択する。
PRが作成された後は、コードレビューなどを経て、Mergeされる。
場合によっては、コードの変更が必要になるかもしれない。その場合は、ローカルで作業を行った後、15のようにpushすればいい。そうすれば、PRの内容も自動的に変更される。

問題がないと判断されれば、Mergeされる。
これでPRは完了。
最後に、後片付けを行う。

ブランチの整理

16 git checkout master
17 git pull upstream master
18 git push origin master

ローカルのリポジトリを最新の状態に保つため、upstreamからpullしてくる。そしてその後、originにpush。
これで、ローカルリポジトリorigin、両方のmasterが、PR後のupstreammasterと同じ状態になる。

これで、作業用に作成したtopic-branchは不要になったので、削除する。

19 git branch -d topic-branch
20 git push origin :topic-branch

まずローカルの、次にorigintopic-branchを削除する。
またPRを作成することになったら、改めてトピックブランチを作成し、同じ手順を繰り返せばよい。

参考資料

JavaScriptのショートサーキット評価

ほとんどのプログラミング言語には、ANDORを表す論理演算子が用意されている。
そして、左辺を評価した時点で論理式の結果が確定した場合には右辺の評価を行わないことを、ショートサーキット評価(短絡評価)という。
例えば、A AND Bという論理式があった場合、Afalseなら、その時点で式全体の結果はfalseで確定するため、Bがどうであるかについてはチェックしない。

JavaScript論理和演算子||論理積演算子&&も、ショートサーキット評価を行う。
これを利用することで、コードを簡略化したり、パフォーマンスを向上させたりすることが出来る。

論理演算子の挙動

ショートサーキット評価を利用するためにはまず、論理演算子がどのように動くのかを理解しないといけない。
なお、以下の説明は、どのような結果を生むのかについての説明であり、内部的な挙動については正確ではない可能性がある。

まずは論理和演算子||から。
左のオペランドを真偽値に型変換して評価し、それがtrueだった場合は左オペランドを、falseだった場合は右オペランドを、返す。
オペランドを返す場合、右オペランドは評価自体を行わない。

let value;
console.log(value || 'valueが未定義です。'); // valueが未定義です。
value = 7;
console.log(value || 'valueが未定義です。'); // 7

論理積演算子&&はその逆で、左オペランドfalseだった場合は左オペランドを返し、trueだった場合は右オペランドを返す。
オペランドを返す場合、右オペランドは評価自体を行わない。

let value;
console.log(value && 'valueが未定義です。'); // undefined
value = 7;
console.log(value && 'valueが未定義です。'); // valueが未定義です。

具体的な使い方

ショートサーキット評価を利用することで、条件分岐を簡単に書くことが出来る。

以下は、&&を使った書き方。

function checkValue(arg){
  if(typeof arg === 'number'){ console.log(`${arg}は数値型です。`); }; // 1は数値型です。
  typeof arg === 'number' && console.log(`${arg}は数値型です。`);  // 1は数値型です。
};

checkValue(1);

checkValue()で定義している2つの文は、どちらも同じ意味である。
論理式 && 文は、if(論理式){文};と同じ動きをする。
だがこのような書き方は、あまり見かけない。

よく使われるのは、以下の書き方である。

const useValue = inputtedValue || 10;

useValueを定義しているが、inputtedValueが存在すればその値を、存在しなければ10を、代入している。

複数の論理和演算子

同じ演算子が並んでいる場合、左から順番に評価していく。
そのため、論理和演算子が複数並んでいたときは、左から順番に評価していき、trueに型変換できるものが出た時点で、それを返す。
trueに型変換できるものがなかった場合は、一番右のオペランドを返す。

let hoge, fuga, muu;
console.log(hoge || fuga || muu); // undefined
fuga = 5;
console.log(hoge || fuga || muu); // 5
hoge = 7;
console.log(hoge || foo || muu); // 7

ちなみに、最後の論理式でfooという未定義の変数を使っており、本来ならエラーになる。
だがこの論理式では、hogeを評価した時点で論理式の評価は止まるので、エラーは出ない。このことからも、論理和演算子ではtrueに型変換できるものが出た時点で評価が終わるということを、確認できる。

簡略化を行うべきか

このように、ショートサーキット評価を利用することで、コードを簡略化できる。
だがこれに対しては、批判的な意見も存在する。可読性が落ちるというのが、その理由である。
確かにやり過ぎれば、可読性は落ちるだろう。
だが例えば、先程の複数の論理和演算子などは、むしろ可読性が向上すると思う。

useData = person.address || person.city || person.country;
if(person.address) {
  useData = person.address;
} else if(person.city) {
  useData = person.city;
} else {
  useData = person.country;
};

どちらも同じように動作するが、前者の書き方を知っていると、後者は冗長に感じる。

ショートサーキット評価とパフォーマンス

ショートサーキット評価を上手く使えば、可読性だけでなく、パフォーマンスも向上する。

let value;
function checkFunc(arg){
  console.log('checkFuncを呼び出しました。');
  return arg;
};

// コードA
// 関数を呼び出さない。
value && checkFunc(true) ? console.log('ok') : console.log('ng') ;

// コードB
// 関数を呼び出してしまう。
checkFunc(true) && value ? console.log('ok') : console.log('ng') ;

上記のコードでは、valuecheckFunc(true)が共にtrueとして評価できる場合にokを表示する。そうでない場合はngを表示する。
この例ではvalueundefinedなので、ngが表示される。
それはコードAでもコードBでも変わらない。
&&の左右を入れ替えただけなのだから、最終的な結果はAもBも同じである。

だがパフォーマンスは異なる。
Aの場合、左オペランドfalseなので、その時点で、つまりcheckFunc(true)を呼び出すことなく、論理式は終了する。
Bでは、左オペランドcheckFunc(true)なので、必ずこの関数が呼び出されてしまう。

このように、重い処理を右オペランドに配置することで、無用な呼び出しを避け、必要なときにのみ呼び出すように出来る。

上記の例ではcheckFunc()はログを表示した後に引数を返すだけだが、ここでそれなりに重い処理を行っていた場合、パフォーマンスには差が生じる。

以下の例で、それを確認できる。

let value;
function checkFunc(arg){
  for(let i=0; i < 100; i++){
    1 + 1;
  };
  return arg;
};

const start = new Date();
for(let i=0; i < 1000000; i++){
  value && checkFunc(true) ? 1 : 0 ;  // checkFunc(true)は呼ばれない
};
console.log(new Date() - start);  // 18 〜 21
let value;
function checkFunc(arg){
  for(let i=0; i < 100; i++){
    1 + 1;
  };
  return arg;
};

const start = new Date();
for(let i=0; i < 1000000; i++){
  checkFunc(true) && value ? 1 : 0 ;  // checkFunc(true)を必ず呼び出す
};
console.log(new Date() - start);  // 240 〜 260

実際の秒数はもちろん環境によって異なるが、私の環境では、前者は18〜21ミリ秒、後者は240〜260ミリ秒、といったところだった。
つまり、10倍以上の差が出ていることが分かる。

参考資料