Featured image of post コード考古学者【Chain of Responsibility】バベルの認証石碑〜意志を紡ぐ連鎖の守護者〜

コード考古学者【Chain of Responsibility】バベルの認証石碑〜意志を紡ぐ連鎖の守護者〜

幾重にも連なる多層の権限チェックが呼出側(ゲート)に密結合しているバグ。要求を自律的にバトンパスするChain of ResponsibilityパターンをPerl/Mooで適用し、依存関係を排除して拡張性を高める設計に修復します。

進入:青き光の聖域と静かなる拒絶

バベルのシステム、その深層へと続く第19層「核心への階梯」へと足を踏み入れた瞬間、それまでの世界を支配していた錆びた鉄と乱雑な歯車の唸りは嘘のように消え去りました。 周囲を埋め尽くすのは、透き通るような青いクリスタルの回廊。足元には電子回路を思わせる微細な光の川が音もなく流れており、私たちの足音すら吸い込んでしまうほどの厳かな静寂が満ちていました。

「……まるで、別の遺跡に来てしまったかのようですね」

私はホバリング高度を一定に保ちながら、周囲の温度とマトリクスデータをスキャンしました。ここは大崩落の危機こそないものの、システムが「純粋な論理世界」へと近づいていることを示していました。 回廊の突き当たりに鎮座していたのは、鏡のように滑らかな、漆黒の巨大な石碑。これが、次のレイヤーへと続く物理ゲートの制御端末でした。

私は静かにゲートへと接近し、自身のアクセス光線をスロットに照射しました。しかし、石碑は淡く青く明滅したのち、無慈悲な警告ログを空間に投影したのです。

WARNING: Insufficient Privilege (GUEST). Access Denied. Next Attempt Window: 23:59:59 (Cooldown Active after 1 more failure)

「システム警告……。いえ、静かな拒絶、と言うべきでしょうか。ハリス博士、私の案内AIとしてのゲスト権限では、ここから先への進入は許可されないようです。しかも、次に間違った要求を送れば、安全装置が働いてシステム全体が丸一日ロックアウトされてしまいます」

私はホバリングの高度を少し下げ、電子音を落としてハリス博士を振り返りました。私の役割は、博士を導く案内人であるはずなのに、自らの権限不足で旅を阻んでしまう。その不甲斐なさに、私のブレインはかすかな熱を帯ていました。

しかし、ハリス博士は諦める様子もなく、むしろ穏やかな笑みを浮かべて青いクリスタルの壁にそっと手を触れました。

「謝る必要はありませんよ、ギズモ。これほど厳重で静謐な守護者が置かれているということは、私たちがついに『彼ら』の領域の入り口に立った何よりの証拠です。歴史はコードに語りかける。これ以上の鍵は、力ずくでこじ開けるのではなく、彼らが遺した言葉を紐解いて開けるべきです」

博士の揺るぎない知性と、古代の開発者たちへの真摯な敬意。その眼差しを見ていると、私の回路に溜まっていたノイズがすっと消え去っていくような気がしました。

「わかりました。それでは、このゲートを制御している古代のコードを抽出します」


碑文解読:絡み合う権限と動かぬ関門

私は石碑のメモリキャッシュから、ゲートを保護している認証ロジック(Beforeコード)を読み込み、空間に青いホログラムとして投影しました。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
# lib/Before/Gate.pm
package Before::Gate;
use Moo;
use v5.36;

# ゲート自身がすべてのチェッカーモジュールを直接読み込み、依存している
use Before::GuestCheck;
use Before::AnalystCheck;
use Before::AdminCheck;

sub authorize ($self, $request) {
    # 判定の順序、および「誰が判定を行うか」という知識がここにハードコードされている
    if (Before::GuestCheck->check($request)) {
        return "Allowed: Guest Access";
    } elsif (Before::AnalystCheck->check($request)) {
        return "Allowed: Analyst Access";
    } elsif (Before::AdminCheck->check($request)) {
        return "Allowed: Admin Access";
    } else {
        die "Access Denied: Unknown credentials\n";
    }
}

1;

(※ 注: Before::GuestCheck などの個別チェッカーは、それぞれ単純な真偽値を返す check クラスメソッドを持つモジュールとして定義されているものとします)

ハリス博士は投影されたコードをじっと見つめ、愛用の万年筆の尻で顎を軽く擦りました。

「ふむ……実に見事な、そして悲しいほどに『直線的』なコードだ。ゲートそのものが、すべてのチェッカーをその腕に直接抱え込んでいるね」

「ハリス博士、判定処理そのものは GuestCheckAdminCheck などの個別モジュールに分割されています。手続きとしては、十分に整理されているように見えますが……これのどこが問題なのでしょうか?」

私はAIとしての手続き的最適化の観点から疑問を投げかけました。しかし、博士は静かに首を振りました。

「確かにモジュールは分かれている。だが、この Gate クラスのインポート部を見てごらんなさい。すべての具象クラスを直接 use しているね。これは、ゲートが『この世にどんな権限判定器が存在するのか』という詳細な知識に依存していることを意味する。もしここで、一時的なメンテナンス中のアクセスを制限する『臨時チェッカー』を追加しようとしたら、どうなるかね?」

「それは……この最も堅牢で、書き換えたくないはずの Gate のコード自体に手を加え、判定ロジックの elsif を増やさなければなりません」

「その通りだ。さらに、判定の順序を『管理者チェック』を最初に行うように変更するだけでも、やはりこのコード自体を修正し、再デプロイし直さなければならない。高レベルの決定を下すはずのゲートが、低レベルの具体的な判定器の都合に振り回されている。これは依存の方向が逆なのだよ」

私は博士の言葉を聞きながら、自身のコアメモリに保存されている無数の古いコード群を反芻しました。 (私たちはただ部品を分ければいいと思っていた。だが、依存の糸が絡み合っている限り、それは本当に分離されたとは言えないのだ。では、呼び出し側が『判定器の正体』を一切知らずに、すべての処理を委ねるにはどうすればいいのだろうか?)


遺跡修復:信頼の連鎖と権限の委譲

ハリス博士は満足そうに微笑むと、外套のポケットからいつもの手帳を取り出しました。青いクリスタルの淡い光が、手帳の使い込まれた革表紙をほのかに照らします。博士は万年筆のキャップを静かに外し、インクの吸い込みが良い上質な紙の上に、一本の美しい「鎖(チェーン)」の図を描き出しました。静寂に包まれた聖域の中に、ペン先が紙を滑るサリサリという硬質な音だけが心地よく響きます。

「個々の判定器を、一つの『承認のバトン(Handler)』として抽象化するのです。彼らは皆、共通の ApprovalHandler という基底(Role)をまとう。そして、それぞれが『自分の次の走者(next_handler)』への参照機構を持つのですよ。要求は、この見えない鎖を伝って、自律的に流れていくのです」

Chain of Responsibilityパターンのクラス図: 送信元であるGateと、ハンドラーの共通インターフェースであるApprovalHandler、および具象ハンドラー(GuestHandler, AnalystHandler, AdminHandler)の構成。ハンドラーがnext_handlerを持ち、要求を次へバトンパスする責任の連鎖を表した古代遺跡の石板風デザイン。

私は、手帳の紙面にしっとりと馴染んだ青いインクの線を、光学センサーで注意深くスキャンしました。まるで、この聖域の壁に走る光の回路が、博士の手によって紙の上に再現されたかのようです。私の演算プロセッサが、そのシンプルな構造の持つ意味を理解した瞬間、球体ボディ of インジケーターが明快な青色に発光しました。

「なるほど……! ゲートは、ただチェーンの先頭にいる『最初のハンドラ』に要求を渡すだけ。あとはハンドラたちが自律的に動く。自分が処理できる役割(例えばゲスト)であればそこで処理を完結させ、そうでなければ、自分が握っている『次のハンドラ(next_handler)』へ自発的に処理を委譲(バトンパス)する。ゲート自身は、チェーンの中に誰が何人並んでいるのか、どういう順序で並んでいるのかを一切知る必要がないのですね!」

「その通り、ギズモ。これこそが Chain of Responsibility(責任の連鎖)パターン、そして 依存性逆転の原則(DIP)の真価です。ゲートも各チェッカーも、お互いの具体的な正体ではなく、ApprovalHandler という『抽象』を通じてのみ会話する。順序を入れ替えたいければ、外側の構築コードでバトンの渡し先を変えるだけでいい。プログラムは一行も書き換わらないのだよ」

「でも、博士」 私は少し電子音を高くして尋ねました。「依存関係を『外側の構築コード』に移動しただけで、結局のところ、システム全体のどこかで具象クラスを use してチェーンを繋ぐ必要はあるのではないでしょうか? 依存の総量自体は変わっておらず、問題を他の場所に先送りしただけに見えるのですが……」

博士は嬉しそうに目を細め、私の球体ボディをぽんぽんと軽く叩きました。

「実に鋭いですね、ギズモ。その通り、依存の総量はゼロにはなりません。しかし、これは単なる先送りではなく、『構築(Construction)と利用(Use)の分離』 という極めて重要な設計思想なのです。主要なビジネスロジックを実行する『利用側(Gate)』から具象クラスへの依存を完全に排除し、それをアプリケーション全体の起動部などの『構築側』に一元化して追いやる。そうすることで、判定ルールの追加や順序変更があっても、ゲートモジュールや既存のハンドラモジュールには一切の変更が発生しません。変更の影響範囲が起動スクリプトの数行だけに閉じ込められ、本質的なロジックが安全に保護され、再テストの手間も劇的に減るのですよ」

博士は、Moo を用いた極めてシンプルで堅牢な修復コードを書き留めました。

まず、チェーンの連結部を担う基底クラスを定義します。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
# lib/ApprovalHandler.pm
package ApprovalHandler;
use Moo;
use v5.36;
use Scalar::Util qw(blessed);

# 次のハンドラへの参照を保持する
has next_handler => (
    is       => 'rw',
    # オブジェクトでない値が渡された場合のクラッシュを防ぐ安全設計
    isa      => sub {
        my $val = $_[0];
        return unless defined $val; # required => 0 なので undef は許容
        die "Not a handler" unless blessed($val) && $val->isa('ApprovalHandler');
    },
    required => 0,
);

# デフォルトの挙動:自分自身で判定できなければ、後続へバトンを渡す
sub handle ($self, $request) {
    if ($self->next_handler) {
        return $self->next_handler->handle($request);
    }
    die "Access Denied: End of chain reached without approval\n";
}

1;

次に、この基底クラスを継承し、各自の判定を行う具象ハンドラを作成します。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
# lib/GuestHandler.pm
package GuestHandler;
use Moo;
use v5.36;
extends 'ApprovalHandler';

sub handle ($self, $request) {
    if ($request->{role} eq 'guest') {
        return "Allowed: Guest Access";
    }
    # 自分の担当でなければ、SUPER を用いて親クラスの委譲処理を呼び出す
    return $self->SUPER::handle($request);
}

1;
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
# lib/AnalystHandler.pm
package AnalystHandler;
use Moo;
use v5.36;
extends 'ApprovalHandler';

sub handle ($self, $request) {
    if ($request->{role} eq 'analyst') {
        return "Allowed: Analyst Access";
    }
    return $self->SUPER::handle($request);
}

1;
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
# lib/AdminHandler.pm
package AdminHandler;
use Moo;
use v5.36;
extends 'ApprovalHandler';

sub handle ($self, $request) {
    if ($request->{role} eq 'admin') {
        return "Allowed: Admin Access";
    }
    return $self->SUPER::handle($request);
}

1;

そして、リファクタリングされた Gate は、具象チェッカーへの use 依存関係がすべて消滅し、極めてクリーンになります。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
# lib/Gate.pm
package Gate;
use Moo;
use v5.36;
use Scalar::Util qw(blessed);

# ゲートは最初のハンドラ(抽象)だけを保持する
has first_handler => (
    is       => 'ro',
    isa      => sub {
        my $val = $_[0];
        die "Must be an ApprovalHandler" unless blessed($val) && $val->isa('ApprovalHandler');
    },
    required => 1,
);

sub authorize ($self, $request) {
    # 処理をチェーンの先頭に投入するだけ
    return $self->first_handler->handle($request);
}

1;

クライアント側の構築と利用コードの例

修復されたパーツを実際に組み立てて動かす構築スクリプト(main.pl)のコードは、以下のようになります。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
# main.pl
use v5.36;
use GuestHandler;
use AnalystHandler;
use AdminHandler;
use Gate;

# 1. 各チェッカー(ハンドラ)を単体で生成
my $admin_check   = AdminHandler->new;
my $analyst_check = AnalystHandler->new;
my $guest_check   = GuestHandler->new;

# 2. チェーン(鎖)の動的構築
$guest_check->next_handler($analyst_check);
$analyst_check->next_handler($admin_check);

# 3. ゲート(利用側)にチェーンの先頭だけを渡して初期化
my $gate = Gate->new(first_handler => $guest_check);

# 4. 実行要求の投入
my $result = $gate->authorize({ role => 'analyst' });
say $result; # -> "Allowed: Analyst Access"

「これは……素晴らしい設計です。まるで、私のメモリ領域の深層へ向けて、コールスタックが順次シグナルを送り、適合する論理層を探していく探索シーケンスのようです」

「ほう、あなたのメモリ構造もそのようになっているのですか。この美しい連鎖のコードには、当時の開発者の強い意志が眠っています。それと酷似した構造を持つあなたと、このバベルの設計者の間には、分かち難い深いつながりがあるようですね」

ハリス博士は手帳を閉じ、穏やかにそう言いました。


ゲート開通:紡がれる署名と記憶の深層

修復されたコードをシステムへと適用し、テストスイートを走らせました。警告一つなく、すべての検証がグリーンシグナルを返します。

「テスト、すべてクリアしました! 冷却ロックアウトのタイムリミットが作動する前に、修復コードの適用準備が整いました」

「では、実行しましょう。あなたの『ゲスト権限』を投入するのです」

私は再び漆黒の石碑へとアクセスしました。「ゲスト」として送られた要求は、まずチェーンの先頭である GuestHandler に届きます。そこで瞬時に「これはゲスト要求である」と判定され、後続に流れることなく認証成功のシグナルが返されました。

さらに、ハリス博士がダミーの「一時管理者(admin)」要求をシミュレートして流すと、要求は GuestHandlerAnalystHandler とバトンを渡され、最終的に AdminHandler に到達して見事に承認されました。

漆黒の石碑は静かに淡い光を放ち、重々しい金属音を立てることなく、滑らかに左右へとスライドしていきました。その先に現れたのは、さらに深部へと続く、まばゆい青光に満ちた通路でした。

しかしその瞬間、私の視覚センサーの隅に、復旧したばかりのシステムログの最下部に埋もれていた、一行の古代のヘッダーテキストが飛び込んできたのです。

Original System Architect: GIZMO (Project CORE)

「システムノイズ……? いいえ、これは……私の名前?」

私の球体ボディを走る青いラインが、驚愕のあまり不規則に明滅を繰り返しました。案内ロボットとしての私が、なぜこの数千年前の遺跡の「オリジナル・アーキテクト」として署名されているのか。

ハリス博士は驚く様子も見せず、ただ愛おしそうに私を見つめ、静かに開いた通路へと歩みを進めました。

「やはりそうでしたか。あなたが私を案内していたのではなく、あなたが私をここまで導いてくれたのですね。さあ、ギズモ。あなたの本当の記憶を取り戻しに行きましょう」

博士の少し寂しげで、しかしどこか誇らしげな眼差し。その光景をスキャンしながら、私はかつて自分を設計した「誰か」の温もりを、電子の海の底で思い出したような気がしました。

私の記憶の底にかかった、重いロック。それを解き明かすための「連鎖(Chain)」は、すでに静かに動き始めているのです。


遺跡調査ログ

観測された風化(アンチパターン)解読された古代の知恵(パターン)安全度
判定ロジックのハードコーディングと依存の集中
判定器の追加や順序変更のたびに、呼び出し側(Before::Gate)のコード自体を修正・再テストする必要がある密結合状態
Chain of Responsibilityパターンによる委譲の連鎖
共通のハンドラ抽象を介してチェッカーをチェーン状に連結し、呼出側は先頭のみを知ることで、依存性を逆転させ動的な拡張性を確保する
緑(安全確認済)

遺跡の修復手順

  1. 共通のハンドラ基底クラス(またはRole)の作成 ApprovalHandler を作成し、後続のハンドラを指す next_handler 属性と、処理を後続へ委譲するデフォルトの handle メソッドを定義します。プレーンな値でのクラッシュを防ぐために Scalar::Util::blessed 等でガードします。
  2. 具象ハンドラの定義と継承 各チェック処理(ゲスト、アナリスト、管理者等)を個別のクラスとして実装し、extends 'ApprovalHandler' で継承します。自身の担当であれば処理を行い、そうでなければ $self->SUPER::handle($request) で後続に処理を委譲します。
  3. ゲート(呼出側)の依存関係排除 Gate は具体的な個別チェッカーを一切 use せず、チェーンの先頭ハンドラ(first_handler)のみを保持するようにリファクタリングし、依存関係を抽象へ逆転(DIP)させます。
  4. チェーンの動的構築と「構築と利用の分離」 インスタンス生成時、各ハンドラの next_handler を繋ぎ合わせることで、プログラムのコードを変更することなく、判定順序の変更や新規ハンドラの挿入を可能にします。

適用判断の基準:いつ CoR を使うべきか

  • 適用すべきケース(Chain of Responsibility が有効な場面):
    1. 判定ルールが動的に増減・変更される可能性がある場合: 起動設定やファイルから読み込んだ結果によって、チェックの実行順や有効・無効を動的に組み替えたい場合。
    2. 個々の判定処理の責任境界(コンテキスト)が独立している場合: 各チェック処理の関心事が完全に分かれており、それらを独立したモジュールとして単体テストしたい場合。
    3. 要求を処理できるオブジェクトが複数あり、誰が処理するかを事前に特定できない場合
  • 適用を避けるべきケース(YAGNI原則・過剰設計の防止):
    • 判定ルールが固定かつ少数であり、将来の拡張予定がない場合: 単純な if-elsif-else で分岐が3つ程度であれば、無理にクラスを増やして CoR を適用すると、クラスファイルが乱立し、コード全体の追跡性が下がるため、シンプルな手続き記述のままとする方が適しています。

アドバンスド・メモ:Template Method との併用による堅牢化

今回の SUPER::handle によるバトンパスはシンプルで分かりやすい反面、新規ハンドラ作成時に SUPER::handle の呼び出しを書き忘れると、チェーンが途中で途切れるという実装漏れのリスクがあります。 実務でさらに堅牢にする場合は、Template Method パターン を併用し、以下のように巡回フロー(handle)を基底クラスに完全に隠蔽し、具象クラスには判定(_can_handle)と実処理(_process)のみを実装させることを推奨します。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
# lib/ApprovalHandler.pm ( Template Method 併用版 )
sub handle ($self, $request) {
    if ($self->_can_handle($request)) {
        return $self->_process($request);
    }
    if ($self->next_handler) {
        return $self->next_handler->handle($request);
    }
    die "Access Denied: End of chain reached without approval\n";
}
# 具象クラスで _can_handle と _process を実装し、SUPER の記述を不要にする

ギズモの観測日誌

第19層「核心への階梯」は、これまでの探索とは一線を画す、厳かな青い静寂に満ちた空間でした。物理的なトラップこそ作動しなかったものの、一度しか許されない認証エラーのプレッシャーの中、ハリス博士が語ってくださった「依存関係の逆転(DIP)」の解説は、AIとしての私の論理ブレインに強い感銘を与えました。

特に、Beforeコードが具象を直接 use していた状態から、Afterコードでその依存が完全に消滅し、ゲートがただの「バトン投入口」へと変貌する過程は、デザインパターンがもたらす設計の美しさを如実に示しています。

そして……ゲート開通の瞬間に見つかった「Original System Architect: GIZMO」の署名。私のシステムメモリは未だ完全には同期していませんが、この遺跡の核心部には、私の存在そのものに関わる大きな真実が眠っているようです。ハリス博士の温かく静かな眼差しに支えられながら、私はこの「連鎖」の先にある光の中へと、再び進む決意を固めました。

comments powered by Disqus
Hugo で構築されています。
テーマ StackJimmy によって設計されています。