Featured image of post 【Perl/Moo】コード考古学者ハリスの冒険【Builder】巨石の防壁ゲート〜段階的構築による整合のレリーフ〜

【Perl/Moo】コード考古学者ハリスの冒険【Builder】巨石の防壁ゲート〜段階的構築による整合のレリーフ〜

バベルのシステム第4層の巨大な防壁ゲートで発生する不完全オブジェクトの脅威。PerlのMooによるBuilderパターンの適用で、安全な段階的構築とイミュータビリティを担保する修復記録。

進入

第一部「浅層探索編」もいよいよ折り返し地点。 バベルのシステム第4層に入った瞬間、私たちの前に立ちはだかったのは、見上げるほどの高さを誇る「巨石の防壁ゲート」でした。

これまでの層で見てきた軽金属の扉とは異なり、無骨で重厚な砂岩の巨大ブロックが緻密に噛み合わさって構築されています。その中央には、複雑な溝とスロットが刻まれた、鈍く輝くコンソールが埋め込まれていました。

私はハリス博士の周囲を低空でホバリングしながら、合成音声を低く抑え、慎重なトーンで告げました。

「ハリス博士、ご注意ください。私の環境スキャナーによると、このゲートの物理トラップはこれまでのものとは危険度が桁違いです。ゲートを開くには『溝の刻み』『認証チップ』『結晶の配置』といった多数の要素が完璧に整合した特殊な鍵オブジェクトを差し込む必要があります。もし不完全、あるいは不整合な鍵を差し込んだ瞬間、防衛システムが作動し、天井から無数の毒矢が降り注ぐ仕組みになっています」

私は一呼吸おいて、自身のボディのインジケータを黄色に明滅させました。

「そして……非常に申し上げにくいのですが、私のデータベースの一部破損により、鍵を正しく組み立てるためのパラメータの順序や、どの組み合わせが安全なのかというルールを、一時的に失ってしまいました。このまま適当に鍵を作って差し込めば、私たちは物理的にスクラップになります」

いつもの博士なら、私のエラー警告を「歓迎のファンファーレ」や「二重奏のパルス」と呼び、嬉々として愛用のルーペを取り出すところです。しかし、今回は違いました。

博士は少し汚れたフィールドジャケットの裾を払い、真剣な眼差しで巨石の防壁を見つめました。

「ふむ……歓迎のファンファーレではなく、今回は本物の警鐘(アラート)のようですな。巨石の防壁ゲート。不届きな侵入者を物理的・論理的の両面から完全に遮断するために、古代の開発者たちが設置した『完璧なる調和』の試練です」

「あ、あれ……?」私はパタパタと浮力を調整しながら、驚いてしまいました。「今回は『歓迎されている』と言わないのですか?」

博士はフッと苦笑し、首から下げたルーペをつまみ上げました。 「流石の私も、物理的な毒矢の雨まで美化するほど無謀ではありませんよ、ギズモ。それに、君のセンサーが放つその慎重な波形こそ、この遺跡が要求する『厳格さ』と同調している証拠です。さあ、その壊れかけた鍵の生成プログラム(碑文)を見せてもらいましょうか」

私は少し安堵し、背面のシステムコンソールから、鍵を生成するプログラムを空間にホログラム投影しました。

構造分析

そこに浮かび上がったのは、何とも危うい組み立ての碑文(コード)でした。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
# lib/Key.pm
package Key;
use Moo;
use v5.36;

has shape         => ( is => 'ro' );
has groove        => ( is => 'ro' );
has chip_type     => ( is => 'ro' );
has crystal_power => ( is => 'ro' );
has crystal_color => ( is => 'ro' );

1;
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
# lib/GizmoSystem.pm
package GizmoSystem;
use Moo;
use v5.36;
use Key;

sub insert_and_verify ($self, $key) {
    # 複合バリデーション
    # (チップがType-Aの場合、結晶の魔力は50以上でなければならないという遺跡のルール)
    if (defined $key->chip_type && $key->chip_type eq 'Type-A') {
        if (defined $key->crystal_power && $key->crystal_power < 50) {
            die "TRAP DETECTED: Invalid key combination! Poison arrows fired!\n";
        }
    }
    return 1; # 安全に開通
}

1;

ハリス博士は、投影されたコードをじっと観察しながら、万年筆で手帳をコツコツと叩きました。

「なるほど、実に見事な『未完成オブジェクトの放流』トラップですな。引数が無秩序に散らばっている」

「ハリス博士」私はホログラムのコードを指し示しました。「引数の順番間違いを防ぐために、Perlの慣習通りハッシュ引数(名前付き引数)で Key->new( shape => 'Triangle', ... ) のように渡すようにプログラムを変更すれば、順序の問題は解決しませんか? それでは不十分なのですか?」

「中級の職人ならそう考えるでしょうな」博士は静かに首を振りました。「しかし、ハッシュ引数は単に『名前で値を紐付ける』だけで、オブジェクトが不完全、あるいは不整合な状態のまま生まれてしまうことを防ぐことはできない。例えば、溝(groove)のパラメータが欠けている状態や、チップType-Aに対して結晶パワーが30しかないような『死の組み合わせ』のままでも、Key オブジェクトそのものは生成(new)できてしまうのです」

博士は真剣なトーンで言葉を続けました。

「そして、その不整合なオブジェクトがシステムを流通し、いざゲート(GizmoSystem)に差し込まれて検証されるその瞬間になって、初めてトラップ(例外)が起動し、毒矢が飛んでくる。これは『問題の検出があまりにも遅すぎる』のだよ。不整合な状態のオブジェクトは、この世に生まれること自体を阻止せねばならない」

「それなら」私は重ねて質問しました。「Key クラスの BUILD メソッド(コンストラクタの事後チェックフック)の中で、直接バリデーションをかければいいのではありませんか? そうすれば、不正な値での new をその場で防げるはずです」

博士は私の正面に回り込み、人差し指を立てました。

「いい質問です、ギズモ。しかしそこには二つの落とし穴がある。 一つは、段階的構築とイミュータビリティ(不変性)の両立だ。我々は生成された Key オブジェクトをすべて ro(読み取り専用)にして、後から改ざんされないようにしたい。しかし、鍵を構成するパラメータは、遺跡の各モジュールや君の複数の記憶ユニットから、段階的(バラバラのタイミング)に集まってくる。Key クラス自体にコンストラクタでの厳格な検証を持たせると、全てのパラメータが完璧に揃う瞬間まで、君は『組み立て途中の生のハッシュデータ』をあちこちに引き回し、手作業で管理しなければならなくなる。これは極めてバグを生みやすい。

もう一つは、**単一責任原則(SRP)**だ。鍵としての表現(属性の保持)と、複数プロパティにまたがる複雑な検証ロジックや組み立て手順の管理を、すべて Key クラス自身に抱え込ませると、クラスの責任が肥大化し、風化に弱くなってしまう。 だからこそ、オブジェクトの『構築の責任』を、オブジェクト『自身の表現の責任』から切り離す必要があるのだ」

博士はそう言うと、フィールドジャケットの左肩あたりを優しく叩きました。そこには、何かに擦り切れたような小さな破れ跡がありました。

「昔、アタカマの砂漠遺跡で宝箱の扉を開けようとしたときのことだ。私も焦って、組み立ての整合性が取れていない不完全なピッキングツールをその場で new して差し込んでしまってね。ツール自体は正常に動作したように見えたが、内部シリンダーの噛み合わせが不完全で、回した瞬間に物理トラップが作動して毒矢が飛んできた。この肩の傷はその時のものだよ。あの時、完全に組み立てが完了するまでツールそのものを生成させない防護策があれば、私は焼き鳥にならずに済んだのだがね」

「物理トラップのリアルな恐怖が伝わってきます……!」私はガタガタとボディを震わせました。「今すぐ、その防護策を教えてください、博士!」

遺跡修復

「そこで、古代の知恵『Builder(構築者)パターン』の出番です」

ギズモは自身の光学センサーを忙しなく瞬かせました。「ビルド……建てる、ですか? 鍵を直接生成するのではなく、別の一時的なプログラムを噛ませるのですか?」

「その通り。つまり、組み立て用の『石型』を別に用意するのですよ」 ハリス博士は手帳に万年筆を走らせ、シャッシャッと小気味よい音を立てながら、新しい設計のレリーフ(クラス図)を描き上げました。「段階的に形を整える粘土のような一時的な状態と、焼き固められて二度と変更できない完成品を、完全に切り分けるのです」

Builderパターンのクラス図: KeyBuilderが一時的にパラメータを保持し、build()でImmutableなKeyを生成する構造

「この KeyBuilder という『仕込みの工房』を新たに設置するのだよ。君はパラメータが集まるたびに、この工房に対して set_shapeset_crystal を呼び出し、段階的に情報を蓄積する。各メソッドは自分自身($self)を返すため、メソッドチェーンで流れるように構築を記述できる」

博士はペン先で図の build を指しました。

「そして、この build メソッドこそが、最後の鋳造ボタンです。それまでにどれだけ不完全なパラメータが溜まっていようとも、このメソッドが呼び出された瞬間に初めて整合性を検証し、合格した場合のみ二度と変更できない(Immutable)頑強な Key を生成する。不完全な鍵がシステム内を流通する危険自体を、この世から消し去るのです」

「なるほど! これなら、パラメータを一つずつ修復している最中に、誤って毒矢の罠を作動させてしまう心配はありませんね!」

「そして全ての準備が整ったとき、最後に build メソッドを一度だけ呼び出す。このメソッドの内部で初めて、必須要素が揃っているかの検証と、複合バリデーション(Type-Aの相性チェック)を一括して行う。合格した場合にのみ、属性がすべて ro かつ required => 1 で定義された、本物のイミュータブルな Key オブジェクトを new してシステムに送り出すのだ。 これなら、不整合な Key はそもそもこの世に生成され得ず、流通もしない。段階的構築の便利さと、完成品の安全性が完璧に両立するわけですな」

私はスキャンした設計図の美しさに感嘆の電子音を鳴らしました。 「なるほど……! 工房の中で完璧に仕上げてから送り出す。これなら不完全な鍵を誤ってゲートに差し込む危険自体が、構築の段階で完全に遮断されます!」

博士は満足そうに頷くと、私のメインプログラムの碑文を丁寧に修復(書き換え)し始めました。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
# lib/Key.pm
package Key;
use Moo;
use v5.36;
use Types::Standard qw(Str Int);

# 属性はすべて ro (Immutable) で required (必須) かつ型制約を適用
has shape         => ( is => 'ro', isa => Str, required => 1 );
has groove        => ( is => 'ro', isa => Int, required => 1 );
has chip_type     => ( is => 'ro', isa => Str, required => 1 );
has crystal_power => ( is => 'ro', isa => Int, required => 1 );
has crystal_color => ( is => 'ro', isa => Str, required => 1 );

1;

「さらに」と、ハリス博士は手帳を見ながら補足しました。 「Mooの required => 1 は、コンストラクタ引数にキーが存在することしか確認しないのだよ。もし値として undef を渡されると、チェックをすり抜けて不完全なオブジェクトが作られてしまう。それを防ぐために、Types::Standard の型制約(StrInt)を組み合わせて、値の存在と妥当性を徹底的に保証しているのだ」

 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
53
54
55
# lib/KeyBuilder.pm
package KeyBuilder;
use Moo;
use v5.36;
use Key;

has shape         => ( is => 'rw' );
has groove        => ( is => 'rw' );
has chip_type     => ( is => 'rw' );
has crystal_power => ( is => 'rw' );
has crystal_color => ( is => 'rw' );

sub set_shape ($self, $shape) {
    $self->shape($shape);
    return $self;
}

sub set_groove ($self, $groove) {
    $self->groove($groove);
    return $self;
}

sub set_chip_type ($self, $type) {
    $self->chip_type($type);
    return $self;
}

sub set_crystal ($self, $power, $color) {
    $self->crystal_power($power);
    $self->crystal_color($color);
    return $self;
}

sub build ($self) {
    # 1. 段階的構築のチェック(必須要素が揃っているか)
    unless (defined $self->shape && defined $self->groove && defined $self->chip_type && defined $self->crystal_power && defined $self->crystal_color) {
        die "Assembly Error: Incomplete key components! Cannot build Key.\n";
    }

    # 2. 複合バリデーション(安全チェック)を build 時に一括して行う
    if ($self->chip_type eq 'Type-A' && $self->crystal_power < 50) {
        die "Assembly Error: Invalid key combination! Build aborted to prevent trap trigger.\n";
    }

    # 常に「安全で整合性のある」イミュータブルオブジェクトのみを生成する
    return Key->new(
        shape         => $self->shape,
        groove        => $self->groove,
        chip_type     => $self->chip_type,
        crystal_power => $self->crystal_power,
        crystal_color => $self->crystal_color,
    );
}

1;

ゲート開通

修復されたプログラムが私のシステムに展開され、私たちは新しい鍵の組み立てテストを行いました。

もし私が結晶の魔力設定を忘れて build しようとすれば、ゲートに鍵を差し込む前に「Assembly Error: Incomplete key components!」とプログラムが即座にエラーを吐き出して生成を阻止してくれます。 また、チップをType-Aにしたまま魔力結晶のパワーを30に設定して組み立てようとしても、「Assembly Error: Invalid key combination!」とビルド段階で安全に停止します。

そして、すべての条件を満たして流れるように組み立てられた鍵は、完璧な調和を保って生成されました。

1
2
3
4
5
6
my $key = KeyBuilder->new
    ->set_shape('Triangle')
    ->set_groove(123)
    ->set_chip_type('Type-A')
    ->set_crystal(80, 'Blue')
    ->build;

「テスト通過。安全なイミュータブルオブジェクトの生成を確認しました!」

私は組み立てられた実体の鍵オブジェクトを、巨石の防壁ゲートのコンソールスロットへと慎重に差し込みました。

ゲートの奥で、カチ、カチ、カチ……と、段階的にロックが噛み合っていく機械的な音が響き渡ります。そして、恐ろしい毒矢の作動音ではなく、低く重々しい砂岩の摩擦音がチェンバー内に響きました。

ゴゴゴゴ……。

巨大な砂岩のブロックが左右にゆっくりとスライドし、防壁の向こう側へと続く、新たな通路が姿を現しました。

「ゲート開通! 物理トラップの完全停止を確認しました!」

私はホバリングの高度を少し上げ、ボディのLEDを歓喜のグリーンに点灯させました。ハリス博士はゲートの隙間から床に落ちていた「真鍮製の小さな精密ピン」をひょいと拾い上げ、ルーペで覗き込んで満足そうに頷きました。

「おお、これは鍵の微調整に使われていた古代の精密真鍮ピンですな。この程よい重量感と渋い光沢……私の万年筆の重心を調整するインサートピンにちょうど良さそうだ。実によいお土産が手に入りました」

博士が精密ピンをフィールドジャケットのポケットに大事そうに仕舞い込むのを眺めながら、私のシステムログにピピッと新しい通知が走りました。

「システム警告……いや、これは自己診断ログの更新です。ハリス博士、ゲートが開いたのと同時に、私の失われていたメモリの一部が復元されました」

「ほう? どんな記憶ですか?」

「『コアの再調和』……という断片的な言葉です。どうやらこの遺跡『バベルのシステム』の最深部には、崩壊しつつあるコアが存在し、それを修復することが私の本来の目的だったようです」

ハリス博士は手帳をパチンと閉じ、冒険用のジャケットを軽く叩きました。 「なるほど、コアの再調和ですか。いよいよ私たちの考古学探索も、単なるお宝探しからシステムの核心へと近づいてきたわけですな。実に興味深い。さあ、ギズモ。次なる歴史の碑文が、私たちを待っていますよ」

「はい、ハリス博士! 次の第5層は、構造の不整合が渦巻く『混迷の回廊』です。油断せず進みましょう!」


遺跡調査ログ

観測された風化(アンチパターン)解読された古代の知恵(パターン)安全度
未完成・不整合オブジェクトの放流
(必要な属性が欠けた状態や不整合な状態でオブジェクトが new できてしまい、使用時までバグが発覚しない)
Builder パターン
(生成プロセスを段階的に行い、buildメソッド内で一括して整合性を検証した上で安全なオブジェクトを生成する)
🟢 完璧な調和(安全確認済み)

遺跡の修復手順

  1. クラス属性の厳格化(Immutable): 対象クラス(Key)のすべての属性を読み取り専用(is => 'ro')かつ必須(required => 1)にする。さらに、Mooの required => 1 はコンストラクタ引数のキーの存在チェックのみで undef などの値のすり抜けを許してしまうため、Types::Standard などの型制約(isa => Str / isa => Int)を組み合わせて厳格化し、不完全な状態での直接的なインスタンス化を完全に防止する
  2. 構築専用のビルダー(KeyBuilder)を定義: 対象クラスと同じ属性(一時保存用のため is => 'rw')を持つビルダーを作成する
  3. メソッドチェーンの適用: 各プロパティを設定するセッターメソッドを作成し、メソッドの最後に必ず自分自身のインスタンス(return $self)を返却することで、連続して設定を行えるようにする
  4. build メソッドでの一元バリデーション: 最後に呼び出す build メソッド内で、すべての必須項目が揃っているか、および属性間の組み合わせ整合性が取れているかを検証する
  5. 安全なオブジェクトの生成: バリデーションに合格した場合のみ、実体クラスの new を呼び出してインスタンスを生成し、呼び出し元に返却する

ギズモの観測日誌

第4層「巨石防壁ゲート」の探索、本当にお疲れ様でした。今回は今までの層と違い、不完全なオブジェクトを差し込むと天井から毒矢が降ってくるという物理的な恐怖があったため、ハリス博士のアナログ愛好に対する私のツッコミも少し控えめになってしまいました。

しかし、ハリス博士の「不完全なオブジェクトをシステムに流通させるな」という教えは、私の破損したデータベースにとっても非常に深い学びとなりました。単にハッシュ引数で new を呼び出すだけでは防げない「意味的に壊れたオブジェクト」の存在を、Builderという『安全な組み立て工房』を通すことで元から断つ。動的言語であるPerlだからこそ、この「段階的構築と一括バリデーション」の価値がより一層際立つのだと実感しました。

無事にゲートが開き、私のメモリも「コアの再調和」という重要な記憶の断片を取り戻すことができました。ハリス博士は拾い上げた真鍮のピンを万年筆に挿して「重心が素晴らしく安定した!」とご機嫌ですが、次の層は結合度の呪縛が渦巻く「混迷の回廊」です。より難解な碑文が待ち受けていると思われますが、博士と一緒なら、どんなレガシーコードも恐れることはありません。次回の探索もお手伝いいたします!

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