はじめに
先日、スタック・オーバーフローを見ているとこんな質問が載っていました。
Ruby On Railsで質問に対してのBA機能 - スタック・オーバーフロー
「BA機能」というのはどうやらベストアンサー機能の略らしいです。(BAって略し方は一般的なの??)
それはさておき、僕が気になったのは質問の最後の部分です。
Processing by BestAnswersController#best as HTML
Parameters
{"authenticity_token"=>"DtGJ+4qzzG2PqEJpa7GH9Fb8pQhGDX0cg+w+qhf0tP/9HIIVYabiJeW0rEiL7iydpa5PpjrdR1V1LeGzfOeJjw==", "comment"=>"43", "note_id"=>"36"}
Note Load (0.2ms) SELECT "notes".* FROM "notes" WHERE "notes"."id" = ? LIMIT 1 [["id", 36]]
Comment Load (0.3ms) SELECT "comments".* FROM "comments" WHERE "comments"."id" = ? LIMIT 1 [["id", 43]]
Completed 500 Internal Server Error in 34ms (ActiveRecord: 1.1ms)
ActiveRecord::AssociationTypeMismatch (Comment(#70182053951140) expected, got Fixnum(#15377440)):
app/controllers/best_answers_controller.rb:5:in `best'
> ここで、エラーがでるのですが、どうすればいいでしょうか?
質問は「ここで、エラーがでるのですが、どうすればいいでしょうか?」で終わっています。
この質問に限らず、スタック・オーバーフローのようなプログラマ向けのQ&Aサイトを見ていてよく思うのは、「エラーが出るとそこで止まって先に進めなくなる人が多いんだなあ」ということです。
僕に言わせれば、「えっ、原因はそこに書いてあるとおりやん?」と思うのですが、プログラミング初心者の方は「エラーが出た!どうしよう、直し方がわからない・・・」と右往左往してしまう人が多いかと思います。
そこでこの記事ではプログラミング初心者のために、エラーが出たら何を確認してどう解決すべきか、というポイントをあれこれ書いていきます。
## 解説動画も作りました!(というか、むしろ動画がメインです)
この記事はYouTubeにアップした解説動画との連動記事です。
というよりむしろ、 **動画の方がメイン** で、僕が伝えたい内容を詳しく説明しています。
こちらの記事は動画の中でしゃべっている話を箇条書きしたような内容になっています。
内容をしっかり理解するためにも、ぜひ動画と合わせて本文を読んでみてください。
(ただし、動画の長さが1時間ちょっとあるので、1.5倍ぐらいの再生速度で見ることをオススメします)
[![Screen Shot 2016-06-25 at 10.26.25.png](https://qiita-image-store.s3.amazonaws.com/0/7465/59a61c02-a06e-8a44-49b9-d46497f2e872.png "Screen Shot 2016-06-25 at 10.26.25.png")プログラミング初心者歓迎!「エラーが出ました。どうすればいいですか?」から卒業するための基本と極意 - YouTube](https://www.youtube.com/watch?v=5fyrGslhUcY)
## この記事で使用するサンプルアプリケーション
今回使用するサンプルアプリケーション(Railsアプリ)は、先ほどのスタック・オーバーフローに載っていたコードをほぼそのまま僕が書き写して作ったものです。
エラーも同じように発生させています。
ソースコード(エラーが発生するコード)はGitHubに置いてあります。
https://github.com/JunichiIto/backtrace-sandbox
サンプルアプリを作っていると、設計や実装に関しても「うーん、これはちょっと・・・」と思うところがいくつかあったのですが、そこを指摘しだすとテーマが際限なく広がってしまいます。
なので、今回はあくまでエラーの修正だけにフォーカスします。
ちなみにエラーが発生するのは以下のように、BA(ベストアンサー)を決定しようとしたときです。
![TTPiOgFgor.gif](https://qiita-image-store.s3.amazonaws.com/0/7465/fbf23323-2480-6085-2b9a-acbcee7f2b39.gif "TTPiOgFgor.gif")
それでは以下が本編です。(ぜひ動画と合わせてどうぞ!)
## エラーが起きた箇所を特定しよう
まずは「どこでエラーが起きたのか」を特定します。
特にスタックトレース(バックトレース)を読み解くスキルは非常に重要です!
- 開発環境であれば画面にエラーの発生箇所を表示される。
- 本番環境であればログを読む。ログにもエラーの発生箇所が表示される。
- エラー画面やログを見て、エラーが起きたファイルと行番号を確認する。
- 実際にエラーが起きたのは自分が書いたコードではなく、gemやフレームワークのコードであることも多い。Railsのエラー画面であれば Full trace リンクをクリックすると、gemやフレームワークのスタックトレース(バックトレース)が表示される。
- スタックトレースは下から上にメソッドが呼び出された順番が表示される。スタックトレースを読むことで、エラーが発生するまでにどのファイルのどの行が呼ばれてきたのかを確認できる。
- Railsの場合、[better_errors](https://github.com/charliesome/better_errors) gemを使うと便利で高機能なエラー画面が表示される。
### ちなみに:今回エラーが発生したのはここです
このサンプルアプリでエラーが発生するのは、 `bestanswer = note.build_best_answer(comment: params[:comment])` の行です。
```ruby
class BestAnswersController < ApplicationController
def best
note = Note.find(params[:note_id])
# この行でエラーが発生!
bestanswer = note.build_best_answer(comment: params[:comment])
bestanswer.save
redirect_to note, notice: 'hogehoge'
end
end
エラーメッセージとエラータイプを「ちゃんと」読もう
英語が苦手だからといってないがしろにしていませんか?
ここは絶対に逃げちゃダメです!
- エラーメッセージを読むとエラーの原因がわかることが多い。
- エラータイプ(例外クラス)も確認する。必要であればAPIドキュメントを開き、どういうときに発生するエラーなのかを確認する。
- 英語から逃げるな!!辞書を引いてでも読め!!
- 技術系の英語はよく使われる単語が決まっているので、そのうち自然と覚えられるはず。
ちなみに:今回のエラーメッセージとエラータイプはこちら
今回はこんなエラーが発生していました。
- エラーメッセージ:Comment(#70227754972740) expected, got String(#70227753082960)
- エラータイプ:ActiveRecord::AssociationTypeMismatch
エラーメッセージを直訳すると、「Commentを期待していたが、Stringが渡ってきた」の意味になります。
また、 ActiveRecord::AssociationTypeMismatch
は、APIドキュメントによると、「関連にアサインされたオブジェクトが不正な型を持っていたときにraiseされる」と(英語で)説明されています。
開発環境でエラーを再現させよう
エラーが再現しないのに、当てずっぽうで修正してはいけません。
- 本番環境でエラーが起きている場合は、まず自分の開発環境で同じエラーを再現させる。
- 再現手順がぱっとわからない場合は、ログからユーザーの操作手順を読み解く。
- ログを読むとユーザーがどの画面を開いて、どのリンクやボタンをクリックしたのか、といったことが(だいたい)わかる。
- HTTPステータスコードの意味もちゃんと理解しておく。(200 = OK、500 = システムエラー、等)
変数の中身やメソッドの戻り値、実行された条件分岐等を確認しよう
プログラムの実行順序やデータの中身を正確に把握しましょう。
- puts を使って、コンソールに情報を表示させる。
- Rails.logger を使って、ログに情報を表示させる。
- デバッガ(byebug)を使って、対話的にデバッグする。
- RubyMineを使って、対話的にデバッグする。RubyMineならIDE内でブレークポイントを付けるだけなので、コードをいじらずに済む。
- better_errorsを使って、ブラウザ内で変数の中身やメソッドの戻り値を確認する。
テストコードを使ってエラーを再現させよう
テストコードは実はデバッグするときにも活躍します。
- テストコード内でエラーを再現できると、毎回ブラウザを操作する必要がなくなる。
- エラー再現の手順が面倒な場合は、先にテストコードを書いた方がトータルの工数が抑えられる。
- テストコードがあれば、コードを修正してからデバッガを起動するまでの時間も短い。
- 工数面のメリットだけでなく、テストコードがあれば今後同じエラーの再発を防ぐこともできる。
- ただし、デバッグの工数を抑えるためには、テストコードをそれなりのスピードで書ける必要がある。
エラーを再現させるためだけのRailsアプリを作ろう
もっと気軽に rails new
してもいいんです!
- 元のアプリケーションが大規模で複雑だったりすると、予想外の原因でエラーが起きている可能性もある。
- なので、ある程度原因の予想を立てて、そのエラーを再現させるためだけのシンプルなRailsアプリを作る。
- そのアプリ内でもエラーが再現すれば予想は正しいので、そのアプリ内でデバッグする。
- エラーが再現しなければ予想が外れているので、別の原因を探る。
エラーを修正して動作確認しよう
さあ、これだけ情報が揃えば簡単にエラーを直せるはず!?
- ここまでに得た情報を元にコードを修正する。
- 画面を操作したり、テストコードを実行したりして、エラーが出なくなったことを確認する。
ちなみに:今回のエラーの原因と解決策はこちら
この記事で紹介しているサンプルアプリケーションでエラーが発生していた原因は、本来Commentクラスのオブジェクトを渡すべきところを、String型のIDを渡していたのが原因です。
なので、IDではなく、Commentオブジェクトを渡せば問題が解決します。
class BestAnswersController < ApplicationController
def best
note = Note.find(params[:note_id])
- bestanswer = note.build_best_answer(comment: params[:comment])
+ comment = note.comments.find(params[:comment])
+ bestanswer = note.build_best_answer(comment: comment)
bestanswer.save
redirect_to note, notice: 'hogehoge'
end
end
ご覧のとおり、ちゃんとベストアンサーが選択できるようになりました!
公式ドキュメントやGitHubのissueを読もう
正確な情報を把握するため、ドキュメントやissueもちゃんと読みましょう。
- 必ずしも自分が書いたコードに原因があるとは限らない。gemやフレームワークのバグを踏んでいる可能性もある。
- GitHubのissueを検索すると、そのエラーが実は修正済みの不具合に起因するものだった、と判明したりすることがある。
- もしくは単純に自分がgemやフレームワークの使い方を知らなかったり、大きな勘違いをしているだけ、ということもある。
- GitHubのREADMEやWikiページ、公式ドキュメント(Railsガイド等)を読んで、必要な情報が載っていないかチェックする。
- やはりここでも英語力が必要。 英語から逃げるな!!辞書を引いてでも読め!! (2回目)
gemやフレームワークのコードを追いかけよう
熟練したプログラマだと、「何かあったらコードを読む」を習慣づけている人も多いです。
- エラーが簡単に解決しない場合は、gemやフレームワークのコードを追いかけることで解決する場合がある。
- デバッガ(byebugやRubyMine)やbetter_errorsを使って、どこでどんなコードが呼ばれているのかを深掘りしていく。
- gemやフレームワークのコードを読むのは初心者の人には敷居が高いかもしれないが、こうしたスキルものちのち必要になってくるはず。早い内から慣れておく方が良い。
エラータイプ(例外クラス)やエラーメッセージでググろう(ただし過信は禁物)
一番簡単、だけど一番危険な方法です。
- エラータイプやエラーメッセージでググると、ネット上であっさり解決方法が見つかったりすることも多い。
- ただし、情報が古かったり、適切でない解決策が載っていたりすることも多々あるので、くれぐれも過信は禁物。 公式ではない情報は疑ってかかること!!
- 意味もわからず盲目的にソースコードをコピペしたりするのも危険。
- 「コピペしようとしているそのコード、自分で説明できますか?」を自問自答する。説明できないならまず、コードの意味を理解することから始めること。
- Stack Overflowの情報はベストアンサーよりも投票数に着目する。(参考)
やみくもにデバッグせず、仮説と検証を繰り返そう
コードを書くときだけでなく、デバッグするときも論理的に取り組みましょう。
- コードを触る前にまず、どこにエラーの原因があるのか、当たりを付ける。
- 「これがエラーの原因である」と仮説を立てたら、その仮説が正しいかどうかを検証できるような変更を加える。(仮説と検証)
- もし仮説が外れていたら、別の仮説を立て、その仮説を検証する。
- このように、エラーが解決するまで仮説と検証を繰り返していく。
二分探索法でエラーの原因を徐々に絞り込もう
大きな的(まと)から小さな的へ、原因を段階的に絞り込んで特定するアプローチです。
- 問題の原因がハッキリしない場合は二分探索法的なアプローチを取る。
- まず、問題の原因をAとBという大きな二つのグループに分ける。
- AとBを調べて、Aの中でエラーが出たら、Aのどこかにエラーの原因がある。
- さらにAを、CとDというグループに分ける。
- Cではエラーが出ず、Dでエラーが出たら、Dのどこかにエラーの原因がある。
- さらにDの中で・・・という手順を繰り返していくと、エラーの原因が特定できる。
2018.8.21追記
二分探索法の具体的な手順について記事を書きました。
こちらもぜひ参考にしてください。
二分探索法でコードの構文エラーの箇所を特定する - Qiita
2018.12.26追記:デバッグは「うまく動かないピタゴラ装置の原因調査」だと考えてみよう
書き始めたら非常に長くなってしまったので、独立した新しい記事として公開しました。
というわけで、この考え方については以下の記事をご覧ください。
デバッグは「うまく動かないピタゴラ装置の原因調査」だと考えてみよう - Qiita
どうしても解決しなければ誰かに質問しよう
ここまで説明してきたテクニックやアプローチをもってしても解決しないなら、誰かに聞きましょう。
初心者に限らず、上級者でもダメなときはダメです。
-
頑張ってもダメなときは誰かに質問する。
-
職場であれば同僚のプログラマに質問する。
-
誰かに説明している途中に「・・・あ、ごめん。原因がわかった」と自分で解決できることもよくある。(テディベア効果)
-
身近に質問できる人がいなければ、ネットのQ&Aサイト(Teratailやスタックオーバーフロー等)で質問する。
-
ネットで質問する場合は、回答者が答えやすくなるように説明する。(参考)
時間を決めてデバッグしよう/気軽に質問できる雰囲気を作ろう
ゲームと一緒で、デバッグも熱中するとあっという間に時間が過ぎてしまいます・・・。
- 延々とデバッグに時間を浪費しない。2時間も3時間も浪費してしまうのは時間のムダ。(特に仕事でコードを書く場合)
- たとえば「30分やってもダメなら諦めて誰かに聞く」と自分で時間を決めてデバッグする。
- チームで開発する場合は気軽に質問できる雰囲気を作る。(みんなで話し合って意識あわせをする)
- 答える方も快く答えてあげる。「えっ、そんなことも知らないの?(苦笑)」とイヤミを言ったりしない。
- トータルで見て生産性が上がるオプションを選ぶ。(何時間も一人で悩むのと、誰かに聞いてぱっと解決するのではどちらが生産性が高いか?)
ハマったときは一度コードから離れてみよう
「一晩寝かせてみたら一発でわかった!」なんてこともよくあります。
- 何時間やっても解決しない(つまりハマってしまった)、というときは一度コードから離れてみる。
- 外を散歩したり、お風呂に入ったり、ぐっすり眠ったりすると、全然違う視点やアイデアが浮かんで、さくっと解決してしまうことがある。
- とはいえ、身近に質問できる人がいるなら、さっさと質問してしまった方がたぶん早い。
Qiitaやブログで知見を共有しよう
世の中、意外と同じ問題で困っている人は多いものです。
- 問題が解決したら、Qiitaやブログで原因と解決策を書く。
- 知見をネット上で共有することで、他のエンジニアの生産性向上に寄与できる。
- 場合によっては他のエンジニアから、さらに良い解決策を教えてもらえることがある。
- 自分の書いた記事が話題になれば、知名度が上がり、転職時に有利になったりする(かもしれない)。
- 良い技術記事の書き方はこちらのリンクを参考にする。
解説動画の紹介(再掲)
いかがだったでしょうか?
ここで再度、解説動画のリンクを載せておきます。
(冒頭で述べたとおり、 動画がメインでこの記事がオマケ です!)
プログラミング初心者歓迎!「エラーが出ました。どうすればいいですか?」から卒業するための基本と極意 - YouTube
スクリーンキャストで実際の画面やコードを見ながら説明を聞くと、さらに理解度がアップするはずです。
まとめ
というわけで、この記事では「エラーが出ました。どうすればいいですか?」から卒業するための基本と極意を説明しました。
エラーが発生した箇所を特定する、スタックトレースを読む、エラーメッセージとエラータイプをちゃんと確認する、デバッガを使って対話的にデバッグする、といった基本テクニックが身に付いていれば、きっと「エラーが出ました。どうすればいいですか?」から卒業できるはずです。
それ以外にも素早く問題解決に至るためのテクニックやアプローチをいろいろと紹介してみました。
独学でプログラミングをやっていたりすると、なかなかこういった泥臭い(?)テクニックを学ぶ機会はないかもしれません。
この記事を読んで、みなさんが「エラーが出ました。どうすればいいですか?」から卒業できれば幸いです。
あわせて読みたい
自分のブログにこの記事を書いた動機をもう少し詳しく書きました。
よかったらこちらもどうぞ。
「エラーが出ました。どうすればいいですか?」から卒業するための記事をQiitaに書きました - give IT a try
この記事にも登場するプログラマ向けQ&Aサイト、スタック・オーバーフローの使い方を詳しく解説したブログ記事です。
今夜わかる「スタック・オーバーフロー」の世界 - give IT a try
「**英語から逃げるな!!辞書を引いてでも読め!!**って言われてもどうすれば・・・??」と思っている方は、こちらのブログ記事にヒントが載っているかもしれません。