例外処理

Javaでのtry-catchを知ってるくらいで、正直語るほど詳しくはありません。何かツッコミどころがあったらヨロシク。業務中に感じたことをベースにしているので言語はABAPですが、例外処理機構が組み込まれた他のプログラム言語でも本質は変わらないはず。参考にしたのはこの辺


(他のプロジェクトのことをよく知らないので、思いっきり推測だけど)ABAPでの開発って、オブジェクト指向をはじめとしてなかなか新しいプログラミング概念が浸透していないのではないかと思います。COBOLに類似しているとも言われますし昔から実績がある言語なので、開発者の間でも「新しい概念なんて取り入れなくても、昔ながらのやり方で十分にアプリケーション開発できてるよ!」という意識があるのでしょう。

ですが、開発対象となるアプリケーションも昔と違って大規模かつ複雑なものとなっているのだから、有用と思われる概念・技術はどんどん取り込んでいかないと対処できません。SAPも少しは考えているようで、オブジェクト指向に慣れていない開発者でもできるだけとっつきやすいように、例えばBADIといった仕組みを用意してGUIツールからOOPを実装できるようなサポートもされてはいます。が、まだまだ現状は…といった感じでしょう*1

ということで、今回はABAPにおける例外処理について、新しいコンセプトであるクラスベースの例外処理*2について考えてみたいと思います。


今回のプロジェクトで人のプログラムを読んでいる中で一番気持ち悪いと感じたのは、フラグでエラー時の処理フロー制御を行っているところ。一例を示すと、こんな感じ。

DATA: err_flg_a, " サブルーチン a でのエラーフラグ
      err_flg_b. " サブルーチン b でのエラーフラグ


サブルーチン a 実行

IF err_flg_a がオンでなければ
  サブルーチン b 実行
ENDIF.

IF err_flg_a がオンでなく、
   err_flg_b がオンでなければ
  サブルーチン c 実行
ENDIF.

IF   err_flg_a がオンならば
  エラーメッセージ A 出力
ELSE err_flg_b がオンならば
  エラーメッセージ B 出力
ENDIF.


サブルーチン a
  いろいろ処理…
  エラーがある場合、err_flg_a をオンにする

サブルーチン b
  いろいろ処理…
  エラーがある場合、err_flg_b をオンにする

無駄にグローバル変数を使っているとかいろいろ突っ込みどころはあるけど、何よりそのフラグ制御に萎えちゃいます…orz。こういう風にエラー処理が伝播している場合は、その都度フラグチェックするのではなく例外処理を使いましょうよ。

TRY.
  PERFORM sub_routine_a.
  PERFORM sub_routine_b.
  PERFORM sub_routine_c.

  CATCH cx_local_exception_a.
    MESSAGE A.
  CATCH cx_local_exception_b.
    MESSAGE B.
ENDTRY.

*** サブルーチン定義
FORM sub_routine_a RAISING cx_local_exception_a.
  ...
  IF sy-subrc <> 0.
    RAISE EXCEPTION TYPE cx_local_exception_a.
  ENDIF.
ENDFORM.

FORM sub_routine_b RAISING cx_local_exception_b.
  ...
  IF sy-subrc <> 0.
    RAISE EXCEPTION TYPE cx_local_exception_b.
  ENDIF.
ENDFORM.

*** ローカル例外クラス定義
CLASS cx_local_exception_a DEFINITION
  INHERITING FROM cx_static_check.
ENDCLASS.

CLASS cx_local_exception_b DEFINITION
  INHERITING FROM cx_static_check.
ENDCLASS.

複数件の伝票をループで処理しようとして、例外aの場合はその伝票のみ処理を中止して、例外bの場合はメイン処理自体を中止したい場合。実機で試してないけど、多分こんな感じ。

...

*** 複数件の伝票(tb_order)を一件ずつ(st_order)処理
TRY.

  LOOP tb_order INTO st_order.

    TRY.
      PERFORM sub_routine_a.
      PERFORM sub_routine_b.
      PERFORM sub_routine_c.

      CATCH cx_local_exception_a.
        MESSAGE A.
    ENDTRY.

  ENDLOOP.

  CATCH cx_local_exception_b.
    MESSAGE B.

ENDTRY.

...

例外bのときは、ループの中からループの外まで抜ける大域脱出だけど、これをフラグ制御でやろうとすると果てしなくややこしいことになりますよね*3


このような例外処理によるメリットは、

  • 正常処理の流れが明確になって、ソースコードを追いやすくなること

逆にデメリットは、

  • (大域脱出によって)例外時の処理があっちこっちに飛ぶこと
  • 例外クラスを定義する必要があること

かな。特に前者のデメリットについて先輩が話していたけど、デバッグ中に例外発生していきなり別のモジュールに遷移されると何が何だか分からずにこのポルナレフのような状態になることが嫌だと。確かに一理ありますね。

けど、そもそものコードが読みやすければ、無理してデバッグで処理フロー追いかけずとも机上でコードを追っていくだけで理解できるはず。きれいにルーチン分けがされていれば、ユニットテストを書いておけば動作確認もできることですし。


一言に例外処理といっても、奥が深いものですよね。今回実際の業務中に適用することを考えて、改めてその深さを実感しました。もうちょい分かりやすい資料を作って、プロジェクトメンバーに推進していこっかな♪

追記

以前読んだ本「Java言語で学ぶリファクタリング入門」に、「例外によるエラーコードの置き換え」と思いっきり実例が載っていました…orz

Java言語で学ぶリファクタリング入門

Java言語で学ぶリファクタリング入門

*1:あくまで、身近なところだけだったらすいませんorz

*2:特に新しくもないかもしれないけど

*3:っていうか、なっています…