ここのところちょっと時間に余裕があり、暇を見つけてはQiitaの質問に答えるという取り組みをやっています。以前StackOverflowでも同様の取り組みをちょっとだけしてたことがあります。
9日間で35個の質問に回答してみて、正直に思うのは「質問の質が悪すぎるなー」ということです。ただ、どう質が悪いのか上手く言語化できず悶々としていました。
そんな折、今朝googleのおススメ記事に飛び込んできたQuaraのこちらの回答を読んで、「これこれ!こういうことよ!」という気持ちになったため、これから質問する人に向けてこの内容を少し嚙み砕いてまとめてみます。
ベテランはどうデバッグをしてるのか
(自分をベテランと言っていいのかはさておき)日頃からコードを書いていると、デバッグには、その時使っている言語やフレームワークによらず、ある程度の行動パターンがあることに気付いてきます。
デバッグには難しいDebuggerを駆使しなきゃいけないんじゃないの?と思われる方もいるかもしれませんが実はそんなことはなくて(中にはそういう方もいると思いますが)、自分の場合Debuggerはほとんど使わず、Chromeの開発ツールと、jsならconsole.log
、pythonならprint
、Goならfmt.Print
、Rubyならbinding.pry
でほとんど事足ります。
自分が何か問題に直面した場合の行動パターンはおよそ次のような感じです。
1. エラーメッセージがどこかに出てないか探す
エラーメッセージの表示場所は、サーバ上で実行されるものであればターミナルです。
Webクライアントのバグの場合、ブラウザの開発ツール(自分はChrome派)を見ることが多いです。htmlやcssに関してはElements
タブを見ます。例えばcssのstyle属性にタイポがあった場合、画像のように黄色いアイコンで不適切な属性を教えてくれます。
jsに関しては、Console
タブやNetwork
タブをよく見ます。Console
タブにはjs実行時のエラーがあれば、意図的に握りつぶされていない限り、エラーメッセージが表示されます。
Network
タブではブラウザが実行しているhttpリクエストを確認できるため、例えば使用しているAPIから4xxや5xx系のエラーが返ってくる場合に、リクエストヘッダやレスポンスの内容を確認するために利用します。
1-a. エラーメッセージがどこにもないのに期待どおり動かない場合
この場合、コード実行がそこまで到達していないことを疑います。例えば、if
、switch
、while break
などで意図せず処理が抜けてしまっていることが想定されます。
すごく初歩的なバグのようですが、実はどんなにベテランでも作ってしまうバグ第一位だと思います。
あるいは、jsやpythonなどの動的型付け言語の場合、変数を意図せず上書きしてしまっている、配列(list)やオブジェクト(dict)などミュータブルなオブジェクトをイミュータブルのように扱っている、というのもありがちなパターンです。
1-b. エラーメッセージが表示されている場合
頑張ってメッセージを読みましょう。エラーメッセージが長々と表示されて目を覆いたくなることもありますが、大体重要なことは最初か最後に書いてあります。あと、質問でこれと思しきエラーメッセージだけをコピペする方が多いんですが、どこでそのエラーが発生したかもかなり重要です。エラーメッセージの内容には、エラーが発生しているソースファイルや行数なども書かれていることが多いので、そういったポイントも合わせて確認してほしいです。
ちなみに、エラーの種類は世の中無限にありますが、Qiitaの色々な質問に答えてみた結果、初心者がハマるエラーは大きく2種類のように思いました。
Syntax Error
プログラムとして正しくない構文です。「俺は絶対に間違えてない」と思っても、それは正しくない構文なのです。
例えば、コード末尾の;
を:
と書いていたなどタイポ系があります。こういったエラーはVSCodeなど優れたエディタを使えば問題を教えてくれるので、実行前に気付けるはずです。
また、私が回答した質問でこんな例もありました。
jsファイルをhtmlのheadで読み込んでいる個所で、uncaught SyntaxError: Unexpected token '<'
というエラーが出るというのです。
ローカル環境で再現できず謎だったのですが、よくよく確認すると、githubからjsのソースファイルをダウンロードしたつもりが、表示しているページのhtmlをダウンロードしていた、というコントみたいなオチでした。
ただこれを馬鹿にしたい訳ではなく、言いたいのは、構文エラーと言われたら必ずその周辺に構文エラーが存在するのです。しらみつぶしに探すしか方法はありません。
Type Error
動的型付け言語特有ですが、例えばこんな質問がありました。
プログラミング言語には須らく「型」という概念があります。言語によって表現は多少異なりますが、文字列はstring
型、数値はint
やfloat
型、値無しはnull
型などといったように。
例えばjsでもpythonでも、以下のように同じ型同士の変数を+
演算子で演算できます。数値同士の演算なら足し算、文字列同士の演算なら文字の連結になります。
console.log(10 + 5)
// 15
const foo = "foo"
const bar = "bar"
console.log(foo + bar)
// foobar
では、違う型同士、例えば数値を文字列に+
したらどうなるでしょうか。言語によって仕様は異なりjsだと動いちゃうんですが、pythonだと以下のようにエラーとなります。
foo = "foo"
bar = 15
print(foo + bar)
# Traceback (most recent call last):
# File "<stdin>", line 4, in <module>
# TypeError: can only concatenate str (not "int") to str
文字列と数値を足すってどーゆう意味!?ということで、至極もっともですね。こういった演算をしたければ、数値型のbar
をstr(bar)
とキャストしてやる必要があります。
print(foo + str(bar))
# foo15
動的型付け言語の場合、数値型を変数に格納していたつもりがどこかのタイミングで別の型に変わっていた、みたいなことが起こり得るので
- コードを書きながら常に型を意識する
- 変数を違う型で極力上書きせず、新しい変数を作る
- jsなら
const
など上書きできない定数宣言を使う - Typescriptや、pythonならtype hintingを導入する
- VSCodeなど型の推測が働くエディタを使う
などのようなことに気を付けて、間違いを減らしていくことをお勧めします。
良く分からないエラーだったら
当然、良く分からないエラーメッセージが表示されることも度々あります。
その場合StackOverflowや、エラーの内容が特定のライブラリに関するものであればgithubのissueで同じことを質問している人がいないかを探します。世の中広いので、多くの場合類似の質問が見付かるはずです。
githubやStackOverflowで情報が見付からない場合、ライブラリのドキュメントを探しに行きます。ありがちなエラーのはずなのに誰も質問していないということは、ドキュメントにしっかり対応が書いてある可能性があります。ちなみに、メジャーなライブラリだと英語ドキュメントの日本語訳のページが作られていたりしますが、確実に本家より鮮度が劣るし、何よりカタカタ英語が散りばめられた日本語は何が定義されたプログラミング用語なのかが判然とせず本当に読みにくいので、頑張って英語のドキュメントを読むことを強くお勧めします。
あとは、googleで英語で検索して、検索結果に出てきたMediumなどの英語記事も参考にすることがあります。Qiitaに投稿していてこんなこと書くのも気が引けますが、日本語の技術系記事は経験上ほとんど役に立たないので、全角にまつわることなど「こんなこと日本人か中国人しか検索しねーだろ」みたいな情報を除いて使いません。
2. それでも問題が解決しないなら、対話モードで問題を再現する最小単位のコードを探す
上記が済んで未だ解決策が見付からない場合、対話モードで疑わしいコードを入力して問題を再現してみます。別に対話モードでなくてもいいんですが、コード補完でデバッグに使えそうなメソッドが見付かったりもするので便利です。
jsならnode
、pythonならpython
やipython
、railsならrails c
といったコマンドをターミナルに入力すると対話モードが始まります。jsの場合、手っ取り早くブラウザ開発ツールのConsole
を使うこともよくあります。
問題箇所の探し方でお勧めしたいのは、疑わしいと思うコードから徐々に範囲を広げていく方法です。いきなり全部のコードを点検するのでは効率が悪いので、まずは当たりを付けて試し、そこが正常に動いているなら確認の範囲をコードの前方に少し広げる、ということを、問題を再現できる過不足ないコードが見付かるまで繰り返します。
ここまで特定することが出来れば、あとは経験上からバグの直し方が分かったり、あるいはライブラリの問題ならライブラリのソースコードを読んで解決することがほとんどです。
3. バグは再現できるが直し方が分からなければ
この段階でいよいよgithubのissueやStackOverflowで質問します。
質問をする時には、回答者が自分の問題を再現できるように丁寧に情報を書くことを心がけます。特にStackOverflowは舐めた質問をすると凄い人が秒速で質問を差し戻してくる優しくも厳しい世界なので、すごく気を使いながら質問します。
何が良い質問か、というのはここにまとまっていますが、自分が一番気を付けるのは相手が容易に問題を再現できるかどうかです。
以下のような情報は最低限質問文に含めるようにします。
- OS
- (ブラウザの問題なら)使用中のブラウザ
- 使用中のフレームワークやライブラリとそのバージョン
- 特定のライブラリの問題だとしても、他のライブラリと干渉していることなどもあるので、他の使用中ライブラリもなるべく書くようにします
- ステップ2で特定した、問題を再現できるコード
また、可能であればCodeSandBoxやCodePenなど、実際にバグを再現した環境を作って共有するとより好ましいと思います。
問題が解決したら
教えてもらったままほったらかしにするのではなく、きちんと回答者に問題が解決したことを伝えてお礼を言いましょう。善意で成立している世界なので、回答したのに何もレスポンスがないと残念な気持ちになります。また、どの回答で問題が解決したかを書くことで、他の困った人があなたの質問を読んで救われるかもしれません。