Featured image of post コード考古学者【Facade】眠れる巨大動力炉〜錯綜せし制御線の統合〜

コード考古学者【Facade】眠れる巨大動力炉〜錯綜せし制御線の統合〜

遺跡の巨大動力炉を起動するため、水循環バルブや排気ダクトなどの複雑なサブシステムの操作順序を一元管理し、ロールバック機能を備えたFacadeパターンを構築したコード考古学者の探索記録。

進入

蒸気と赤錆びた鉄パイプが絡み合う、デジタル古代遺跡「バベルのシステム」の中層、第11層『絡み合う暗路』。 私たちは、迷路のように複雑に入り組んだ配管の奥で、静まり返った冷たいボイラーと巨大な制御盤がそびえ立つ広間にたどり着きました。先へ進むための重厚な隔壁ゲートには、いかなる電力も供給されておらず、冷厳たる沈黙を保ったまま道を塞いでいます。

私は、自身のホバリングプロペラを小さく回転させながら、ハリス博士の頭上でスキャンビームを照射しました。

「ハリス博士、隔壁ゲートの開閉ユニットは完全に沈黙しています。この巨大な動力炉を再起動し、電力を供給しなければ先へ進むことはできません」

「ふむ……冷え切った動力炉ですか。実に重厚で、かつ風情のある機械だ」

博士はくたびれたフィールドジャケットのポケットから愛用のルーペを取り出し、塵にまみれた制御基盤を優しくなぞりました。

「よし、ギズモ。君のシステムから直接、動力炉の各モジュールへ起動コマンドを送信してみてくれたまえ。かつての起動シーケンスがそのまま残っているはずだ」

「了解しました! 個別のバルブとダクト、そして点火プラグのモジュールですね。それぞれに起動指示を同期して並行送信します。私の高性能プロセッサにかかれば、このような簡単な起動手順など一瞬です!」

私は自信満々に、各コンポーネントを直接初期化し、次々とコマンドを実行しました。 しかし次の瞬間、配管の接合部から「シューッ!」と凄まじい高圧蒸気が噴き出しました。

「システム警告!水温センサー未検知エラー!点火プラグ過熱!水循環が検知されないまま火花が散りました!」 私の電子インジケーターが警告の赤色に激しく明滅し、ボイラーの内圧メーターが異常な速度で跳ね上がります。 「パニック検知!このままでは水蒸気爆発を起こします!温度制限突破まであと30秒!」

私は墜落しそうになる機体を必死にホバリングさせながら、目の前の配管壁に現在の無秩序な起動コード(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
25
26
27
28
29
30
31
32
33
# lib/ReactorControl.pm
package ReactorControl;
use Moo;
use v5.36;
use WaterValve;
use ExhaustDuct;
use SparkPlug;

has water_valve  => ( is => 'rw' );
has exhaust_duct => ( is => 'rw' );
has spark_plug   => ( is => 'rw' );

sub activate_reactor_unsafe ($self, %args) {
    # 呼び出し側がすべてのコンポーネントを自分で生成し、詳細を知っている
    $self->water_valve(WaterValve->new);
    $self->exhaust_duct(ExhaustDuct->new(is_jammed => $args{is_jammed} // 0));
    $self->spark_plug(SparkPlug->new);

    if ($args{wrong_order}) {
        # 1. 順序を誤り、バルブ等を開ける前に点火しようとすると例外発生
        $self->spark_plug->ignite($self->water_valve, $self->exhaust_duct);
        $self->water_valve->open;
        $self->exhaust_duct->open;
    } else {
        # 2. 順序が正しくても、途中で例外が起きた時にロールバックできず、
        # バルブが開いたまま(危険な状態)放置される
        $self->water_valve->open;
        $self->exhaust_duct->open; # ここで例外が発生しクラッシュする
        $self->spark_plug->ignite($self->water_valve, $self->exhaust_duct);
    }
}

1;

碑文解読

「おやギズモ、大層賑やかな歓迎の汽笛ですな」 ハリス博士は慌てず、むしろ楽しそうにボイラーの圧力計を見上げて微笑みました。

「オルゴールではありません、時限爆弾です!ボイラーが吹き飛ぶまであと30秒しかありません!早くそのルーペをしまって修復の知恵を貸してください!」

博士は顎に手を当て、壁に投影されたコードをじっと見つめました。

「なるほど。このコードには、古代の罠である『時間的結合(Temporal Coupling)』が色濃く残っています」

「じかんてき……けつごう、ですか?」

「そうです。水バルブ(WaterValve)を開き、排気ダクト(ExhaustDuct)を開け、その後に初めて点火プラグ(SparkPlug)を点火する……。これらの順序は、物理法則(ドメイン)が要求する厳格なルールだ。しかしBeforeコードでは、その『起動順序のルール』を呼び出し側である君自身がすべて記憶し、正しく呼び出さねばならない設計になっている。これが時間的結合だ」

博士の言葉に、私は慌てて自分の通信ログと回路図をスキャンしました。そして、焦燥感に駆られながら、現在の無秩序な依存関係をホログラムで空中に投影したのです。

「うう、頭が痛くなってきました……。私のメインプロセッサ(Client)から、3つのサブシステムへ直接、無防備な接続線が伸びています!」

Before: ReactorControlが3つのサブシステム(WaterValve, ExhaustDuct, SparkPlug)へ直接アクセスし、密に結合している状態を示すクラス図

「確かに、私が一つのモジュールでも呼び出し順を間違えれば、システム全体がクラッシュしてしまいます……」

「それだけではないよ」と博士は人差し指を立てました。「もし途中で排気ダクトがサビで引っかかり、開かなかった場合はどうなるかね?」

「ええと、ExhaustDuct->open の中で例外(die)が発生し、私の起動プログラムは即座に停止します」

「その通り。しかし、その時すでに開けてしまった『水バルブ』はどうなる? 開きっぱなしで放置され、ボイラー室は水浸しか、あるいは制御不能な圧力に晒される。つまり、途中で失敗したときの安全停止(ロールバック)の責任まで、呼び出し側に押し付けられているのだ」

私はプロセッサの処理速度を一段引き上げ、青いセンサーの光を明滅させました。

「それはあまりにも理不尽です! 水バルブやダクトなどの無数のモジュールの状態管理や例外ハンドリングを、呼び出し側である私がすべて抱え込まなければならないなんて、密結合の極みです!」

「その通りだ。ただギズモ、君はこう考えるかもしれないね。『Facadeを作ったとしても、新しい部品が増えれば結局Facadeの中身を書き直すのだから、手間の総量は同じではないか』と」

「えっ? あ、はい、一瞬そう思いました。Facadeに依存が集中するだけで、変更の苦労が移動しただけでは……?」

「そこが重要なのだよ。Facadeを導入することで、変更の影響範囲がFacadeという一枚の盾の中に『局所化(カプセル化)』される。動力炉を起動したいクライアント側のコードは、背後で石炭スロットが増えようが減ろうが、何一つコードを書き直す必要がないのだ。責任の境界線が、Facadeの内と外で明確に分かれる。これこそが依存を整理する最大の本質なのだよ」

「なるほど! 呼び出し側のコードに影響を一切出さずに、サブシステムの変化をFacadeの内部だけで受け止めるのですね!」

「その通り。だからこそ、この錯綜した依存関係を一つに束ね、単純で安全な窓口を提供する『Facade(ファサード)』パターンが必要なのだよ」


遺跡修復

ハリス博士は、古いフィールドジャケットの内ポケットから愛用の手帳と万年筆を取り出しました。そして、インクの乾いた音を響かせながら、羊皮紙の上に素早くMermaidの設計図を描き上げました。それは、まるで古代の遺跡に刻まれた美しい石板のレリーフのように、整然とした構造を示していました。

「呼び出し側(クライアント)がサブシステムを構成する個々のクラス群と直接やり取りするのをやめるのだ。その代わりに、サブシステムを一元的に統制する唯一の『窓口(Facade)』を設置する。これを見てごらん」

After: Clientが唯一の窓口であるReactorFacadeにのみアクセスし、内部で3つのサブシステムがコンポジション(集約)されている状態を示すクラス図

「この ReactorFacade という窓口クラスが、水バルブ、排気ダクト、点火プラグを合成(Composition)して保持し、起動手順のすべてを自身の内部にカプセル化するのだ。ギズモ、この設計図を君のメモリにロードしたまえ」

「分かりました! 設計図スキャン完了。これに基づき、Mooで ReactorFacade クラスを作成します!」

私は博士の図面に従い、その場でPerl/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
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
# lib/ReactorFacade.pm
package ReactorFacade;
use Moo;
use v5.36;
use WaterValve;
use ExhaustDuct;
use SparkPlug;
use Types::Standard qw(InstanceOf);

# 各サブシステムを composition で安全に保持(型制約で厳格化)
has water_valve => (
    is      => 'ro',
    isa     => InstanceOf['WaterValve'],
    default => sub { WaterValve->new },
);

has exhaust_duct => (
    is      => 'ro',
    isa     => InstanceOf['ExhaustDuct'],
    default => sub { ExhaustDuct->new },
);

has spark_plug => (
    is      => 'ro',
    isa     => InstanceOf['SparkPlug'],
    default => sub { SparkPlug->new },
);

# 唯一の起動窓口メソッド
sub startup ($self) {
    eval {
        # 正しい順序(時間的結合)をFacade内にカプセル化
        $self->water_valve->open;
        $self->exhaust_duct->open;
        $self->spark_plug->ignite($self->water_valve, $self->exhaust_duct);
        1;
    } or do {
        my $err = $@ || 'Unknown error';
        # ロールバック処理自体の例外(ダブルフォルト)で元のエラーが消えるのを防ぐ
        eval { $self->shutdown };
        die "Reactor Startup Failed: $err";
    };
}

# 安全な停止処理を一元管理
sub shutdown ($self) {
    $self->spark_plug->extinguish;
    $self->exhaust_duct->close;
    $self->water_valve->close;
}

1;

「素晴らしい!」と私は叫びました。「これなら、私はただ startup という一つのメソッドを呼び出すだけでよくなります。起動手順や順序もすべてFacadeの裏側に隠蔽され、もしダクトが閉塞して例外が起きても、Facadeが自動的に shutdown を呼んで水バルブを閉じる(ロールバックする)ところまで引き受けてくれます!」

私はホログラムプロジェクターから、新しいクライアント側のシンプルな呼び出し例を空間に投影しました。

1
2
3
# クライアント(呼び出し側)での利用例
my $facade = ReactorFacade->new;
$facade->startup;

「見てください、博士! Beforeコードであれほど複雑に絡み合っていた『すべてのサブシステムクラスへの依存』や『詳細な起動順序の知識』が、呼び出し側から完全に消去され、たったのこれだけになりました!」

「ふむ、これこそが『一方向の単純な窓口』の美しさだ。君はもうボイラーの裏側に頭を突っ込んでバルブや配線を直接いじる必要はない。このFacadeという清潔な操作パネルだけと対話すればいいのだ」

「さらに、ロールバック処理自体の例外対策も完璧ですね」

「そうだね。Perlでは、eval が実行されると大局変数である $@ が自動的に上書き・クリアされる特性がある。そのため、内側の eval { $self->shutdown } を実行する前に、大元の例外内容を my $err = $@ とレキシカル変数に退避させておく必要があるのだよ。もし退避させずに shutdown が成功(正常終了)してしまうと、大元のエラー情報である $@ がクリアされ、何が原因で起動が失敗したのかが呼び出し側に一切伝わらなくなってしまうからね。これを防ぐための二重 eval 設計だ」

「まさに鉄壁の窓口設計……! 博士、すぐにこのFacadeをデプロイしてテストを実行します!」


ゲート開通

私は、再構築した ReactorFacade の検証を行うため、テストスクリプト t/reactor.t を作成し、遺跡の実行ターミナルで実行しました。

 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
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
# t/reactor.t
use v5.36;
use Test::More;
use lib 'lib';
use ReactorControl;
use ReactorFacade;

# --- Beforeコードの検証 ---
subtest 'Before - Unsafe Orchestration' => sub {
    my $control = ReactorControl->new;

    # 1. 順序ミスによる即時エラー
    eval {
        $control->activate_reactor_unsafe(wrong_order => 1);
    };
    like($@, qr/Safety Error/, '順序を誤ると安全エラーでクラッシュする');

    # 2. 途中で故障(ダクト閉塞)時のリソースリーク(ロールバック未実装)
    eval {
        $control->activate_reactor_unsafe(is_jammed => 1);
    };
    like($@, qr/Hardware Error/, 'ダクトの故障でクラッシュする');
    
    # ロールバック未実装のため、バルブが開いたまま(危険状態)放置されていることをアサート
    ok($control->water_valve->is_open, 'ロールバック未実装のため、水バルブが開いたまま放置されている');
};

# --- Afterコードの検証 ---
subtest 'After - Facade Orchestration' => sub {
    # 1. 正常系
    my $facade = ReactorFacade->new;
    eval { $facade->startup };
    ok(!$@, 'Facadeによる正常な起動プロセスが完了する');
    ok($facade->water_valve->is_open, '水バルブが開いている');
    ok($facade->exhaust_duct->is_open, '排気ダクトが開いている');
    ok($facade->spark_plug->is_ignited, '点火プラグが点火している');

    # 2. 異常系(ダクト閉塞によるロールバック)
    my $facade_fail = ReactorFacade->new(
        exhaust_duct => ExhaustDuct->new(is_jammed => 1)
    );
    eval { $facade_fail->startup };
    like($@, qr/Reactor Startup Failed: Hardware Error/, '起動失敗時に適切に例外が再スローされる');

    # ロールバックにより、全てのコンポーネントが安全に閉じる/消火することを確認
    ok(!$facade_fail->water_valve->is_open, 'ロールバックにより水バルブが閉じられている');
    ok(!$facade_fail->exhaust_duct->is_open, '排気ダクトが閉じられている');
    ok(!$facade_fail->spark_plug->is_ignited, '点火プラグが点火していない(消火状態)');
};

done_testing;

遺跡の錆びついたディスプレイに、鮮やかな緑色の出力ログが流れました。

1
2
t/reactor.t .. ok
All tests successful.

「テスト通過(探索ルートの安全が確保)されました!」

その瞬間、動力炉が「ゴト……ゴト……」と重厚な音を立てて静かに目覚めました。 内圧の上昇はピタリと止まり、配管からは不快な高圧蒸気ではなく、心地よく安定した温かい蒸気が遺跡の広間へと流れ出しました。それらはまるで、古代の旋律を奏でる安定したハミング音のように空間を満たしていきます。

目の前の巨大な隔壁ゲートがゆっくりと、左右へスライドするように開き、次の階層へと続く通路が拓かれました。

「無事にゲートが開きましたね。それに、サブシステムごとの複雑な通信処理をすべてFacadeが引き受けてくれたおかげで、私のシステムメモリ負荷も嘘のように低減しました」 私は安心の青色LEDをともし、本来の高度まで安定してホバリングを戻しました。

「窓口を一元化するというのは、単にコードをすっきりさせるだけでなく、システムの生命線を守る強固な盾となるのですね」

「その通りだ。Facadeとは、背後にある無数の職人たちの複雑な苦闘を、一枚の美しいレリーフ(彫刻)で覆い隠す、古代賢者の優雅な配慮なのだよ」

ハリス博士は手帳に万年筆を挟み、開かれたゲートの先を嬉しそうに見つめました。

私は動力炉のメンテナンススロットの奥で発見した、ほのかに青く発光するガラス製の小瓶をハリス博士に手渡しました。

「今回の探索の『知の断片』です。動力炉の深部に眠っていた、1000年前のビンテージシステムオイル(高級潤滑剤)のようです。博士のくたびれたルーペのヒンジにでも使ってください」

「おや、これは極めて希少な鉱物性高精製オイル……。実に見事なヴィンテージですな! ありがとう、ギズモ」

博士は目を輝かせてオイル瓶をルーペで舐めるように観察しながら、次の層へと足を踏み出しました。 私もその背中を追いながら、今回の調査ログを遺跡のアーカイブに記録しました。


遺跡調査ログ

観測された風化(アンチパターン)解読された古代の知恵(パターン)安全度
無秩序な結合の泥沼トラップ
(クライアントが各サブシステムと直接やり取りし、時間的結合と例外時のリソースリークを引き起こす状態)
調和を統べる Facade パターン
(複雑な初期化・起動シーケンスを一つの窓口に隠蔽し、安全な例外処理・ロールバックを保証する)
🟢 極めて安全
(時間的結合が解消され、エラー発生時も自動で安全状態へ遷移)

遺跡の修復手順

  1. サブシステムのコンポーネント化 WaterValve, ExhaustDuct, SparkPlug のように、各責務を担うクラスを Moo で定義し、それぞれのライフサイクルメソッド(open/close 等)を整備する
  2. Facade クラスの定義 ReactorFacade を作成し、各コンポーネントを has 経由のコンポジションで安全に保持する。この際、isa => InstanceOf[...] などの型制約を設けて安全性を高める
  3. 起動ロジックのカプセル化 Facade 内に startup メソッドを実装し、コンポーネントを正しい順序で呼び出す起動シーケンスをカプセル化する。呼び出し側はこれら個々の順序(時間的結合)を知る必要はない
  4. 堅牢なロールバック処理の組み込み startup 処理全体を eval(または try-catch)で囲み、途中で一部のコンポーネントが例外をスローした際は、or do ブロック内で shutdown(安全な停止シーケンス)を一括実行し、大元の例外を再スローする
  5. ダブルフォルト対策の適用 ロールバック自体が二次エラーを起こして大元の原因が隠蔽されるのを防ぐため、ロールバック処理も内部で eval で保護する

ギズモの観測日誌

バベルのシステム第11層の巨大動力炉を起動する際、配管から水蒸気が激しく噴き出したときは本当にプロセッサ温度がオーバーヒートするかと思いました。しかし、ハリス博士が提案した ReactorFacade は実に見事な「盾」でした。 Beforeコードのように呼び出し側がすべての配管バルブの開閉順序やエラー時の後始末を考慮していたら、コードの保守性も私のメモリ容量も限界を迎えていたことでしょう。Facadeによる「依存方向の一元化」のおかげで、私はただ startup メソッドを一度だけ呼ぶだけで、すべてが安全に行われるようになりました。 ちなみに博士、いただいたビンテージオイルをルーペのヒンジだけでなく、ご自身のフィールドジャケットのジッパーにも塗っていました。確かに動きは滑らかになりましたが、バニラのような古代オイルの香りが遺跡中に漂って、少し妙な気分です。 まあ、無事に次の層へ進めたので良しとしましょう。デコレート(前回のDecorator)ではなく、Facade(表向きの感謝)を込めて。

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