Featured image of post コード探偵ロックの事件簿【Factory Method】縛られた生成ライン〜newの呪縛と密室の工場〜

コード探偵ロックの事件簿【Factory Method】縛られた生成ライン〜newの呪縛と密室の工場〜

「new」はただの演算子ではなく、最も強力な接着剤だ。通知クラス増殖に伴いスパゲッティ化する決済処理をFactory Methodパターンで解きほぐすコード探偵の推理。

「誰か助けてくれ! 俺の書いた素晴らしい決済ロジックが、ただの『通知屋さん』に成り下がってしまったんだ!」

僕——バックエンドエンジニアのマイク——は、藁にもすがる思いでその古びたドアを押し開けた。ガラスにはかすれた文字で「レガシー・コード・インベスティゲーション(LCI)」と書かれている。 部屋の中は、サーバーラックと無数のモニターが発する熱気で息苦しいほどだった。デスクの上には飲みかけのカフェイン飲料が乱雑に置かれ、その奥の革張りの椅子に、季節外れのツイードのジャケットを着込んだ男が深く腰掛けていた。

「騒々しいね、ワトソン君。依頼なら静かにしたまえ」

自称・コード探偵のロックは、手元のキーボードから目を離さずに、電子タバコのパイプをふかした。

「マイクです。あの、決済完了のシステムが……」 「名前など些細なことだよ。それより君のコードからは、ひどくベタついた接着剤のにおいがする。システム中が身動き取れなくなっているんじゃないかね?」

僕は図星を突かれて言葉に詰まった。 「……はい。最初は『決済完了時にメールを送る』だけだったんです。でも、SMS対応しろ、Slackにも流せ、次はPush通知だ、と要求が絶え間なく続いて、今や身動きが取れません」

僕は持参したラップトップを開き、彼に画面を見せた。


現場検証:Hardcoded Instantiation(newの呪縛)

僕のコードベースの中核、OrderService.pm には以下のような惨状が広がっていた。

 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
# --- 通知機能の各クラス ---
package EmailNotifier {
    use Moo;
    sub send ($self, $message) { return "Email sent: $message"; }
}

package SmsNotifier {
    use Moo;
    sub send ($self, $message) { return "SMS sent: $message"; }
}

package SlackNotifier {
    use Moo;
    sub send ($self, $message) { return "Slack message sent: $message"; }
}

# --- メインロジック(問題があるクラス) ---
package OrderService {
    use Moo;
    use Carp qw(croak);

    sub complete_order ($self, $order_id, $notification_type) {
        my $message = "Order $order_id has been completed.";
        my $notifier;

        # !!強烈な接着剤(new)の現場!!
        if ($notification_type eq 'email') {
            $notifier = EmailNotifier->new();
        } elsif ($notification_type eq 'sms') {
            $notifier = SmsNotifier->new();
        } elsif ($notification_type eq 'slack') {
            $notifier = SlackNotifier->new();
        } else {
            croak "Unknown notification type: $notification_type";
        }

        # 通知の実行
        my $result = $notifier->send($message);
        return $result;
    }
}

「どうです? 動くことには動くんですけど……」僕は弁解するように言った。

ロックはパイプの煙をふっと吐き出し、大仰なため息をついた。 「動けばいいというものではないよ、ワトソン君。君は大きな勘違いをしている。初歩的なことだが、『new』はただのインスタンス生成演算子じゃない。システム上で使える最も強力な『接着剤』だ。

「接着剤?」僕は首を傾げた。

「そうだ。OrderService は、注文を完了させるのが本来の仕事のはずだ。なのに今のこいつは、『どの通知クラスをどうやって生成するか(newするか)』という余計な知識まで抱え込んでいる。

ロックは画面の if-elsif ブロックを指差した。 「通知の種類が増えるたびに、君はこの決済の心臓部である OrderService のお腹を切り開いて、新しい elsif をねじ込んでいる。これではいつか、心臓(決済ロジック)を傷つけてバグを生む。典型的なOpen-Closed Principle(開放閉鎖の原則)違反だね」

「じゃあ、ど、どうすれば……。通知しないわけにはいかないんですよ」

「簡単なことさ。『使う責任』と『作る責任』を切り離せばいい。

ロックはエディタの新しいタブを開き、猛然とタイピングを始めた。


推理披露:Factory Methodの導入

「ピザ屋を想像したまえ」ロックは軽快なタッチ音を響かせながら語り始めた。「客は『マルゲリータをくれ』と注文して、出てきたピザを食べる。これが**『使う側(OrderService)』**だ」

僕は黙って頷いた。

「客がいちいち厨房に入って、小麦粉から生地をこねて、オーブンの温度を調整して焼き上げたりしないだろう? それは**『作る側(Factory)』**の仕事だ。君の OrderService は、ピザの生地まで自分でこねているようなものなんだよ」

鮮やかな切り離し(After)

ロックは、僕のコードからべったりと張り付いた new の塊を切り剥がし、新しいファイル NotifierFactory.pm に移した。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
# --- Factory クラス ---
package NotifierFactory {
    use Moo;
    use Carp qw(croak);

    # 生成ロジック(どの種類をどう生成するか)をここに閉じ込める
    sub create ($self, $type) {
        if ($type eq 'email') {
            return EmailNotifier->new();
        } elsif ($type eq 'sms') {
            return SmsNotifier->new();
        } elsif ($type eq 'slack') {
            return SlackNotifier->new();
        } else {
            croak "Unknown notification type: $type";
        }
    }
}

「そして、肝心の OrderService のお腹の中はこうなる」

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
# --- メインロジック(改善されたクラス) ---
package OrderService {
    use Moo;

    # 工場(Factory)を外部から受け取る
    has 'notifier_factory' => (
        is => 'ro',
        required => 1,
    );

    sub complete_order ($self, $order_id, $notification_type) {
        my $message = "Order $order_id has been completed.";

        # 工場に「このタイプの通知器を作ってくれ!」と頼むだけ
        my $notifier = $self->notifier_factory->create($notification_type);

        my $result = $notifier->send($message);
        return $result;
    }
}

僕は画面を見て息を呑んだ。「OrderService から、EmailNotifierSlackNotifier の名前が完全に消え去っている……!」

「その通り。OrderService が知っているのは、『工場に create と頼めば、自分に代わって正しい通知オブジェクト(Notifier)を作ってくれる』ことだけだ。そして受け取ったオブジェクトに対して send を呼ぶ(使う)ことに専念できるのさ」

1
2
3
4
5
6
# 実行する側(例えばコントローラーやバッチスクリプト)
my $factory = NotifierFactory->new();
my $service = OrderService->new( notifier_factory => $factory );

# 利用
$service->complete_order('12345', 'email');

「なるほど!」僕は思わず身を乗り出した。「これなら明日、営業部から『次はLINE通知を追加してくれ』って言われても、OrderService は一切変更しなくていい! NotifierFactory の中に line 分岐を一つ足すだけで済むんですね!」

(※注:より高度な構成では、Factoryの中の if すらなくすために、設定ファイルや文字列から動的にモジュールを require する『Abstract Factory』的なアプローチに進むこともできるが、今回は基礎としての分離に留められているようだった)


事件の終わり:テスト用の「モック工場」と平和なビルド

「それだけではないよ、ワトソン君。この分離にはもう一つ決定的なメリットがある」

ロックは最後に、テストコードの断片を僕に見せた。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
# テスト専用の「偽物の工場」を作る
package MockFactory {
    use Moo;
    sub create ($self, $type) {
        return MockNotifier->new(); # 絶対に外に通信しない偽物
    }
}

# 決済完了テストを呼び出すときだけ、偽物を注入する
my $mock_factory = MockFactory->new();
my $test_service = OrderService->new( notifier_factory => $mock_factory );

$test_service->complete_order('999', 'sms'); 

「『作る責任』を外部の Factory に完全に委譲したからこそ、テストの時だけ『絶対にメールやSMSを誤送信しない、ダミーの工場(MockFactory)』にすり替えることができるんだ」

「……完璧です、ロックさん」 僕の顔から、さっきまでの絶望感は消え去っていた。「これなら他の開発者が触っても壊れないし、テストで本番の顧客に誤送信する事故も防げます。僕はこれで、決済処理という本業に堂々と集中できます!」

僕は深々と頭を下げ、熱気のこもった探偵事務所を後にした。 背後では、コード探偵が次の獲物(レガシーコード)のにおいを嗅ぎつけるように、静かにパイプの煙を燻らせていた。


探偵の調査報告書

容疑(アンチパターン)真実(パターン)証拠(効果)
Hardcoded Instantiation(ハードコードされたインスタンス化)。メインロジック内で具象クラスを直接 new しており、新しいクラスを追加するたびにコアロジックの修正を余儀なくされる状態。Factory Method パターン。オブジェクトの生成ロジックを専用の工場(Factory)クラスに分離し、利用側は工場に生成を依頼する設計方式。オブジェクトの「生成」と「使用」の責任が完全に分離され、結合度が低下した。テスト時にはダミーの工場を注入(DI)することで、外部通信を伴わずに安全なユニットテストが可能になった。

推理のステップ

  1. 容疑者の特定: OrderService 内で具象クラス(EmailNotifierなど)を直接 new している接着ポイントを特定する。
  2. 工場の設立: 生成ロジック(if-elsif)を丸ごと引き剥がし、新しい NotifierFactory クラスに移動させる。
  3. 依存性の注入: OrderService は自身で生成せず、外部から notifier_factory を受け取るように変更する。
  4. 使用の専念: OrderService 内ではファクトリの create メソッドを呼び出し、生成されたオブジェクトの send メソッドを使用するだけにする。

ロックより

ワトソン君。君は「new」という魔法の言葉をあまりにも軽く使いすぎていたようだね。 コードベースにおいて、具象クラスを直接名指しで呼び出す行為は、強力な接着剤で二つの部品を永遠にくっつけてしまうことと同義だ。

「生成」のコードが複雑になり始めたり、複数の場所で同じ newif の羅列が現れたら、それは専門の工場を建てるべきタイミングだ。 工場に生産を任せれば、君は「その部品をどう使うか」という本来の仕事に専念できる。

システムの心臓部には、接着剤を持ち込まないことだ。 さて、次なる難事件(スパゲッティコード)が私を呼んでいる。さらばだ、ワトソン君!

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