構造的部分型 vs 名前付き型:Hackの型システムを「支配」する境界線
Hackのチーフアーキテクトとして、一つだけ断言しておく。「型が合っているから」という理由だけで設計を決めるのは、二流のエンジニアが陥る罠だ。
Hackにおける`shape`(構造的部分型)と`class`(名前付き型)は、単なる記法の違いではない。それは、データが「ドメインを表現する実体」なのか、それとも「一時的な通信の断片」なのかという、設計思想の分岐点だ。
今回は、この境界線をどう引き、どのようにコードに魂を込めるべきか、その極意を伝授する。
—
1. 構造的部分型(Shape)の本質:使い捨ての「透明性」
`shape`は、その名の通り「形状」だけを定義する。HHVMの実行時において、`shape`は単なる連想配列のラッパーに近い。
Shapeを使うべき唯一の正当な理由:
- 境界値(APIレスポンス、DBレコードの断片)の受け渡し: 外部とのインターフェースにおいて、名前をつけるまでもない一時的なデータの塊。
アンチパターン:ドメインロジックへの侵食
よく見かけるのが、`shape`をモデル層で使い回すケースだ。これは「型による隠蔽」を放棄し、コード全体を「データの構造」に依存させる。結果、特定のフィールド名が変更された瞬間に、システム全体で大規模なリファクタリング地獄が待っている。
—
2. 名前付き型(Class)の真価:境界を定義する「権威」
`class`は、単なるデータ保持器ではない。それは「状態の正当性」を保証する要塞だ。
Classを定義すべき時:
- ビジネスルールが存在する場合: 「空であってはならない」「特定のフォーマットを満たす必要がある」といった制約は、コンストラクタでチェックすべきだ。
- 振る舞い(Method)が必要な場合: データを処理するロジックがそのデータに付随するなら、それはクラスの責任範囲だ。
—
3. 実践:保守性を最大化する設計パターン
以下の例を見てほしい。APIから取得したデータを、ドメインモデルへ昇華させるための「型境界」の設計だ。
namespace App\Domain;
/
- 【良い設計】
- 外部通信の断片は shape で受け取り、
- 即座にドメインモデル(class)へ変換する。
/
// 外部からの型定義はシンプルに
type TUserApiResponse = shape(
‘id’ => int,
‘username’ => string,
‘email’ => string,
);
final class User {
// コンストラクタで「データの正当性」を保証する
public function __construct(
private int $id,
private string $username,
private string $email,
) {
if (Str\is_empty($username)) {
throw new \InvalidArgumentException(‘Username cannot be empty’);
}
}
// ファクトリメソッドで shape から変換
public static function fromShape(TUserApiResponse $data): this {
return new self($data[‘id’], $data[‘username’], $data[‘email’]);
}
}
// 利用側:型安全が守られている
function processUser(TUserApiResponse $raw): void {
// 構造的部分型はここまで。これより先はドメインモデルに渡す。
$user = User::fromShape($raw);
// … 以降は $user を用いた堅牢なロジックを展開
}
なぜこの設計が最強なのか?
1. 関心の分離: APIのスキーマ変更(例:`id`が`string`に変更された)の影響を `TUserApiResponse` に閉じ込めることができる。
2. 型チェッカーの恩恵: `fromShape` で不正なデータを排除するため、アプリケーションの深部では常に「有効な `User` オブジェクト」のみが生存する。
3. HHVMの最適化: `class`はプロパティアクセスが最適化されており、配列アクセス(`shape`)よりも実行効率が良い。特にホットパスではこの差が響く。
—
4. チーフアーキテクトからの戒め:迷った時の判断基準
もしあなたが設計で迷ったら、以下の基準で自問自答せよ。
- 「その型を別の関数に渡すとき、ロジックを伴うか?」
- No → `shape` で十分だ。構造が分かればいい。
- Yes → `class` にせよ。ロジックの所在を明確にすべきだ。
- 「そのデータは、将来的にフィールドが増える可能性があるか?」
- Yes → `class` のコンストラクタやセッターを隠蔽し、カプセル化せよ。
パフォーマンスの真実
`shape` はコード記述が速い分、型チェッカーが構造を追うために演算コストを支払う。一方で `class` は、HHVMのJITコンパイラがプロパティのメモリ配置を予測しやすいため、高負荷な環境下では圧倒的に有利に働く。
—
結びに:型は「縛り」ではなく「自由」
多くの開発者が型を「書くのが面倒な制約」と捉えている。だが、Hackにおける厳格な型システムは、「コードが何をしているかを完全に掌握するための地図」だ。
`shape` で外部の混沌をすくい上げ、`class` という厳格な型で秩序を構築する。このコントラストこそが、メンテナンス性の高いプロダクションコードを生む秘訣である。
さあ、型定義という名の「設計」を楽しんでくれ。あなたのコードが、何年経っても色褪せない堅牢な城塞となることを期待している。