元底辺エンジニアが語る、エンジニアとしての生き様、そしてこれからの生き方

生き様166. エラー設計、してますか?

»

案外疎かにされているエラー処理

個人でプログラムをしている時は意識しないもの。
そして、業務でプログラムしていると必ず意識しなければいけないもの。
この答えは幾つかあるのですが、その中の一つが「エラー処理」です。

そして、これは業務の中でも疎かにされることが多々あります。
今回は、そんな「エラー処理の設計」について、スポットを当てます。

エラー処理とは?

まずは、「エラー処理」というものがどういうものかを定義しましょう。
実は「エラー処理」について勘違いしているプログラマは多く見られます。

  1. 仕様通りで問題のない動作
  2. 仕様通りだが想定外の動作(処理が継続可能)
  3. 仕様想定外で処理が続行できない動作

プログラムを実行した場合、この3つの動作が発生します。
この内、1と2については、意識して処理が作成されています。
ですが、3については意識されていなかったり、2と混同されてるケースが多く見られます。

2と3を合わせて「エラー処理」と呼ぶ文化も見られます。
しかし、今回はこの3つをしっかりと分類し、それぞれこう呼びます。
「(1)正常系処理」「(2)異常系処理」「(3)例外系処理」

異常系処理の定義

異常系処理は、厳密には「エラー処理ではない」と考えています。
白栁の捉え方ですが、正常系処理の中に含まれるものと考えているのです。

ECサイトの例で考えてみましょう。
例えば、ショッピングカートにアイテムがない状態で購入操作をしようとした場合。
例えば、商品在庫がないのにカートに追加しようとした場合。
それぞれ、ユーザーの操作とそれに対する画面の反応が中断されてはいけません。
その上で「その操作は正常に完了できなかった」という旨を知らせる必要があるでしょう。
それは、ユーザーとしては「想定外」の動作だが 決して、操作や反応が中断されたり、システムが終了することはあってはいけません。

つまり、「ユーザーは想定していない動作」でも「処理としては継続」となるのです。
この「処理としては継続」する部分が、正常系の中に含まれると考えているポイントです。
そして、この処理を「エラー」として混同してはいけない最大のポイントともなります。
異常系処理の後に行う処理は、正常系処理に繋がらなくてはいけないのですから。


例外系処理の定義

意図しない動作によって継続が困難になった場合の処理を「例外系処理」と定義します。
例えば、参照している外部APIのサーバーとの通信が確立できなかった場合。
例えば、例えばファイルがロックされていて書き込むことができなかった場合。
想定されるケースは無限にありますが、そのどれもが通常の操作では意識できません。

ですが、例外処理を考えるべきポイントは、ある程度まで簡単に見つけられます。
それは、プログラミング言語には「例外処理」という仕組みがあるからです。

またややこしい言葉がでてきましたね。「例外系処理」と混乱してしまいそうです。
「例外系処理」は、処理全体の概念的なもので設計レベルの話。
「例外処理」は、実際のコードの書き方の1つでコーディングレベルの話。
ということで分けると混乱しないはずです。

処理途中で例外が発生した場合、例外処理のコードを記述します。
多くの場合は、その処理の中で例外に対する解決処理を記述します。

例えば、それは例外から復旧する為の処理かもしれません。
例えば、それは例外を記録するだけの出力処理かもしれません。
例えば、それは例外を上位の処理に伝える為の処理かもしれません。

極マレに「何もしない」ということもあります。
それは「例外処理」であっても、「正常系処理」や「異常系処理」として扱う場合があるからです。

また、正常に処理が継続されていても、例外系処理として扱われるケースがあります。
これは、実際にはあるのですが、例を挙げるのが中々難しいものです。
例えばバッチ処理では、前提となる条件が満たされていなかった場合が該当します。


異常系処理と例外系処理の混同

白栁はこれらの「例外系処理」こそが、エラー処理だと考えています。
今回は「異常系処理」と「例外系処理」をこの中に含める人の為に、あえて3つの言葉を分けています。

しかし、両者を混同する文化があるのも、無理の無いことなのです。
異常系処理と例外系処理が混同されるには、プログラミングの歴史的な背景があります。
それが「戻り値」というプログラミング処理です。

「戻り値」「返り値」「返値」etc…
様々な呼び方がありますが、今回は「戻り値」で統一します。
言わずもがな、処理がどういう状態で終わったか、を呼び出し元へ知らせる値です。
処理結果を返す為の値とも言えるでしょう。

実はこの「戻り値」、我々が使用しているアプリケーションも持っています。
今では意識することはほぼありませんし、この仕組を利用することもありません。
ですが、身近なコマンドは、様々な応答や結果を返してくれます。
これらのコマンドは、全て小さなアプリケーションです。

C言語ぐらい昔のプログラミング言語は、コマンドの様に単純な処理しかできません。
プログラミング言語として、例外処理の実装も充実していません。
エラーの検知はできますが、それを上位処理へ伝える手段が現代からみると貧弱です。
ですので、処理内でエラーが起こったことは、戻り値で伝えるという文化がありました。

その基準では「全てのコードが最終行まで実行できること」が重要と言われていました。
処理の最後にそれぞれの派生処理の実行結果を総合して、処理の成否を判断したのです。
これは、上記の「異常系処理」の考え方にとても近い、いや同じと言って良いでしょう。

今でも、この考え方を基準としているプログラマは多く存在します。
昔から活動しているプログラマだけでなく、そのプログラマから薫陶を受けた若いプログラマにも見受けられます。
C言語やJavaでも、入門書がこの様な考え方を伝えているものもあります。

一見、処理の辻褄が通っている様に見えてしまうのが厄介なところです。
ですが、運用面を考えた場合、両者を混同してはいけないことに気付けるはずです。
より早く、より正確に、問題の発生とその理由を伝え、対処する。
それが一番【めんどくさくない】方法なのですから。


エラー処理の考え方の変遷と正解

ハードウェア環境、プログラミング言語環境。
この2つの環境の発展で、エラー処理の考え方が変わっています。

現代では、潤沢なメモリと高速化された処理の結果、「やり直す」コストが減少しています。
ですので、トラブルがある場合は「早期リターン」で個々に対処していく方向へ変化しています。

一昔前であれば「全てチェックして問題のないことを確認してから実行」でした。
もちろん、現代においても膨大な処理を限られた時間で行う場合には有効な考え方です。

一番大事なのは、「例外処理の握りつぶし」をしないことです。
対処するべきエラーを見落として、放置しないことは、大前提でしょう。
対処方法が判断できなければ、ログに書き出すだけでも良いのですから。

処理で想定していない動作が発生した時、どう対処するべきかの方法は無数にあります。
その中に「絶対的な正解」はありません。
そのケースごとで「正解」は異なります。

その判断の為には、運用を考えることです。
その状態の後にどの様な動きをするべきかを検討していきましょう。
その結果、きっと「正しい処理」が見えてくるはずです。

以上!

Comment(2)

コメント

技術進歩が早いのでずっと初心者

「生き様153. 汚くてもいいからまず書く!」(https://el.jibun.atmarkit.co.jp/korezama/2022/07/korezama0153.html)にコメントした「想定外の条件」が今回のこの「エラー処理」にあたるのかなと思います。
シングルタスク・順次処理の時代から、マルチタスク・非同期処理、クラウドネイティブ(適切な比較用語がわからず^^;)になるにつれ想定外の動作や事象が増えて、疎かにしているつもりはなくてもエラー処理不足を感じています。

コメントを投稿する