プログラムは、1行づつ処理されていきます。
その中で、ありえない結果が出たとき、どうすればよいでしょうか?
例えばゼロで割り算したり、ネットワーク上のトラブルでデータベースに接続できなかったり、ユーザーが仕様上ありえない入力をしたり・・・
都度プログラマーが対応できれば良いのですが、それも不可能です。
エラーが発生したとき、どのように振る舞うかを決めるのが例外処理です。
他の言語では、try catch構文と呼ばれることが多いです。
本記事では、エラーと例外処理について解説します。
目次
例外処理とは何か
もう少し例外処理について、詳細に見ていきましょう。
まずは原因からです。
システム上のエラーは、次のようなものがあります。
- ゼロで割り算した
- ネットワーク上のトラブルで接続不可
- 想定している場所にファイルがない
- プログラム上のミス
ユーザー起因のエラーは、次のようなものがあります。
- 仕様上ありえない値を入力した
- 想定外のキー操作をした
- 型のちがう値を入力した(数値の欄に文字、など)
これらがおおまかな原因です。
プログラマーや設計者は、上記のケースを想定してプログラムはどう振る舞うべきかを考えなくてはなりません。
画面上に「divided by 0 (ZeroDivisionError)」などといったユーザーがどう対処してよいか分からない表示をさせてはいけません。
(Webシステム開発では何らかのフレームワークを使うことが多いです。フレームワーク上にはたいてい入力値チェックがあらかじめ準備されています。それをバリデーションといいます)
例外処理をソースコードで
プログラムの実行中に何らかのエラーが発生すると、プログラムは例外処理を探します。それがあれば実行します。
ソースコードで見ていきましょう。
begin エラーの発生が予想される箇所 rescue 例外処理 end
エラーの発生が予想される箇所で、何らかのエラーが発生したとします。すると例外処理が実行されます。
また、エラーの詳細を得る仕組みも用意されています。エラーが発生すると自動的に例外オブジェクトが生成されます。
begin エラーの発生が予想される箇所 rescue => 例外オブジェクト 例外処理 end
これを使って、まずは日本語でやさしくエラー表示するまで頑張りましょう。
例外処理を実装する
単純にゼロで割り算した場合どうなるか、試してみましょう。
puts 1/0
これだけです。
実行してみるとこんなエラーが返ります。
`/': divided by 0 (ZeroDivisionError)
これをやさしく「入力値に問題があります(ゼロで除算)」と表示させましょう。
begin puts 1/0 rescue => e puts "入力値に問題があります(ゼロで除算)" end
[実行結果]
入力値に問題があります(ゼロで除算)
雰囲気はお分かりいただけたでしょうか?
エラー補足後、どうしたいか
上記はまだ簡単な例で、単にエラーが発生しても普通に終わるだけです。
それだけではありません。
エラーの原因を解消する
ファイル操作においてディレクトリがない、よって読み書きができないという例です。
こういう場合は単に「欲しい場所にディレクトリを作る」という対応が考えられます。
その他、プログラム上で何とかなる理由ならいっそ「何とかするプログラム」をrescueに書いてしまえばよいのです。
無視して続行
ユーザーが任意入力欄に何も入力しなかった、などです。
ユーザー側からすれば何も入力することがない、しかしシステム上どうしても何かの値が必要という場合です。
つまりnilで処理するとエラーになるので、長さゼロの文字列(一般的に空文字と呼ばれる)に置き換えて後続処理を実行するという例です。
ユーザーからすれば何もなかったかのように終了します。
強制的に終了させる
ユーザーがありえない値を入力したので、エラーを表示してそこで終了させるといった例です。
ありえないまま後続処理して、ありえない値がDBに入って問題が発生するのは防げます。
より複雑な例外処理例:ファイルへの書き込み
もう少し複雑な例外処理として、ファイル操作を例にあげます。
- ファイルのオープン
- ファイルへ何か書き込む
- ファイルのクローズで終了
ここで、ファイルのオープンは確実だが、書き込んだときに何かのエラーが発生したと想定します。
def file_opentoclose file = File.open("sample.txt", "w") file.write("何か書き込む") # このあたりで何かエラーが発生したと想定 file.close end file_opentoclose
対処するパターンごとに解説していきます。
必ず実行させたいものがある ensure
エラーの発生にかかわらず、必ず実行させたい処理がある場合はensureを使います。
begin エラーが発生しそうな処理 rescue => 変数名 例外が起こった場合の処理 ensure 必ず実行される処理 end
先の例でいうと、ファイルのクローズです。
書き込みに成功しようが失敗しようが、必ず閉じなければいけません。
ということは、ensureでクローズする処理を書けばよいのです。
def file_opentoclose file = File.open("sample.txt", "w") begin file.write("何か書き込む") # このあたりで何かエラーが発生したと想定 rescue => e puts "ファイルの書き込みに失敗しました : " + e.message ensure file.close end end file_opentoclose
(「【Ruby入門】入力と出力」にて解説したとおり、本来ならファイル操作はブロックで行います。ブロックで行えばオープンとクローズを意識する必要はありません。)
超強引に例外処理を試したい場合は、以下のようにします。
def file_opentoclose file = File.open("sample.txt", "w") begin file.write("何か書き込む") # このあたりで何かエラーが発生したと想定 1 / 0 rescue => e puts "ファイルの書き込みに失敗しました : " + e.message ensure puts "ensureでファイルクローズ" file.close end end file_opentoclose
[実行結果]
ファイルの書き込みに失敗しました : divided by 0 ensureでファイルクローズ
無理やり発生させたエラーですが、エラーの発生とensureを通過したことを確認できました。
再度処理する retry
retryを使い、成功するまで再実行することができます。
def file_opentoclose file = File.open("sample.txt", "w") begin file.write("何か書き込む") # このあたりで何かエラーが発生したと想定 rescue => e puts "ファイルの書き込みに失敗しました。10秒後に再実行します。" sleep 10 retry ensure puts "ensureでファイルクローズ" file.close end end file_opentoclose
ファイルの書き込みが成功するまで続けます。
処理例外を指定する
ある程度エラーが限定されて、そのエラーごとに対処を振り分けたいときがあります。
begin エラーが発生しそうな処理 rescue Exception1, Exception2 => 変数名 Exception1, Exception2が起こった場合の処理 rescue Exception3 => 変数 Exception3が起こった場合の処理 ensure 必ず実行される処理 end
実装は省略しますが、これで例外処理の振り分けが可能です。
まとめ
本記事では、エラーと例外処理について解説しました。
プログラムが複雑になればなるほど、例外処理の重要度が高まります。
本記事を読んで、実際に試してエラーと例外処理の理解を深めてくださいね!