【実務・中級編】sealedインターフェースとfinalクラス:PHPの無秩序なクラス継承を代数的データ構造風に制御する – Hack言語 コア・静的型システムとHHVMのアーキテクチャ解析バイブル

PHPの無秩序な継承に終止符を。Hackの `<<__Sealed>>` で実現する「代数的データ構造」の極意

PHPという言語は、その柔軟性ゆえに「誰でも継承できるクラス」を量産しがちだ。しかし、大規模なシステムにおいて、継承ツリーが誰によってどこまで拡張されるか制御不能な状態は、技術的負債の温床に他ならない。

型システムにおいて「全ての可能性が把握できていること」は、最強のデバッグコスト削減策だ。今日は、Hackが持つ `<<__Sealed>>` 属性を活用し、PHPの無秩序な継承から脱却して「代数的データ構造(ADT)」的な堅牢な設計へ昇華させる手法を伝授する。

—

1. なぜ「無秩序な継承」が地獄を招くのか

PHPの `final` クラスは「継承の禁止」しかできない。しかし、実務では「特定のインターフェースを実装してほしいが、意図しないクラスには実装させたくない」というケースが頻発する。

例えば、決済ステータスのハンドリング。

// PHPの一般的な設計
interface PaymentStatus {}
class Pending implements PaymentStatus {}
class Success implements PaymentStatus {}
class Failed implements PaymentStatus {}
// 誰でも勝手に class Fraudulent implements PaymentStatus {} を作れてしまう

この状態で、`match` 文で網羅性チェック(Exhaustive Matching)を行おうとしても、未知の派生クラスが混入するリスクを排除できず、`default` 節に逃げざるを得なくなる。この「`default` 節への依存」こそが、バグの隠れ家だ。

—

2. `` による継承のホワイトリスト化

Hackの `<<__Sealed(...)>>` を使うと、コンパイラに対して「このインターフェース(またはクラス)を直接継承・実装できるのは、ここで指定したクラスだけだ」と宣言できる。

これにより、「型チェッカーが全ての派生クラスを把握している」という状態が確定する。

実践:堅牢な決済ステートマシン

<<__Sealed(Pending::class, Success::class, Failed::class)>>
interface PaymentStatus {
public function getMessage(): string;
}

final class Pending implements PaymentStatus {
public function getMessage(): string => “決済処理中です”;
}

final class Success implements PaymentStatus {
public function getMessage(): string => “決済完了しました”;
}

final class Failed implements PaymentStatus {
public function getMessage(): string => “決済に失敗しました”;
}

—

3. 型チェッカーによる「網羅性チェック」の恩恵

`<<__Sealed>>` を適用した状態で `match` 文を使うと、Hackの型チェッカーは驚異的な能力を発揮する。もし新しい状態を追加し忘れた場合、コンパイル時に容赦なくエラーを吐いてくれる。

function handlePayment(PaymentStatus $status): void {
// ここで全てのケースを網羅していない場合、Hackはエラーを出す
$message = match($status) {
is Pending => $status->getMessage(),
is Success => $status->getMessage(),
is Failed => $status->getMessage(),
// ここに default を書く必要はない。
// もし Failed を書き忘れたら、即座にコンパイルエラーになる。
};
echo $message;
}

この設計の強みは、「後から要件が増えたときに、漏れが自動的に検出される」点にある。リファクタリングの際、テストコードを走らせるまでもなく、型チェッカーが「君、ここも修正し忘れているよ」と教えてくれるのだ。

—

4. HHVMアーキテクチャがこの設計を愛する理由

なぜこれがパフォーマンスに良いのか。
HHVMのJITコンパイラは、型情報が確定しているコードに対して最適化を行う。`<<__Sealed>>` によって継承関係が限定されると、仮想関数呼び出し(vtable lookups)の推論が容易になり、メソッドのインライン化や、不要なガード命令の排除が効率化される。

無秩序な継承ツリーは、実行時まで型が確定しないため、JITにとっての「不確定要素」となる。一方、ADT的な設計は、HHVMが低レイヤーで最適化を仕掛けるための強力なヒント(型制約)になるのだ。

—

5. 実務への応用アドバイス:移行のステップ

いきなり全てを書き換える必要はない。以下の手順で着実に導入せよ。

1. コアとなるドメインモデルを特定する: 決済、注文状態、APIレスポンスの型など、「状態の列挙」に近いインターフェースを探す。
2. `<<__Sealed>>` を付与する: 最初は `<<__Sealed>>` でコンパイルエラーになる箇所を修正していく。
3. `default` を排除する: `match` 文から `default` を削る。これで網羅性チェックが有効になる。
4. HSLの `Vec` / `Dict` と組み合わせる: HSL(Hack Standard Library)のデータ構造と組み合わせることで、さらに型安全性を高められる。

最後に

「PHP流の書きやすさ」は、往々にして「未来の自分への借金」だ。`<<__Sealed>>` は、その借金を利子付きで返済させないための、型システムからの防波堤である。

コードレビューでこのパターンを見かけたら、それはそのエンジニアが「予測可能なコード」を書こうとしている証左だ。迷わず LGTM を送るといい。システムは、書かれた通りに動くのではない。「書かれなかった可能性」を排除した分だけ、強固になるのだ。

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