デバッグ:最も見つけにくい問題まで解決するための必須ルール(2004)
(dwheeler.com)- David J. Agans の Debugging は、バグが見つかった後に原因を突き止めて修正するための デバッグの基本を扱い、初級・中級開発者だけでなく熟練者にも繰り返し立ち返るべき原則を提供する
- 本書は 9つのルールで構成され、システム理解、失敗の再現、観察、分割統治、変更の管理、監査証跡、前提の点検、外部の視点、修正の検証を実務事例と結びつけている
- 古い技術やコンピューター以外の事例も登場するが、核心は特定のツールではなく 問題を絞り込む思考法にあるため、ハードウェア・ソフトウェアのデバッグ全般に適用できる
- 断続的な問題のように扱いにくいバグには Make it Fail の助言が特に有用だが、Heisenbug という用語を直接扱っていない点は惜しい
- GDB の使い方やテストの書き方を説明する本とは異なり、デバッグの全体像に集中しているため、ツールの使い方や回帰テストは別資料で補う必要がある
本書が対象とする問題
- David J. Agans の Debugging: The 9 Indispensable Rules for Finding Even the Most Elusive Software and Hardware Problems は、バグが検知された後に原因を突き止め、実際に修正する過程を扱う
- 特定の技術やツールよりも、ソフトウェアおよびコンピューターハードウェア開発者に必要なデバッグ原則を整理している
- 初級者と中級開発者に特に適しており、熟練者にとっても切迫した状況で見落としがちな基本を思い出させる役割を果たす
- 経験で身につけるデバッグを、原則と事例に圧縮して伝えている点が強みである
9つのデバッグルール
-
システムを理解せよ
- マニュアルを読み、全体構造を把握し、基本原理と細部の動作を理解する
- 使っているツールが何を見せ、何を隠しているのかもあわせて確認する
-
失敗させよ
- 問題を再実行し、最初から始めて 失敗条件を直接刺激する
- 失敗をまねるのではなく実際の失敗を誘発し、断続的なバグを生む制御されていない条件を探す
- すべてを記録し、統計を過信せず、まれなことが実際に起こり得ると受け入れる
- デバッグツールを捨てず、問題をあぶり出すために活用する
-
考えるのをやめて見よ
- 推測で複雑な修理に取りかかる前に、まず 観察可能なデータを確保する
- 失敗と細部を調べ、内部計測を作るか外部計測を追加する
- 深掘りを避けてはならないが、観察そのものが動作を変え得る Heisenberg 効果に注意する
- 推測は結論ではなく、探索範囲を狭める道具としてだけ使う
-
分割統治せよ
- 逐次近似(successive approximation)で探索範囲を狭め、バグがどちら側にあるのかを判断する
- 目立つテストパターンを使い、悪い状態から出発して原因を絞り込む
- すでに分かっているバグやノイズを先に取り除き、調査対象を単純化する
-
一度に一つだけ変えよ
- 重要な要因を分離し、直す前に何が間違っているのかを理解する
- テストも一度に一つずつ変え、正常なケースと比較する
- 最後に動作していた時点から何が変わったのかを確認する
-
監査証跡を維持せよ
- 何をどの順序で行い、結果がどうだったのかを 監査証跡として残す
- 些細に見える詳細も原因になり得るため、出来事を相互に関連づけて記録する
- 設計過程の監査証跡もテストに役立つため、必ず書き留めておく
-
プラグを確認せよ
- 当然だと思っていた前提を疑い、最初から確認し直す
- 問題を探すために使うツール自体もテスト対象に含める
-
新しい視点を得よ
- 一人で行き詰まったときは、他の人や別の説明方法を通じて新たな洞察を得る
- マネキンに問題を説明するだけでも考えが整理されることがある
- 専門性を活用し、経験者の言葉に耳を傾け、プライドよりも症状の共有と助けを求めることを優先する
-
直していなければ、直ったことにはならない
- 修正後は本当に直ったのかを確認し、自分の変更が実際の原因を取り除いたのかを検証する
- 問題は自然には消えないため、原因とプロセスの両方を直さなければならない
事例が原則を生かす方法
- ルール一覧だけを見ると無味乾燥だが、詳細な説明と 事例談が原則を実際の状況へ引き寄せている
- 多くの事例は技術的な細部にまで踏み込むため、一部の読者には負担に感じられるかもしれない
- 古い技術を扱った事例もあるが、核心は特定の技術ではなく原則なので大きな問題にはならない
- すべての事例がコンピューティングに関するものではなく、家の配線に関する興味深い事例も含まれる
- コンピューターハードウェアとソフトウェアをまったく知らなければ、多くの例を追うのは難しい
- ルール説明の後には、複数のルールが一緒に適用される話、読者向けの簡単な練習問題、ヘルプデスクのヒント、締めくくりの発言が続く
特に際立つ点と限界
- 「考えるのをやめて見よ」という原則が特に重要である
- 多くの人は仮説を証明または反証するデータを集める前に、推測で問題を直そうとするからである
- 「直していなければ、直ったことにはならない」も強く印象に残る原則である
- 修正の有無だけでなく、何が原因で、なぜ直ったのかまで確認しなければならない
- 「失敗を刺激せよ、失敗をまねるな」という議論は、本書の他の部分ほど明確ではないが、理解する価値のある点である
- 断続的な問題は通常もっとも扱いにくく、本書は Make it Fail でそれに対処する直接的な助言を提供している
- Heisenberg には言及しているが、ソフトウェア開発で一般的な用語である Heisenbug は扱っていない
- Heisenbug とは、観察したり隔離しようとしたりすると消えたり動作が変わったりするバグを意味する
他の資料との違い
- 本書は デバッグの基本原則を中心に据えている点で、ツールの説明書やテスト本とは区別される
- Richard Stallman らの Debugging with GDB: The GNU Source-Level Debugger は、主に特定の技術やツールのコマンドを説明している
- Norman Matloff の Guide to Faster, Less Frustrating Debugging のように一般的な助言を含む資料もあるが、Agans の本ほど範囲は広くない
- Boris Beizer の Software Testing Techniques のようなテスト本は、バグを発見するためのテスト作成に焦点を当てており、発見されたバグを修正する方法は相対的にあまり扱わない
- バグを見つけた後は、そのバグに対するテストを回帰テストスイートに追加すべきだが、テストと回帰テストは本書の範囲外である
補足資料と惜しい点
- 本書の関連ウェブサイト debuggingrules.com には、関連情報へのリンクと、ダウンロードして印刷できる9つのルールのポスターがある
- ルールを理解するうえで重要な サブルールの完全な一覧が、本書やウェブサイトの1ページにまとまっていない点は惜しい
- シンボリックデバッガー、デジタルロジックプローブ、ddd on gdb のような一般的なツールや問題タイプについて、より具体的な助言と例があればさらに有用だっただろう
- 同じルールをコンピューティング以外の一般的な問題解決へ広げた別の本も必要に思えるが、本書の事例はコンピューターを扱わない読者には技術的すぎる
- 基本原則は一見すると当然に見えても、初心者は学ぶ必要があり、熟練者も繰り返し思い出す必要がある。本書はその学習と想起に適している
1件のコメント
Hacker Newsの意見
いま存在している壊れたコードに「修正」を継ぎ足して動かそうとする誘惑が、いちばん有害だと思う
壊れたコードは変更できる箇所が多すぎて直しにくく、動いているコードを壊すほうがはるかに簡単
クリスマス用電飾の列が丸ごと点かないときに電球を1つずつ交換してみるやり方は、壊れた電球が複数あると失敗する
代わりに最小動作例から始め、少しずつ追加してエラーが出る地点を探すべきで、実際には最初からやり直したほうが時間を節約できる場合も多い
本番環境のつかみにくい問題で、チームの一部メンバーが問題のルーチンを書き直し、残りがデバッグしている間に、書き直し版のほうが先にデプロイされたことがある
少なくとも一度は、「直った」問題に無限に時間を使うわけにはいかず、元のバグを結局見つけられなかった
ルール0は慌てないこと
締め切りや怒っている顧客は明晰な思考を妨げるので、信頼できる優秀なマネージャーがエンジニアをそのプレッシャーから守ってこそ、問題解決に集中できる
良いマネージャーが私たちを別の通話に移し、「あの人たちの言うことは無視して集中してくれ。私が対応する」と言ってくれて、それ以来そのマネージャーへの尊敬が大きく増した
きちんとやる時間がないのなら、なぜ二度やる時間はあると思うのか
エンジニアが本来の仕事に集中できるよう、上から降ってくるものを防ぐ役割という意味
動いているバージョンに戻したうえで、危機レベルのプレッシャーなしにデバッグできるなら、そのほうがずっとよい
4番「分割統治」には
git bisectが大いに役立つ正常なコミットが1つあり、その後の数十〜数百個の中に悪いコミットが1つある状況なら、数ステップで問題のコミットやコードを絞り込める
使い方の例は https://nickjanetakis.com/blog/using-git-bisect-to-help-find... にある
見知らぬ大規模コードベースをライブコンサル中にこの方法で素早く絞り込めた。そうでなければ、壊れ得る範囲が広すぎただろう
git bisectがあるので、「実際の」ブランチに入るすべてのコミットは個別にビルドでき、その時点で既知のテストを通過し、知る限りデプロイ可能であるべきだという規律を守っているすべてのキーストロークを保存したり、最後の「Fixes.」コミットまで残したりする原則より、これを重視している。そうした原則は二分探索を役に立たなくするからだ
頻繁に使うわけではないが、最も大きく謎めいたバグで一度でも的中すれば、何日分もの手がかりを一気に与えてくれるので十分に価値がある
git bisectの背景にある一般原理を教えてくれた壊れたシステムと動くシステムを比較し、差分を体系的に取り除いて欠陥を見つける方法だ
ソフトウェアやハードウェア以外にも適用できるし、以前、同じジェットスキーが2台あったときには、片方を直しながらもう片方と比較できて助かった
git bisectそのものより、もっと一般的な二分探索の原理コミット範囲だけでなく、システム空間を分割するときにも使える
たとえば10段階のワークフローが壊れているなら、5段階目は正常かを確認したり、ハードウェア問題ではない/であると絞り込んだりできる
問題の原因が、いま二分探索しているリポジトリのコードコミットではない可能性がある場合は特に重要
bisectは素晴らしいが、この本の哲学と思考法である「ルール」と、実用的な助言である「ツール」は区別すべき「どのツールを使おうか」から出発する人は、「これは以前は動いていなかったか?」から出発する人より不利になる
世の中はツールであふれていて、すべてのツールを頭に保存しようとするとおかしくなるので、まず哲学を受け入れるほうがよい
git bisect runについて書いた記事を添えておく。本当に驚くべき小さなツールだhttps://andrewrepp.com/git_bisect_run
正しいファイルを正しいマシンで修正しているか確認しなければならない
最近は必ず、一時的に致命的エラーを起こす行を追加して、いまのファイルが正しいか、状況によっては行も正しいかを確認している
自分の変更が実際に影響しているかを確認するためだ
追加のルールもある
「すべて自分のせいだ」:コンパイラのバグやハードウェアエラーの可能性もあるが非常にまれなので、何よりもまず自分のコード変更を疑うべき
「バグを見つけたら、その家族と友人まで探せ」:同じ種類のことがほかにどこで起きているかを考え、確認すべき
「ユーザーを第一に、保守プログラマーを第二に、コンピューターを最後に最適化せよ」
要約は https://blog.codinghorror.com/the-first-rule-of-programming-... にある
たいていは、エラーを出すコードをより単純なケースに削っていく過程で、自分のロジックのバグを見つけることになる
1、2回は実際に開発者へ報告する価値のある結果が残ったが、たいていはユーザーが数百人以下でテストが多くないライブラリだった
結局、あり得なさそうなコードのエラーではなく CPU の問題だと分かって、大いに安堵した
それでも、原因と結果の連鎖をさらに何度か二分探索して確かめるのが一番よい
「システムを理解せよ:マニュアルを読み、すべてを深く読み、基礎を知り、ロードマップを知り、ツールを理解し、詳細を調べよ」という助言は、やや奇妙に聞こえる
コードにバグがあったら、まず使っているライブラリの700ページのマニュアル全体を読み、関連書籍を7冊読み、1、2か月後になってようやくバグを見るべき、という意味のように見える
実際にこの助言に従うプログラマーが一人でもいるのか気になる
Atwood と Spolsky が Stack Overflow を作ったのは2008年で、人々は「Camel book」のような名前で本を知り、ただ知識を持っていた時代だった
0. https://stackoverflow.blog/2021/12/14/podcast-400-an-oral-hi...
「すべてを深く読め」が必ずしも「使っているライブラリの700ページのマニュアル全体をまず読め」という意味ではない
git bisectに問題があるなら、Stack Overflow の断片をいくつか貼り合わせる代わりに、https://git-scm.com/docs/git-bisect でそのトピックについてもう少し深く理解できるただし、数か月の作業の成果が単一のバグを雑に直すことだと考えるのが間違いだ
目標は、その種のバグを可能な限りすべて本当に直し、そもそも使わなくなることだ
代替案は、知らないシステムに落下傘で入り、理解しないままあれこれ触って、テストが緑になったら PR を出し、これ以上壊していないことを祈るやり方だが、そういうことを日常的にやるのは悪夢に近い
付け加えると、使っているライブラリのマニュアルが700ページあるなら、おそらく間違ったライブラリを使っている可能性が高い
10番目のステップとして、バグを CI テストに追加して回帰を防ぐべきではないだろうか
修正前には CI が失敗し、修正後には通ることを確認すべき
特に5年以上前のコミットがあり、コンポーネント/ライブラリなので IE のための奇妙なハックがかなりあったからだ
テストによっては作成に時間がかかったり複雑だったりし、保守も必要で、テスト群がすべての境界条件を検査できるわけではないことも受け入れなければならない
本番まで行ったバグが再び発生し得るという意味ではあるが、単純なミスなら、ほかの何百という潜在的なミスより再発可能性が高いとは限らない
結局は状況次第で、テストを書くのは無料ではない
深い原因が再び有効になって同じ問題が再発したり、自分が直した事実を誰も知らず、皆がバグがまだあるかのように回避策を使い続けたりする例を数え切れないほど見てきた
「直った!」に到達した後でも、簡単な記録と根本原因分析を残しておくと、ほかの人の役に立つ
テストが長年積み上がった後、CI はどれくらい速く回せるのか、長期的に残し続けることにまだ意味はあるのか
こうした考え方を子どもや自分自身、他の人に植え付けたいなら、少なくとも次のものを薦める
The Martian by Andy Weir https://en.wikipedia.org/wiki/The_Martian_(Weir_novel)
https://en.wikipedia.org/wiki/Zen_and_the_Art_of_Motorcycle_...
https://en.wikipedia.org/wiki/The_Three-Body_Problem_(novel)
To Engineer Is Human - The Role of Failure in Successful Design By Henry Petroski
https://pressbooks.bccampus.ca/engineeringinsociety/front-ma...
https://en.wikipedia.org/wiki/Surely_You%27re_Joking,_Mr._Fe...!
特に「gumption traps」という概念がよい
価値の硬直という罠にはまったなら、どうせ遅くならざるを得ないのだから、意図的にペースを落とし、すでに通ってきたところを見直して、重要だと思っていたものが本当に重要だったのかを確認すべきだ
ただ機械をしばらく眺めているだけでも間違いではなく、釣り糸を見るように見守っていると、小さな事実がそっと「興味はあるか」と尋ねてくる瞬間が訪れる、という一節は人生の指針に近い
出来事が荒い論理の飛躍でただ起きていくだけで、実際にはごく薄い科学的な言葉遊びをかぶせたファンタジーに近いと思う
数年前に似たような記事を書いたことがある。ここで言及されている元の本は読んでいない状態だった
https://explog.in/notes/debugging.html
Julia Evans のデバッグ zine もとてもよい: https://wizardzines.com/zines/debugging-guide/
デバッグに成功したあとも、仕事は終わりではない
“Three Questions About Each Bug You Find” <http://www.multicians.org/thvv/threeq.html> の要点は3つある
このミスはほかの場所にもあるのか、このバグの背後に隠れている次のバグは何か、こうしたバグを防ぐために何をすべきか