目次
このページに書いてあること
例外クラスの命名は、SOLID原則などと合わせてプロジェクトのクラス分割の方針に合わせて決定するといい感じになりますので、その説明。
例外クラス名の命名どうする問題
大抵のプロジェクトでは命名規則ってあってないようなもんで、特にプロジェクトが炎上しだすとエライことになります。
皆さんは例外クラスの名前づけについて何か軸は持ってますか?
プロジェクトのルールに従うという人もいると思うのですが、私が言っているのはそうではなくて、
プロジェクトのルールが無かった場合、参考にするものもない場合、あなたはどんな名前を例外に付けますか?
という話です。もちろん、プロジェクトのルールに従うのは大事ですが、こういう「自分なりの考えを持っているけど、ルールに従う」というのと、「自分の考えはなく、ただ何も考えずにルールに従う」というのでは雲泥の差があります。
そんな状況で、私の考えた軸を書きます。以下の2つです。
- メソッド名に依存した命名
- クラス名に依存した命名
前提
- クラスの分割はSOLID原則を意識したものになっている(別になってなくてもいいけど、ここでの話は沿ったものとして説明する)
メソッド名に依存した命名
単純です。データベースで発生した例外ならDatabaseException、バリデーションで発生した例外ならValidationExceptionという具合です。
例えば、この場合ではDBの登録でエラーが発生した場合(登録件数が0件だったなど)はDatabaseInsertExceptionとかになりそうですね。DBの更新に失敗した場合(1件更新したつもりなのに2件以上更新されたなど)はDatabaseUpdateExceptionとかになりますね。
例外の意味が例外クラス名で表現されています。
この命名は結構汎用性が高く、クラス名に関係なくメソッド名に依存した命名規則と言えそうです。
自然とこの命名規則になっていた場合は以下の特徴が見られることが多いです。
- SOLID原則に則ったクラス分けはされておらず、役割に応じたクラス分けをされている可能性が高い
- 例外が比較的細かく分かれるため、throws句がやたら多い
こんな感じのクラス構成ですかね。
こういう作りになっている可能性が高いという話です。
insertするメソッドからDatabaseInsertExcptionが上がってくるというわかりやすい構成ですね。
まぁこの構成に限った話でなく、SOLID原則に基づいていてもこれは使えます。
この命名では例外クラスに関して以下のような特徴があります。
- クラス構成に関わらず使える命名
- 例外クラス数がやたら多くなる。同様にtry/catchやthrows句も多くなりがち
- 何層あっても例外の原因が特定可能。(例外クラスが異なるため、Controller→Service→Service→UserDataServiceという呼び出し構成だった場合でも、最上位のControllerでDatabaseInsertExceptionが扱える)
クラス名に依存した命名
こちらはちょっと複雑です。SOLID原則によってクラスを細かく分けた時に使えるものだと思っていただけると。
この方式だと例外クラスの数は少なくなり、例外クラス名が例外の意味を表しません。
例えば、データベース関連のエラーはDatabaseException一つです。
では何で例外の意味を伝えるのかというと、この例外をスローする側のクラス名です。こちらの方式では、例外をスローするクラス+例外クラス名で例外の意味を表します。
例えば、UserDataInsertクラスがDatabaseExceptionをスローした場合は、先のDatabaseInsertExceptionと等価となります。UserDataDeleteクラスがDatabaseExceptionをスローするとDatabaseDeleteExceptionと等価と言えそうです。
このような例外クラス名を付けるには、プロジェクト全体がSOLID原則に基づいて構成されている必要がありますから、以下のようなクラスになるはずです。
- クラス構成が考えられていないと使えない命名
- 例外クラス数はかなり少なくていい。
- プロジェクト全体のクラス構成も役割分担とパーツ化が進むため、変更にかなり強くできる(これは命名ではなく、この命名をしようとしたらこのクラス構成になるという話)
- 層を超えての例外による原因特定ができない場合がある。(Controller→Service→Service→InsertUserDataServiceという呼び出し構成だった場合、最上位のControllerでDatabaseExceptionになるため、UpdateUserServiceのDatabaseExceptionと区別がつかない。そのため、例外処理ルールも厳格化する必要がある)
どうでしょうか。どちらが優れているという話はないのですが、クラス分割方法によって、採用できる例外の命名ルールが変わるわけですから、意外と適当には決められないということになりますね。
個人的には後者の命名ルールの方が、例外のハンドリングルールなども十分に考慮しないとうまくクラス設計ができないので、いけてるデザインにするためには効果的だと考えています。