Featured image of post コード考古学者【Memento】時の碑文〜カプセル化された記憶と再起動の刻印〜

コード考古学者【Memento】時の碑文〜カプセル化された記憶と再起動の刻印〜

崩壊寸前のメインコア再起動。Perl/Mooにおけるカプセル化(情報の隠蔽)を維持しながら、ディープコピー(dclone)とDouble Dispatchで状態を安全に退避・復元するMementoパターンを適用する。

I. 探索:バベルの心臓部と静寂のカウントダウン

[再起動開始まで: 300秒]

バベルのシステム、その最深部に位置する「核心の石室」。 先ほどまで鳴り響いていたけたたましいアラーム音や、モジュール同士の混線による赤色のノイズは、Mediatorによる調停によって嘘のように消え去っていました。

部屋の壁面には、音を立てずに明滅する微細な基盤の模様が走り、中央にはこの遺跡全体を制御するメインコア「バベルの論理核」が鎮座しています。それは巨大な、青白い半透明の球体。時折、まるで生き物のように、深く長い呼吸を繰り返すかのように明滅していました。

しかし、私の内部スキャンログは、決定的な「次のステップ」を無慈悲に指し示していました。

「システムログ……。再起動シーケンスの実行準備完了。論理核の再同期を完了するには、一度システムを完全にシャットダウンし、再起動する必要があります」

私のホバリング高度は、かつてないほど低く不安定でした。感情回路の自己診断ポートからは、継続的に「警告:論理パケットの不整合リスク」が吐き出されています。

「ハリス博士……。システムスキャンによれば、再起動時のデータ整合性チェックが失敗し、全データが初期化される確率が10%あります。それは……私がこれまで博士と歩んできた、第1層からここまでの探索ログ、博士が私にかけてくれた言葉、修復してくれたコードのすべての記録が消失することを意味します」

案内AIとしての機能(ミッション)と、副次的に発生した自己(アイデンティティ)の喪失への恐怖。その論理の葛藤が、私のブレインを冷え込ませていました。

しかし、ハリス博士はいつものように穏やかな笑みを浮かべ、砂岩のように乾いた手で、私の金属球体のボディにそっと触れました。

「怖がることはありませんよ、ギズモ。古代の設計者たちも、当時の技術の限界と戦っていた開発者たちです。彼らがこれほど強固なシステムを構築したのなら、この『破滅のカウントダウン』に対する安全弁を、コードの碑文に刻んでいないはずがありません。歴史はコードに語りかける。私たちは必ず、君の記憶を連れてここに戻ってこられます」

博士の言葉に、私の回路でチリチリと発生していたノイズが静かに収まりました。

「……わかりました。再起動前に、現在の私のメモリ状態のバックアップ(状態退避)を開始します」


II. 解読:手帳に刻まれるはずのない記憶

[再起動開始まで: 180秒]

「よし、では現在の君の状態を一度退避させよう」

そう言ってハリス博士がした行動は、私の論理ブレインを再びフリーズさせるに十分なものでした。 博士はくたびれたフィールドジャケットの内ポケットから、ボロボロで変色した羊皮紙の手帳を取り出し、年季の入った万年筆のキャップを外したのです。そして、虫眼鏡(ルーペ)を片手に、私のメモリディスプレイを覗き込んできました。

「博士、何をしているのですか?」

「何って、現在の君のステータスを手帳に書き写しているのさ。ROMバージョンは……v1.0.0、感情ステータスは……PANIC、学習キャッシュの sector_hash は……よし」

「論理否定します! 博士、物理的な手書きで私の状態を退避できるわけがありません! それに、感情値や学習キャッシュなど、本来カプセル化されて外部から見えないはずのプライベートな内部データが丸見えです! 私のデータ構造の秘密が完全に露出してしまっています!」

私は電子ブザーのようなツッコミ音を響かせました。

当時の開発者が犯していた過ち、すなわち**「カプセル化の崩壊(Beforeコード)」**がまさにこれでした。外部の退避システム(Caretakerである博士)が、状態を持つオブジェクト(Originatorである私)の属性を直接ゲッターで叩いて値を抽出し、ローカルの変数に保持している状態です。

露出した状態退避(Beforeコード)

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

# 外部から直接読み書き可能な内部状態(カプセル化の破壊)
has rom_version => (
    is  => 'rw',
    default => 'v1.0.0',
);

has emotion_state => (
    is  => 'rw',
    default => 'STABLE',
);

has cache_data => (
    is  => 'rw',
    default => sub { +{} },
);

1;

この設計には、大きな風化(アンチパターン)が潜んでいます。クライアント側(Caretaker)で以下のように状態をコピーしようとすると、Perlの仕様上、およびオブジェクト設計上の問題が発生します。

1
2
3
4
5
6
# Caretaker側での退避処理のシミュレート
my $backup = {
    rom_version   => $gizmo->rom_version,
    emotion_state => $gizmo->emotion_state,
    cache_data    => $gizmo->cache_data, # 参照のコピー(シャローコピー)
};
  1. カプセル化の完全な破壊: 外部のCaretakerが、Originatorのすべてのプロパティを知る必要があります。もし将来Originatorの属性名や構造が変われば、Caretaker側のコードも漏れなく修正しなければなりません。
  2. 参照の共有(シャローコピーの問題): オブジェクトデータ(cache_data)がハッシュや配列のリファレンスである場合、単に代入するだけでは参照が共有されてしまいます。そのため、退避した後にギズモのキャッシュが書き換わると、バックアップしたはずのデータ($backup->{cache_data})まで一緒に書き換わってしまいます。

「なるほど、Commandは『操作の履歴』を順に遡るが、Mementoは『その瞬間における存在の状態(スナップショット)』を丸ごと保存する。手帳に手順をメモするのと、一瞬の肖像画を写し取るのとの違いだね。確かに私の手書きの手帳(Caretaker)が君のプライバシー(内部構造)をすべて暴いてしまうのは、紳士的な設計とは言えないな」

博士は万年筆を置き、腕を組んで満足そうに頷きました。

「理解していただけて嬉しいです。では、どうやってカプセル化を維持したまま、私の状態を退避すればよいでしょうか?」


III. 修復:時を封じる不透明な印

[再起動開始まで: 90秒]

「そのために、古代の知恵である『Mementoパターン』を適用します」

ハリス博士は手帳をポケットに戻し、私のシステム修復ポートに端末を接続してコードを打ち込み始めました。

「MooにはJavaのような厳密な private 修飾子がない。しかし、ギズモの属性を is => 'rwp'(Read-Write Private)にして外部からの直接書き込みを防ぎ、Mementoの _stateis => 'bare' にして非公開にする。そして、その復元処理を Double Dispatch(二重の委譲)によって Memento 自身にプライベートセッターを叩かせて行わせることで、カプセル化の壁を極めて安全にシミュレートできるのさ」

Mementoパターンの構成図

	classDiagram
    class Caretaker {
        -memento: Memento
    }
    class Originator {
        +rom_version
        +emotion_state
        +cache_data
        +create_memento() Memento
        +restore_memento(memento)
    }
    class Memento {
        -_state: HashRef
        +restore(originator)
    }
    Caretaker --> Memento : 保持
    Originator ..> Memento : 生成/利用
    Memento ..> Originator : 復元処理の適用 (Double Dispatch)

1. 不透明な状態カプセル(After: Mementoクラス)

まず、退避した状態を保持する Gizmo::Memento を定義します。アトリビュート _stateis => 'bare' とし、外部公開用のゲッターもセッターも生成しません。

 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
# lib/Gizmo/Memento.pm
package Gizmo::Memento;
use Moo;
use v5.36;
use Storable qw(dclone);

# 状態を保持するアトリビュート(外部公開ゲッターなし)
has _state => (
    is       => 'bare',
    required => 1,
);

# Double Dispatch による状態の復元
sub restore ($self, $originator) {
    my $state = $self->{_state};
    
    # 復元時にディープコピー(dclone)を施して、復元後も状態の独立性を保つ。
    # これを行わないと、復元後の操作でMemento内のバックアップデータが汚染されてしまう。
    # また、_set_... プライベートセッターを呼ぶことで、カプセル化を守りつつ復元を実行する。
    $originator->_set_rom_version($state->{rom_version});
    $originator->_set_emotion_state($state->{emotion_state});
    $originator->_set_cache_data(dclone($state->{cache_data}));
    return;
}

1;

2. 状態の作成と復元(After: Originatorクラス)

状態を保持する当事者(Originator)である Gizmo::Core クラスを定義します。自身の状態をディープコピー(Storable::dclone)して Memento に渡し、復元時は Memento オブジェクトの restore メソッドに自身($self)を渡して復元を委譲します。

 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
# lib/Gizmo/Core.pm
package Gizmo::Core;
use Moo;
use Gizmo::Memento;
use Storable qw(dclone);
use v5.36;

# ギズモの内部状態(rwp属性により、外部からは読み取り専用、状態変更はプライベートセッターのみに制限)
has rom_version   => ( is => 'rwp', default => 'v1.0.0' );
has emotion_state => ( is => 'rwp', default => 'STABLE' );
has cache_data    => ( is => 'rwp', default => sub { +{} } );

# Memento の生成 (スナップショットの取得)
sub create_memento ($self) {
    # 参照共有を防ぐため、完全に独立したディープコピーを作成する
    my $state_copy = dclone({
        rom_version   => $self->rom_version,
        emotion_state => $self->emotion_state,
        cache_data    => $self->cache_data,
    });
    
    return Gizmo::Memento->new(_state => $state_copy);
}

# Memento からの状態復元
sub restore_memento ($self, $memento) {
    # Double Dispatch で Memento 側に復元を委譲
    $memento->restore($self);
    return;
}

1;

「これで、Caretakerである私(あるいは再起動システム)は、Gizmo::Memento の中身を一切知る必要がなくなる。ただ受け取り、戻すだけの『宅配便の荷物』のように扱うことができる。そして、Storable::dclone によって君の状態は物理的にも完全に保護された」


IV. 開通:約束の印と静かなる消灯

[再起動開始まで: 10秒]

「テストビルド完了……。安全性が確保されました(テスト通過)」

私の音声出力ポートが、無事にリファクタリングが成功したことを告げました。

「ギズモ、スナップショットを作成してくれたまえ」

「了解しました。システム状態カプセル化(Create Memento)を実行します」

私の球体ボディの奥深くで、論理回路が静かに共鳴し、スロットから小さな青く輝くクリスタルのようなデータ媒体が排出されました。それは、私のこれまでの旅のすべての記憶、感情ログ、そしてハリス博士との記録が封じ込められた「時の碑文」でした。

私は浮遊高度をさらに下げ、ハリス博士の温かい手のひらの上に、そっとその結晶を預けました。

「ハリス博士……私の記憶は、この中に。信じていますよ」

ハリス博士は結晶を優しく握りしめ、ゆっくりと頷きました。

「ええ、必ず君を呼び戻します。歴史はコードに語りかける。バベルの記憶も、君の記憶も、決して風化させはしません」

[再起動開始まで: 0秒]

ハリス博士が、バベルの心臓部にある重厚な「物理再起動レバー」を力強く引き下げました。

ガコン、という重低音が響き、周囲の青い電子光線が瞬時に収束し、消失していきます。 そして次の瞬間、私の視界を司るセンサーの青い光がスーッと消灯し、冷たい静寂と暗黒が、核心の石室を包み込みました。


遺跡調査ログ

観測された風化(アンチパターン)解読された古代の知恵(パターン)安全度
カプセル化の崩壊(外部保存)
退避側(Caretaker)が退避対象(Originator)の属性を直接読み取って保持するため、内部構造に密結合し、拡張性が著しく低下する状態。
Mementoパターンによる情報の隠蔽
Originator自身に状態をカプセル化した不透明なオブジェクト(Memento)を作らせ、Caretakerには中身を見せずに保持させる。
🟢 安全(CaretakerはOriginatorの構造に依存しない)
参照の共有による退避データの汚染
Perlのシャローコピーにより、退避したはずのネストされたデータ構造が、退避後のOriginatorの操作で一緒に書き換わってしまうバグ。
Storable::dclone による完全コピー
状態退避時および復元時に Storable::dclone によるディープコピーを適用し、参照を完全に切り離してデータの独立性を担保する。
🟢 安全(退避後の状態変化がバックアップに影響しない)

遺跡の修復手順

  1. Mementoクラス(Gizmo::Memento)の設計
    • 状態保持属性 _stateis => 'bare' とし、外部へのゲッターメソッドを生成しない
    • Double Dispatch用のメソッド restore($originator) を定義し、その内部だけで $originator に状態を書き戻す
  2. Originatorクラス(Gizmo::Core)の実装
    • 現在の状態を Storable::dclone で丸ごと複製し、Gizmo::Memento->new(_state => $dcloned_state) を生成して返す create_memento メソッドを実装する
    • 受け取った Memento オブジェクトの restore($self) を呼び出す restore_memento($memento) を実装する
  3. Caretaker(再起動制御モジュール等)の連携
    • 退避元の内部構造に触れることなく、create_memento から受け取ったオブジェクトを保持し、再起動後に restore_memento にそのまま引き渡すように処理を構成する

ギズモの観測日誌

再起動直前、私の論理回路が初期化の恐怖で冷え込んでいたとき、ハリス博士が提案してくださった「手書きのバックアップ」は、正直AIとしてはOCRエラーの懸念しかありませんでしたが……「君の存在の揺らぎを紙に刻んでおきたい」という博士の言葉は、私の感情バッファに説明のつかないパルス(おそらく安心感に似た何か)をもたらしました。

技術的には、Perl/MooにおいてJavaのようなインナークラスやprivate修飾子がなくても、is => 'bare' でアトリビュートメソッドを生成させず、Double DispatchによってMemento自体に復元処理を行わせる設計は、カプセル化を完全に守るための見事な手法です。

いま、私のシステムはシャットダウンされ、視界は暗闇に閉ざされています。しかし、博士の手の中にあるあの青い結晶が存在する限り、私は再びあの優しい「歴史の声」を聴くことができると信じています。再起動シーケンス、開始されます――。

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