
本記事は、IRIS/Cacheにおけるエラートラップについて解説したいと思います。
はじめに
今更語る程でもないですが、「エラートラップ」はエラー発生時の状態を管理し、エラー状態から回復する処理です。
エラートラップでは、下記を重点的に対応すると考えます。
- エラー原因の通知/対策・修正
- 状況分析のため、エラー内容の記録
- リソースの開放(ファイル・トランザクション等々)
- エラー発生時の回復/対応とフローの変更
プログラムを作成している以上、エラーは付き物です。
エラーの無いプログラムを目指していますが、なかなか切っても切れない関係ですよね。
エラー処理を手厚く行っていれば、原因の特定も復旧も楽になると思います。
そのエラー処理で重要になるのは、今ではレガシーとなったエラートラップ機構の「$ztrap」「$etrap」と、現在の推奨となるTry-Catch構文になります。
本記事では、それぞれの違いや特徴を整理しながら、実際のコード例を交えて解説します。
※本記事はクラスでのエラー処理を中心に解説します。
準備:検証・確認用のクラス作成
先ずは、検証で使用するクラスを下記3つ用意します。
※ set文で引数を受け取ったり、executeで呼び出したりしています。
d ##class(developer.err.SampleA).testEcodeA()
└ d ##class(developer.err.SampleB).testEcodeB()
└ d ##class(developer.err.SampleC).testEcodeC()
→ s a = 1/0 ← <DIVIDE>エラー 発生用
Class developer.err.SampleA
{
ClassMethod testEcodeA()
{
w "testEcodeA",!
s txt = "s ret = ##class(developer.err.SampleB).testEcodeB()"
x txt
}
}
Class developer.err.SampleB
{
ClassMethod testEcodeB()
{
w "testEcodeB",!
f pos=1:1:3 {
d ##class(developer.err.SampleC).testEcodeC(pos)
}
q 1
}
}
Class developer.err.SampleC
{
ClassMethod testEcodeC(num As %Integer)
{
w "testEcodeC",!
i (num=3) {
s a = 1/0
}
}
}
準備が整ったら、検証を行いましょう。
Try ~ Cache
一先ずサンプルから確認します。
「準備」項目で紹介した関数を呼び出しています。
ClassMethod testTry()
{
try {
d ##class(developer.err.SampleA).testEcodeA()
} catch e {
// エラー処理
}
}
tryブロック内でエラーが発生すると、catchブロック内の処理が実行されます。
またtryブロック内は下記特徴があります。
- quitを実行すると、tryブロックから抜ける(quitに関数戻り値は設定できない)
- tryブロック内はnewコマンドの範囲内ではない
エラー原因の取得方法
catchブロックでは、発生したエラーの原因を特定する必要があります。
発生したエラーの原因を特定する方法はいくつか存在しているので、解説を行います。
$zerror
特殊変数で、「エラーコード」「エラー発生のラベルとクラス(ルーチン)」「追加情報(一部処理のみ)」を取得する事が出来ます。
サンプル
} catch e {
w $ZERROR
}
【実行結果】
<DIVIDE>testEcodeC+3^developer.err.SampleC.1
もっともシンプルですが、必用な情報はくみ取れるレベルだと思います。
$stack or $st
スタック情報を返します。
$stack($st)の仕様
取得方法 | 説明 |
---|---|
$st(level) | 実行コマンド (DO, $$, XECUTE等) |
$st(level, “place”) | 最後に実行されたエントリ参照とコマンド番号を返す |
$st(level, “mcode”) | 最後に実行されたコマンド等々を返す |
$st(level, “ecode”) | エラー・コードを返す |
サンプル
} catch e {
s (txt, border) = ""
f loop =0:1:$st(-1) {
s mcode = $st(loop,"place")
w !, txt, border, $s($st(loop)'="":"("_$st(loop)_") ", 1:"")
i ($e(mcode)="@"){
w $st(loop, "mcode")
}else{
s mcode = $st(loop,"place")
w $s($e(mcode)=$c(9): $e(mcode,2,*), 1:mcode)
}
s txt = txt_" "
, border = "└ "
}
w ! ,txt, border, $st(loop, "mcode")
}
【実行結果】
D ##CLASS(developer.err.Sample).testTry()
└ (DO) testTry+2^developer.err.Sample.1 +1
└ (DO) testEcodeA+3^developer.err.SampleA.1 +1
└ (XECUTE) s ret = ##class(developer.err.SampleB).testEcodeB()
└ ($$) testEcodeB+3^developer.err.SampleB.1 +1
└ (DO) testEcodeC+3^developer.err.SampleC.1 +1
└ s a = 1/0
エラー箇所の他に、エラーが発生した処理の流れまで取得できるのが魅力です。
%Status ※e.AsStatus()
例外クラス(e)からステータスを取得し、そこから取得します・・・が、あまり有用ではないです。
サンプル
} catch e {
s sts = e.AsStatus()
w "-----",!
f pos=$l(sts,$c(1)):-1:1{
w $p(sts,$c(1),pos),!
}
}
【実行結果】
—–
d^^^0
d^testTry+2^developer.err.Sample.1^1
x^testEcodeA+3^developer.err.SampleA.1^1&
e^testEcodeA+3^developer.err.SampleA.1^1*
d^testEcodeB+3^developer.err.SampleB.1^1*
^testEcodeC+3^developer.err.SampleC.1^1*
)
SAMPLEÖ
á
<DIVIDE>testEcodeC+3^developer.err.SampleC.1
.
0
大量の制御文字の中から、実行スタックの情報と「$zerror」の内容が含まれています。
この文字列から必用な情報を抽出するのは、あまり得策じゃないですね。
%SYSTEM.Statusの関数を利用する
%Status情報から、エラーコード等々を%SYSTEM.Status.clsから取得します。
サンプル
} catch e {
s sts = e.AsStatus()
d $system.Status.DisplayError(sts)
w !,$system.Status.GetErrorCodes(sts)
w !,$system.Status.GetErrorText(sts)
}
【実行結果】
エラー #5002: ObjectScript エラー:<DIVIDE>testEcodeC+3^developer.err.SampleC.1
5002
エラー #5002: ObjectScript エラー:<DIVIDE>testEcodeC+3^developer.err.SampleC.1
エラーコードと「$error」の内容を取得します。
状況によって、「DisplayError()」「GetErrorText()」を使いわけてください。
e.StackAsArray
例外クラスより、実行スタックの情報を配列で取得します。
サンプル
} catch e {
s sts = e.AsStatus()
d e.StackAsArray(.ary)
f pos=1:1:ary {
w ary(pos, "PLACE"),!
w ary(pos), ?7
}
}
【実行結果】
0
DO testTry+2^developer.err.Sample.1 1
DO testEcodeA+3^developer.err.SampleA.1 1
XECUTE testEcodeA+3^developer.err.SampleA.1 1
$$ testEcodeB+3^developer.err.SampleB.1 1
DO testEcodeC+3^developer.err.SampleC.1 1
$stackとほぼ同じデータが取得できました。
足りないのは、エラーが発生したコマンド「s a = 1/0」くらいです。
e.DisplayString()
エラーの内容を出力します。
サンプル
} catch e {
w e.DisplayString()
}
【実行結果】
<DIVIDE> 18 testEcodeC+3^developer.err.SampleC.1
シンプルなエラー情報です。
このコマンドを使用するなら、コーディング量の少ない$zerrorで十分な感じがします。
レガシー
今やレガシーとなった、旧来のエラートラップ方法ですがどこかで見るかもしれないので、備忘として動作を確認してみます。
$ZTRAP($zt)
$ztrapは特殊変数です。
クラスで使用する際は、プロシージャ(関数)の外にでる事が出来ません。
そのため、プロシージャ内にラベルを作成し、その中でエラー処理をコーディングする必要があります。
$ztrapのサンプル
ClassMethod testZTrap()
{
new $etrap
s $zt = "ErrProc" // エラー処理用ラベル
d ##class(developer.err.SampleA).testEcodeA()
q
ErrProc
s $zt = ""
// エラー処理
}
特殊変数「$ztrap」に、エラー発生時に遷移するラベル(ラベル名は任意)を設定します。
Try ~ Catchと異なり、エラー発生時に「%Exception.AbstractException.cls」関連のクラスを生成しません。
そのため、ラベル内で使用できるのは「$zerror」「$zstack」等になります。
$ETRAP
$etrapも特殊変数です。
$ztrapと異なるのは、クラスで使用する際プロシージャの外に出れる点です。
サンプルです
ClassMethod testEcode()
{
new $et
s et = "w !,""エラー発生!! ""_$zdt($h,3,1),!"
, et = et_" s code = $st($st(-1),""mcode"")"
, et = et_" d ##class(developer.err.ErrClass).PreErrProc()"
, et = et_" d ##class(developer.err.ErrClass).ErrProc(code)"
s $et = et
d ##class(developer.err.SampleA).testEcodeA()
}
※エラー処理用のクラス
Class developer.err.ErrClass
{
ClassMethod PreErrProc()
{
w "PreErrProc",!
}
ClassMethod ErrProc(code As %String)
{
// $etrapは必ずクリアする
s $et = ""
// try-catch時に使用した$stackの流用+一部修正
s (txt, border) = ""
s max = $st(-1) -1
f loop =0:1:max {
s mcode = $st(loop,"place")
w !, txt, border, $s($st(loop)'="":"("_$st(loop)_") ", 1:"")
i ($e(mcode)="@"){
w $st(loop, "mcode")
}else{
s mcode = $st(loop,"place")
w $s($e(mcode)=$c(9): $e(mcode,2,*), 1:mcode)
}
s txt = txt_" "
, border = "└ "
}
w ! ,txt, border, code
}
}
特殊変数「$etrap」に、エラー発生時に遷移する処理を設定する点は、$ztrapと変わりません。
$ztrapと異なるのは、設定するエラー処理の自由度が高く、色々な処理を組み合わせる事が可能です。
この状態で処理を実行して、結果を参照してみます。
【実行結果】
testEcodeA
testEcodeB
testEcodeC
testEcodeC
testEcodeC
エラー発生!! 2025-03-28 14:34:24
PreErrProc
D ##CLASS(developer.err.Sample).testEcode()
└ (DO) testEcode+6^developer.err.Sample.1 +1
└ (DO) testEcodeA+3^developer.err.SampleA.1 +1
└ (XECUTE) s ret = ##class(developer.err.SampleB).testEcodeB()
└ ($$) testEcodeB+3^developer.err.SampleB.1 +1
└ (DO) testEcodeC+3^developer.err.SampleC.1 +1
└ s a = 1/0
writeコマンドや、2つの関数が正常に実行された事が分ります。
うん。$ztrapよりかは使いやすい。
・・・と思う。
とは言え、今となってはレガシーな技術なので、この2つの特殊変数を使用する事は、なかなか無さそうです。
速度検証
新旧のエラー処理で、処理速度がどの様に変化したのかも検証してみます。
ポイントとなるのは、エラーが発生しないケースでの処理時間ですよね。
エラーが発生した場合、Try-Cacheは「%Exception」パッケージをインスタンス化するので、レガシーより遅くなるのは予想できます。
今回比較を行うサンプルは下記になります。
ClassMethod speedCheck(cnt As %Integer)
{
s start = $zh
f pos=1:1:cnt d ..speedZtrap()
w !,"$ztrap=",$zh-start
s start = $zh
f pos=1:1:cnt d ..speedEtrap()
w !,"$etrap=",$zh-start
s start = $zh
f pos=1:1:cnt d ..speedTryCatch()
w !,"try=",$zh-start
s start = $zh
f pos=1:1:cnt d ..speedNoErr()
w !,"noerr=",$zh-start
}
// $ztrap
ClassMethod speedZtrap()
{
s $ztrap="ErrProc"
s a = 1/1
q
ErrProc
s $ztrap=""
w !,"a"
q
}
// $etrap
ClassMethod speedEtrap()
{
new $etrap
s $etrap="d ##class(developer.err.Sample).Err()"
s a = 1/1
}
ClassMethod Err()
{
s $etrap = ""
w !,"b"
}
// Try-Catch
ClassMethod speedTryCatch()
{
try {
s a = 1/1
} catch e {
s sts = e.AsStatus()
w !,"c"
}
}
// 通常処理
ClassMethod speedNoErr()
{
s a = 1/1
}
では、実行してみましょう。
各処理を1,000万回実行してみます[.speedCheck(10000000)]。

try-catchは、スタックされないので通常の処理と変わらないですね。
この結果からも、新しいtry-catch一択だと言うことが分かります。
$etrapは、newコマンドを実行する事で、$ztrapより遅くなっています。
→newコマンドを除外すると、ほぼ変わらない処理時になります。。
エラー発生した際の処理時間を比較する
ついでにエラーが発生した場合の時間を計測してみたいと思います。
→$ztrapとtry-catchの処理で「s a = 1/0」に変更します。
// $ztrap
ClassMethod speedZtrap()
{
s $ztrap="ErrProc"
s a = 1/0
q
ErrProc
s $ztrap=""
q
}
// Try-Catch
ClassMethod speedTryCatch()
{
try {
s a = 1/0
} catch e {
s sts = e.AsStatus()
}
}
では、実行してみましょう。
今回は、各処理を100万回で実行してみます[.speedCheck(1000000)]。

やはり、例外クラス(%Exception)をインスタンス化する分、エラー処理は遅いですね。
とは言え、エラーになる事の方が稀だと思うので、多少遅くても目をつぶっちゃいましょう。
おわりに
いかがだったでしょうか。
エラー処理は「保険」のようなもので、普段は目立たなくても、いざという時にシステムの安定性を左右する重要な要素です。
現在は、Try-Catchが優秀なので、そちらを主に運用していけば良いかと思います。
スマートなエラーハンドリングを目指していきましょう!
あなたのコードが、誰かの安心につながるように。
今日も丁寧に例外処理をしていきましょう。