例外Exception
を投げるthrow
キーワードについて,その意義を改めて考えます。
(Unity 2018.3.0f2)
始めに
今回は,以下の記事中にあるスクリプトを例として御説明いたします。
スクリプトの用意
throw
キーワードは2か所にありますが,上から順に見て行きましょう。
Try-Catch キーワードを使う場合
LastElement.cs
// 例外が生じるおそれのある命令 try { // 指定したインデックスの要素を返す return objects[LastIndexNumberOf(objects) + shift]; } // 引数の例外をキャッチした場合 catch (ArgumentOutOfRangeException) { // 例外を投げる throw new ArgumentOutOfRangeException(nameof(shift), ShiftExceptionMessage); }
1つ目のthrow
キーワードは,catch
キーワード内で使われています。
これはtry
キーワードと組み合わせて使う物で,try
キーワード内で起こり得る例外を拾う役割が有ります。
それでは,ここで起こり得る例外とは何でしょうか。
それは,object[]
配列のインデクサー内に有るshift
パラメータです。
配列のインデクサーは「0以上,(配列の要素数+1)以下」の整数を指定する必要が有りますが,ユーザーの入力したshift
の値によっては範囲外になってしまうおそれが有ります。
そこで,引数が範囲外だった場合の例外ArgumentOutOfRangeException
をcatch
キーワードで拾います。
しかし,そもそもインデクサーが範囲外だった場合の例外ArgumentOutOfRangeException
は自動的に投げられる物です。
なぜ,改めて自分で用意する必要が有るのでしょうか。
実験として,先程の抜粋部分を以下のように書き換えてみてください。
LastElement.cs
// 指定したインデックスの要素を返す return objects[LastIndexNumberOf(objects) + shift];
try-catch
キーワードを省く事で,例外処理をしないよう変更しました。
ここで,次のようなスクリプトを別途用意して実行してみましょう。
NewBehaviourScript.cs
using UnityEngine; public class NewBehaviourScript : MonoBehaviour { void Start() { // 整数配列 var ints = new int[] { 0, 1, 2 }; Debug.Log(ints.GetLastElement(-3)); } }
要素数が3の配列ints[]
に対して「最後の要素から3つ前の要素」を指定しているため,引数が範囲外だった場合の例外ArgumentOutOfRangeException
が発生するはずですね。
UnityEditor の Console 画面には,次のように表示されたと思います。
例外メッセージが表示されているので問題無いように見えますが,2行目に御注目ください。
Parameter name: index
と書かれていますね。
今回は拡張メソッドを作るのも使うのも私(あなた)なので,この例外メッセージの意味が分かります。
しかし実際にこのメッセージを読む事になるユーザーは,拡張メソッドの中身を知りません。
この拡張メソッドが仮にオープンソースであれば解決は出来るでしょうが,手間が掛かります。
ましてや非公開ソースやアセットともなれば,このメッセージを読み解くのは難しいでしょう。
ですからthrow
キーワードを使う事で,ユーザーの為に例外を分かりやすく書き換える必要が有るわけです。
先程のLastElement.cs
を元に戻して再度実行すると,下図のように表示されると思います。
これで,よりユーザーに親切なコードとなりました。
Try-Catch キーワードを使わない場合
2つ目のthrow
キーワードでは,try-catch
キーワードを使わずにif
分岐で例外を拾っています。
LastElement.cs
// 要素が1つもない場合 if (count < 1) { // メソッド呼び出しの例外を投げる throw new InvalidOperationException(CountExceptionMessage); } // 末尾インデックス番号をリターン return (count - 1);
このcount
の値によって実際に例外が発生するのは先程のGetLastElement()
メソッド内であるため,より早い段階で例外を投げるようにしています。
こちらも実験として,先程の抜粋部分を以下のように書き換えてみてください。
LastElement.cs
// 末尾インデックス番号をリターン return (count - 1);
if
部を省く事で,例外処理をしないよう変更しました。
ここで,先程のNewBehaviourScript.cs
を以下のように書き換えて実行してみましょう。
NewBehaviourScript.cs
using UnityEngine; // 拡張メソッド GetLastElement() を使うため using static LastElement; public class NewBehaviourScript : MonoBehaviour { void Start() { // 整数配列 var ints = new int[] { }; Debug.Log(ints.GetLastElement()); } }
要素数が0の配列ints[]
に対して「最後の要素」を指定しているため,メソッド呼び出しの例外InvalidOperationException
をスローしてほしい所です。
UnityEditor の Console 画面には,次のように表示されたと思います。
1つ目の例に同じくParameter name: index
と書かれているのも問題ですが,そもそも例外の種類がInvalidOperationException
ではなくArgumentOutOfRangeException
になってしまいました。
内部的にはインデクサーを伴うobjects[LastIndexNumberOf(objects) + shift]
という処理を行っているため,本来はこの例外メッセージが正しいのです。
しかしユーザーは引数を指定しないints.GetLastElement()
を呼び出しているつもりなので,この例外メッセージは解決のヒントになるどころか惑わせてしまいますよね。
LastElement.cs
を元に戻して再度実行すると,下図のように表示されると思います。
これで,よりユーザーに親切なコードとなりました。
終わりに
例外の種類は今回扱った物だけではありませんので,その他の例外に関しては以下のリンク先などを御参照ください。
チームで開発する場合にも適切なメッセージを出す事は重要ですし,自分の製作物でも時間が経てば分かりにくくなるものです。
「いつ,誰が見ても分かる」という事を意識して,製作したいものですね。
以上,throw
キーワードの必要性に関する記事でした。