Hackの型定義を掌握せよ:大規模開発を破綻させない「型階層設計」と名前空間戦略
Hackにおける `type` と `newtype`。これらを単なる「別名」として扱っているなら、今すぐその思考を捨てるべきだ。
大規模なHHVMプロジェクトにおいて、型定義は単なるドキュメントではない。それは型チェッカー(HH_CLIENT)に対する「契約書」であり、コンパイル後の実行効率と開発者の認知負荷を決定づけるアーキテクチャそのものだ。
今回は、数百万行規模のコードベースを渡り歩いてきた私が、チーム開発で型定義を「武器」に変えるための階層化戦略を伝授する。
—
1. `type` vs `newtype`:不透明性の重要性
Hackで最も初歩的かつ致命的なミスは、すべてを `type` で定義することだ。
- `type` (Alias): 単なる別名。互換性を重視する場合に使用。
- `newtype` (Opaque Alias): 定義されたファイルの外からは、その実体(`shape`や`string`など)が見えない。
大規模プロジェクトにおいて、ドメインモデルの整合性を保つには`newtype`によるカプセル化が不可欠だ。例えば、`UserId`をただの`int`として扱えば、どこかで誤って`OrderId`を代入しても型チェッカーは黙認する。`newtype`を使えば、それはコンパイルエラーとなる。
—
2. 名前空間による「型階層」の構築戦略
型定義が肥大化すると、ディレクトリ構造と名前空間の不一致が悲劇を招く。以下の設計パターンを推奨する。
推奨ディレクトリ構成
src/
Types/
Domain/ # ドメイン固有の型定義
User.hh
Order.hh
Infrastructure/ # API通信やDBのデータ構造
External/
Common.hh # プリミティブな再利用型
実践:堅牢な型定義のコード例
namespace App\Types\Domain;
/
- UserIdを不透明な型として定義することで、
- int型との誤った混同をコンパイル時に防ぐ。
/
newtype UserId = int;
/
- 外部APIとの境界線を明確にするShape定義
/
type UserProfile = shape(
‘id’ => UserId,
‘email’ => string,
‘metadata’ => dict
);
// 型の生成用ファクトリ(モジュール内でのみ公開)
function createUserId(int $id): UserId {
return $id;
}
—
3. なぜ「型定義の重複」がパフォーマンスを殺すのか
型チェッカーは、定義が複雑になればなるほど(特に再帰的な型や巨大な`shape`の結合)、推論コストが増大する。
- `shape`の巨大化を避ける: 50個以上のフィールドを持つ`shape`は即座に分割すべきだ。
- 不必要な `typedef` を作らない: 型チェッカーは名前の解決にコストを払う。頻繁に参照される型はネストを浅く保て。
- `async` 境界での型指定: 非同期APIのレスポンスをそのまま型にせず、`Internal型`へ一度変換するレイヤーを挟むことで、将来的なAPI変更の影響範囲を最小化できる。
—
4. プロダクションで使える「型安全なAPIラッパー」
外部APIとの連携において、JSONの構造を直接コードに埋め込むのは愚策だ。以下のように、型定義とバリデーションをセットで提供せよ。
namespace App\Infrastructure\UserApi;
use type App\Types\Domain\UserId;
/
- 外部からの入力を型安全なDomain型へ変換するゲートウェイ
/
final class UserTransformer {
public static function fromJson(mixed $data): shape(‘id’ => UserId, ‘name’ => string) {
if (!is_dict($data) || !isset($data[‘id’]) || !is_int($data[‘id’])) {
throw new \InvalidArgumentException(‘Invalid API Response’);
}
// 型チェッカーはここでUserIdとintの互換性をチェックする
return shape(
‘id’ => (UserId)$data[‘id’],
‘name’ => (string)($data[‘name’] ?? ‘Unknown’),
);
}
}
—
チーフアーキテクトからの提言
Hackの厳格さ(Strict Mode)は、「コードを書くスピード」を落とすものではなく、「壊れる可能性を排除する」ための先行投資だ。
1. 境界線を守れ: `newtype`を使って、ドメインの型が漏れ出さないようにする。
2. 名前空間で整理せよ: 誰がどの型を定義すべきか、ディレクトリ単位で責任を明確にする。
3. 型をドキュメントにするな: コードを読めば型がわかるように設計する。コメントで型を書いている時点で、その設計は敗北している。
型チェッカーが静かであればあるほど、君の設計は美しい。明日からのコードレビューで、曖昧な型定義を見つけたら徹底的に指摘してほしい。それが大規模開発におけるエンジニアの矜持だ。