CLOSE

PowerBuilder に限らずアプリケーション開発では、堅牢なエラーハンドリング設計がシステムの品質と保守性を左右し得る重要な要素となります。
今回は、画面・UI からシステム最底層までをカバーする「3層エラーハンドリング・アーキテクチャ」の実装アプローチを解説します!

Java と PowerBuilder のエラー処理思想の共通点と違い

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 (ユーザー定義例外の基本クラス)

最大の違い:データウィンドウ (DataWindow) の存在

上記のように共通した例外処理の構造になっていますが、PowerBuilder には Java と決定的に異なる存在があります。
それは、強力な画面・データ管理コンポーネントである「データウィンドウ」です。

データウィンドウ上で起きる SQL エラーや型チェックエラーは、TRY-CATCH の例外 (Throwable) として投げるのではなく、イベントハンドラー (DBError や ItemError) でハンドリングするのが PowerBuilder 独自の歴史的かつ強力なアーキテクチャです。
そのため、堅牢な PowerBuilder アプリを構築するには、これから説明する 3 つのレイヤーに応じた使い分けが不可欠となります。


3 つのレイヤー別・エラーハンドリングの実装例

PowerBuilder でエラーハンドリングを実装したサンプルソースを作成しました。
DB をドロップダウンデータウィンドウで指定した値で検索し、検索結果をデータウィンドウに表示し下記の処理を行います。

  • 登録 (新規登録ボタン押下)
  • 更新 (データウィンドウの行を直接編集)
  • 削除 (削除ボタン押下)
  • データの変更内容を COMMIT (処理確定ボタン押下)

【サンプルアプリケーション】

今回は、下記のレイヤーごとにエラー処理を行い、レイヤー 1 から順にエラーを捕捉します。
イベント (レイヤー 1) や Try-Catch (レイヤー 2) では捕捉できなかった、想定外のエラーを Application オブジェクトの SystemError (レイヤー 3) で処理し安全にシステムを終了させます。

【エラー処理のレイヤー構造】

  • レイヤー 1:画面・UI層
    • データウィンドウのイベント (DBError / ItemError) で標準エラーを抑止・制御
    • 入力値チェックを行い、エラー発生後もシステムの稼働を継続する
  • レイヤー 2:業務ロジック・データ処理層
    • Try-Catch と カスタム Exception オブジェクトで一括トランザクション制御
    • 業務ロジックの検証とトランザクション制御を行い、エラー発生後もシステムの稼働を継続する
  • レイヤー 3:システム全体(最底層)
    • Application オブジェクトの SystemError イベントで落ちる前のログ出力と安全な終了
    • 想定外のエラー発生時に、システムを安全に停止する

【レイヤー 1: 画面・UI レイヤー】データウィンドウでの入力チェック・SQL エラー

データウィンドウコントロールの枠内で起きるエラーは、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 標準のダイアログの表示を抑止することを推奨します。

【レイヤー 2: 業務ロジック・データ処理レイヤー】Try-Catch と 独自例外の伝播

業務ロジック層における例外伝播の仕組みは、以下の 3 つの役割で成り立っています。

  • 独自例外の作成 (uo_business_exception)
    Exception クラスを継承して、業務エラーの例外オブジェクトを事前に作成する。
  • 業務ロジックでの検証と独自例外の Throw (n_cst_order_service)
    業務ロジックを実装し、業務エラー発生時に、呼出元に独自例外として Throw する。
  • Try-Catch で捕捉
    Try 句から業務ロジックを呼び出し、Throw された独自例外を捕捉し、処理を継続する。

【処理確定ボタン (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

【レイヤー 3 : システム全体 (最底層)】SystemError イベントでの最終防衛

アプリ内のどこかで 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 制御の強力な武器をあわせ持っています。

  • 【レイヤー 1: 画面・UI層】
  • 画面側 (フロントエンド) で簡単な入力チェック

  • 【レイヤー 2: 業務ロジック・データ処理層】
  • 業務固有のロジック (バックエンド) に関する詳細なチェック

  • 【レイヤー 3: システム全体 (最底層)】
  • 想定外のエラーを処理する最後の砦

今回ご紹介した 3 層を意識することで、エラー捕捉の抜け漏れを防ぎ、PowerBuilder アプリケーションを障害に強く保守性の高いものにしていきましょう。

x instagram facebook youtube