title | tags | author | slide |
---|---|---|---|
PHP の Error/Exception の分類方法やまとめ方を考えてみたときのメモ。 |
PHP JavaScript error exception メモ |
ndxbn |
false |
Logic | Runtime | 意味 |
---|---|---|
Domain | Range | 変数が、事前に定義された範囲から逸脱してる。 |
Out Of Range | Out Of Bounds | 無効なインデックス、キー。 |
Invalid Argument | Unexpected Value | 関数側か、関数の呼び出し側か。 |
Length | Overflow / Underflow | すでに長さが変だった / 操作したら長さが変になる。 |
説明 | Logic | Runtime | 説明 | 共通してそうなもの |
---|---|---|---|---|
a value does not adhere to a defined valid data domain. | Domain | Range | indicate range errors during program execution. Normally this means there was an arithmetic error other than under/overflow. | 変数が、事前に定義された範囲から逸脱してる。 |
when an illegal index was requested. | Out Of Range | Out Of Bounds | if a value is not a valid key. | 無効なインデックス、キー。 |
xxx | Invalid Argument | Unexpected Value | xxx | 関数側か、関数の呼び出し側か。 |
if a length is invalid. | Length | Overflow / Underflow | xxx | すでに長さが変だった / 操作したら長さが変になる。 |
https://www.php.net/manual/ja/class.logicexception.php と https://www.php.net/manual/ja/class.runtimeexception.php
LogicException は 「プログラムのロジック内でのエラーを表す例外です。 この類の例外が出た場合は、自分が書いたコードを修正すべきです」。
RuntimeException は「実行時にだけ発生するようなエラーの際にスローされます」。 これは、LogicException と比べて考えてみると、「運用で作られたデータや、ネットワークが切れたなど、外部要因によって発生する」。 入力されたデータ、渡された引数、実行環境、前提となる条件や要件 などを修正すべき。
「データ構造」と「アルゴリズム」で考えがちかなって思ったのだけれど、 Logic というのは「データ構造とアルゴリズム のセットで成り立つもの」なので、そうではない。
LogicException は内部要因で発生する例外。
RuntimeException は外部要因で発生する例外。
ここでいう外部と内部は、コード実装者にとっての外部内部。
例えば「ゼロ除算」はその変数の型を「ゼロ以外の整数」と定義すれば LogicException になる。なので、前提条件というか、時と場合によって外部・内部は変わるんじゃないかなぁと重たt。 しかし、インターフェース設計をするときに、わざわざ「ゼロ以外の整数」はあまりにも Promitive な型なので、ユーザ定義型を用意するのはコスパ悪く、普通はやらない。
例外ケースになることのほうが圧倒的に多いことが容易に想像できるのなら、型を定義するでしょうけれど、その型定義の中で使う例外はなにになるんだい?という話になる。
観点が悪い。
単純に、LogicException か RuntimeException か。
数学用語の Domain(定義域: x の値の範囲)と Range(値域: y の値の範囲)と考えると、それぞれが「関数の入力・出力」に対応していることがわかる。 Domain は関数に渡す前に「あれ、なんかへんだな」とわかるし、Range は「実行して戻ってきた値が思ってたのと違う」ということ。
Domain は「事前に定義されたドメインデータ」に含まれない、という例外。 Range は「事前に定義された境界が明確なデータの範囲」から逸脱する、という例外。
RangeException の説明に「アンダーフローやオーバーフロー以外の計算エラーが発生した」とあるので、「なにか操作をしようと思ったら変だった(Domain)」「なにか操作をしたら、変になった(Range)」と言い換えることもできる。 (「ドメインデータに含まれない」は、「使おうと思ったらデータがおかしかった」と言い換えることができるので。)
Domain と Range が同じ意味合いで対応する という話は、RangeException の説明に「これ(RangeEaception)は実行時版の DomainException です」とあるので確定。
単純に、Logic Exception か Runtime Exception か。
Valid な値が、列挙するのが馬鹿らしいほどの数がある場合に使う例外。
データを列挙できるかできないか?という点で、Domain/Range と OutOfRange/OutOfBounds を使い分ける。(という使い分けでいいのか?)
OutOfRange は、配列やタプルに対して、インデックス(ほとんどの場合で自然数)に入っているデータが決まっている場合に、未定義な場所を参照しようとして発生する Null Pointer Exception 的なものだと思う。
例えば foo: [string, number, string]
というタプルにおいて foo[5]
とアクセスした場合に OutOfRange になる。
PHPではタプルを型定義できないけれど、タプルの不整合はコンパイル時に検出可能なので、ドキュメントに書かれている通り「(本来なら)コンパイル時に検出できる」ということになる。結局の所、実装者のミスなので、 LogicException
に属していることに納得感がある。
TypeScript の場合、例で挙げたような foo: [string, number, string]
というタプルにおいて foo[5]
とアクセスしようとすると、コンパイル時にエラーになるので、コードで OutOfRangeException
を throw
する機会がない。
OutOfBounds は、配列やタプルではなく、連想配列や Map に対して、使おうとしたキー名が Invalid な場合に発生する。
これは型で表現できない動的な情報なので、 RuntimeException
に属することに納得感がある。
Invalid Argument は呼び出された関数側で throw する。 普通に引数の Validation したときに Invalid なら投げる。
Unexpected Value は関数を呼び出した側で throw する。 関数を呼び出して戻ってきた値が意図した通りになっているかを確認する用途。
例えば https://github.com/sebastianbergmann/diff/blob/master/tests/Utils/UnifiedDiffAssertTrait.php みたいなやつ。
そもそも Validation の文脈で例外を投げようっていう発想がダメ
そもそも Domain か OutOfBound で迷ってる時点でおかしいんじゃね?
フロントエンドが実装をミスってる可能性もあるので、「一応確認してくれ」という意図で、Domain Exception にしておくのでもいい気がしている。
サーバサイドから見たら「外部から来たデータ」であることに変わりはないので、LogicException は不適切なので、Out Of Bounds にしよう派なので、自分で実装するときは Out Of Bounds にする。
JavaScript には、PHP の例外のような使い方をする(と思われる) Error が 3つ 2つある。
TypeError
: 値が期待される型でない場合のエラーRangeError
: 値が配列内に存在しない、または値が許容範囲にない場合のエラーReferenceError
: 存在しない変数が参照された場合のエラー
これらは、もしかして PHP の Domain/Range OutOfXxx InvalidXxx のそれぞれに対応するんじゃね?と思ったりした。
ReferenceError
は Undefined な 変数 にアクセスしようとした場合の話であって、 オブジェクトの Undefined なキー ではない(はず)なので、PHP で言うところの Error レベルの話で、Exception と対比させるのは間違いかもしれない。
PHP Exception | JavaScript Error | 意味 |
---|---|---|
Domain Range |
Reference |
RangeError と見るべきかもしれない。 |
OutOfRange OutOfBound |
Range |
まぁ、普通に範囲エラーだよな。 |
InvalidArgument UnexpectedValue |
Type |
用途的に考えて同じ。 |
- 受け取った変数の型がおかしい:
InvalidArgument
:Logic- 変数の Length がおかしい:
Length
:Logic - 必要なインデックスがない:
OutOfRange
:Logic - その他、なにかが明らかにおかしい:
Domain
:Logic
- 変数の Length がおかしい:
- (なにか処理)
- 必要なキーがなくなった:
OutOfBounds
:Runtime - Length や数値がおかしくなった、なりそう:
Overflow
Underflow
:Runtime - その他、なにかが明らかにおかしくなった:
Range
:Runtime
- 必要なキーがなくなった:
- 関数呼び出し側でのみ知りうる情報と照らし合わせて、戻ってきた値がなにかおかしい:
UnexpectedValue
こうやってみると、 Length や Domain になっているものがなぜ InvalidArgument ではないのか?という疑問が出そう。
Length や Domain は InvalidArgument のより具体的な例外 って扱いなのかな?
Domain/Range は、コンパイル時に検出可能・不可能って話をしているのだから、意味的には同じになるけれど、実装レベルでは全然違うタイミングになる、みたいなアレはあるかも?わからん
継承関係を再整理すると、こうなるんじゃね?