終了エラーを発生させるthrow

前回、いわゆる例外処理を行うためのtry-catch制御構文を紹介した。try-catch制御構文を利用することで、try {}ブロックの中で発生するエラーを捕捉し、catch {}やfinally {}で後始末を行うことができることを示した。これに関連する機能として今回はthrow文を取り上げようと思う。

try-catch制御構文はオブジェクト指向のプログラミング言語など、比較的若い最近のプログラミング言語には組み込まれていることが多い。try-catch制御構文を使う場合、いろいろな例外処理をcatch {}やfinally {}の方で処理したくなるが、そんな場合に使えるのがthrow文だ。

throwを実行すると、その場で終了エラーを発生させることができる。throwによって発生させられるエラーはtry-catch制御構文の捕捉の対象となる。明示的にエラーを発生させたい場合にはこの機能を使うことになる。

throwの基本的な使い方

throwの基本的な使い方は次のとおり。throwは引数に何も指定しなくてもよいし、式を書いてもよい。

throwのもっともシンプルな使い方

throw

例えばPowerShell Coreのインタラクティブ状態でthrowを実行すると次のような結果が得られる。ScriptHaltedエラーが生成されていることがわかる。

引数なしでthrowを実行してScriptHaltedエラーが発生していることを確認

PS /Users/daichi> throw
ScriptHalted
At line:1 char:1
+ throw
+ ~~~~~
+ CategoryInfo          : OperationStopped: (:) [], RuntimeException
+ FullyQualifiedErrorId : ScriptHalted

PS /Users/daichi>

throw文には引数としてエラーメッセージを指定することができる。

throwにメッセージを指定して実行

throw "エラーメッセージ"

次のようにエラーメッセージを指定してthrow文を実行することで、エラーメッセージが認識されていることを確認できる。

throwにエラーメッセージを指定して実行した場合のサンプル

PS /Users/daichi> throw "エラーメッセージ"
エラーメッセージ
At line:1 char:1
+ throw "エラーメッセージ"
+ ~~~~~~~~~~~~~~~~
+ CategoryInfo          : OperationStopped: (エラーメッセージ:String) [], RuntimeException
+ FullyQualifiedErrorId : エラーメッセージ

PS /Users/daichi>

throwにはテキストだけではなくオブジェクトを指定することもできる。例えば次のような使い方をすればプロセス情報が引数としてthrowで使われることになる。

throwの引数にはオブジェクトを指定することもできる

throw (Get-Process pwsh)

例えば上記を実際に実行してみると、次のようにSystem.Diagnostics.Processオブジェクトがわたされていることを確認できる。$error[0].targetobjectにGet-Process pwshの結果が入っていることも確認できる。

PS /Users/daichi> Get-Process pwsh

 NPM(K)    PM(M)      WS(M)     CPU(s)      Id  SI ProcessName
 ------    -----      -----     ------      --  -- -----------
      0     0.00      93.53     263.18   45719 703 pwsh


PS /Users/daichi> throw (Get-Process pwsh)
System.Diagnostics.Process (pwsh)
At line:1 char:1
+ throw (Get-Process pwsh)
+ ~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo          : OperationStopped: (System.Diagnostics.Process (pwsh):Process) [], RuntimeException
+ FullyQualifiedErrorId : System.Diagnostics.Process (pwsh)

PS /Users/daichi> $error[0].targetobject

 NPM(K)    PM(M)      WS(M)     CPU(s)      Id  SI ProcessName
 ------    -----      -----     ------      --  -- -----------
      0     0.00      95.52     270.43   45719 703 pwsh


PS /Users/daichi>

try-catchとthrowの動作を知る

前回はtry-catch制御構文を紹介したが、エラーが発生した場合にどのように処理が流れていくかを把握しておくことが大切だということがおわかりいただけただろう。try-catch制御構文とthrowを使う場合、どのように処理が流れていくのかを知っておくことが大切だ。

まず、次のようにtry {}の中でthrowを実行してみる。

try {}の中でthrowを実行する

try
{
    "tryに入りました"
    throw "try内で終了エラーを発生"
} catch {
    "catchに入りました"
} finally {
    "finallyに入りました"
}

実行すると次のようにtry→エラー発生→catch→finallyと処理が流れていることがわかる。明示的にエラーを発生させただけで、前回紹介したtry-catch制御構文通りの動きが起こっていることがわかる。

try {}の中でthrowを実行させたサンプル

PS /Users/daichi> try
>> {
>> "tryに入りました"
>> throw "try内で終了エラーを発生"
>> } catch {
>> "catchに入りました"
>> } finally {
>> "finallyに入りました"
>> }
tryに入りました
catchに入りました
finallyに入りました
PS /Users/daichi>

次に、catchの中でもthrowを実行するケースを考えてみる。

catch {}の中でもthrowを実行してみる

try
{
    "tryに入りました"
    throw "try内で終了エラーを発生"
} catch {
    "catchに入りました"
    throw "catch内で終了エラーを発生"
} finally {
    "finallyに入りました"
}

実行すると次のようになる。catch {}でthrowが実行されると、先にfinally {}を処理した上でthrowの内容が処理されていることがわかる。

catch {}の中でthrowを実行すると、finallyを処理した上でthrowが処理されることがわかる

PS /Users/daichi> try
>> {
>> "tryに入りました"
>> throw "try内で終了エラーを発生"
>> } catch {
>> "catchに入りました"
>> throw "catch内で終了エラーを発生"
>> } finally {
>> "finallyに入りました"
>> }
tryに入りました
catchに入りました
finallyに入りました
catch内で終了エラーを発生
At line:7 char:1
+ throw "catch内で終了エラーを発生"
+ ~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo          : OperationStopped: (catch内で終了エラーを発生:String) [], RuntimeException
+ FullyQualifiedErrorId : catch内で終了エラーを発生

PS /Users/daichi>

それではさらにfinally {}の中でもthrowを実行させてみよう。

finally {}の中でもthrowを実行してみる

try
{
    "tryに入りました"
    throw "try内で終了エラーを発生"
} catch {
    "catchに入りました"
    throw "catch内で終了エラーを発生"
} finally {
    "finallyに入りました"
    throw "finally内で終了エラーを発生"
}

実行すると次のようになる。どうやらcatch {}の中のthrowが実行され処理がfinally {}に移り、finally {}の中のthrowに到達するとこのthrowが実行されて処理が終わるようだ。catch {}の中のthrowには処理は戻っていないことがわかる。

PS /Users/daichi> try
>> {
>> "tryに入りました"
>> throw "try内で終了エラーを発生"
>> } catch {
>> "catchに入りました"
>> throw "catch内で終了エラーを発生"
>> } finally {
>> "finallyに入りました"
>> throw "finally内で終了エラーを発生"
>> }
tryに入りました
catchに入りました
finallyに入りました
finally内で終了エラーを発生
At line:10 char:1
+ throw "finally内で終了エラーを発生"
+ ~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo          : OperationStopped: (finally内で終了エラーを発生:String) [], RuntimeException
+ FullyQualifiedErrorId : finally内で終了エラーを発生

PS /Users/daichi>

try-catch制御構文を活用してエラー処理をしようとした場合、このあたりのフローを把握していくことが大切だ。ここでthrowを実行すればここに処理が飛ぶはずだ、と思い込んでいると、実はそこには処理は飛ばないといったことが起こりかねないからだ。

try-catch制御構文かif制御構文か

try-catch制御構文を使うか、if制御構文を使ってエラーが発生する前または発生した後に処理を切り分けていくかは、なかなかどちらがよいか難しいところがある。プログラミング言語によってはtry-catchの方を使うしか方法がないこともあるし、逆にtry-catchの機能が提供されていないのですべてif制御構文で判断しながら処理していかなければならないこともある。

どちらを使うかは書き手の好みの問題もあるのでなんとも言えないのだが、少なくとも他人が書いたコードが読めるようにtry-catch制御構文とthrowの関係を知っておく必要はあるだろう。

参考資料