二種類のエラー
Andrej Karpathyは、ソフトウェアのエラーを「予期されるエラー」と「予期しないエラー」の2種類に分類し、それぞれ適切な対処方法(前者は回復処理、後者はクラッシュ)を提案している。
キーポイント
エラーの2分類
ソフトウェアエラーは「予期されるエラー」(正常動作中のエラー)と「予期しないエラー」(バグによるエラー)の2種類に分類される。
予期されるエラーの対処
ユーザー入力エラーやネットワーク障害など、開発者の過失ではないエラーは回復可能であり、エラー結果を返して適切に処理すべきである。
予期しないエラーの対処
アサーションエラーやヌルポインタ例外など、バグを示すエラーは回復を試みず、プログラムをクラッシュさせる方が長期的な信頼性向上につながる。
ログレベルの使い分け
予期されるエラーはWARN/INFOレベルで記録し、予期しないエラーはERROR/FATALレベルで記録すべきである。
影響分析・編集コメントを表示
影響分析
この記事は、エラーハンドリングの基本的な哲学と実践的な指針を提供しており、特にAIシステム開発において堅牢性と信頼性を高めるための重要な視点を示している。Karpathyの経験に基づく洞察は、複雑なAIアプリケーションの開発現場で実用的なガイドラインとして活用できる。
編集コメント
AIシステムの信頼性向上に不可欠なエラーハンドリングの基本原則を、経験豊富な開発者の視点から明確に解説した実用的な内容。
要約すると、私の考えではエラーは2つのカテゴリーに分類されます。予期されるエラー(「ユーザーが無効なデータを入力した」場合など)は通常の運用の一部であり、開発者の過失ではなく、適切に処理されるべきものです。予期しないエラー(「ヌルポインタ例外」など)は開発者の過失であり、バグを示している可能性が高く、クラッシュを許容しても構いません。
エラーハンドリングは、プログラミングとユーザーエクスペリエンスにおいて重要ですが、しばしば軽視される部分です。
長年にわたり、私はソフトウェアにおける2種類のエラーについての見解を培ってきました。これは主にウェブおよびアプリケーション開発のキャリアに基づいていますが、これらの学びが広く適用可能であることを願っています。
私の考えでは、エラーは2つのカテゴリーに分類されます。予期されるエラーと予期しないエラーです。
予期されるエラー
予期されるエラーは通常の操作中に発生します。例を挙げます:
- バリデーションエラー: ユーザーが無効なデータを入力した場合。ユーザーが何を入力するかは制御できません!
- ネットワークエラー: ユーザーのネットワークが失敗した場合。ユーザーがインターネットを切断したり、接続が遅かったりしても、それはあなたの過失ではありません!
- パーミッションエラー: プログラムが何かの実行を許可されていない場合。禁じられたファイルを魔法のように読み取ったり、ユーザーのパスワードを修正したり、ウェブカメラの権限を奪ったりすることはできません!
これらが発生しても、開発者は過失を犯しておらず、それを防ぐためにできることはしばしばほとんどありません。これらのエラーの存在自体はバグではありません(ただし、それらを処理しないことはバグになり得ます)。これらはプログラマーの責任ではありません。
予期されるエラーは回復可能です。これは、警告をログに記録したり、ユーザーにメッセージを表示したり、代替手段(フォールバック)を使用したりすることを意味するかもしれません。
予期されるエラーは、throw、raise、panic してはいけません。代わりに、エラー結果を返すべきです。これは言語ごとに異なりますが、多くの場合、Result型、成功値とnullのユニオン型、またはエラーコードとして表現されます。このパターンはエラーを処理する方向に導き、ソフトウェアの信頼性を高めたいならば、そうすべきです。
予期されるエラーは、WARNまたはINFOレベルのログメッセージを使用すべきです。これは解決すべき問題ではないからです。警告が大量に発生し始めた場合は、アラートを設定することも検討すべきでしょう。
予期しないエラー
予期しないエラーは決して発生すべきではありません。もし発生したら、それはバグです!例を挙げます:
- アサーションエラー: 例えば、関数が空でない文字列を引数に取る必要がある場合、誰かがその契約(前提条件)に違反したことになります。
- ロジックエラー: 例えば、AがBに依存しているのに、Bが適切に初期化されていない場合などです。ヌルポインタ例外も通常は予期しないエラーです。
- 無効なデータエラー: 通常、データベースが有効なデータを返すと想定できます。そうでなければ、どこかにバグがある可能性が高いです。
一般的に、これらのエラーを回復しようとすべきではありません。クラッシュ、panic、throwしても構いません。
さらに踏み込んで言うと、私は予期しないエラーはプログラムを完全にクラッシュさせるべきだと考えることがよくあります。短期的には混乱を招きますが、長期的にはクラッシュがソフトウェアの信頼性を高めると考えています。そうすれば、これらの問題について、自分自身のテストではなく、不満を持つユーザーから報告を受ける可能性が高くなります。
予期しないエラーは、ERRORまたはFATALレベルのログメッセージを使用すべきです。なぜなら、それらは本当の問題を示しているからです。最良の場合でも誤った想定を示しており、最悪の場合、どこかに深刻なバグがあります。
線引き
「予期される」と「予期しない」の境界線は、タスクによって異なります。
一方の極端な例として、プロトタイプや簡単なスクリプトを作成している場合、すべてのエラーは予期しないものとみなせるでしょう。ネットワーク、ファイルシステム、ユーザー入力に関する問題を処理しないと決めるかもしれません。これは単なる小さなスクリプトやアイデアなので、気にする必要はないでしょう。
もう一方の極端な例として、50年のミッションを担う宇宙探査機のコードを書いている場合、壊滅的なハードウェア障害を含め、ほとんどすべてのエラーが予期されるものとなります。
ほとんどのプログラムはその中間に位置し、どのエラーが予期しないものかを判断しなければなりません。例えば、あなたのプログラムにおいてメモリ割り当てエラーは予期されるものですか?それは場合によります。
私の経験では、ソフトウェアをより信頼性の高いものにしたいなら、ますます多くのエラーを「予期される」ものとして扱う方向に向かうでしょう。通常の運用でも、多くのことがうまくいかなくなる可能性があるからです。例えば、私のチームは最近、Node.jsアプリを書いているにもかかわらず、メモリ割り当てエラーに対処しなければなりませんでした。
RustやZigのような一部のプログラミング言語は、多くのエラーを予期されるものとして分類します。一方、JavaScriptやPythonのような他の言語は、それらを予期しないものとして分類します。例えば、GoでJSONをパースする場合、コンパイラはエラーを処理することを強制しますが、Rubyではそうではありません。私は、本番用ソフトウェアにはより厳格なコンパイラを、スクリプトやプロトタイプにはより緩やかな言語を好む傾向があります。これは一部、エラーに対するそれらの言語の哲学によるものです。(読者の中のRust愛好家は、この投稿全体がRustのエラー哲学と非常に似ていることに気づかれるでしょう。)
明確にしておきます。これはあくまで私の個人的な考え方です。このようにエラーを分類することが有用だとわかりました。もしあなたがエラーについて異なる考え方を持っているなら、(1) ぜひお聞かせください。(2) ソフトウェアのエラーハンドリングについて考えていることを嬉しく思います。
原文を表示
In short: in my mind, errors are divided into two categories. Expected errors (think “user entered invalid data”), which are part of normal operation, aren’t the developer’s fault, and should be handled. Unexpected errors (think “null pointer exception”) are the developer’s fault, likely indicate a bug, and are allowed to crash.
Error handling is an important, but often neglected, part of programming and user experience.
Over the years, I’ve developed an opinion about the two types of error in software. This is primarily informed by a career in web and application development, but I hope these learnings are widely applicable.
In my mind, errors are divided into two categories: expected and unexpected.
Expected errors
Expected errors happen during normal operation. Examples:
Validation errors when the user enters invalid data. You can’t control what the user types!
Network errors when the user’s network fails. It’s not your fault if the user turns their Internet off or has a slow connection!
Permission errors when your program isn’t allowed to do something. You can’t magically read forbidden files, fix a user’s password, or steal webcam privileges!
The developer hasn’t made a mistake when these happen, and there’s often little they can do to prevent it. The existence of these errors is not a bug (though failing to handle them can be). These aren’t the programmer’s fault.
Expected errors are recoverable. This might mean logging a warning, showing a message to the user, or using a fallback.
Expected errors should not throw, raise, or panic. Instead, they should return an error result. This works differently in every language, but is often a Result type, a union of null and the success value, or an error code. This pattern pushes you toward handling the error, which you should if you want to make your software reliable.
Expected errors should use WARN or INFO log messages because this isn’t a problem to solve. You may want to set up an alert if you start getting lots of warnings.
Unexpected errors
Unexpected errors should never happen. If they do, you’ve got a bug! Examples:
Assertion errors. For example, a function must be called with a non-empty string, and someone violated the contract if they didn’t.
Logic errors. If Thing A depends on Thing B, but Thing B isn’t properly initialized, that’s unexpected. Null pointer exceptions are also typically a surprise.
Invalid data errors. You can usually assume your database will give back valid data. If it doesn’t, you’ve probably got a bug somewhere.
You should generally not try to recover these errors. It’s okay to explode—crash, panic, and throw.
To get even more radical: I often think unexpected errors should completely crash the program. It’s disruptive in the short term, but I find crashes make software feel more reliable in the long run. You’re more likely to hear about these problems from annoyed users—if not your own testing.
Unexpected errors should use ERROR or FATAL log messages because they indicate a real problem. At best, they indicate an incorrect assumption. At worst, there’s a serious bug somewhere.
Drawing the line
The line between “expected” and “unexpected” depends on the task.
At one extreme: if you’re making a prototype or quick script, I reckon all errors are unexpected. You might decide not to handle problems with the network, filesystem, or user input. Who cares? This is just a little script or idea.
At the other extreme: if you’re coding for a space probe on a 50-year mission, almost all errors are expected, including catastrophic hardware failures.
Most programs lie somewhere in between, and you have to decide which errors are unexpected. For example, are memory allocation errors expected in your program? It depends.
In my experience, if you want to make your stuff more reliable, you’ll trend toward expecting more and more errors. Lots can go wrong on a normal day! For example, my team recently had to deal with a memory allocation error, even though we’re writing a Node.js app.
Some programming languages, like Rust and Zig, classify many errors as expected. Others, like JavaScript and Python, classify them as unexpected. For example, when you parse JSON in Go, the compiler makes you handle the error; not so in Ruby. I tend to prefer stricter compilers for production software and looser languages for scripts and prototypes, in part because of their philosophy about errors. (The Rustaceans among you probably notice that this whole post is very similar to Rust’s error philosophy.)
To be clear: this is just what I think. I’ve found it useful to categorize errors this way. If you think about errors differently, (1) I’d love to hear it (2) I’m glad you’re thinking about error handling in software.
関連記事
今日のまとめ
AI日報で今日の重要ニュースをまとめ読み