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

Line 1: Error: Invalid Blog('by Esehara' )

または私は如何にして心配するのを止めてバグを愛するようになったか

>> Zanmemo

あと何かあれは 「esehara あっと じーめーる」 か @esehara まで

エラーメッセージを読む

はじめに

 そういえば、周りの人がコードを書いているときに、自分はやっているけど、意外と人がやらなくて、あまり意識されず、言及されないことの一つに、エラーコードを読むというのがある。

 こういうことを書くと、「えっ、普通は読むでしょ」という話になるんだけど、「これ動かないんだけど」と言われるコードが、エラーコードに書いてあることをそのまま修正すれば直ったことがあったり、またその場ではわからなくても、検索して一番最初に出てきたStackOverflowの修正でことを足りることがあったりして、なんだろうなーと思って考え始めたのがきっかけ。

 そういうことについて、「ンモー、なんでエラーメッセージを読まないの」と思う自分がいるのだが、一方でじゃあ「エラーメッセージの読み方」なんて、誰も教えてくれなかったことに気がつく。

 書いていて、えらく当たり前のことを書いているな、と自分でも思ったけど、もしちょっと始めたばかりの人にエラーについて教えるなら、こういう話をするかな、みたいに読んでくれるといいです。

エラーメッセージの目的

 まず最初に明確にしなきゃいけないのは、エラーメッセージの目的だ。当然のことながら、エラーメッセージは、あるプログラムが処理をしようとして失敗したことをお知らせするためのメッセージだ。

 あなたが使っている言語が相当先鋭的でない限り(つまり、成長過程であるわけではない限り)、通常以下の情報が表示されるはずだ。

  • どのファイル(ライブラリ)
  • どの行で
  • どの位置で
  • どんなエラーか発生したか

 自分はPythonを使っているので、Pythonを引き合いにするとするなら、例えば文法間違いを示すところのSyntax Errorであったり、あるいは定義されていない関数を呼び出そうとしたさいのName Errorなんかがある。

 当然のことながら、実行前に解析してエラーが出る、というよりも、実行時にエラーが起きてしまうことのほうが諸事情は厄介なときがある。  

スタックトレースを読む

 最近のプログラミングの場合、普通はクラスのメソッドが連鎖的に呼び出されたりすることのほうが多いだろう。その場合、それが起きたエラーの位置が問題ではない可能性がある。  

# -*- coding: utf-8 -*-

class InvalidClass(object):
    true_attr = 1


def should_error():
    ins = InvalidClass()
    print(ins.undefiend_attr)


if __name__ == "__main__":
    should_error()

 例えば、このようなコードはshould_error関数の内部におけるins.undefined_attrという部分でエラーになる。当然で、InvalidClassには、undefined_attrという属性がないからだ。そういう場合は、InvalidClassを見に行く筈だ。

 もちろん、このように「浅い」エラーの場合は、普通は近場を見ればいいことになるし、そうなっているのが、たぶん自分としては嬉しいコードなのだが(むしろ、このような側面から、細かく関数を定義する習慣は悪いものではないし、その変数が使われている位置と余りにも離れていたり、あるいはガンガン属性を変更したりしていると、何が発生しているのかわからないことが多くなる)、しかし例えば「ある関数の中で、他のところで定義されたものを使っている」という場合、どのような関数として呼び出された流れなのかをチェックすることがある。

 上の場合なら、should_error関数が呼び出され、print関数が呼び出される。このような一連の流れを「コールスタック」と呼んでいたと思う。そして、このような一連の流れを「エラースタック」と呼んだりする。ちなみに、スタックはデータ構造に関係している。

まず、一息ついて調べる

 例えば、最近の事例だと、下のようなエラーが発生した。

print("{}".format("foobar"))

 これは、Python2.7なら動くけど、Python2.6では動かない事例だ。この場合、zero length field name in formatというエラーが出る。これは、Python2.6の場合、文字列にバインドされているフォーマットに対して省略が出来ないことと関係している。

 もちろん、これに詳しい人がいるならその人に聞いたらいいと思うけど、寡聞にして詳しくない場合、検索してみるといいと思う。自分は、大抵「エラーメッセージ + プログラミング言語」とかで調べているが、フレームワークを利用している場合は、それを含めて検索する。

 大抵の言語であるならば、その問題については、既にぶち当たっている人がいると思う。エラーメッセージのみで調べる場合は、そのライブラリが自作とか、あるいは利用者が誰もいないとかではないのならいいと思う。

 英語はスラスラ読める必要はないが、読むのが苦ではないくらいには、読めたほうがいいとは思う。というのは、もしそのエラーについて詳しく書いてあるところがあったとしても、日本語であるとは限らないからだ。もし、英語で書かれているからスルーするということになると、結果として、そのエラーの周りをぐるぐると周り続けてしまったりする。

 ちなみに、上記については、Stackoverflowを見て知った。

エラーを起こすという観点からのユニットテスト

 自分は基本として、自動的にテストコードを走らせるようなことをやっているのだけれども、PythonやPHPなどのコードは、実際にそれを走らせて見ないと結果がわからないというところがある。例えば、下は実際にあったコードだ。

def should_error(error_dict):
    if error_dict["foo"]:
        print(error_dict["bar"])
    elif not "foo" in error_dict:
        print("not found foo key")

 これの意図は、元々は連想配列において、fooがあるかどうかをチェックし、それを処理するものだったようなのだが、しかし、キーの存在チェックを先にやらないために、意図した動作にならないということだった。

 こういう間違いについては、正直ユニットテストなどを利用して、実際に「こういう意図で動くはずだ」というのをテストで実行するのがたぶんいいのかな、と思う。誰しも、こういう凡ミスはやるものだし、自分も、割とタイプミスを起こすタイプの人間なので、定期的なテスト実行によるエラーはとても助かっている。

まとめ: エラーは友達、怖くないよ

 正直なことをいってしまうと、エラーというのは怖いし、なんとなくメッセージがずらずらと流れて来て、さらに英語だったりすると拒絶反応を起こすのも仕方なくて、だからどちらかというと致命的なエラーじゃない限り、なんとなーく処理してくれる言語のほうが好まれる場合もあるのは事実なんだと思う。

 とはいえ、自分みたいにバグと隣り合わせコーディングをしていると、エラーメッセージは基本的に頼れるヤツだし、エラーメッセージも、自分が何をやってエラーをしているのか教えてくれているわけだから、ちゃんと読んでくれるといいなあと思います。

 ちなみに、データベースの障害とかに関しても、例えばmongodbなんかは、そこそこ使えるログを吐いて死んでる場合もあるので、まずエラーメッセージが存在しているかみたいなのを確認する習慣というのは損じゃないかなと思います。