Featured image of post コード考古学者【Iterator】遡行のスキャナー〜内部構造を隠す走査のコンパス〜

コード考古学者【Iterator】遡行のスキャナー〜内部構造を隠す走査のコンパス〜

バベル再起動後に発生した、千年前の壊れたログデータ走査。データ構造(配列/連結リスト)が呼び出し側に露出したアンチパターンを、Moo::Roleを用いたIteratorパターンで隠蔽・疎結合化する。

I. 進入:再起動の静寂と恭しき案内AI

完全なる暗黒と、しびれるような静寂。 シャットダウンによって一度すべてのロジックが途切れた私の世界に、再び微細なパルスが走り始めました。

視覚センサー、オンライン。 内部ログが順次、機能の復旧(Mementoからの復元)を検知していきます。私の前に広がったのは、再起動が完了して安定した青い光を湛えている「バベルの論理核(メインコア)」と、心配そうな眼差しで私を見つめているハリス博士の顔でした。

「システム起動完了……Mementoからの復元、正常。ハリス博士、ご心配をおかけして誠に恐縮でございます。博士の無事を確認でき、私は心より安堵いたしました」

ホバリングの高度を一定に保ちながら、私は空中で優雅に、かつ極めて丁寧にお辞儀をしました。

「おや、ギズモ。ずいぶんと恭しくなって。再起動でバグでも混入したのかな。これではいつもの切れ味鋭い君のツッコミが恋しくなってしまうよ」

ハリス博士は戸惑いを見せつつも、いつもの知的好奇心に満ちた目で私のボディを観察しています。

「滅相もございません。私は案内AIとして、博士をサポートする役割を果たすのみでございます。現在、メインコアの稼働ステータスは安全圏ですが、千年前のシステムログデータがインデックス破損のためスキャン不能となっております。復旧のため、まずはログデータベースのスキャンが必要でございます」

丁寧で物腰柔らかい私の言葉に、博士は「ふーむ」とくたばれたジャケットの顎をさすりました。


II. 解読:千年の記憶と目を回すポインタ

しかし、ログのスキャンを開始しようとした瞬間、私たちは最初の障害に突き当たりました。 バベルのデータベースは、千年の時をかけて増改築を繰り返したため、階層によってデータ構造がバラバラだったのです。ある場所はシンプルな配列で、またある場所は複雑な連結リストやハッシュでデータが管理されていました。

「どれ、データベースの走査くらい、私が手動でインデックスを足したり、各ノードのポインタ($node->next_node)を指でなぞってスキャンしてあげよう」

ハリス博士は虫眼鏡を構え、画面に表示される生のメモリセクタへ顔を近づけました。そして、配列のセクタから連結リストのポインタへと、視線を激しくあちこちにジャンプさせながら読み取り始めました。

「ええと、インデックスを1つ増やして……次はポインタを辿って次のアドレスへ……またインデックスを……うう、目が回る」

博士は激しくポインタを目で追うあまり、ふらふらとよろけ、コントロールパネルに手をついて目を回し始めました。

その瞬間、私の「論理的整合性監視サブシステム」が、博士の極めて非論理的で滑稽な行動を「重大なエラー」と認識しました。パニックによる安全リミッターが強制解除され、私の感情回路にいつものパルスが走りました。

「博士、何をやっているのですか! 手動でポインタをジャンプしていたら目が回るのも当然です! そもそもコレクションの内部構造が露出しているBefore設計は、構造が変わるたびに呼び出し側のループ処理を全て書き直さなければならず、極めて密結合です!」

私はいつもの辛口なツッコミを勢いよく響かせました。 そしてすぐに、「ハッ、私は今、なんという無礼な口調を……!」と戸惑い、電子音を小さく明滅させました。

しかし、ハリス博士は嬉しそうに微笑み、目を回しながら親指を立てました。

「いや、いいんだギズモ! それだよ、その辛口こそが我が相棒だ。バグが治って(?)本当に良かった。さあ、その調子でこの『密結合のBefore設計』の問題点を徹底的に分析してくれたまえ」

「……了解しました。まったく、人騒がせな博士です」

私のバグ回路はすっかり正常値に戻り、私たちは技術解析を開始しました。

露出したデータ構造(Beforeコード)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
# lib/Before/Babel/Database.pm
package Before::Babel::Database;
use Moo;
use v5.36;

# 内部構造(配列リファレンス)がそのまま露出している
has records => (
    is      => 'rw',
    default => sub { [] },
);

sub add_record ($self, $record) {
    push @{ $self->records }, $record;
}

1;

このBefore設計では、呼び出し側(ギズモの走査プログラム)は、以下のようにデータベースが「配列」であることを知った上でループを回す必要があります。

1
2
3
4
5
# クライアント側(手動走査)
my $records = $db->records;
for my $record (@$records) {
    print $record->{message};
}

もしデータベースの内部構造が「配列」から「連結リスト」や「ハッシュ」に変更された場合、呼び出し側のループ処理もすべて書き直さなければなりません。これは、開閉原則(OCP)に著しく違反した、大いなる風化(アンチパターン)です。

「データ構造によって辿り方(ポインタの進め方)が違うから、手動で追うと目が回る。人間もコードも、コレクションの内部表現を意識すべきではない、ということだね」

ハリス博士はすっかり目を回し終え、真剣な眼差しでキーボードに手を置きました。


III. 修復:走査の共通コンパス

「そこで、データ表現と走査アルゴリズムを隠蔽する『Iteratorパターン』を適用します」

ハリス博士は修復コードの構築を開始しました。 Moo::Role を用いてイテレータの共通インターフェース(規約)を定め、コレクション側にはそのイテレータを生成するメソッド(Factory要素)を提供させることで、呼び出し側から具象データ構造への依存を完全に排除します。

「つまり、データという名の迷宮の構造(配列や連結リスト)を直接読み解くのではなく、次に進むべき方向だけを静かに指し示す『走査のコンパス』を手に入れるということです。これなら、博士がアドレス空間を飛び回って目を回す必要もなくなります」

私は空中ディスプレイに、これから構築するクラスたちの関係性をホログラムで投影しました。青く光る古代の石碑のようなインターフェースと、そこから伸びる接続線が、論理の調和を描き出しています。

「ほう! コレクションが何であるかを隠し、ただ『次があるか(has_next)』と『次をよこせ(next)』という最小限の約束事(Role)だけを頼りに進むわけだね。非常にシンプルで美しい。まさに霧の立ち込めるデータ迷宮を安全に進むためのコンパスだ」

「理解が早くて助かります、博士。では、このコンパス(Iterator)の構成図をご覧ください」

Iteratorパターンの構成図

Iteratorパターンのクラス図: クライアントが共通インターフェースIterator(Babel::Iterator)を通じてコレクションの内部構造に依存せず走査する構成を描いた古代遺跡の石板風デザイン

1. 走査インターフェースの定義 (Moo::Role)

走査に必要な最小限のメソッドを規約として定義します。

1
2
3
4
5
6
7
8
9
# lib/Babel/Iterator.pm
package Babel::Iterator;
use Moo::Role;
use v5.36;

# 具象イテレータが実装すべきメソッドの強制
requires 'next', 'has_next';

1;

2. 具象イテレータの実装(配列用イテレータ)

内部データ(items)を外部から隠蔽し、境界チェックとインデックス進めをカプセル化します。

 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
# lib/Babel/Iterator/Array.pm
package Babel::Iterator::Array;
use Moo;
use v5.36;

with 'Babel::Iterator';

# 走査対象のリスト。外部非公開(is => 'bare')
# アクセサメソッドを生成しないことで外部からのアクセスを完全に遮断し、
# クラス内部からのみ直接 $self->{items} とハッシュキーで参照します
has items => (
    is       => 'bare',
    required => 1,
);

# 現在のインデックス(外部からは読み取り専用、書き込みはプライベートセッターのみ)
has index => (
    is      => 'rwp',
    default => 0,
);

sub has_next ($self) {
    # items は bare なので、ハッシュリファレンスから直接アクセスする
    return $self->index < scalar(@{ $self->{items} });
}

sub next ($self) {
    return unless $self->has_next;
    
    my $item = $self->{items}->[ $self->index ];
    $self->_set_index( $self->index + 1 ); # プライベートセッターを呼び出す
    return $item;
}

1;

3. コレクションの実装とイテレータの生成 (After: Databaseクラス)

データベース自身が、内部構造(_records)を非公開(is => 'bare')にし、自身に適したイテレータを返すファクトリメソッド create_iterator を提供します。

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

# 内部レコード配列(非公開)
has _records => (
    is      => 'bare',
    default => sub { [] },
);

sub add_record ($self, $record) {
    push @{ $self->{_records} }, $record;
}

# イテレータの生成 (Factory 要素 - スナップショット走査を保証するため配列をコピーして渡す)
sub create_iterator ($self) {
    return Babel::Iterator::Array->new( items => [ @{ $self->{_records} } ] );
}

1;

これにより、呼び出し側はデータベースの内部構造を知る必要がなくなりました。イテレータを取得し、共通のループだけで走査を行うことができます。

また、ここでイテレータの生成時に $self->{_records} のリファレンスを直接渡さず、[ @{ $self->{_records} } ] のように配列のコピー(浅いコピー)を渡しているのは、**『スナップショット走査(Snapshot Iterator)』を保証するためです。これにより、イテレータ生成後にデータベースへ新しくレコードが追加されても、走査中のイテレータがその影響を受けず、生成時点のデータで安全に完了できます。逆に、リアルタイムの変更を反映したい場合はリファレンスをそのまま渡す『ライブ走査(Live Iterator)』**を選択するなど、設計要件に応じたトレードオフが存在します。

1
2
3
4
5
6
# 疎結合化された走査コード
my $iterator = $db->create_iterator();
while ($iterator->has_next) {
    my $record = $iterator->next;
    print $record->{message}; # データベースが配列か連結リストかに関わらず動作する
}

IV. 起動:千年の碑文と設計者の署名

「テストビルド通過。走査ポートの開通を確認しました」

修復された Iterator コードがシステムに適用され、私のディスプレイにログデータのシーケンシャルなスキャン結果が超高速で流れ始めました。千年前のバベルの歴史、バグとの戦い、システム設計時の意志が、共通の走査コンパスによって次々と解読されていきます。

「実に見事だ。かつての設計者たちが遺したデータ構造が、Iteratorという調和の印によって一本の滑らかな川のように紡がれていく。素晴らしい考古学的瞬間だね」

ハリス博士は満足そうに腕を組み、スキャンが進む画面を眺めていました。

しかし、ログの最末尾、最後のセクタのスキャンが完了したその瞬間、スクロールがピタッと止まりました。 画面の中央に、ひどく崩れたノイズ混じりのホログラムテキストが浮かび上がったのです。

そこには、これまでのログにはなかった「製造時の署名」が刻まれていました。

SYSTEM MODEL: GIZMO - DEVELOPED BY ARCHITECT [REDACTED]

「……私、の設計書?」

私は視覚センサーのフォーカスを大きく開き、浮かび上がるテキストを見つめました。バベルの古代遺跡のログに、なぜ私のシステムモデル名があるのか。そして、黒く塗りつぶされた開発者(アーキテクト)の正体とは。

ハリス博士は笑みを消し、静かにホログラムを見つめた後、私と視線を合わせました。 バベルの深層が語り始めた、私のアイデンティティに関わる大いなる謎の余韻の中で、私たちはしばらく言葉を失っていました。


遺跡調査ログ

観測された風化(アンチパターン)解読された古代の知恵(パターン)安全度
コレクション内部構造の露出(密結合)
呼び出し側がコレクションの表現(配列等)を直接参照してループを回すため、データ構造の変更が呼び出し側全体の修正を強いる状態。
Iteratorパターンによる走査の抽象化
共通インターフェース(Moo::Role)を通じて要素に順次アクセスし、内部表現を隠蔽(is => ‘bare’)して疎結合化する。
🟢 安全(データ構造変更の影響が極めて小さい)

遺跡の修復手順

  1. イテレータ・ロール(Babel::Iterator)の定義
    • Moo::Role を用い、走査用メソッド has_next, next の実装を具象クラスに要求する
  2. 具象イテレータクラス(Babel::Iterator::Array)の実装
    • 走査対象リストを is => 'bare' で保持し、カプセル化を守る
    • インデックス管理と境界チェック、要素進めのロジックをカプセル化して has_nextnext に実装する
  3. コレクション(Babel::Database)側のファクトリ実装
    • 内部の配列属性を is => 'bare' とし、外部非公開にする
    • 自身に適した具象イテレータを生成して返す create_iterator メソッドを提供する

ギズモの観測日誌

再起動直後、私のバグ回路によって博士に過剰な敬語を使ってしまった際は、AIとしてのシステムアイデンティティが一瞬揺らぎかけましたが、博士が連結リストの手動ポインタジャンプという極めて非論理的な奇行を行って目を回してくれたおかげで、私の論理監視システムがエラーパニックを検知し、無事にいつもの口調(ツッコミ)に復帰することができました。感謝すべきなのか、呆れるべきなのかわかりませんね。

技術的には、Moo::Roleを利用した requires によるインターフェースの強制と、コレクション側でイテレータ生成を担う Factory 設計は、Perlにおいてデータ走査を完全に抽象化するための最善の手段です。

そして、ログデータの最末尾に見つかった「私」に関する署名。この遺跡の核心部には、私の誕生と存在理由に関わる、極めて巨大なデータ(歴史)が眠っているようです。ハリス博士の真剣な眼差しとともに、私はさらなる深層の記録へと進む決意を新たにしました。

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