本記事はLife is Tech ! Tokai Advent Calendar 2020 23日目の記事になります。
さて、突然ですがみなさん例外処理ってしてますか??
馬鹿にすんな!!当たり前だろ!!!って皆さんはブラウザバックでOKです。
趣味でPythonとか触ってる分には特に意識することもないんですが、コードを書いていればそれなりに必要になる場面があるのでサクッと紹介していきます。(今回はC++を対象に書きますが、どの言語でも構造はそんなに変わらないと思うので自分のメイン言語の例外についても調べてみてください。)
2020/12/26 追記
@yumetodo さんよりいただいたご指摘の内容をもとにプログラムを一部修正しました。
たとえば
まずは以下のような簡単なプログラムを考えます。(プログラム自体に特に意味はありません。)
int hoge(){
vector<int> array(3);
array = {1, 2, 3};
int index = 9; // 9番目の要素にアクセスするよ
int num = array.at(index); // error : index out of range
cout << "正常終了" << endl; // 上の行でエラーが発生するため実行されない(はず)
return 0; // returnもされない(はず)
}
3行目に注目すると、int
型の動的配列array
の中身は、{1, 2, 3}
で初期化されています。
つまりarray
の要素数は3つしかないため、要素番号は0~2までしか扱えないはずですね。
しかし、4-5行目で10番目の要素にアクセスしようとしています。要素数が足りないため、ここではout of range
のエラーが出るでしょう。(コンパイル時の設定によっては出ませんが、初期化していない要素にアクセスできてしまうのが危険であることは理解できるかと思います。)
if
文で処理してみる
想定外の挙動をしないようプログラムを書きたい場合、以下のように条件分岐をしてみるのも一つの手かもしれません。
if (index > array.size()) {
cout << "異常終了" << endl;
return -1; // 異常終了の場合-1をreturnする
} else {
// やりたい処理をここに書くよ
}
return 0; // 正常終了の場合0をreturnする
この場合、戻り値によって処理が正常に終了したか、異常に終了したかを判別することができます。
しかし、上記のようなやり方では通常の処理と例外処理が混在してしまい(明確に例外の処理だけを切り分けられない)、それに付随して、コードの可読性が下がるというような問題点があります。
特に複数人で作業をしていたりするとその影響は顕著で、統一的に例外処理が行われていなくコードの読解が難しくなりがちです。(ただの分岐なのかエラー処理なのか分かりにくかったりする)
また、他にもいろいろありますが
「例外処理 メリット デメリット」
とかで検索するともっと詳しい方々の説明が出てくると思うので今回は割愛します。
try - catch
を使ってみよう
例外はtry - catch
を用いると明示的に監視できます。
処理自体はtry
、throw
、catch
という3つのキーワードから構成されます。
今日はこの3単語だけ覚えて帰ればOKです。
手順1. try
ブロックを記述しよう
まず、例外が発生する可能性のあるコードをtry
ブロックとして{ }
で囲みます
try {
//例外が発生する可能性のあるコード
}
手順2. 例外をthrow
しよう
throw
を実行することで、例外を投げることができます。
throw
の使い方はreturn
と同じような雰囲気であると捉えればよく、
try {
//例外が発生する可能性のあるコード
throw exception;
}
のような形になります。exception
には投げる例外の値を指定します。
throw
で例外を投げるとプログラムの実行はtry
ブロックから即座に抜け出し、投げられた例外に対応したcatch
ブロックへと移ります。
手順3. catch
ブロックを記述しよう
try
ブロックの中で例外が発生した場合、catch
ブロックでそれを受け取ります。
catch
ブロックは例外を受け取るとブロック内に記述されている例外処理を実行するため、ここでは例外を受け取った場合の処理を用意します。
catch (type arg) {
//例外処理
}
type arg
には、型と変数名を指定します。
一般的にはポインタの形式で記述して参照を渡すことが多いです。(不要な値のコピーを回避するため)
catch
ブロックは発生した例外がtype arg
部分の型と一致した場合に実行されます。
catch
ブロックは、try
ブロックの直後に記述します。
プログラム例
上記を踏まえて、最初のサンプルプログラムを改良したのが以下のプログラムになります。
int hoge(){
vector<int> array(3);
array = {1, 2, 3};
int index = 9;
try {
if (index >= array.size()){
throw "error : index out of range.\n";
}
} catch (char *str) {
cout << str;
}
int num = array.at(index);
return 0;
}
index
が配列の要素数を超えていた場合の処理をtry
ブロック内に記述し、分岐内で例外をthrow
しています。
誰が見てもこの部分のコードは例外処理だとわかるようになりましたね。
今北産業
- 例外処理しようね
- 基本はtry - catchで書くよ
- throwで例外投げるよ
おわりに
例外処理って必要に駆られないとなかなか触れる機会がないと思うので、これを機に今後の開発で意識されるようになればと思います。