フィヨルドブートキャンプ Part 2 Advent Calendar 2024 の20日目の記事です。
🎅 フィヨルドブートキャンプ Part 1 Advent Calendar 2024
🎄 フィヨルドブートキャンプ Part 2 Advent Calendar 2024
昨日の記事は以下になります。
part1 : f-kt6gさんのMacBookを使ってネットワークのパケットキャプチャを行ってみた
- ご自身の経験とFBCで学んだ知識を活かした記事でとても勉強になりました!図も分かりやすい!
part2 : hagiさんのFBCの学習の振り返り
- 自分も同じくらいの時期に学習を開始したのですが、進まれるスピードが速くとてもすごいなーと思っておりました!毎日欠かさず日報を書かれていて尊敬します。
はじめに
私はフィヨルドブートキャンプ(以下、FBC)内で開催されている3つの輪読会に参加しています。
- 現場で使える Ruby on Rails 5速習実践ガイド(現場Rails)
- マスタリングTCP/IP - 入門編 -
- Good Code, Bad Code ~持続可能な開発のためのソフトウェアエンジニア的思考(現在、5人で共同主催中💪)
FBCへの参加を決めた理由の一つに、コミュニティ活動が活発であることがあり、できる限り月一で開催されるミートアップや輪読会といった定期的なイベントに積極的に参加しています。
しかし、フルタイムでの仕事をしながら学習しているため、どうしてもカリキュラム外のインプットやアウトプットに十分な時間を割くことができず、気になる知識を深掘りする余裕が持てていないのが現状です。
そんな中、アドベントカレンダーに参加する機会をいただきましたので、この記事では現場Rails輪読会で出会い、興味を持ったgit bisect
というコマンドについて、実際に手を動かしながら理解を深めていきたいと思います。
Git Bisect
「Git Bisect」は、二分探索(バイナリサーチ)を利用して過去のコミットを探索できるツールです。
このツールを使うことで、過去のコミット履歴の中から、どの時点のコミットでバグが発生したかを効率的に調査できるようになります。
二分探索をざっくり理解する
二分探索はソートされた配列などに用いられる探索アルゴリズムです。
配列の中から検索値を探索する際、その配列の最小値と最大値を探し、中央値を取得します。
検索したい値が中央値より小さい(左)か、大きい(右)かを判断し、範囲を半分に絞りながら目的の値を探索していきます。
より具体的に理解するために0~9の合計10個の値が格納された配列があるとして、この中から3という値を探索していくイメージ図を作成してみました。
この配列では最小値が0、最大値が9となるため、中央値は4になります。
中央値(4)は検索値(3)より大きいため、中央値より大きい値は探索から外します。
残った範囲の最小値(0)と最大値(4)でまた中央値を求めます。
中央値(2)は検索値(3)より小さいため、中央値より小さい値をさらに探索から外します。
最後に残った範囲の最小値(2)と最大値(4)で求めた中央値は3となり、検索値と一致したため探索終了となります。
上記は単純な数値の探索ですが、Git Bisectで探したいものはバグを含んだコミットです。
2分探索をどのように活かしながらコミットを探していくかを、実際に動かしてみながら確認していきます。
Git Bisectを動かしてみる
準備
エラーの発生するコミットを含んだリポジトリを作成しました。 コードの中身は意味のないものですが、テストが落ちるようになっています。
- sample_code.rb
def hello a_num = 5 b_num = 2 if a_num + b_num == 5 p "こんにちは" end end def goodbye c_num = 3 d_num = 2 if c_num + d_num == 5 p "こんばんは" end end
- sample_test.rb
require 'minitest/autorun' require './sample_code' class SampleCodeTest < Minitest::Test def test_hello assert_equal "こんにちは", hello end def test_goodbye assert_equal "こんばんは", goodbye end end
現状でテストを実行すると以下のようにエラーが表示されます。
test_hello
というメソッドでエラーが起きているのが分かります。
探索の開始
開始コマンドを実行
探索を開始するにはgit bisect start
コマンドを使用します。
探索範囲を設定
git bisect start
を実行したら、バグが混じったコミットを特定するために、探索する範囲を設定します。
git bisect bad
は現在バグが起きている(問題がある)コミットを設定します。二分探索の範囲の終端となります。git bisect good
はバグが発生する前(問題がない)のコミットを設定します。二分探索の範囲の始端となります。
コマンドをまとめると以下になります。
$ git bisect start $ git bisect bad <現時点で把握しているバグの入ったコミットハッシュ/タグなど> $ git bisect good <現時点で把握しているバグの入っていないコミットハッシュ/タグなど>
実行開始と範囲設定はワンライナーで実行することも可能です。
git bisect start bad <現時点で把握しているバグの入ったコミットハッシュ/タグなど> good <現時点で把握しているバグの入っていないコミットハッシュ/タグなど>
実際にリポジトリのコミット履歴を用いて範囲を設定していきます(画像は見やすいように番号を振っています)。
コミットメッセージにも書いてある通り、⑦09a1d50
のコミットにバグが仕込んでありますが、
最新のコミットである⑩の時点でバグに気づいたという想定で進めていきます。
まず、現在バグが起きている地点(範囲の終端)を設定します。
test_hello
というテストが落ちていることから、hello
メソッドでエラーが起きていると想定します。
最新のコミットである⑩はgoodbyeメソッドに関する追加になるため、goodbye
に関するコミットは関係ないと仮定して、⑨を最新のバグが起きているコミットとして設定します。
次に、この時点ではバグが起きていなかったと考えられる地点(範囲の始端)を設定します。
先頭のコミットから見ていくと⓪はReadme、①は空の新規ファイルを追加しただけのコミットになります。
この時点ではバグは起きていないものの、テストを記載する前ということもあり、今回の影響範囲には含まれないと想定し範囲からは除外します。 その次の②ではテストを新規追加しています。今回はテストを追加した時点でバグが起きていたかもしれないと仮定し、②を始端と設定します。
これで、探索範囲が決定しました。
図にすると以下のようなイメージになります。
準備が整ったので実際に探索を行っていきます。
探索はコミットを確認しながらgood
かbad
か設定して手動でチェックする方法と、探索とテストを自動実行させる方法の2パターンがあります。
手動でチェックする方法
まず、以下のコマンドを実行して探索を開始します。
$ git bisect start $ git bisect bad f0a6d92(⑨のコミット) $ git bisect good 78425f8(②のコミット)
bad
とgood
を設定したところ、②と⑨の中央値である⑤ (2 + 9 / 2
)のコミットにチェックアウトされます。
git log --oneline
を見てみると、HEADが⑤の時点に移動されているのがわかります。
⑤に移動したら、テストを行います。
テストが成功したらバグは⑤の後から発生し 逆に、テストが失敗したらバグは⑤より前から存在しているという事になります。
テストを実行してみると、成功することが分かりました。
テスト結果から、このコミットにはgood
を設定します。
good
を設定することで、このコミット以前のコミットは範囲から除外され⑤が範囲の始端となります。
git bisect good
を実行すると、次のコミットに移動します。
例のバグが混入した⑦ (5 + 9 / 2
)のコミットに移動となりました。
ここでテストを実行してみると、もちろんテストは落ちます。
このコードはbad
となるので、git bisect bad
を実行します。
ここで終わりではなく、終端となった⑦と現在の始端である⑤の間の⑥のコミットに移動します。 この結果次第で、バグが混入した時点が決定になります。
テストを実行すると、テストは成功することがわかりました。
git bisect good
を設定するとコミット⑦が最初にバグが混入されたコミットである、というメッセージが表示されます。
これで探索は終了となりました。
エラーが起きた時点のコミットを特定できたので、該当のコミットのソースをより詳細を調査したり、rebase -i
などを用いながら修正をしたりすることが可能になりました。
探索が完了しgit bisect
を実行する前に戻りたい場合はgit bisect reset
を実行します。
無事、探索前のコミット地点である⑩にHEADが戻りました。
その他の機能
good
とbad
を判定していくにあたり、自分が何をどう設定したかわからなくなりそうですが、
git bisect log
を実行することで自分が今まで設定ログを見ることができます。
また現在の探索範囲を表示してくれるgit bisect visualize
というコマンドもあります。
自動で探索
上記の探索を自動で行うコマンドもあります。
探索範囲を指定後、git bisect run <テストスクリプトの実行>
を実行することで、
先ほど手動で行った流れ(テストの実行→コミットの移動)を自動で行ってくれます。
コマンドを実行すると、以下のようなログが表示された後に該当のコミット⑦にたどり着きます。
$ git bisect run ruby sample_test.rb running 'ruby' 'sample_test.rb' Run options: --seed 54666 # Running: "こんにちは" . Finished in 0.000372s, 2688.1720 runs/s, 2688.1720 assertions/s. 1 runs, 1 assertions, 0 failures, 0 errors, 0 skips Bisecting: 1 revision left to test after this (roughly 1 step) [09a1d5037241886b6dca8ad7049140374d529ae6] fix: 変数aをa_numに変更(ここでバグ混入) running 'ruby' 'sample_test.rb' Run options: --seed 65091 # Running: F Failure: SampleCodeTest#test_sample_code [sample_test.rb:8]: Expected: "こんにちは" Actual: nil bin/rails test sample_test.rb:7 Finished in 0.000428s, 2336.4486 runs/s, 2336.4486 assertions/s. 1 runs, 1 assertions, 1 failures, 0 errors, 0 skips Bisecting: 0 revisions left to test after this (roughly 0 steps) [42ac7fd58ccce402b34fe9a51b764687c0655f8c] fix: a + b の結果で分岐する running 'ruby' 'sample_test.rb' Run options: --seed 49017 # Running: "こんにちは" . Finished in 0.000319s, 3134.7964 runs/s, 3134.7964 assertions/s. 1 runs, 1 assertions, 0 failures, 0 errors, 0 skips 09a1d5037241886b6dca8ad7049140374d529ae6 is the first bad commit commit 09a1d5037241886b6dca8ad7049140374d529ae6 fix: 変数aをa_numに変更(ここでバグ混入) sample_code.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-)
テストをちゃんと書いておくことで、より効率的にバグコミットを探すことができることが分かりました。
まとめ
git bisect start
で探索を開始し、git bisect good
で探索の始端、git bisect bad
で探索の終端をセットする- 上記を設定すると範囲の真ん中のコミットに自動で飛ぶので、手動または自動で探索を行う
git bisect log
やgit bisect visualize
など便利なコマンドがたくさん用意されている- テストを書いておくことでより効率的にバグを探索できる
参考記事
git-bisect – Git コマンドリファレンス(日本語版)
git bisectで「誰だよ!バグコミット仕込んだの!」を解決する #Git - Qiita
git bisect で問題箇所を特定する #Git - Qiita
git bisectで「いつの間にか壊れていた」を探して直す(Gitの小ネタおれおれAdvent Calendar 2022 – 24日目) | Ginpen.com
(余談)輪読会の良さみ
私はFBCに入って初めて輪読会というものに参加しました。輪読会に参加する良さは以下だと考えています。
自分にない視点を得ることができる
- 一人で読んでいたらさっと読み飛ばしてしまうようなところも、参加者分のアンテナを張りながら読むことができるので、より深く本の内容を理解することができる。
- 今回調べた
git bisect
もこのコマンドなんですかね?!と輪読会中に話し合ったおかげで興味を持ち、今回の記事を書くに至りました。
- 今回調べた
- 輪読中に分からないことが出てきた際も、参加者の知識で解決することができたり、できなくても関連した知識を共有したり、毎回新しいことを知ることができる。
- 調べた結果分からないままだとしても、自分の中に一度調べたというインデックスが残る。
- 一人で読んでいたらさっと読み飛ばしてしまうようなところも、参加者分のアンテナを張りながら読むことができるので、より深く本の内容を理解することができる。
繋がりを作れる
- 人それぞれ学習スタイルが違う中で、定期的に集まって話せる人がいるだけでモチベーションがかなり違うと感じています。
- 課題で詰まっている時なども、輪読会中の雑談に救われることが多々あります
- 日々忙しくて参加できないということもあると思いますが、興味があるけど参加できていない、、という状態だとしたら(私も最初はかなり勇気がいりました)、ぜひ一度飛び込んでみるのをお勧めします!
- 定期的なイベントの場合、途中参加はハードルが高く感じてしまいますが、単発でも途中で行けなくなってもOKな輪読会がほとんどなので、いつからでも遅くないと思います!
- FBCの漬け水に浸かっている人はみんなとても優しい人ばかりなので、飛び込んでみてください。
- 人それぞれ学習スタイルが違う中で、定期的に集まって話せる人がいるだけでモチベーションがかなり違うと感じています。
積読解消
- 輪読会が5年寝かしていたマスタリングTCP/IPを読むきっかけになりました。
終わりに
自分の知らなかったgitの機能を知ることができてとても勉強になりました。
今回はサンプルリポジトリで動作を確認するだけしかできませんでしたが、課題に取り組む際などに使う機会があれば積極的に使ってみようと思います。
輪読会をはじめ、FBCに入ってから日々自分の知らない知識に出会いながら楽しく学習を進められています。
1日でも早く卒業して就職したい気持ちもありますが、楽しむ気持ちを忘れずに学習を続けていきたいと思います!