俺流デバッグ 入門講座
対象読者
- デバッグの基本を身に着けたい人
- 効率的なデバッグ方法を探している人
注意事項
筆者は、普段Rubyをメインに使用しているため、コード例や効率的なデバッグ方法の具体例はRubyのコードが主になります。
このデバッグ方法は、まだまだ発展途上です。もっと効率的な方法だったり、やりやすい方法はあると思っています。なので、無理に全部を真似る必要はありません。
デバッグをできるようになると何が変わる?
自分や同僚が書いたコードのレビューで見つけたバグの修正がスムーズにできます。
エラーやトラブルをデバッグし解決することで、できるだけ早くサービスを復旧できるようになります。
エンジニアとして働いていると、以下のようなシチュエーションに遭遇すると思います。
- 自分が書いたコードを動作確認したら、よくわからないエラーが起きて、原因特定に時間がかかった
- 自分が書いたコードを本番反映したら、エラーが起きて、すぐに修正しないといけない
- 本番サーバーで実行されるコードの処理時間が長くて、サービス全体のページ表示速度が低下している
- ある時、突然サーバーが高負荷になり、サービスを復旧しないといけない
こんなシチュエーションになったときは、できるだけ素早く対応して、作業を完了させたいです。
これらを素早く対応するために大切な要素の1つは、「デバッグすること」であると思います。
デバッグとは
テストなどによって発見された誤作動・不具合について、その原因やプログラム上での位置を探索・特定し、意図したとおり動作するように修正する作業のことをデバッグという。
引用 https://e-words.jp/w/デバッグ.html
今回は、「問題が起きている箇所を発見して、原因を特定する方法」にフォーカスして説明します。
これは、大きく2つの工程に分類することができます。
- 発生している問題の内容と発生している箇所を正確に把握すること
- 問題の原因を特定すること
それぞれの工程についての詳細を説明します。
問題の内容と発生している箇所を正確に把握すること
まずは、正確に問題を把握することが重要です。
ここを間違えると、問題を解決することが遠のいてしまいます。
正確に問題を把握するためには、3現主義と5W1Hを意識して情報収集することができると、後のデバッグ作業へ移ることができます。
三現主義
三現主義とは現場に実際に足を運び、現物を直接自分の目で確認して、現実をしっかり踏まえてから物事を考え、問題を解決するということです。
3つの現を取って、3現主義と呼ばれています。
この考えは、不良品を使用したお客さんが命を落とすような工業製品を生産・販売しているメーカー等が取り入れているものです。私は、前職で完成車メーカーの生産設備エンジニアとして働いている時に、設備トラブルの対応するために必須な考えとして、活用していました。
エラー対応を3現主義に当てはめるとこうなります。
例えば、エラー対応の時に、自分が一番早く気づいたとします。情報収集する際には、問題(現場)を初めて見ますし、どんな影響(現実)が発生していて、インフラまたはコード上(現物)で起きているのかを調査するかと思います。
この3現主義を活かして、問題を把握していきます。
また、同僚から開発チームへエラーの打ち上げがあった時は、その同僚から状況を聞くことが多いと思います。その方が開発サイドでない場合もあるため、聞いた話をそのまま鵜呑みにすることはお勧めしません。問題を把握するための部分的な情報として扱い、自分自身でも問題を見に行くことは欠かさずにおこなったほうがいいです。
5W1H
一度は聞いたことがあるかもしれません。5W1Hを意識して、問題を把握し、手元でエラーを再現できることにつながることもあり、問題を解決しやすくなります。さらに、5W1H形式で書いておくことで、トラブル対応の一次報告や対応完了報告を上司や同僚へ報告するための雛形にすることもできます。
5W1Hは以下のように書き上げます。
(Who)誰が
(When)どんなタイミングで
(Where)どのページで
(How)どのように操作すると
(What)何が起きるのか
(Why)なぜか
これらを組み合わせるとどうなるのか
3現主義で情報を集めて、それを元に、できるだけ5W1H形式でテキストへ落とし込みます。これを行う理由は、主に自分の頭の中を整理するために利用します。私はいつも落とし込む先は、紙に書いたり、エディタに打ち込んだりしています。落とし込む先は、速くかつやりやすい方法が良いです。そして、情報を俯瞰して見れるなら、紙でもエディタでもなんでもいいと思っています。
例えば、今在籍している会社でトラブルが発生した際は、私はこんなふうにエディタへ書いていました。詳細はぼかしています。
==================
1/27 16:20 #errors チャンネル上からエラーを受け取った。
1/27 16:25 AWSのコンソール上のグラフを見る感じ、エラーを受け取った時刻で大きな変動があることを確認した。
証拠としてスクショ貼っつけた。
1/27 16:28 本番サイトへアクセスして、ログインやページへアクセスできることは確認できたので、Railsのエラーログとアクセスログを確認した。
エラー発生した時刻付近のエラーログを、確認する。
1/27 16:35 問題が発生したログを見つけた。ググる。
このあたり、エラーの概要や発生箇所がわかったため、一旦ここまでで情報収集は終わりで原因特定に動きます。
==================
問題を把握できたら、自分だけで解決できそうか否か関係なく、開発チームや上司へ一報入れたほうがいいです。報連相大事。
上記のようにAWSのインフラで発生していることが分かればいいですが、どこで問題が発生しているかを、いつも簡単に把握できるわけではありません
そこで、問題発生箇所を素早く見つけるための方法を紹介します。
問題発生箇所を探すためには
問題を把握しつつあるが、どこで発生しているかを闇雲に探すことは、時間がもったいないです。
たとえば、登山中にスマホを落としたとしましょう。
どこで落としたか分からない時に、片っ端から山を歩いて、落としたスマホを見つけるのは大変です。
自分が歩いた登山道を記録していれば、その道中の付近にスマホを落としているはずという「ヒント」になるため、スマホを探しやすくなります。
可能な限り早く発生箇所を発見するために、Web アプリケーションから出力されるエラー文や、ログ、DATADOG 等の監視ツールを「ヒント」として活用する必要があります。
開発環境で探す場合
私が所属している会社の開発環境では DATADOG といった監視ツールを活用できません。そのため、ほとんどの場合は、WebアプリケーションやRedisなどから出力されるログやエラー画面に記載されているエラー文を頼りに探します。
自社のプロダクトのソースコードからのエラーの場合、エラー文を頼りに、ググるなり pry-rails Gemやdebug Gemを活用して、問題を起こしてるコードを探す方がいいです。私の一押しはdebug Gemです。
NokogiriのようなGemからのエラーの場合、Githubのレポジトリのissueや公式ドキュメントや Stackoverflowから情報を検索したほうがいいです。いきなりググるよりかは、欲しい解決方法に当たる確度が少し高い気がします。
また、検索するキーワードの選択も重要です。エラー文をそのままブラウザの検索フォームにコピペして検索することもいいですが、「Gem名 どんなことをやりたいか」(できれば英語)で検索すると自分が欲しい答えに出会いやすいです。
例えば、Nokogiri GemであるCSSが付与されているHTML要素を取得したい場合、このようにわたしは、検索します。
nokogiri find css value
とか nokogiri find css class
本番環境で探す場合
本番環境ではアプリケーションのログやエラー文を以外に、DATADOGやRollbarといった監視ツールを活用することができます。
Rails側のコードが原因で問題が発生している場合、Rollbarはデバッグする際の強力な武器になります。発生している箇所が分かれば、上記に記載した検索方法で問題をさらに把握していきます。
RDSやRedisでエラーが発生しているときには、AWSのコンソール等を確認など、情報収集する範囲を広げるしかありません。
このへんは、会社ごとにインフラ環境やプロダクトが異なりますし、トラブルによってケースバイケースなので、深く書きません。
問題の原因を特定する
問題を把握できたら、解決方法を決めるために問題の原因を特定していきます。ここから、本格的にソースコードや設定ファイルなどを確認していくことになります。
手元のコードで、私がいつもやっている原因を特定する作業をお見せできればと思います。
原因を特定するためのヒントは、上記で紹介した3現主義と5W1Hで収集した情報です。
以下のコードで、あるCSVにある一行から平均値を算出したいとします。
numbers = [1 ,2, 99, 23, 56, nil, 123, 343, 44, nil]
puts numbers.sum / numbers.count
平均値を求めるこのコードを実行すると、エラーが発生します。
center_value.rb:3:in `+': nil can't be coerced into Integer (TypeError)
from center_value.rb:5:in `sum'
from center_value.rb:5:in `<main>'
デバッグツールを活用して、問題の把握と原因を特定していきます。
今回使用するツール
私イチオシのデバッグツールです。エラー文の内容から、コードを止める箇所を決めます。今回は puts 部分でエラーが起きているため、その手前で停止するようにします。
require 'debug'
ENV['EDITOR'] = 'nvim'
numbers = [1 ,2, 99, 23, 56, nil, 123, 343, 44, nil]
binding.b
puts numbers.sum / numbers.count
この状態で、コードを実行すると、デバッグツールが立ち上がります。
この中では、Rubyのコードを実行できます。また、1行ずつ実行することや次のブレイクポイントまでステップ処理させることやエディタを立ち上げて、ファイルを編集することもできます。
私は、普段 Neovimを使用しているために、ENV['EDITOR'] = 'nvim'
としています。以下のようにデバッグツールからエディタを起動させることができます。
VS Codeを使用している方は、ENV['EDITOR'] = 'code'
とすれば、VS Codeが立ち上がるかと思います。
あるメソッドを継承したクラスのメソッドがどうように振る舞うのかを確認したい。
require 'debug'
class Fruits
def red
binding.b
puts "りんご"
end
def price
100
end
end
class Vegetables < Fruits
def red
binding.b
super
puts "トマト"
end
end
fruit = Fruits.new
fruit.red
vegetables = Vegetables.new
vegetables.red
実行するごとに、継承元と継承先のクラスそれぞれでコードを止めるのは大変なので、複数箇所に止める箇所を入れていきます。ここではsuper
の挙動を知りたいので、こんな感じで、ブレイクポイントを入れています。
ここで、dubug Gemの便利なcontrol-flowコマンドを活用して、デバッグすることで、継承の挙動を把握することができます。
詳しくはこちらを参照してください。すごい便利で使いやすいです。https://github.com/ruby/debug#control-flow
まとめ
- debug Gem を使おう。
- デバッグするには、一つずつの工程を確実に行うこと。
- 自分だけでデバッグしていて、15分かかって進捗0であれば、ヘルプを求める。
- debug Gemを使おう。重要なので、もう1度
Let’s enjoy coding!!
Discussion