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は複雑かもしれないが、機能モジュールの追加は簡単だろう。
コピーする列や取得条件を変えたければ対象の 「何かコピー」 の中に手を入れる。 しかし変更はその中だけで、他のモジュールには影響しない。
なんかもういいかなと思ったが、眺めているうちにやっぱりまだ駄目な気がしてきた。
機能モジュールの追加は簡単かもしれないが、それぞれの 「何かコピー」 の中の処理は全部同じパターンなんだよな。
- 開始ログを出力する。
- コピーするためのSQLを実行する。
- 正常終了ログを出力する。
- 処理件数を返す。
- 例外発生時は例外を再スローする。
処理毎に違うのは、ログに出力する処理モジュールの名前とコピー用のSQLだけ。 まあ 「だけ」 なんて言ってもそれが本体だし、他は微々たるものなのだが。 機能追加は、既存のモジュールを丸ごとコピー・ペーストして名前とSQLを変更するだけで、それは関心毎に注力できているとも言えるのだが。 でも微々たるものであっても、繰り返せば面倒臭い。 二つ三つならいいけど、それ以上になるとかなり面倒。 ボイラープレートコードっていうんだっけ? こういうの。
ということで、明日はその辺をなんとかすることを考える。