概要
例外(エラー)が起きた際に処理(リカバリー)を施すようにしていますか?例外の処理を施すことで、デバッグのしやすさ、素早い不具合修正、例外時に適切な動作を行うことができます。ここでは、例外が起きた際の処理や表現方法について学んだのでまとめます。
例外をスローする
例外をスローするというのは、例外を投げるという意味です。つまり、予期しないことが起きましたよ!ってユーザーまたは開発者に伝えることを意味します。簡単なサンプルを記載します。
const getAge = (age: number):number => {
if(age < 0) {
throw new RangeError('マイナスの年齢なんてありません。'); // 例外スローされて以降は、実行されない
}
return age;
};
try {
let age = getAge(-1);
console.log('ok!'); // 表示されない
} catch (error) {
console.error(error.message); // マイナスの年齢なんてありません。
}
例外をスローしている個所は、3行目のthrow new RangeError('マイナスの年齢なんてありません。');
の部分です。例外をスロー、つまりこの行が実行されると、それ以降の命令は実行されず、呼び出し元、そのさらに呼び出し元、と処理がどんどん巻き戻っていきます。最終的に回復処理を行うtry/catch
節のペアにあたるまで巻き戻ります。そして、catchの中が実行されます。
そうすると、開発者モードには、エラー表示され、エラーが出た個所と、何のエラーが出たのかを開発者に知らせることができます。アプリ利用ユーザーに知らせるためには、alert(error.message);
などと記述すればよいでしょう。
例外がスローされなかった場合、catchは実行されずconsole.logでokが表示されて、正常に終了します。
複数の例外がある場合
より細かく例外をスローしたい場合は、カスタムエラー型を作成し、例外をスローさせることができます。
// 1. カスタムエラー
class AgeIsNegativeFormatError extends RangeError{};
class AgeIsTooBigError extends RangeError{};
// 2. 関数処理を記述
const getAge = (age: number):number => {
if(age < 0) {
throw new AgeIsNegativeFormatError('マイナスの年齢なんてありません。');
}
if(age > 200) {
throw new AgeIsTooBigError('年齢が大きすぎます。');
}
return age;
};
// 3. try/catchの中ので実行
try {
let age = getAge(1000);
console.log('ok!');
} catch (error) {
if(error instanceof AgeIsNegativeFormatError) {
console.error(error.message); // マイナスの年齢なんてありません。
} else if(error instanceof AgeIsTooBigError) {
console.error(error.message); // 年齢が大きすぎます。
} else {
throw error;
}
}
まず、カスタムエラーを作成します。RangeErrorクラスは標準のエラークラスなのでこちらを継承することで、カスタムエラーを作成します。こちらのカスタムエラーを使用して、2. の実際に関数内の処理を記述していきます。それぞれの例外に応じてエラーをスローする記述をします。
エラーのcatch文では、if文に制御し、エラーの種類を分けてユーザーにエラーを知らせます。
これでエラーを適切に知らせることができまました。
しかし、TypeScriptではgetAge関数を使用する際に、try/catchを使用して例外を表示しなさい!指示しているわけではないため、実際に使用する際には、例外処理を加えずに実装することができます。
class AgeIsNegativeFormatError extends RangeError{};
class AgeIsTooBigError extends RangeError{};
const getAge = (age: number):number => {
if(age < 0) {
throw new AgeIsNegativeFormatError('マイナスの年齢なんてありません。');
}
if(age > 200) {
throw new AgeIsTooBigError('年齢が大きすぎます。');
}
return age;
};
// 例外処理をどうするのかを記述しないで関数を呼び出してもエラーはなし
let age = getAge(1000);
なので、getAge
関数を作成した本人は、try/catchで例外時の処理を施すようにすることができても、他の人がgetAge
関数を使用する際は、例外処理を忘れる可能性が高くなります。
それを防ぐために、JSDocs表記でそれを明示的に示す方法があります。
/**
* @throws {AgeIsNegativeFormatError} 0より小さい値のエラー
* @throws {AgeIsTooBigError} 200歳より大きい場合のエラー
* @param {number} age - 年齢
* @return {number} age - 年齢(0-200)
* @function
*/
const getAge = (age: number):number => {
if(age < 0) {
throw new AgeIsNegativeFormatError('マイナスの年齢なんてありません。');
}
if(age > 200) {
throw new AgeIsTooBigError('年齢が大きすぎます。');
}
return age;
};
JSDocsに残すことで、利用者には強制ではないが、例外処理を記述するようにとのヒントとして残すことができます。
半強制的に例外外処理をするようにするにはどうすればいいのだろうか?次の章で述べる例外を返す処理を実施します。
例外を返す
先ほどはthrow
を使用することで例外をスローしていました。なので、getAgeの返り値は、numberだけとなっています。そこを、return
を利用して例外を返す処理に変更します。
class AgeIsNegativeFormatError extends RangeError{};
class AgeIsTooBigError extends RangeError{};
const getAge = (age: number): number | AgeIsNegativeFormatError | AgeIsTooBigError => {
if(age < 0) {
return new AgeIsNegativeFormatError('マイナスの年齢なんてありません。'); // throw -> return
}
if(age > 200) {
return new AgeIsTooBigError('年齢が大きすぎます。'); // throw -> return
}
return age;
};
let age = getAge(1000);
if(age instanceof AgeIsNegativeFormatError) {
console.error(error.message); // マイナスの年齢なんてありません。
} else if(age instanceof AgeIsTooBigError) {
console.error(error.message); // 年齢が大きすぎます。
}
関数getAge
の返り値は、 number | AgeIsNegativeFormatError | AgeIsTooBigError
となります。それぞれに対しての処理を施す必要があるという事を、関数利用ユーザーに知らせることができます。それによって、利用者に例外処理を半強制することができるというわけです。
まとめ
例外処理の仕方には、例外をスローする方法、例外を返す方法の2つ紹介しました。プロジェクトにあわせて利用しやす法を選択してみてください。
参考
https://future-architect.github.io/typescript-guide/exception.html
オライリージャパン 「プログラミングTypeScript」 Boris Cherny 他