PL/SQL を構造的に考える

2020/04/19

PL/SQL
で業務コードを書くという仕事があり、元々は俺の部署に仕事の依頼を流すということだったので、どうやったら運用保守も楽になるのか考えていたんだけど。結局、別部署の人たちがうんこなコードを書いて、俺にレビューを通さずに作って「ソースコードの管理をお願います」とか言ってきてる。

仕様書もコードをちら見したけどダメダメっぽいし、レビューすらしないで常に負債を作り続けるのなら、この業界引退してくれんかな。
インタビューで聞いた大雑把な要件はこんな感じ。

要件

  • 今まで各業務でバラバラだった送信先データの取得する PL/SQL をつくる
  • 仕様統一するつもりだが、部署によって違う可能性がある
    数テーブルから、A にデータが無ければ B、B に無ければ C
    というようになっているらしい。この順序が各業務によってバラバラで、且つ、何らかの条件に寄ってもテーブルの順序が変わるらしい。
  • 各業務から呼ぶ PL/SQL は一つで統一する
    各業務のデータの取得する仕様は違うが、各業務呼び出す PL/SQL
    は一つにする。

結果

ハッキリは見てないが、これを引数 9 ~ 11 個の冗長的な PL/SQL
を作ってやがった…
だれがその物凄いパターンのテストを行い、今後の保守をするんだ。

俺ならこうする

  • 各業務で共通的な仕様を決定し、その PL/SQL を作る
  • 共通仕様に則れない業務は業務固有の PL/SQL を作る
  • これらの PL/SQL をラップする PL/SQL を作る

こうすることでのメリットは次の通り。

  • 共通仕様が明確になる
  • 共通仕様から外れている業務が明確になる
  • 各 PL/SQL のバグが影響を及ぼすスコープの範囲が縮まる
  • 単体テストがやりやすい
  • 共通仕様から外れている業務が共通仕様に移る時の修正が他業務に影響を及ぼさない。逆も又然り

よーし、ちょっと久々に ORACLE やってみるかー!

sqlplus でログイン

sqlplus misono/misono@misono

misono 好きすぎてすみませんすみません。

共通仕様の関数を作成する

CREATE OR REPLACE FUNCTION misono.DEFAULT_SENDTO
(
    USERID IN NUMBER
)
RETURN VARCHAR2
IS
    RTN   VARCHAR2(32);      -- Return Text
BEGIN
    RTN := 'DEFAULT_' || USERID;
    RETURN RTN;
END;
/

hoge 業務の関数を作成する

CREATE OR REPLACE FUNCTION misono.HOGE_SENDTO
(
    USERID IN NUMBER
)
RETURN VARCHAR2
IS
    RTN   VARCHAR2(32);      -- Return Text
BEGIN
    RTN := 'HOGE_' || USERID;
    RETURN RTN;
END;
/

各関数をラップする関数を作成する

set serveroutput on;

CREATE OR REPLACE FUNCTION misono.SENDTO
(
    CODE   IN VARCHAR,
    USERID IN NUMBER
)
RETURN VARCHAR2
IS
    IS_FUNC CONSTANT VARCHAR(60) := 'SELECT COUNT(*) FROM USER_SOURCE WHERE NAME = :data1';
    NAME  VARCHAR2(32);      -- Function Name
    FLG   NUMBER DEFAULT 0;  -- Function Exists
    RTN   VARCHAR2(32);      -- Return Text
    TXT   VARCHAR2(500);     -- Work
BEGIN

    NAME := CODE || '_SENDTO';
    -- Function Exists
    EXECUTE IMMEDIATE IS_FUNC INTO FLG USING NAME;

    if FLG = 0 then
        NAME := 'DEFAULT_SENDTO';
    end if;

    TXT := 'SELECT ' || NAME || '(:data1) FROM DUAL';
    EXECUTE IMMEDIATE TXT INTO RTN USING USERID;
    dbms_output.put_line(TXT);

    RETURN RTN;
EXCEPTION
    WHEN NO_DATA_FOUND THEN
        dbms_output.put_line('nodata');
END;
/

実際に試してみるとこんな感じで、hoge 業務以外は default に落ちている

SQL> select sendto('fuga', 2345) from dual;

SENDTO('fuga',2345)
----------------------------------------------------
DEFAULT_2345

SQL> select sendto('HOGE', 2345) from dual;

SENDTO('HOGE',2345)
----------------------------------------------------
HOGE_2345

SQL> select sendto('HOG', 2345) from dual;

SENDTO('HOG',2345)
----------------------------------------------------
DEFAULT_2345