【IRIS/Cache】%Statusについて

本記事は、IRIS/Cacheにおける%Statusについて解説したいと思います。

※この記事は下記の方向けになります。
  • IRIS/Cacheの基礎文法に慣れている方
  • %Statusの活用方法を知りたい方

はじめに

本記事では、IRIS/Cacheのロジックで頻出する、「%Status(%Library.Status.cls)」の使い方について解説したいと思います。

%Statusは、例外処理や関数の戻り値として活用する場面が多いです。
また、他の言語とはやはり異なる側面もあり、新規ObjectScriptユーザーは少々抵抗感があるかと思います。

これから解説する内容で、それらの垣根が超えれれば幸いです。

では、開始します。

%Statusについて

基本情報

ObjectScriptでは、関数の戻り値やAPIの応答で「%Status」を利用しており、正常に終了したかどうかを判定しており、エラー処理の要となっています。

処理が正常に終了した場合は1を取得し、何らかの問題が発生した場合はエラー情報が取得できます。

%Status作成方法

%Statusを作成するには、3通りがあります。

s sts = $$$ERROR([エラー・コード], [メッセージ1], [メッセージ2], ...)
s sts = $$$ERR([エラー・コード])
s sts = $system.Status.Error([エラー・コード], [メッセージ1], [メッセージ2], ...)

マクロ「$$$ERROR」は、「%occStatus.inc」のインクルードが必要になります。
 →「%RegisteredObject」にはデフォルトでインクルードが設定されています。

エラーコードに関しては、「%occErrors.inc」から取得すればよいと思います。
メッセージに関しては、ドキュメントを参照して確認して下さい。(「一般的なエラー・メッセージ」で検索して下さい)。
 →3,000件くらい存在しています。

【サンプル】

%Statusから例外クラスを作成し、throwしています。

ClassMethod makeStatus()
{
	try {
		throw ##class(%Exception.StatusException).CreateFromStatus(
			$$$ERROR($$$FileCanNotCopy, "D:\Temp\from\hoge.txt", "D:\Temp\to\hoge.txt")
		)
	} catch e {
		w !, $system.Status.GetErrorText(e.AsStatus())
	}
}

【実行結果】
エラー #5024: ファイル ‘D:\Temp\from\hoge.txt’ を ‘D:\Temp\to\hoge.txt’ にコピー できません

分かりやすいエラー内容ですね。
%Fileの関数は、成否が%boolean値で返って来る関数が多いので、throwしたい場合はこのような手段を使うと良いと思います。

%Statusの合成

複数のエラーが同時に発生した場合はどうしましょう?
今までのサンプルでは、1つのエラーに対し1つの%Statusしかthrowしていませんでした。

複数の%Statusをthrowしたい時はどうしましょう?

そんな時は、%Statusを合成しちゃいましょう!

配列で合成

サンプル

ClassMethod compStatus(errCode As %Integer, arg...)
{
	#dim list as %Library.ListOfDataTypes
	try {
		s sts1 = $$$ERROR($$$FileCanNotOpen, "D:\Temp\hogehoge.txt")
		, sts2 = $$$ERROR($$$FileNameInvalid, "D:\Temp\hogehoge.txt")
		s cmpSts = $System.Status.AppendStatus(sts1,sts2)
		
		throw ##class(%Exception.StatusException).CreateFromStatus(cmpSts)
	} catch e {
		w !, $system.Status.GetErrorText(e.AsStatus()),!

		w $System.Status.GetOneStatusText(e.AsStatus(),1),! // 個別もOK
		w $System.Status.GetOneStatusText(e.AsStatus(),2),!
	}
}

【実行結果】
エラー #5005: ファイル ‘D:\Temp\hogehoge.txt’ を開けません
エラー #5006: ファイル名 ‘D:\Temp\hogehoge.txt’ が正しくありません

ファイル ‘D:\Temp\hogehoge.txt’ を開けません
ファイル名 ‘D:\Temp\hogehoge.txt’ が正しくありません

二つのエラーが表示されています。

合成関数のコーディングが大変だと思ったら、下記マクロでも合成が可能です。

s sts3 = $$$ERROR($$$DirectoryNameInvalid, "D:\Temp\")
s cmpSts = $$$ADDSC(cmpSts,sts3) // マクロ版

できれば多重エラーなんて見たくない光景ですが、エラーの対応時間が少なくなるのであれば、御の字ですね。

合成したエラーを分解する

複数のエラーがthrowされてきても困る!
1つづつ分解してロギングしたい!

そんな時は、関数「GetErrorText()」を使って、%Statusを分解してみましょう

【サンプル】

} catch e {				
	d $system.Status.DecomposeStatus(e.AsStatus(), .eList, "d")
	f pos=1:1:eList {
		w eList(pos),!
	}
}

第3引数の「”d”」はお好みで。
これで1行1行ロギングが可能になりました。

埋め込んで合成

次は埋め込み構造で合成してみます。
先ほどとは異なり、合成を行う関数に「EmbedStatus」を使用しています。

【サンプル】

ClassMethod compStatus(errCode As %Integer, arg...)
{
	#dim list as %Library.ListOfDataTypes
	try {
		s sts1 = $$$ERROR($$$FileCanNotOpen, "D:\Temp\hogehoge.txt")
		, sts2 = $$$ERROR($$$FileNameInvalid, "D:\Temp\hogehoge.txt")
		s cmpSts = $System.Status.EmbedStatus(sts1,sts2)
		
		s sts3 = $$$ERROR($$$DirectoryNameInvalid, "D:\Temp\")
		s cmpSts = $$$EMBEDSC(cmpSts,sts3) // マクロ版
		
		
		throw ##class(%Exception.StatusException).CreateFromStatus(cmpSts)
	} catch e {
		w !, $system.Status.GetErrorText(e.AsStatus()),!
	}
}

【実行結果】
エラー #5005: ファイル ‘D:\Temp\hogehoge.txt’ を開けません
> エラー #5006: ファイル名 ‘D:\Temp\hogehoge.txt’ が正しくありません
> エラー #5007: ディレクトリ名 ‘D:\Temp\’ が不正です

色々なthrow

%Statusをthrowするのであれば、マクロを活用するとコーディングが減って楽になります。
これらのマクロは、「%occStatus.inc」をインクルードすれば全て利用できます。

  • $$$ThrowOnError([処理])
  • $$$THROWONERROR([%Status], [処理])
  • $$$TOE([%Status], [処理])
  • $$$ThrowStatus([%Status])
  • $$$ThrowSQLCODE([SQLCODE], [メッセージ])
  • $$$ThrowSQLIfError([SQLCODE], [メッセージ])

$$$ThrowOnError

筆者が、普段よく使っているマクロです。
本当にお世話になっています。

このマクロは、引数に%Statusか%Statusを返す処理を実行し、「%Status」を判定してエラーの場合throwします。

【サンプル】

ClassMethod getStatus() As %Status
{
	q $$$ERR($$$TooManyErrors)
}
ClassMethod testThrow()
{
	try {
		$$$ThrowOnError(..getStatus())
	} catch e {
		w !, $system.Status.GetErrorText(e.AsStatus())
	}
}

$$$ThrowOnError, $$$TOE

$$$ThrowOnErrorと$$$TOEは、全く同じ内容です。

戻り値「%Status」を変数に取得できるので、catch部でエラーを握りつぶして後続処理でエラーを解析する事も可能です。

サンプル

ClassMethod testThrow()
{
	try {
		$$$TOE(sts, ..getStatus())
	} catch e {}
	w !, $system.Status.GetErrorText(sts)
}

$$$ThrowStatus

%Statusが成功であろうと失敗であろうと、「##class(%Exception.StatusException).ThrowIfInterrupt()」を実行し、必ずエラーとしてthrowします。

$$$ThrowSQLCODE

これは、「##class(%Exception.SQL).CreateFromSQLCODE()」を実行してthrowします。

$$$ThrowSQLIfError

SQLCODEは、0, 100, 負の整数値で、このマクロは「SQLCODE<0」の条件時に「CreateFromSQLCODE()」を実行します。

%Statusの判定

%Statusを判定するとして「%Status=1」は推奨されていません
%Statusを判定するためのマクロや関数が用意されているので、それらを活用しましょう。

%Statusの判定
  • $$$ISOK([%Status]), $SYSTEM.Status.IsOK([%Status])
  • $$$ISERR([%Status]), $SYSTEM.Status.IsError([%Status])
  • $$$QuitOnError([%Status])
  • $$$QUITONERROR([%Status], [処理])

$$$ISOK, $SYSTEM.Status.IsOK

%Statusが成功している時、「true」を返します。
 →実際の処理は、「%Status」に対し、否定を重ねる「”%Status」をしています。

$$$ISERR, $SYSTEM.Status.IsError

%Statusが失敗している時、「true」を返します。
 →これも「’%Status」しているだけです。

ClassMethod testCheck()
{
	i ($$$ISERR(..getStatus())) {
		w !,"エラー発生"
	}
}

$$$QuitOnError

%Statusが失敗している時、quitを行い%Statusを返します。

ClassMethod testCheck() As %Status
{
	$$$QuitOnError(..getStatus())
}

$$$QUITONERROR

引数に、%Statusの受け取りと処理を渡します。
%Statusが失敗している時、quitを行い%Statusを返します。

$$$QuitOnErrorの方が引数少ないし良いですね。
これは使わないと思います。

新規エラーコードの作成

システムによっては、既存のエラーメッセージでは物足りない時・・・
そんな時は、カスタムしたエラー・コードとメッセージを作成しましょう。

%MessageDictionary

カスタム・エラーを作成するには、%MessageDictionary(%Library.MessageDictionary.cls)を使用します。

では、オリジナルのカスタム・エラーを作成していきましょう!

xmlファイルの作成

新規作成するエラー情報を作成します。
ファイル名は適宜命名してください(今回はcustomError.xml)。

カスタム・エラー情報を作成するには、<Message>エレメントを編集する事になります。
 ※「Language」「Domain」以外は定型

項目説明
Idエラーコード
Name名称(用途不明 設定しなくても動くみたい?)
エラー・メッセージになります。
%1, %2…と設定する事で、エラー時の引数を充てる事が可能です
<?xml version="1.0" encoding="UTF-8" ?> 
<MsgFile Language="ja">
  <MsgDomain Domain="UserErrors">
    <Message Id="-1000" Name="CustomErr1">坊やだからさ。</Message> 
    <Message Id="-1001" Name="CustomErr2">すごい・・・%1が熱中するわけだ。</Message> 
    <Message Id="-1002" Name="CustomErr3">それでも%1ですか!%2</Message>   
  </MsgDomain>
</MsgFile>

xmlファイルのインポート

カスタム・エラーを作成したら、次は反映するネームスペースでインポートします。

インポートコマンド

zn "SAMPLE"
s path="[xml配置dir]\customError.xml"
w ##class(%MessageDictionary).Import(path)

インポート内容の確認
 →ターミナルに張り付けて確認してみて下さい。

CheckMessage	;
	s ms = ##class(%MessageDictionary).%New()
	, ms.Domain = "UserErrors"
	, ms.Language = "ja"
	s id=""
	f {
		d ms.GetNextMessage(.id) q:id=""
		w "エラー・コード:"_id_", メッセージ:"_ms.GetMessage(id)
	}
d CheckMessage

【実行結果】
エラー・コード:-1002, メッセージ:それでも%1ですか!%2
エラー・コード:-1001, メッセージ:すごい・・・%1が熱中するわけだ。
エラー・コード:-1000, メッセージ:坊やだからさ

使用してみる

では、カスタム・エラーの運用を確認してみます。

【サンプル】

ClassMethod testCustomError(errCode As %Integer, arg...)
{
	#dim list as %Library.ListOfDataTypes
	try {
		throw ##class(%Exception.StatusException).CreateFromStatus(
			$$$ERROR(errCode, arg...)
		)
	} catch e {
		w !, $system.Status.GetErrorText(e.AsStatus())
	}
}

実行結果

あー、うん。
ちゃんとオリジナルのメッセージが出力されていますね。

これでシステム固有のエラーメッセージを表示する事が出来ました。
エラー処理も、より柔軟に対応できますね。

カスタム・エラーの削除

不要になったメッセージを削除するには下記コマンドを使用します。

w ##class(%MessageDictionary).Delete([Language]) // Language > "ja"

コマンドを実行すると、一切合切消えてしまいます。

おわりに

いかがだったでしょうか。

%Statusの正しい扱い方を理解する事で、ObjectScriptにおけるエラー処理の柔軟性や信頼度が大きく向上すると思います。

また、システムの安全な運用や保守性を高めるためにも、公式が用意しているユーティリティメソッドを活用していきましょう。

日々、より堅牢なコード設計を目指していきたいですね。