本郷ではたらく社長のブログ -7ページ目

とれないバグをとれるようにする3つの方法

最近は、今進んでいるプロジェクトがもうすぐひと段落するので、僕はデバッグをがりがりすることが多いです。バグがあるときになって帰れないので、終電を逃すことが多く、最近は日記を更新できていませんでした(>ω<)

前々から、僕はけっこうプロジェクト終盤でもICPCでもデバッガーとなることが多いのですが、そのときにためた、バグをとるポイントなどまとめてみることにします。

といっても、GDBを使うととれるようなバグは、GDBを使えばとれますのであんまり難易度が高いものではありません。悩んでもなかなかみつからない、なかなかとれない類のバグをとるのが僕は好きですが、そのときに何を考えてとっているのかを紹介します。といっても、僕はここらへんのことは自己流なので、当然もっといい方法があると思っています。なので、コメントなど熱烈歓迎です!!

難しい類のバグをとる際に一番重要なのは、バグにかかわるあらゆる状況をおしなべて把握・検証することです。たいてい、ここが原因だろう、と思っているところじゃないところにバグがあります。そういうところにあるバグだからこそ、とるのが難しいのですけど。といっても、やみくもにコードを眺めていてもなかなか見つかりません。僕は、バグをとるとき、次の3つの方法で、なるべく多くの状況を抜けなくチェックするようにしています。

(1)盲点を突く

前提を捨てることが必要です。このバグはおそらくここが原因だろう、というような前提をもっていると、たいていそこはすでによく自分でもチェックしているはずです。原因だと思っているコードよりも、そうじゃないところにバグは潜んでいます。なので、普段コードを書く上であまり気にしていない部分などを重点的にチェックすることが必要です。

よく抽象化されているコードでも、結合が0というわけではありません。逆に、疎結合であるために、モジュール間の関連がほとんどないと思いこんで、わずかな関連から漏れだすバグを見落としてしまいます。ここはよく抽象化されているから安全だろう、という前提は、デバッグのときには置くことができません。

僕がよくやるのは、コードを、1行1行しらみつぶしにチェックしていきます。どんなに抽象化されていたとしても、ちょっとでも関連があれば、関連しているコードをしらみつぶしにチェックしていきます。ありとあらゆる部分を疑います。疑心暗鬼になるぐらい、徹底的に疑います。複数人でコードを書いている時は、ここは大丈夫だろう、と他の人が指摘している部分をより重点的にチェックします。大丈夫だ、という指摘を、逆に盲点がそこに潜んでいるかもしれない、というヒントとしてとらえればよいと思っています。

たとえば、配列の添え字などは、とるのが難しい典型的なバグの一つです。また、プラスとマイナスを間違えてしまうようなバグも、非常にとるのが難しいです。行単位だけでなく、トークン単位でしらみつぶしにチェックしていきます。最初はしんどいですけど、(2)で紹介するような、バグパターンみたいなものを持っておけば、だんだん慣れていくと思っています。

(2)パターンを蓄積する

毎回、起こりうる状況をすべて考えるのは、必要なことですが、なかなかできることではありません。バグが起こりうるパターンを蓄積していくことも必要です。パターンにとらわれてしまうと、パターンから外れたバグをとれなくなってしまう、という危険性はあるのですが。しかしながら、パターンを蓄積しておき、毎回そのパターンを網羅するようにしたほうが、見落としが少なくなります。見落としが少なくなるメリットの方が大きいので、僕はバグが起こるパターンをできる限り蓄積し、バグをとるときにはそのパターンを網羅的にチェックすることにしています。

このとき、できるかぎりたくさんのバグが起こるパターンを蓄積していくことが必要です。自分が今まで起こしたバグだけではなく、できれば、ペアプログラミングなどによって、他の人がバグを起こすパターンも蓄積し、自分のものにしていく必要があります。

そのためには、できるかぎり、異なるバックグラウンドをもった人となるべく多くペアプロの経験を積むことが一番よいです。僕の場合は、ICPCのときは、他のメンバーがアルゴリズムが強かったので、難しいアルゴリズムを書いていく際に起こりうるバグみたいなものを蓄積していきました。Sedueを作ったときは、システムプログラミングに関してどういうバグがありうるのかを蓄積することができました。

できるかぎり多くの人とペアプロすると、パターンの蓄積ができるのはもちろんのこと、当然のことながら、今まで持っていなかったバグのパターンに出会うことも多く、自分にとって意外性の高いバグが起こるということを経験できます。ようは、パターンを網羅するだけでなく、あえて外してみるタイミング、みたいな直感を養うことができます。なかなか本などで勉強しても、このような経験はできないと思います。難しいバグは、自分が考えていなかった部分にあるものなので、その意味でも、自分が考えていなかったことを気づかせてくれるペアプロは、デバッグ能力を身に付ける上でも有効だと考えています。

(3)わからなかったら一人で悩まない

なんだか当たり前のようなことですが、なかなか職業でプログラムを書いていると、むずかしいのがこの点です。どうしても時間単位でものごとを考えてしまいがちなのですが、1人で5時間なやむより、2人で1時間なやんでバグをとったほうが結果的にはお得です。

一人でなやまないで、相談することのメリットは、相手にバグを探してもらえるというだけでなく、もう一つ重要な点があります。それは、相手に対して、自分が考えていることを伝えるプロセスが発生する、ということです。自分一人で考えていると、考え方が局所的になりがちです。おそらくここがバグっているだろうとそこだけを見てしまいます。ただ、ひとに説明するとなると、バグにいたるまでの過程から、コードの仕組みを詳しく説明することになります。

説明することによって、改めて流れを自分の中でも整理できる、というのは、デバッグにおいても非常に重要になってきます。ようは、説明するときに、説明になやんだり、ぱっと出てこなかった部分にバグが潜んでいる可能性が高いです。(1)の盲点とも関係してきますが、盲点となっている部分を列挙するために、ひとに説明するということは非常に有効です。

大体僕はこんなことを考えて、いつもデバッグをしています。思考としては、適度に発散させるのがコツなんではないかと。できるかぎりいろいろなことを発想して、可能性を探ると、とれないバグはとれると思います。あとは、ディスプレイに対する動体視力は、意外と重要かもしれません。結局は、早くデバッグするためには、できるかぎり多くの情報を一度にみて、パターンにマッチさせていくことが重要なので、パターンをあたりまえのように把握できるようにして、あとは単位時間あたりに見れる情報量を地道に増やしていくのがよいのだと思います。

バグは、入らないのが一番だし、バグとりの間は、あんまり生産をしているわけでもありません。ただ、ひとつのバグがシステムを止めてしまうこともあるので、決して無視できないポイントです。あんまり生産的な活動でないからこそ、すばやく対処するために、自分の中で方法論を確立していくことが必要だと思います。逆にいうと、デバッグ作業は、GDBなど使ってチェックするのは方法として当たり前になっていますが、それ以上のレイヤーになると行き当たりばったりになりがちです。行き当たりばったりだと、効率的なデバッグは難しいのではないかと思います。

今回は、僕はこうしているよ、という一例をしめしただけなのですが、自分なりの方法論さえ確立できれば、それによって、複雑なバグとりに対する意識みたいなものは確固たるものになるのではないかと思います。そうなれば、常に神経を払ってバグをとれるようになるのではないかと。TODOリスト的なものは、スケジュール管理においても、生産性をあげるためにも重要ですけど、それと同じような仕組みを、自分の中に確立できればよいのではないかと。

帰宅

というわけで、帰宅しました。

なんだかんだで僕はデバッグが好き(?)なので、デバッグはじめちゃうと終わるまで家に帰れないw
というかバグがあるともやもやしていて、もやもやしていると眠れないのですね。
Sedueを開発していたころを思い出します。

Sedueは、初めてPFIが世の中に出した製品なのでとても思い入れが深いのですが、
冗長化とか自動復旧とか、自動管理とか、つめこみたいものをつめこみまくったので、
かなりデバッグはしねるプロダクトでした。けっこうデバッグで徹夜しました。でも、
液晶プロジェクタでログをながしながらバグをとっていく作業は、複数人でやっていたということもあって
エキサイティングではありました。思いっきり負荷をかけて、1日たつと
落ちてしまったりとか。ただ、検索エンジンという性格上、落ちることは許されないので、
そういうバグが見つかると、徹底的に精査してバグをとったり。

バグとりは、けっこう一人でとるのは、精神的にしんどい。だからこそ、チーム開発が生きてくるとも
思っています。たくさんの人数で別々のコードを書くよりも、
お互いのコードを完璧に理解しあった3人のほうが、ずっと生産性が高いと思います。
抽象化により、モジュール間の結合を緩くすることによって、問題の切り分けはずいぶん楽にはなりますが、
やはりシステムプログラミングでは、完全に疎結合にすることは難しいです。どんなシステムでも、
ある一定以上の規模になると、複雑な部分は必ず存在します。そういう部分の開発だけでも、
3人ぐらいのコアチームで開発できると、生産性はぐっと上がるのかと思います。Sedueは、だいたい
3チームぐらいで開発していて、インターフェイス作るチーム・検索コアを作るチーム・システムを作るチーム
がいたのですが、どのチームも2・3人ぐらいで開発していたので、生産性としてはかなり高かったのではないかと
思います。

ふと昔のことを思い出して、エントリを書いてみました。そろそろSedueも、開発してからけっこうたつので、
近々、Sedueの設計思想とか、いろいろ書いてみたいなあ。

ペアデバッグ

今日の仕事が終わって、いま帰宅中です。

今日は、たなかさんが担当しているプログラムのバグが、かなり手ごわかったので、3時間ほどデバッグのてつだいをしておりました。
とりあえず、致命的なバグは見つかったのでよかったよかった。

マルチスレッドプログラミングは、いつみても難しいです。ロックとか特に。いろいろなリソースを、いろいろな関数が確保して、で、関数同士呼び合ったりすると、もうどこでデッドロックが起こるのかわからない。まあそこで速度的なトレードオフも考えながらロックを埋め込んで、完璧に動作させるのが僕はけっこう好きなんですけど、ギリギリの状況だと精神にこたえる。

ICPCに出ていた時も、僕はデバッガーだったので、ひさしぶりに昔を思い出してしまいました。Sedueを開発したときも、スレッド周りは非常に設計が難しくて、自分たちでライブラリを作り込んで使っていたりしました。DBなんかは、複雑すぎて、デッドロックは避けられないからデッドロックを検知して、原因となるスレッドを終了させてあげるわけですけど、もうちょっと小規模になると、まあデッドロックをなくすことはできるので、知恵の輪を解くみたいなかんじです。

こんな問題も、STMがもっと実用化すれば解決するのでしょう。STMが普及したら普及したで、また新しい問題は出てくるのでしょうけれども、パフォーマンスチューニング的な要素はあれども、プログラムがまったく動かなくなる、という問題はなくなります。そしたら、マルチスレッドプログラミングはもっと敷居は低くなるのかな。なるとうれしいです。