【実務・中級編】『Partial Mode』から『Strict Mode』への移行:段階的リファクタリングのロードマップ – Hack言語 コア・静的型システムとHHVMのアーキテクチャ解析バイブル

『Partial Mode』から『Strict Mode』への移行:段階的リファクタリングのロードマップ

テックリードの私たちが新しいコードベースやレガシーなPHP資産のHack化(HHVMへの載せ替え)に着手するとき、最初に直面する絶望は「すべてのファイルを一瞬で `<<__STRICT__>>` にできない」という現実だ。

世の中のチュートリアルは「今日から厳格な型システムで安全な開発を!」と無邪気に言うが、数百万行のレガシーコードベースにおいて、動的型付けの許容(Partial Mode)を完全に排除することは、稼働中のエンジンを飛行中にオーバーホールするようなものだ。

しかし、恐れる必要はない。HHVMの型チェッカー(`hh_client`)の挙動と、Hackの型システムが持つ真の設計思想を理解していれば、システムを停止させることなく、確実に `<<__STRICT__>>` へ到達する美しくロジカルなロードマップを描くことができる。

今日は、そのための極限の知見を授けよう。

—

1. なぜ「Partial Mode」は時限爆弾なのか?

Hackには主に2つのモードがある。

  • Partial Mode (` 未定義の型や動的な性質をある程度許容する。PHPからの移行期のための逃げ道。
  • Strict Mode (`>`): すべての式、変数、戻り値に型が必須であり、推論不可能な曖昧さを一切排除する。

多くのチームが犯す最大の過ちは、「Partial Modeのコードは安全だ」と錯覚することだ。Partial Modeにおいて、型チェッカーは `mixed` や `Any`(不明な型)の蔓延を許す。結果として、ランタイムエラーの温床が温存され、リファクタリングのたびに未知のバグが牙をむく。

我々の目標は明確だ。境界を制圧し、コアロジックを Strict の要塞に閉じ込めること。

—

2. 移行の3ステップ・ロードマップ

段階的リファクタリングを成功させるためには、以下の3つのフェーズを厳守せよ。

Phase 1: 境界(Boundary)の型定義と不変性の担保

まず、外部入力(HTTPリクエスト、DB、外部API)と内部ロジックの境界を明確にする。Partialなコードから流れてくるデータを、ここで完全に型安全な構造体に変換(あるいは検証)する。

Phase 2: リーフ(Leaf)ノードからStrict化

依存関係を持たない末端のクラスや関数(ユーティリティ、値オブジェクト、純粋関数)から `<<__STRICT__>>` を適用していく。依存先がすでに型安全であれば、リーフのStrict化は驚くほど容易だ。

Phase 3: コア・オーケストレーターの制圧

依存関係の根幹にあるサービス層やコントローラーをStrict化する。ここまで来れば、型チェッカーはあなたの最強の味方となり、変更漏れや型の矛盾をコンパイル時に完全に駆逐してくれる。

—

3. プロダクションコードで見る「移行期のデザインパターン」

それでは、実務の現場でそのまま応用できるコードを見ていこう。
ここでは、外部からの曖昧なデータを安全に受け取り、Strictなドメインロジックへと引き渡す設計パターンを示す。

以下のコードは、Partial Modeのレガシーなエントリーポイントから、Strict Modeで構築された堅牢なドメイン層へデータを安全に流し込む実装だ。

// —————————————————————————–
// File: src/Domain/UserOrderProcessor.hack
// Mode: Strict (コアロジックは常に厳格であるべきだ)
// —————————————————————————–
<<__STRICT__>>

namespace HackEnterprise\Domain;

/

  • 注文データを表すイミュータブルな形状(Shape)定義。
  • Strict Modeでは、配列のキーの存在や型曖昧さは一切許されない。

/
type TOrderPayload = shape(
‘user_id’ => int,
‘amount’ => float,
‘currency’ => string,
‘items’ => vec,
);

final class UserOrderProcessor
{
/

  • 厳格に型付けされたドメイン処理メソッド。
  • ここには動的な緩慢さは存在しない。

/
public function __construct(
private int $maxAllowedAmount,
) {}

public function process(TOrderPayload $payload): bool
{
// 金額のバリデーション(ビジネスロジック)
if ($payload[‘amount’] > $this->maxAllowedAmount) {
throw new \InvalidArgumentException(
\Str\format(‘Order amount %f exceeds maximum limit.’, $payload[‘amount’])
);
}

// 堅牢な処理のシミュレーション
// vecやDictなどのコレクションAPIもHackの厳格な型システムによって最適化される
foreach ($payload[‘items’] as $item) {
// 処理…
}

return true;
}
}

境界を守るアダプター(PartialからStrictへの橋渡し)

次に、まだ移行が完了していないレガシーなコントローラーやAPIエンドポイント(Partial Mode)から、先ほどのStrictなクラスを安全に呼び出すためのアダプターを書く。

// —————————————————————————–
// File: src/Legacy/LegacyOrderApiAdapter.hack
// Mode: Partial (外部連携やレガシーコードとの混在領域)
// —————————————————————————–

  • 外部からの信用できない動的データ(mixed)を受け取る。
  • ここで型アサーションやバリデーションを行い、Strictの世界へ送り出す。
  • /
    public function handleRawRequest(mixed $rawInput): string
    {
    // 1. 入力がMap/形状として解釈可能かチェック
    if (!\is_array($rawInput) && !$rawInput is dict<_, _>) {
    return ‘Error: Invalid input format’;
    }

    // 2. 境界での型安全なキャストとデフォルト値のフォールバック
    // Partial Modeであっても、ここで厳密にキーを検証することで事故を防ぐ
    try {
    $userId = Shapes::idx($rawInput, ‘user_id’);
    $amount = Shapes::idx($rawInput, ‘amount’);
    $currency = Shapes::idx($rawInput, ‘currency’);
    $items = Shapes::idx($rawInput, ‘items’);

    // 厳格な型へのマッピング(ここでバリデーションを兼ねる)
    $payload = shape(
    ‘user_id’ => $this->ensureInt($userId),
    ‘amount’ => $this->ensureFloat($amount),
    ‘currency’ => $this->ensureString($currency, ‘USD’),
    ‘items’ => $this->ensureStringVec($items),
    );

    // 3. Strictなドメイン層へ処理を委譲
    $processor = new Domain\UserOrderProcessor(10000);
    $success = $processor->process($payload);

    return $success ? ‘Success’ : ‘Failed’;

    } catch (\Exception $e) {
    // ログ出力等のハンドリング
    return ‘Error: ‘ . $e->getMessage();
    }
    }

    // — 境界防衛のためのヘルパー群(Runtime Boundary Enforcement) —

    private function ensureInt(mixed $val): int
    {
    if ($val is int) {
    return $val;
    }
    if ($val is string && \is_numeric($val)) {
    return (int)$val;
    }
    throw new \InvalidArgumentException(‘Expected integer value.’);
    }

    private function ensureFloat(mixed $val): float
    {
    if ($val is float) {
    return $val;
    }
    if ($val is int) {
    return (float)$val;
    }
    if ($val is string && \is_numeric($val)) {
    return (float)$val;
    }
    throw new \InvalidArgumentException(‘Expected numeric/float value.’);
    }

    private function ensureString(mixed $val, string $default): string
    {
    if ($val is string) {
    return $val;
    }
    return $default;
    }

    private function ensureVec(mixed $val, (function(mixed): T) $mapper): vec
    {
    if ($val is keyset<_> || $val is vec<_> || $val is dict<_, _> || $val is \Traversable<_>) {
    $result = vec[];
    foreach ($val as $item) {
    $result[] = $mapper($item);
    }
    return $result;
    }
    return vec[];
    }

    private function ensureStringVec(mixed $val): vec
    {
    return $this->ensureVec($val, $item ==> {
    if ($item is string) {
    return $item;
    }
    return (string)$item;
    });
    }
    }

    —

    4. HHVMアーキテクチャの観点から見たStrictの優位性

    なぜ私たちはここまでして `<<__STRICT__>>` にこだわるのか?
    それは、HHVMのJITコンパイラ(RepoAuthoritativeモードなど)が、型情報に基づいて生成する機械語の最適化レベルが劇的に変わるからだ。

    1. 動的ディスパッチの排除: Partial ModeやPHP互換モードでは、プロパティアクセスやメソッド呼び出しのたびに「型が何か」のランタイムチェック(Tag check)が挟まる可能性がある。Strict Modeでは型が静的に確定するため、JITはこれらをインライン化し、C/C++並みの直接メモリアクセスに変換できる。
    2. メモリフットプリントの削減: 曖昧な型(`mixed` や `varray`/`darray` の古い構造)を排除し、Hack固有の `vec` や `dict`、そして `shape` を駆使することで、HHVM内部のメモリ管理(SmartPtrやApcify)が効率化され、GCの負荷が激減する。

    —

    テックリードからの総括

    リファクタリングとは、コードの美しさを競うお遊戯ではない。「変更容易性と実行時の信頼性を最大化するためのエンジニアリング投資」だ。

    「とりあえず動きからいいや」と Partial Mode のまま放置されたコードは、技術的負債の利息を複利で払い続けることになる。
    境界を定義し、アダプターで外部の混沌をいなし、コアロジックを `<<__STRICT__>>` の要塞へ引き上げる。このロードマップを忠実に実行できたチームだけが、HHVMの真のパフォーマンスと、バグの起きない極上の開発体験を手にすることができる。

    さあ、今日のコードレビューから、曖昧な型を駆逐しよう。

    タイトルとURLをコピーしました