はじめに
凄腕エンジニアさんから学んだ例外の話、たくさん読んでいただけているみたいでありがとうございます。
はてなブックマークのコメントなども読ませていただき、勉強させていただいています。
コメントを読んでみて、自分の記事がちょっと誤解を与えてしまっている部分もあるのかもしれない。。。と感じましたので、今回補足記事としてこの記事を書きます。
【例外が起こった時の挙動を決めるのはプレゼンテーション層】について
この表現によって、例外は常にプレゼンテーション層でtry-catchをするものなんだと誤解を与えてしまったかもしれません。
この表現で伝えたかったこととしては、例外の挙動を決めるのは上位レイヤーであるため、下位レイヤーである業務ロジックの部分でtry-catchして例外が起こった時の挙動を決めるのはあまり良くないということでした。
システム例外と業務例外と区分したとして、Webアプリであればどちらも基本的にはtry-catchはせずにWebフレームワークの例外処理機構を利用するのが良いと思っています。
(システム例外が出たらそのまま、業務例外を投げてもそのままcatchせずに、Webフレームワークがキャッチしてくれるのを頼る)
try-catchを書く場面としては、例外を握りつぶしても良い場合、トランザクションでロールバックが必要な場合、このロジックで例外が出たらWebやバッチなどのプレゼンテーションに関係なくある場所へ通知させたいというような場合などになると思います。
あとは回復可能かどうかの観点になると思います。
具体で例を挙げると
- ネットワークエラーをキャッチしたらリトライしてみる
- DBの再接続をしてみる
- DBのコネクションをリセットする必要がある
- ファイルにアクセスできなかったらリトライしてみる
- ファイルがなかったら作成する(事前にファイルの存在チェックをしていても、その存在チェック終了後に消されたりもあると思うので。。。)
- ファイルストリームを閉じる
- 入力不備があったからこういうレスポンスを返す(今時は入力不備はバリデートで弾くのが主流かと思いますが、たとえとして。。。)
などを実現するためにtry-catchを書くのはOKだと思っています。
逆にいうと、回復可能ではない場合はtry-catchは書いちゃいけません。
(回復不可能なのにcatchしても意味ないのと、変にtry-catchをして例外を握りつぶさないようにするためです。)
(システムエラーは回復可能ではないのでcatchしないのはやはり理にかなっていると思っています。)
ちょっと話がずれますが、Javaの検査例外でtry-catchをさせるのは、回復処理を忘れさせない意図があるんだと思っています。
(検査例外の闇は調べながら色々見えたのですが、そこは触れないでおきます。。。)
Result型について
はてなブックマークでのコメントでResult型を使った方が良いよというようなコメントがありました。
Result型に関しては、本当に緊急度が高いエラー(例外)とそうではないエラーをちゃんと分けるという思想が背景にあるのではと思っています。
調べた感じですと、言語レベルで例外やエラーに対する考え方がしっかりしていて、例外は使わないようにしよう!と方針決めされているような言語の文脈でエラーを返却する値で表現するという方法は流行っている印象を持ちました。
本当に緊急度が高いエラー(例外)とそうではないエラーをちゃんと分けるという思想や、Result型を使うことでそのメソッドがエラーを出す可能性があるのかが型によって直感的にわかるのがメリットなのだろうなと思いました。
例外を利用していると、Javaの検査例外を使う以外は、そのメソッドが例外を投げる可能性があるのかどうか、可能性があるとしたらどのような例外を投げるのかなどはコードを実際に見にいかなければわかりません。
それゆえ例外を投げるというのは、本当はcatchすべきものをcatchしていなかった。。。このメソッドがこういう例外を出すとは思っていなかった。。。となってしまうリスクもあります。
(Result型もreturnの値を無視してしまうという可能性もなくはないと思いますが。。。)
Result型を調べていて下記の疑問が残りました。
- 階層が深くなった時にきつくないのか(大域脱出したくならないのか)
- できる限り上の層で例外(言語の文脈によってはエラー)が起こった時の挙動を決めてあげたい時やWebフレームワークの例外機構を生かしたいという時に大変ではないのか(バケツリレーして上の層に値を持ってきて、、、というのが大変そう)
例外を採用している言語とWebフレームワークを利用しているのであれば、Result型よりも例外を投げてWebフレームワークに例外処理を任せる とした方が楽でいいことが多い気もしています。
下位の層は例外を投げる、その例外をどこかの上位層(基本はWebフレームワークの例外機構)がキャッチするだけで良い という例外の考え方の方が、返却された値に対してif文を書いたり、バケツリレーするよりもコードがシンプルになる気もしています。
あとはResult型などの、エラーを返却する値で表現する方法は、【ただ、throwではなくてreturnでResult型などを返すようにしているだけ】という手段や構文の話というよりは、例外やエラーに対する考え方や哲学を言語単位で実現するために行き着いた地点であるような気がしていて、
Result型などのエラーを返却する値で表現する方法は、言語単位の例外やエラーに対する考え方や哲学、仕組みに大きく依存しているのではないかと思っています。
ですので、Result型などのエラーを返却する値で表現する方法を積極的に使うべき場面は、例外を使わないという思想や哲学が利用している言語に強く存在していること、Result型やタプルなどの仕組みが標準で用意されていること、公式でエラーを、返却する値で表現しているコードが推奨されていること などの条件が整ったときのみにしておいた方がよさそうな印象です。
逆に、例外に関して特に考えも哲学もないような言語で、Result型という仕組みも標準で存在していなく、公式でエラーを、返却する値で表現しているコードが推奨はされてはいないようなTypeScriptでは、Result型を使う旨味は少ないのではないかと思っています。
(実際はどうなのかはわかりませんが。。。)
(フロントエンドの文脈では、階層が深くなるようなことは少ない気もしますし、ライブラリが例外機構を持っているみたいな話はあんまり聞かないので、マッチするのかもしれませんね。。。)
実務でResult型を使って開発をしたことがないので、自分が調べた感覚での話になってしまいましたが、調べた感じですと自分の携わっているプロダクトではResult型は使わなくてよかったのではないかと思っています。
今回紹介したパターンがアンチパターンかどうか
今回自分が携わっているプロジェクトではアンチパターンではなかったと思っています。
(アンチパターンも言語やプロジェクトや学んできた内容など文脈で変わってくるものだとは思いますが。。。)
システムにおいて、「原因を素早く特定し、速やかに復旧させること」と「あるエラーが引き金になって、さらに大きなエラーに引き起こさないようにすること」が重要だと思っていて、その両方を満たせているのと、開発体験が良いからです。
例外が起こった場所は基本、独自例外クラス名やエラーメッセージでソースコード上からすぐに調べることができますし、例外なので、処理が進んでいても例外がthrowされれば処理はそこで中断して上の階層に戻すことができます。
【申請済み】例外が起きたら、フロントエンドでも【申請済み】例外をcatchして、申請済みの人に対する案内をしてあげる、そういう方針で開発を進めているのですがかなり良い感触を得ています。
(開発しやすい、調査しやすい、バグの改修がしやすい。)
あとは独自例外を投げている箇所を見ると、そのメソッドを呼ぶ時の事前条件がみえてくるのもメリットだと感じています。
(こういう状態でこのメソッド呼んだら例外になるのね。。。じゃあこういう状態を満たさないといけないんだ。。。という理解になります。)
あとは今回の方針だとコードがシンプルになったりもします。
(深いバケツリレーも必要なく、返ってきた値に対してif文でエラーが起こったかどうかなどを確認する必要がないため)
総じて、現在自分が携わっているプロジェクトでは凄腕エンジニアさんから学んだ例外の話に書いてある考え方、方針でメリットをたくさん享受できているので、アンチパターンではなかったのだろうなと思っています。
今までいくつかのプロダクト開発に携わってきましたが、例外を利用してこんなに開発体験が良くなるんだ!という体験をしたのが今回で初めてでした。
凄腕エンジニアさんから学んだ例外の話に書いてある考え方や方針は教科書通りの、有名な技術書に書いてあるような正解ではないかもしれません。
ですが、今回の自分が携わったプロダクトでは正解だったと、この方針でよかったと思っています。
最後に
今回補足の記事を書かせていただきましたが、はてなブックマークのコメントを読んだりしながら、例外についての体系的な知識が足りないと思ったので、ネット上に上がっている例外に関する記事をたくさん呼んだり、「ソフトウェア設計のトレードオフと誤り」や「Good Code、Bad Code」などの書籍の例外の章を読んだりしていました。
そのアウトプットも近々Qiitaにアウトプットしようと思っているのでその時はまた記事を読んでもらえると嬉しいです!
最後に、【凄腕エンジニアさんから学んだ例外の話】のQiita記事を読んでくださった人、はてなブックマークでコメントくださった方々、本当にありがとうございました!
おかげさまで、自分もたくさん学ぶことができていて、感謝です。
これからも引き続き、アウトプット記事を読んで、コメントいただけると嬉しいです。
補足
凄腕エンジニアさんから学んだ例外の話 を書いた後にたくさんインプットした例外のことを記事にしました。
もっと汎用的に、広い範囲の例外のことを学びたいという方がいらっしゃいましたら是非下記記事に目を通してみてください。