『自走プログラマー ~Pythonの先輩が教えるプロジェクト開発のベストプラクティス120』を読んでいて、ふと昔同僚とやった例外処理の議論を思い出したので書いてみる。
議題: 例外処理は一番上で行うべきなのか、それともできるだけ下で行うべきなのか
例外処理はどこで行うのが適切か。Controller層あるいはFrontControllerと呼ばれる箇所でtry-catch(Pythonだとtry-except)を書くべき(上で書くべきだよ派)なのか、それとも各ModelやServiceと呼ばれるロジック層で書くべき(下で書くべきだよ派)なのか。
話したときは、たしか"これは宗教論争だ。個人の好みでは。"ということで終わったと記憶しているが、本当にそうか。
結論: ケースバイケースだが、基本、両方。
上にプロジェクト共通の処理を書く必要がある(まあ大抵は)
生の例外をそのまま言語やフレームワークのデフォルトの扱いに任せていいプロジェクトであれば、そうすればいい。
しかしログを残したいとか、エラーメッセージを直で返すのはいくらなんでも慎みに欠ける(エンドユーザー向けだといよくある判断)ようなケースはそうはいかない。
何が起きてもいいように、例外の最上位クラス(Exception)ですべての例外をキャッチして、既定の処理をかける必要がある。
だから、少なくとも上にtry-catchか、それに近いことが行えるものを設置するのは必須だろう。ASP.NET Coreだと例外ハンドラーという専用のメソッドがあるのでtry-catchではないのだが、考え方は同じと思う。
// これはpythonではなくてC#
if (app.Environment.IsDevelopment())
{
app.UseExceptionHandler("/error-development");
}
else
{
app.UseExceptionHandler("/error");
}
下にも必要に応じて書く必要がある。
一番上に書いていれば例外処理は十分かというとさにあらず。
よくあるのはDBのトランザクション処理周りである。もしサービス層でトランザクションのコードを書くスタイルだと、おなじ場所で例外処理をかけることが多い。
# これは実際のコードではない。雰囲気を伝えるのにそれっぽく書いているだけ。
try:
con.begin()
# (何かの処理)
con.commit()
except:
con.rollback()
また、返り値を返すのではなく例外を投げるタイプの関数を呼んでいて、かつそこで例外を投げたくない場合もあるだろう。
try:
result = executeOrRaise()
except:
pass
if result <> "SUCCESS":
recover()
こんな場合でも、例外を握りつぶさないといけないので、やっぱり必要になる。
結局、上と下の両方で必要に応じて書かないとだめということである。