✨ 要約🔬 技術概要
あなたは、忙しいレストランを経営するシェフだと想像してください。ほとんどの時間、あなたは完璧な料理を作り、幸せな顧客に提供しています(これが正常な動作 です)。しかし、時にはトラブルが起こります。オーブンが壊れたり、客が在庫のない食材を注文したり、配送が遅れたりします(これらが**例外(エクセプション)**です)。
コンピュータ・プログラミングの世界では、これらの「うまくいかない事態」は**例外(エクセプション)**と呼ばれます。開発者は、これらのエラーをキャッチして適切に処理し、レストラン全体が火の海にならないようにするための特別なコードを書きます。
この論文は、25の異なる実世界のレストラン(ソフトウェア・システム)に入り込み、スタッフが日々の訓練(テストスイート)の中で、実際にどれほど頻繁にこれらの災難への対処を実践しているかを調査した、フードインスペクター(食品検査官)のチームのようなものです。
調査結果を、分かりやすく整理して解説します。
1. 「訓練」と「本番」の違い
インスペクターたちは、シェフ(開発者)が完璧な料理を作る練習には非常に長けている一方で、オーブンに火がついた時にどうすべきかの練習はほとんどしていないことを発見しました。
統計: チェックした100の調理ステーション(メソッド)のうち、訓練中に実際に問題に遭遇したのは、わずか約21 箇所でした。
例え: これは、100人のうち79人が、火災警報器が鳴っていることすら全く想定せずに料理を続けている火災訓練のようなものです。
2. ミスは実際にどのくらいの頻度で起きるのか?
問題が発生したステーションについて、インスペクターはそのミスがどのくらいの頻度で起きているかを調べました。
統計: 問題が起こり得るステーションにおいて、平均して、問題を試行した10回に1回 しか、実際に問題は発生しませんでした。
例え: 例えば、ステーキを焦がしてしまう可能性があるシェフを想像してください。彼が100枚のステーキを焼いたとしても、焦げるのは10枚だけです。残りの90枚は完璧です。ほとんどの場合、「焦がす」という出来事は稀なイベントなのです。
3. 「稀な」災難 vs 「よくある」災難
インスペクターは、性質の異なる2種類の「災難が発生しやすい」ステーションを見つけました。
「稀な」災難 (8割のケース): 問題が起こり得るステーションの多くは、めったに問題が起きません。例えば、「もし客が『ユニコーンバーガー』を注文したら、激怒せよ」というルールがあるステーションがあるとします。しかし、誰もユニコーンバーガーを注文しないため、シェフが激怒することはありません。
「よくある」災難 (2割のケース): 中には、常に失敗するステーションもあります。例えば、「もし客が『グルテンフリーピザ』を注文したら、激怒せよ」というステーションを想像してください。もし顧客の90%がグルテンフリーピザを注文する場合、このシェフは絶えず激怒していることになります。
ひねり: このような稀なケースでは、「激怒する(例外を投げる)」こと自体が、そのステーションの正常な 動作になっています。論文は、コンピュータのコードがエラーを投げたからといって、それが必ずしも「壊れている」あるいは「異常である」ことを意味するわけではない、と主張しています。時には、そのエラーこそが期待された結果なのです。
4. 「隠れた」エラー
最も興味深い発見の一つは、エラーが発生しているものの、決して「マネージャー(テストスイート)」には見えないエラーについてです。
例え: スーシェフ(副料理長)がお皿を落としたけれど、ヘッドシェフ(料理長)がノイズキャンセリングヘッドホンを装着していて、その音を聞いていない状況を想像してください。スーシェフは素早くお皿を拾い上げ、調理を続けます。マネージャーはすべて順調だと思っていますが、実際にはお皿は落とされていたのです。
現実: 調査の結果、多くのエラーがコードの内部で発生し、安全網(try/except ブロック)によって即座にキャッチされ、トップレベルのテストには到達していないことが分かりました。テストは、エラーが起きたことさえ知らないのです。たとえエラーが発生していたとしても。
5. 「コストのかかる」安全網
最後に、この論文はエネルギーの無駄についても指摘しています。
例え: シェフが、万が一のためにコンロのすぐ横に、巨大で重く、高価な消火器を置き続けている状況を想像してください。しかし、それは年に一度しか使いません。その消火器は持ち運ぶのが重く、場所も取ります。
提案: 論文は、エラーが極めて稀にしか 起きないステーション(前述の「ユニコーンバーガー」の例など)においては、重い消火器を用意しておくよりも、調理を開始する前に注文が正しいかどうかをチェックする方が良い場合があると示唆しています。これにより、キッチンはより高速で効率的になります。
まとめ
この論文は私たちに以下のことを伝えています:
ほとんどのエラーは稀である: 現実の世界ではあまり起こらないため、私たちは「もし失敗したら」というシナリオをテストすることは滅多にありません。
エラーが「普通」であることもある: 特定のタスクにおいては、「失敗すること」がシステムの標準的な動作となる場合があります。
隠れたエラーを見逃している: 多くのエラーは発生し、即座に修正されていますが、そのためテストにはその存在すら気づかれません。
より効率的になれる: 時には、ほとんど起こらない問題に対して、重くて高価な安全メカニズムを使用しており、それをよりシンプルなチェックに置き換えることができるかもしれません。
著者らは、シェフ(開発者)がこれら稀な災難のシナリオを練習するのを助け、どの安全網が重すぎるかを判断するための、より優れたツールが必要であると提言しています。
技術要約:例外的な振る舞い:それらはどの程度の頻度でテストされているのか?
問題提起
例外(Exception)は、発生頻度が低いと予想されるエラーケースを処理するための基本的なプログラミング構成要素であり、過剰な if/else チェックによるコードの煩雑化を避けるために使用されます。既存の研究やベストプラクティスは、高品質なテストスイートは正常系(ハッピーパス)と例外的な振る舞いの両方をカバーして回帰を防ぎ、バグを捕捉すべきであることを示唆していますが、実証的な証拠によれば、開発者は主に正常系の振る舞いをテストしていることが示唆されています。
現在の文献には重大な欠落が存在します。先行研究は、例外がテストレベルまで伝播することを明示的にアサートする(例:assertRaises を使用する)「例外テスト」にほぼ排他的に焦点を当ててきました。これらの研究は、例外がテストへと伝播する前に、コード内で(try/except ブロックを介して)ローカルに捕捉され、処理されるケースを考慮していません。その結果、例外的な振る行為が、テストハーネスに伝播しないものを含め、現実世界のシステムにおいて実際にどの程度実行されているのかは不明なままです。さらに、実行時に例外が発生する頻度(稀な発生から、例外が常態となる振る舞いまで)についても、テストの観点から深く探求されていませんでした。
手法
著者らは、25の現実世界のPythonシステム のテストスイートを分析する実証研究を実施しました。データセットには、人気のあるサードパーティ製ライブラリ(Flask、Requests、Pylintなど)と標準ライブラリのモジュール(email、pathlib、argparseなど)が混在しており、5,372個の実行されたメソッド 、1,790万回のメソッド呼び出し 、および140万回の発生した例外 を網羅しています。
研究では以下の手法を用いました:
インストルメンテーション: 著者らは、Pythonの sys.settrace 関数に基づいた動的解析ツールである SpotFlow を利用しました。このツールは、メソッドレベルでの実行を監視するためにテストスイートにインストルメンテーションを施します。
データ収集: テスト実行中に、インストルメント化されたシステムは以下を記録しました:
どのメソッドが実行されたか。
特定のメソッドが実行時に例外を発生させたかどうか。
各メソッドへの総呼び出し回数。
例外を発生させた呼び出しの回数(例外発生呼び出し)。
分析: 収集されたデータは、異なる粒度に焦点を当てた3つのリサーチクエスチョン(RQ)に答えるために分析されました:
RQ1 (メソッドレベル): 実行時に例外を発生させるメソッドはどのくらいあるか?
RQ2 (コールレベル): 例外を発生させるメソッドへの呼び出しは、実際にどの程度の頻度で例外につながっているか?
RQ3 (システムレベル): 例外を発生させるメソッドおよび呼び出しは、異なるシステム間でどのように変化するか?
主要な結果
RQ1: 例外発生メソッドの普及度
実行されたメソッドの 21.4% (5,372個中1,150個)が、テスト実行中に少なくとも1つの例外を発生させました。
残りの 78.6% は例外のないメソッドでした。
例外を発生させるメソッドは、例外のないメソッドよりも複雑であり、より集中的に実行されていました。中央値において、例外を発生させるメソッドは、例外のないメソッドよりも 4倍多くの呼び出し を受け、3倍多くのパス を実行していました。
本研究では 200種類の異なる例外型 が特定されました。その多くは汎用的なもの(例:ValueError、TypeError)でしたが、特定の例外は単一のメソッドによって発生する場合が多く見られました。
RQ2: 例外発生の頻度
例外を発生させるメソッドにおいて、例外発生呼び出しの中央値の頻度は 10% (10回の呼び出しにつき1回)でした。
例外発生頻度の分布は、高度に偏っています:
稀 (≤10%): 例外を発生させるメソッドの50%がここに該当します。
時折発生 (>10% ~ ≤50%): 28.4%のメソッド。
頻繁に発生 (>50% ~ <90%): 9.6%のメソッド。
ほぼ常に発生 (≥90%): 11.8%のメソッド。
観察: 例外を発生させるメソッドの**約80%**は、例外を稀に発生させます。しかし、かなりの少数派(約20%)は頻繁に例外を発生させており、特定のPylintのメソッドなどのケースでは、例外を発生させることが「通常」または「期待される」振る舞い(100%の呼び出しで発生)となっています。
RQ3: システムによる差異
25システム中22システム において、例外を発生させるメソッドよりも例外のないメソッドの方が多いことが分かりました。
25システム中19システム において、メソッドあたりの例外発生呼び出しの中央値の割合は 30%未満 でした。
システム間には大きな差異があります。例えば、csv ライブラリは53.3%の例外発生メソッドを持っていましたが、Error Corrector はわずか5.6%でした。
意義と示唆
本論文は、ローカルで処理される例外を無視しているため、現在の例外的な振る舞いの理解は不完全であると主張しています。研究結果は、例外の発生が常に「異常」または「稀な」イベントであるという仮定に疑問を投げかけています。
著者らは、研究者および実務家に対して以下の3つの主要な示唆を提案しています。
新しいテストツールの開発: ほとんどの例外発生メソッドは、実行頻度が低い(多くの場合10%未満)ため、既存のテストスイートではエラーハンドリングコードの多くが未テストのままになっている可能性があります。著者らは、例外が発生し、かつそれがローカルで捕捉されてテストへと伝播しないケースも含め、例外的なケースをカバーするテストを特定できるツールの開発を提案しています。このようなツールは、エラーハンドリングロジックにおけるカバレッジの欠如を開発者に警告することができます。
「異常な」振る舞いの再評価: 本研究は、例外の発生が必ずしも「異常」ではないことを強調しています。特定のメソッド(「ほぼ常に発生」のカテゴリーにあるものなど)においては、例外を発生させることは標準的な制御フローの一部です。例外的な振る舞いのテストに取り組む研究者は、例外が発生することがバグやエッジケースを示すメソッドと、例外の発生が期待される結果であるメソッドを区別しなければなりません。この区別を怠ると、テストの品質やバグ検出に関する誤った評価につながる可能性があります。
コストの高い try/except ブロックのリファクタリング: Pythonにおいて、EAFP(Easier to Ask for Forgiveness than Permission:許可を求めるより許しを請う方が容易)というコーディングスタイルは、しばしば try/except ブロックを導きます。これは例外が稀な場合には効率的ですが、例外を捕捉することは計算コストがかかります。本研究は、例外を頻繁に発生させるメソッド(例:90%以上の呼び出しで KeyError や AttributeError が発生するもの)を、リファクタリングの強力な候補として特定しています。これらを明示的なチェック(例:if key in dict)に置き換えることで、パフォーマンスを大幅に向上させることができます。
結論
本実証研究は、伝播する例外とローカルで処理される例外の両方を考慮に入れ、現実世界のPythonシステムにおいて例外的な振る舞いがどの程度の頻度でテストされているかについて、初の包括的な視点を提供します。結果は、ほとんどの例外発生メソッドは低頻度で実行されるものの、コードベースの非自明な部分が例外を頻繁な制御メカニズムとして利用していることを示しています。著者らは、これらの振る舞いをテストするためのより優れたツールが必要であること、また、例外が頻繁に発生するコードパターンにおいてパフォーマンスの最適化が可能であることを結論付けています。
毎週最高の computer science 論文をお届け。
スタンフォード、ケンブリッジ、フランス科学アカデミーの研究者に信頼されています。
受信トレイを確認して登録を完了してください。
問題が発生しました。もう一度お試しください。
スパムなし、いつでも解除可能。
週刊ダイジェスト — 最新の研究をわかりやすく。 登録 ×