
本記事は、IRIS/Cacheにおける例外クラス(%Exception)について解説したいと思います。
はじめに
IRIS/Cacheでは、Try-Catch構文とともに%Exceptionパッケージのクラス群を使うことで、エラーハンドリングをより柔軟に設計できます。
全ての例外クラスは、「%Exception.AbstractException.cls」を継承しており、エラーに関する情報は、統一された構造を持つオブジェクトとして処理する事が可能です。
また、従来の$ztrapや$etrapによるエラー処理では難しかった、状況に応じたメッセージを含ませる事が出来るのも大きな特徴です。
「そこで実際に何が起こったのか?」を離れたcatchコード・ブロックで取得/解析できるのは、従来のエラー処理と比較して、強力なアドバンテージだと思います。
では、例外クラスを見ていきましょう。
例外クラスの種類
先ずは、例外クラスの種類を確認します。
※PythonException(Python用), CPPException(IS社内用)は使用できないので除外
%Exception.AbstractException
例外クラス全ての親クラスになります。
ただし、クラスキーワードに「Abstract」が付与されているので、このクラスを使用する事はできません。
%Exception.General
一般的な例外クラスを作成する際に利用します。
使い方は後述します。
%Exception.SystemException
<DIVIDE>や<UNDEFINED>等の、システムレベルのエラー発生時に自動生成されます。
エラー状況の切り分けの為にも、このクラスをインスタンス化してthrowする事は、やめた方が良いかと思います。
%Exception.StatusException
使用頻度が高い例外クラスです。
%Stasusを生成して、関数「CreateFromSTatus()」の引数にセットしてthrowします。
→%Statusの生成方法に関しては、別の記事で記載します。
サンプルです
ClassMethod StatusError()
{
try {
throw ##class(%Exception.StatusException).CreateFromStatus(
$$$ERROR($$$TooManyErrors)
)
} catch e {
w $system.Status.GetErrorText(e.AsStatus()),!
w e.%ClassName(1)
}
}
ファイル操作系は、関数の戻り値が%Boolean型が多いので、戻り値「0」を受け取った際は、%Statusを生成してthrowしたりします。
ロジックレベルのエラー表現を行うのに最適です。
%Exception.SQL
SQL実行時のエラーをthrowする際に利用します。
埋め込みSQLでは「SQLCODE, %msg」、ダイナミックSQLでは「res.%SQLCODE, res.%Message」を引数にして、関数「CreateFromSQLCODE」から例外クラスを取得します。
サンプルです
ClassMethod SQLError()
{
try {
&sql(select className into:クラス名 from developer_data.HogeHoge)
throw ##class(%Exception.SQL).CreateFromSQLCODE(SQLCODE, $g(%msg))
} catch e {
w $system.Status.GetErrorText(e.AsStatus()),!
}
}
例外クラスの使い方
基本的には「%Exception」クラスを生成し、throwでポイッとcatchコード・ブロックに投げるだけです。
今回は、一般的な例外クラス「%Exception.General」を利用して、エラー処理を行ってみたいと思います。
%Exception.General
クラス「%Exception.General.cls」を使用して、例外クラスを作成してみます。
引数 | 説明 |
---|---|
pName | エラーの名称 例)<DIVIDE>、<UNDEFINED>等々 |
pCode | エラーコード %occErrors.inc参照 |
pLocation | エラー発生個所 |
pData | 追加情報 → 一番重要。詳しい情報を設定する |
pInnerException | 他のエラーを抱える事が可能 |
サンプル
ClassMethod errGeneral()
{
try {
// サブエラー
s subE = ##class(%Exception.General).%New(
"<inErr>", $$$CacheError, , "インナーエラー"
)
// メインエラー
throw ##class(%Exception.General).%New(
"<SAMPLE>", $$$GeneralError, $$$info(3), "サンプルエラー", subE
)
} catch e {
d $system.Status.DisplayError(e.AsStatus())
}
}
【実行結果】
エラー #5035: 一般例外 名前 ‘<SAMPLE>’ コード ‘5001’ データ ‘サンプルエラー’
> エラー #5035: 一般例外 名前 ‘<inErr>’ コード ‘5002’ データ ‘インナーエラー’
errGeneral+3^developer.err.Sample
エラー発生箇所を自動取得する
throwする度に「pLocation」を作成するのは大変です。
特にライン番号なんて、毎回数えるのはとてもしんどいです。
そこで、システム設定のグローバル「callererrorinfo」を設定する事により、エラー箇所(pLocation)を自動で取得する事が可能になります。
s ^%oddENV("callererrorinfo", [ネームスペース名]) = 1
// 例) s ^%oddENV("callererrorinfo", "SAMPLE") = 1
先ほどの関数から、「pLocation」の引数を除去した形で実行してみたいと思います。
ClassMethod errGeneral()
{
try {
s subE = ##class(%Exception.General).%New(
"<inErr>", $$$CacheError, , "インナーエラー"
)
throw ##class(%Exception.General).%New(
"<SAMPLE>", $$$GeneralError, , "サンプルエラー", subE
)
} catch e {
d $system.Status.DisplayError(e.AsStatus())
}
}
では、実行してみます。
【実行結果】
エラー #5035: 一般例外 名前 ‘<SAMPLE>’ コード ‘5001’ データ ‘サンプルエラー’ [er rGeneral+5^developer.err.Sample.1:SAMPLE]
> エラー #5035: 一般例外 名前 ‘<inErr>’ コード ‘5002’ データ ‘インナーエラー’ [errGeneral+2^developer.err.Sample.1:SAMPLE]
おぉ!
黄色い下線が、今回のグローバル設定で追加された情報になります。
自動でlocationが取得できるのはいいですね。
では次は、値を2に変更します。
s ^%oddENV("callererrorinfo", [ネームスペース名]) = 2
【実行結果】
エラー #5035: 一般例外 名前 ” コード ‘5001’ データ ‘サンプルエラー’ [e^OnAsStatus+1^%Exception.General.1^1 e^AsStatus+1^%Exception.AbstractException.1^1 e^errGeneral+9^developer.err.Sample.1^1 d^^^0:SAMPLE]
> エラー #5035: 一般例外 名前 ” コード ‘5002’ データ ‘インナーエラー’[e^OnAsStatus+1^%Exception.General.1^1 e^AsStatus+5^%Exception.AbstractException.1^1 e^errGeneral+9^developer.err.Sample.1^1 d^^^0:SAMPLE]
location情報がガッツリと表示されています。
このグローバル設定を、本番環境に設定するかの判断は検討が必要だと思いますが、開発環境に関しては行ってよいと思います。
設定値に関しては、1 or 2お好みで!
例外クラスの拡張
PythonやJAVA等は、例外の種類によってエラー処理を細分化する時があります。
下記のような感じですね。
def err_sample():
try:
print('Let's GO')
except FileNotFoundError as e:
print('FileNotFoundError!!')
except TypeError as e:
print('TypeError!!')
else:
print('error!!')
IRIS/Cacheで同じように作成する事は難しいですが、近い形で実現したいと思います。
カスタム例外クラスを作成する
先ずは、Pythonの例外「FileNotFoundError」「TypeError」に相当するエラーハンドルを作成します。
エラーハンドルは、「%Exception.AbstractException」を継承する必要があります。
Extendsに指定してください。
先ずは手始めに「FileNotFoundError」から作成します。
※サンプルなので、必用最低限の機能しか実装していません。
Class developer.err.FileNotFoundException Extends %Exception.AbstractException
{
Method OnAsStatus() As %Status [ CodeMode = expression, Private ]
{
$$$ERROR($$$FileDoesNotExist,i%Name_i%Location_$select(i%Data'="":$select($extract(i%Data)="^":" ",1:" *")_i%Data,1:""),,,,,,,,,,$select(i%iStack="":$lb(""),1:i%iStack))
}
}
お次は「TypeError」を作成します。
「$$$PropertyTypeInvalid」は、それっぽいだけで選択している感じです。
特に深い意味はありません。
Class developer.err.TypeException Extends %Exception.AbstractException
{
Method OnAsStatus() As %Status [ CodeMode = expression, Private ]
{
$$$ERROR($$$PropertyTypeInvalid,i%Name_i%Location_$select(i%Data'="":$select($extract(i%Data)="^":" ",1:" *")_i%Data,1:""),,,,,,,,,,$select(i%iStack="":$lb(""),1:i%iStack))
}
}
エラーハンドルを作成したら、さっそく使ってみましょう
サンプルは↓です
ClassMethod exception(mode As %Integer)
{
try {
i (mode = 1){
s e = ##class(developer.err.FileNotFoundException).%New(,, $$$methodName_"^"_$$$className, "〇×ファイル")
}elseif(mode = 2){
s e = ##class(developer.err.TypeException).%New(,, $$$methodName_"^"_$$$className, "%Integerではない")
}else{
s e = ##class(%Exception.General).%New("<SAMPLE>", $$$GeneralError, $$$methodName_"^"_$$$className, "サンプルテスト")
}
throw e
} catch e {
i (e.%IsA("developer.err.FileNotFoundException")){
w !, $system.Status.GetErrorText(e.AsStatus())
}elseif (e.%IsA("developer.err.TypeException")){
w !, $system.Status.GetErrorText(e.AsStatus())
}else{
w !, $system.Status.GetErrorText(e.AsStatus())
}
}
}
%Exceptionの判別は、「e.%IsA([例外クラス名])」で行っていますが、「e.%ClassName()」でも可能です。
では、一先ず動作させてみましょう。
d ##class(developer.err.Sample).exception(1)
>> エラー #5012: ファイル 'exception^developer.err.Sample *〇×ファイル' が存在しません
d ##class(developer.err.Sample).exception(2)
>> エラー #5418: プロパティタイプに誤り: exception^developer.err.Sample *%Integerではない
d ##class(developer.err.Sample).exception(3)
>> エラー #5035: 一般例外 名前 '<SAMPLE>' コード '5001' データ 'サンプルテスト'
一先ず「それっぽく」動かせました。
ただ、エラー・コードでも同様に分岐できるので、エラー・コードだけでは細分化できないケースや特殊なケースで使用すると良いと思います。
導入に関しては、業務ルールや構築ルールを定めて、計画的に検討してください。
おわりに
いかがだったでしょうか。
%Exception関連のクラスを上手に活用する事により、明確で柔軟なエラーハンドリングが実現できると思います。
例外処理は、単なるエラー処理で留めるのは勿体ないと思います。
保守性の高いアプリケーションの一環として、設計に組み込むと良いと思います。
信頼性のあるコーディングを目指していきたいですね!