エラーハンドリングとはエラーが出ることをあらかじめ予測してエラーへの対処をプログラムする処理である。ここでは例外処理と同じ意味で考える。
phpのtry-catchやpythonのtry-exceptにあたります。
外部のapiを使うなど想定外(例外)のエラーが起きそうなところに書くことが多い。
処理の内容は、エラーに対しての復旧やログ出しを行うことが多い。
一方で、エラーハンドリングを適切に行わなければネストが起こり、ログの重複や可読性の低下などが起こる。
例えばこんな書き方をしたことがある。
# controller
def index(request):
try:
hoge = Hoge()
hoge.main()
# ...
except Exception as e:
# サーバーエラーを返す
# Hoge class
def main(self):
try:
# 外部apiを叩く処理
except Exception as e:
# Log出しやリトライ
# throw エラー
そこで、エラーハンドリングの最適解を調べた。
結論
結論としてはそもそもエラーハンドリングを行わない。
正直エラーハンドリング難しい、駆け出しとしてはそんな難しいことを考えることより一旦書かない結論の方が良いと思います。
本当にtry-catch必要?考え直すことが重要。
理由は起こりうる全てのエラーがキャッチされてしまうから。つまり、便利すぎるが故に扱いづらいと言った感じ?
本当に信頼性のあるコードを書く方法は、人の典型的な弱さを考慮に入れるシンプルなツールを使うことであって、副作用のあることや、プログラマの無謬性を前提とする漏れのある抽象化を隠している複雑なツールを使うことではない。
そうは言ってもフレームワークの都合上使いたい時は以下のようにするとよさそう。
Good
スコープを短くする
上記で言ったように例外の理解は難しい。スコープを短くすることで、どの処理がエラーになったか理解しやすくなる。
例外の捕捉を細かくする。
引数エラーとdbのデッドロックエラーを同じ例外として捕捉しない。また、捕捉できるような書き方をしない。
ログを出す。
エラーの原因によって適切な処理が変わる
エラー原因 | UIへの通知 | プログラムの流れ |
---|---|---|
ユーザー | 修正箇所 | 再操作 |
内部のプログラム | システムトラブル | ログを出す(リトライ or 安全な終了) |
外部のプログラム | システムトラブル | ログを出す(リトライ or 安全な終了) |
リトライか終了は状況による。
いずれにせよ例外が生じたままプログラムを走らせてはいけない。
ログの内容。
どこで。なぜ起きたか。エラー内容。を完結に書く。
*機種依存を避けるため、英語が好ましい
また、ログのレベルを適切に設定することで、緊急度を分ける。
level | 詳細 |
---|---|
CRITICAL | プログラム自体が実行を続けられないことを表す重大なエラー。 |
ERROR | より重大な問題により、ソフトウェアがある機能を実行できないこと。 |
WARNING | 動作への影響はないが、問題が起こりそうな時 |
INFO | 処理や利用者の情報を残す |
DEBUG | トラブルの調査用、調査時のみ出力するような設定 |
参考:https://docs.python.org/ja/3/howto/logging.html
BAD
例外の握り潰し
エラーが発生した事実が伝わらず、処理が進んでしまう。
ログが出なくなり、不具合の特定が難しくなる。
try:
# ...
except Exception as e:
return
例外を投げる
エラーハンドリングを使いたくないのでそもそもthrowしないでねということ。
従属する処理を全て確認しないといけないので面倒。
エラー情報を格納したオブジェクトをreturnすると良い。
googleのc++規約
「例外」がないからGo言語はイケてないとかって言ってるヤツが本当にイケてない件
参考