2018 03 06

PL/SQLのリファクタリングの3

ログ周りの改善。

という方針で考えた結果、ログ周りはtypeオブジェクトとした。

その結果。

まずはログ関連。

create or replace type Bat_Logger is object ( m_pkg varchar2(30) -- , member procedure bgn_log( p_fnc in varchar2 ) , member procedure end_log( p_fnc in varchar2 , p_cnt in number ) , member procedure err_log( p_fnc in varchar2 , p_msg in varchar2 ) , member procedure inf_log( p_fnc in varchar2 , p_sts in varchar2 , p_cnt in number , p_msg in varchar2 ) ) ; / create or replace type body Bat_Logger is member procedure bgn_log( p_fnc in varchar2 ) is begin inf_log( p_fnc , '処理開始' , 0 , null ) ; end ; -- member procedure end_log( p_fnc in varchar2 , p_cnt in number ) is begin inf_log( p_fnc , '正常終了' , p_cnt , null ) ; end ; -- member procedure err_log( p_fnc in varchar2 , p_msg in varchar2 ) is begin inf_log( p_fnc , '異常終了' , 0 , p_msg ) ; end ; -- member procedure inf_log( p_fnc in varchar2 , p_sts in varchar2 , p_cnt in number , p_msg in varchar2 ) is pragma AUTONOMOUS_TRANSACTION ; begin insert into バッチログ ( 日時 , 処理モジュール , 処理状況 , 処理件数 , 補足事項 ) values ( systimestamp , m_pkg || '.' || p_fnc , p_sts , p_cnt , p_msg ) ; commit ; exception when others then rollback ; dbms_output.put_line( dbms_utility.format_error_stack() ); dbms_output.put_line( dbms_utility.format_error_backtrace() ); end ; end ; /

パラメーターの型が テーブル名.列名%type になっていないのは、そう書くとコンパイルエラーになったから。 なんで駄目なのか。 依存関係の方向に制約があるのだろうか。 それともオブジェクトが特別扱いなのか。 マニュアルを見ても何も書いてないようなのだが。 見落としているだけなのかな。

まあこれはこういうものだとして、次は処理本体。

create or replace package 月次処理 is PKG_NM constant varchar2(30) := '月次処理' ; logger Bat_Logger ; -- function 当月分コピー return number ; -- function 納品コピー( p_bgn in date , p_end in date ) return number ; -- function 返品コピー( p_bgn in date , p_end in date ) return number ; end ; / create or replace package body 月次処理 is function 当月分コピー return number is FNC_NM constant varchar2(30) := '当月分コピー' ; FIN_DT constant number := 15 ; v_bgn date ; v_end date ; v_cnt number := 0 ; begin logger.bgn_log( FNC_NM ) ; -- v_bgn := add_months( trunc( sysdate, 'MONTH' ), -1 ) + FIN_DT ; -- 先月16日00:00:00 v_end := add_months( v_bgn, 1 ) - 1 / ( 24 * 60 * 60 ) ; -- 当月15日23:59:59 -- v_cnt := v_cnt + 納品コピー( v_bgn, v_end ) ; v_cnt := v_cnt + 返品コピー( v_bgn, v_end ) ; commit ; -- logger.end_log( FNC_NM, v_cnt ) ; return 0 ; exception when others then rollback ; dbms_output.put_line( dbms_utility.format_error_stack() ); dbms_output.put_line( dbms_utility.format_error_backtrace() ); logger.err_log( FNC_NM, sqlerrm ) ; return 1 ; end ; -- function 納品コピー( p_bgn in date , p_end in date ) return number is FNC_NM constant varchar2(30) := '納品コピー' ; v_cnt number := 0 ; begin logger.bgn_log( FNC_NM ) ; -- insert into 月次納品 ( ISBN , 納品先ID , 工場出荷日 , 納品予定日 , 納品実績日 , 価格 ) select ISBN , 納品先ID , 工場出荷日 , 納品予定日 , 納品実績日 , 価格 from 日次納品 where 納品実績日 between p_bgn and p_end ; v_cnt := sql%rowcount ; -- logger.end_log( FNC_NM, v_cnt ) ; return v_cnt ; exception when others then raise ; end ; -- function 返品コピー( p_bgn in date , p_end in date ) return number is FNC_NM constant varchar2(30) := '返品コピー' ; v_cnt number := 0 ; begin logger.bgn_log( FNC_NM ) ; -- insert into 月次返品 ( ISBN , 納品先ID , 返品予定日 , 返品実績日 , 払戻価格 , 負担割合 , 返品理由 ) select ISBN , 納品先ID , 返品予定日 , 返品実績日 , 払戻価格 , 負担割合 , 返品理由 from 日次返品 where 返品実績日 between p_bgn and p_end ; v_cnt := sql%rowcount ; -- logger.end_log( FNC_NM, v_cnt ) ; return v_cnt ; exception when others then raise ; end ; begin logger := new Bat_Logger( PKG_NM ) ; end ; /

処理本体とログ周りが分離できたのと、ログ出力に必要なパラメーターのみを指定できるようになったことで、本体側がかなりすっきりした気がする。 これまで毎回同じ値として設定していたパッケージ名は、使用するパッケージの初期処理内でインスタンス生成する際に指定するようにしている。

Open / Close Principle だっけ?

オブジェクト指向の考え方だけど、現状でもだいたい乗っかってるような気もする。

同じような何かコピーする機能を追加したければ 「何かコピー」 を作ればいい。 コピーするためのSQLは複雑かもしれないが、機能モジュールの追加は簡単だろう。

コピーする列や取得条件を変えたければ対象の 「何かコピー」 の中に手を入れる。 しかし変更はその中だけで、他のモジュールには影響しない。

なんかもういいかなと思ったが、眺めているうちにやっぱりまだ駄目な気がしてきた。

機能モジュールの追加は簡単かもしれないが、それぞれの 「何かコピー」 の中の処理は全部同じパターンなんだよな。

  1. 開始ログを出力する。
  2. コピーするためのSQLを実行する。
  3. 正常終了ログを出力する。
  4. 処理件数を返す。
  5. 例外発生時は例外を再スローする。

処理毎に違うのは、ログに出力する処理モジュールの名前とコピー用のSQLだけ。 まあ 「だけ」 なんて言ってもそれが本体だし、他は微々たるものなのだが。 機能追加は、既存のモジュールを丸ごとコピー・ペーストして名前とSQLを変更するだけで、それは関心毎に注力できているとも言えるのだが。 でも微々たるものであっても、繰り返せば面倒臭い。 二つ三つならいいけど、それ以上になるとかなり面倒。 ボイラープレートコードっていうんだっけ? こういうの。

ということで、明日はその辺をなんとかすることを考える。