
PowerBuilder に限らずアプリケーション開発では、堅牢なエラーハンドリング設計がシステムの品質と保守性を左右し得る重要な要素となります。
今回は、画面・UI からシステム最底層までをカバーする「3層エラーハンドリング・アーキテクチャ」の実装アプローチを解説します!
Java 開発の経験があるエンジニアにとって、PowerBuilder のモダンな例外処理は非常に親しみやすい設計になっています。
PowerBuilder には Java と同様の TRY-CATCH-FINALLY 構文があり、オブジェクトの継承関係もほぼ同じ構成をとっています。
【Java 例外クラス階層】
java.lang.Throwable
├── java.lang.Error (システム障害・メモリー不足等の回復不能なエラー)
└── java.lang.Exception (プログラムで対処可能な例外)
├── java.lang.RuntimeException (実行時例外)
│ ├── NullPointerException
│ ├── ArithmeticException
│ └── IndexOutOfBoundsException
└── (その他の検査例外 - try-catch または throws が必要)
├── IOException
└── SQLException
【PowerBuilder の例外階層構造】
Throwable
├── RuntimeError (PBVM が発生させるシステム実行時エラー)
│ ├── NullObjectError
│ ├── DivideByZeroError
│ ├── DWRuntimeError (DataWindow 関連)
│ └── OLERuntimeError / PBXRuntimeError
└── Exception (検査例外の親クラス CATCH または THROWS 指定が必要)
└── UserException (ユーザー定義例外の基本クラス)
上記のように共通した例外処理の構造になっていますが、PowerBuilder には Java と決定的に異なる存在があります。
それは、強力な画面・データ管理コンポーネントである「データウィンドウ」です。
データウィンドウ上で起きる SQL エラーや型チェックエラーは、TRY-CATCH の例外 (Throwable) として投げるのではなく、イベントハンドラー (DBError や ItemError) でハンドリングするのが PowerBuilder 独自の歴史的かつ強力なアーキテクチャです。
そのため、堅牢な PowerBuilder アプリを構築するには、これから説明する 3 つのレイヤーに応じた使い分けが不可欠となります。
PowerBuilder でエラーハンドリングを実装したサンプルソースを作成しました。
DB をドロップダウンデータウィンドウで指定した値で検索し、検索結果をデータウィンドウに表示し下記の処理を行います。
【サンプルアプリケーション】

今回は、下記のレイヤーごとにエラー処理を行い、レイヤー 1 から順にエラーを捕捉します。
イベント (レイヤー 1) や Try-Catch (レイヤー 2) では捕捉できなかった、想定外のエラーを Application オブジェクトの SystemError (レイヤー 3) で処理し安全にシステムを終了させます。
【エラー処理のレイヤー構造】
データウィンドウコントロールの枠内で起きるエラーは、DBError や ItemError などのエラーイベントで捕捉し、独自にカスタマイズしたメッセージを表示できます。
下記コードは SQL の制約違反や重複キーエラーが発生した際に、標準ダイアログを出さずにユーザーへ分かりやすいメッセージを表示する例です。
【データウィンドウの DBError イベント】
String ls_err_msg CHOOSE CASE sqldbcode CASE 1, 2627 // キー重複など ls_err_msg = "商品コードが重複しています。" CASE ELSE ls_err_msg = "DB 更新エラーが発生しました。~r~n" + & "コード: " + String(sqldbcode) + "~r~n" + & "詳細: " + sqlerrtext END CHOOSE MessageBox("更新失敗", ls_err_msg, StopSign!) RETURN 1
【データウィンドウの itemError イベント】
String ls_col_name ls_col_name = This.GetColumnName() // 入力値のチェック IF ls_col_name = "order_qty" THEN MessageBox("入力エラー", "注文数量には正しい数値を入力してください。", Exclamation!) ELSE MessageBox("入力エラー", "入力された値の形式が正しくありません。", Exclamation!) END IF RETURN 1
RETURN 0 で返却した場合に表示される PowerBuilder 標準のダイアログには、下記の画像のように実行された SQL が表示されます。

これは開発者目線では便利ですが、内部情報の流出やユーザーの混乱を招く恐れがあるため、このような内部的なエラーを表示するべきではありません。
RETURN 1 を返却し PowerBuilder 標準のダイアログの表示を抑止することを推奨します。
業務ロジック層における例外伝播の仕組みは、以下の 3 つの役割で成り立っています。
【処理確定ボタン (cb_save) の Clicked イベント】
IF dw_1.AcceptText() <> 1 THEN Return // 変更・追加・削除されたデータがあるか確認 IF dw_1.ModifiedCount() = 0 AND dw_1.DeletedCount() = 0 THEN MessageBox("案内", "変更されたデータはありません。", Information!) Return END IF // カスタム非ビジュアルオブジェクトのインスタンス化 n_cst_order_service luo_service luo_service = CREATE n_cst_order_service // DB 更新とトランザクション制御 TRY // 業務ロジック呼び出し (エラー時は内部で uo_business_exception が THROW される) IF luo_service.of_save_orders(dw_1) = 1 THEN COMMIT USING SQLCA; MessageBox("完了", "データを正常に保存しました。") END IF CATCH ( uo_business_exception e_biz ) // 【業務エラーの捕捉】ロールバックしてメッセージ表示 ROLLBACK USING SQLCA; MessageBox("業務エラー", e_biz.GetMessage(), Exclamation!) CATCH ( RuntimeError e_run ) ROLLBACK USING SQLCA; MessageBox("エラー", "更新中にシステムエラーが発生しました:~n" + e_run.GetMessage(), StopSign!) FINALLY // エラー発生有無を問わず、FINALLY 句が実行される // オブジェクトの破棄 DESTROY luo_service END TRY
【uo_business_exception の of_set_error】
// エラーコードの設定 il_error_code = al_code // 親クラス(Exception)の標準機能へメッセージを設定 This.SetMessage(as_msg)
【n_cst_order_service の of_save_order】
Long ll_i, ll_qty // 業務チェック: 注文数量が 0〜1000 の範囲内か確認 FOR ll_i = 1 TO adw_target.RowCount() ll_qty = adw_target.GetItemNumber(ll_i, "order_qty") IF IsNull(ll_qty) OR ll_qty < 0 OR ll_qty > 1000 THEN // 条件違反時に独自例外を CREATE してスロー uo_business_exception luo_ex luo_ex = CREATE uo_business_exception luo_ex.of_set_error(2001, String(ll_i) + "行目の注文数量が不正です(0〜1000の範囲で指定してください)。") THROW luo_ex // ここで処理を中断し、呼び出し元(TRY-CATCH)へ割り込む END IF NEXT // DB 更新処理の実行 IF adw_target.Update(True, False) <> 1 THEN // Update 失敗時に一括ロールバック用の例外をスロー uo_business_exception luo_db_ex luo_db_ex = CREATE uo_business_exception luo_db_ex.of_set_error(9001, "データベースの更新処理に失敗しました。") THROW luo_db_ex END IF Return 1
アプリ内のどこかで TRY-CATCH されなかった例外や、予期せぬクラッシュ (未捕捉の NullObjectError 等) が発生した場合、制御は最終的に Application オブジェクトの SystemError イベント へ到達します。
ここではアプリを安全に終了させる直前に、エラーの内容 (発生スクリプト、行番号、メッセージ) をログファイルへ出力します。
【アプリケーションオブジェクトの SystemError イベント】
String ls_err_msg ls_err_msg = "【致命的なシステムエラーが発生しました】~r~n~r~n" + & "エラー番号: " + String(Error.Number) + "~r~n" + & "発生オブジェクト: " + Error.Object + "~r~n" + & "イベント/関数: " + Error.ObjectEvent + "~r~n" + & "行番号: " + String(Error.Line) + "~r~n" + & "メッセージ: " + Error.Text // 画面にエラー詳細を表示 (実務ではここでログファイル出力を行う) MessageBox("アプリケーション停止", ls_err_msg, StopSign!) // トランザクションの安全な切断とアプリ終了 ROLLBACK USING SQLCA; DISCONNECT USING SQLCA; Halt Close
いかがでしょうか。
PowerBuilder のエラーハンドリングは、Java と同等のモダンな TRY-CATCH 構文を備えつつ、データウィンドウ特有のエラーイベントという UI 制御の強力な武器をあわせ持っています。
画面側 (フロントエンド) で簡単な入力チェック
業務固有のロジック (バックエンド) に関する詳細なチェック
想定外のエラーを処理する最後の砦
今回ご紹介した 3 層を意識することで、エラー捕捉の抜け漏れを防ぎ、PowerBuilder アプリケーションを障害に強く保守性の高いものにしていきましょう。