Hackを掌握する極限の知見:HHVM型チェッカーの『Incremental Check』を極限まで引き出すモジュール設計
コードベースが数百万行を超えたとき、開発者の生産性を殺す最大の魔物は「型チェックの待ち時間」だ。
HHVMの型チェッカー(`hh_client` / `hh_server`)は、世界でも類を見ないほど高速に動く。しかし、依存関係の網の目がスパゲッティのように絡み合った瞬間、その牙城は崩れ去る。単一のファイルを修正しただけなのに、型チェッカーが全ファイルの再スキャン・再推論を始め、数秒の静寂がオフィスを支配する――この絶望を君も味わったことがあるはずだ。
なぜその遅延が発生するのか? 原因はシンプルで、「インクリメンタルチェック(Incremental Check)のグラフ構造を破壊する結合度」にある。
今回は、Hackの厳格な静的型付け(Strict Mode)の恩恵を1ミリも落とすことなく、HHVMのメモリ内依存グラフ(Dependency Graph)を最適化し、型チェックを常にミリ秒単位に保つためのモジュール設計の極意を伝授しよう。
—
1. HHVM型チェッカーの裏側:なぜ「巨大なファサード」はインクリメンタルを殺すのか
HHVM型チェッカーは、デーモンとして常駐し、ファイル変更を検知して差分(Incremental)の解析を行う。この時、チェッカーはどの型がどの型に依存しているかをDAG(有向非巡回グラフ)で管理している。
ここでやってはいけないアンチパターンが、「何でも屋のGodクラス(またはGodモジュール)」の作成だ。
// 【アンチパターン】すべてのドメイン知識が集中したファサード
<<__Strict>>
namespace HackExpert\AntiPattern;
final class SystemContext {
// このクラスに依存した瞬間、このクラスを変更しただけで
// プロジェクト全体の全ファイルの型チェックが走り直す。
public function __construct(
public UserSession $session,
public DatabaseConnection $db,
public Logger $logger,
public PaymentGateway $payment,
) {}
}
この `SystemContext` をあちこちのコンポーネントがインポートし始めると、HHVMの依存グラフ上では「中心のノードから全葉ノードへ向かう爆発的な辺(Edges)」が生成される。結果として、1文字変えただけで全コードベースが「影響範囲」と判定されてしまうのだ。
—
2. 依存性の方向を制御する「イミュータブル・アイランド(不変の島)」パターン
インクリメンタルチェックを最大限に活かすための鉄則は、「変更頻度の高いコード」と「変更されない基盤コード」の間に、厳格な境界(Boundary)を引くことだ。
HHVMの型チェッカーは、インターフェイスや型エイリアス(Type Alias)が正しくカプセル化されていれば、実装の詳細(Implementation Details)の変更を上位や横のモジュールへ伝播させない。
これを体現するプロダクションコードの設計を見ていこう。ドメインロジックを完全に分離し、依存関係を一方向に強制する構造だ。
実装例:型安全かつインクリメンタルに優しいモジュール設計
以下のコードは、厳格モード(`<<__Strict>>`)の下で、依存の方向を制御し、HHVMのキャッシュ効率を最大化するコンポーネント群である。
namespace HackExpert\Production;
/
- 【不変の島 1: コ契約(Contracts)レイヤー】
- このファイル群はプロジェクト内で最も変更されない。
- ここを変更しない限り、他のビジネスロジックファイルの再チェックコストはゼロになる。
/
interface IReadonlyRepository
public function findById(TId $id): ?TEntity;
}
/
- 【不変の島 2: 値オブジェクト(Value Objects)】
- プリミティブな型への依存を断ち切り、ドメインの正確性を担保する。
/
final class UserId implements \HH\JsonSerializable {
public function __construct(private int $id) {
invariant($this->id > 0, ‘UserId must be a positive integer.’);
}
public function toInt(): int {
return $this->id;
}
public mixed toJson_DEPRECATED(): int {
return $this->id;
}
}
/
- 【ビジネスロジック・レイヤー】
- 具体的なIOやグローバルステートを持たず、インターフェイスと値オブジェクトのみに依存する。
- このファイルを修正しても、インクリメンタルチェックはこのファイルとその直近の依存先しか走らせない。
/
final class UserProcessor {
// 具象クラスではなく、インターフェイスに依存させることで
// データベース層の改修がこのファイルに波及(Propagation)するのを防ぐ。
public function __construct(
private IReadonlyRepository
) {}
public function execute(int $rawId): string {
$userId = new UserId($rawId);
// strictモードならではの厳格な型推論とnull安全
$user = $this->userRepo->findById($userId->toInt());
if ($user === null) {
throw new \OutOfBoundsException(“User not found.”);
}
return Str\uppercase($user);
}
}
—
3. コードレビューの現場から:なぜその記述は非効率なのか?
テックリードとしてコードレビューを行う際、以下の兆候を見つけたら即座にリジェクトすべきだ。
1. `use` 文の網状結合(Web of Uses)
あるファイルの上部に、プロジェクト内のあらゆる名前空間からクラスがインポートされている場合、それは設計の敗北を意味する。HHVM型チェッカーは、インポートされたファイル群のタイムスタンプやシグネチャ変更を常に監視コストとして計上する。
> 対策: 必要なのは具象クラスのインポートではなく、抽象(Interface / Type Alias)のインポートである。可能な限り抽象に依存せよ。
2. ジェネリクス(Generics)の過度な動的解決の乱用
Hackのジェネリクスは非常に強力だが、境界(Constraints)を曖昧にしたコードや、`mixed` や `dynamic` へ安易に逃げたコードは、型チェッカーの推論エンジン(Inference Engine)に余計な負荷をかける。
> 対策: 型パラメータには常に厳格な制約(上界・下界)を課し、チェッカーが推論の枝刈り(Pruning)を行えるようにせよ。
—
4. HHVM型チェッカーを極限まで加速させるための設定術
コード設計だけでなく、プロジェクトルートの `.hhconfig` もインクリメンタルチェックの速度に直結する。以下のチューニングを施すことで、チェッカーのメモリ効率と速度が劇的に改善される。
; .hhconfig の推奨設定スニペット
; 自動オートロードの範囲を厳格に絞り、不要なファイルをスキャン対象外にする
allowed_fixme_codes_strict = 0
enable_experimental_features = datatypes
; 無駄なファイル監視を防ぐために除外ディレクトリを明示する
ignore.use_default_ignores = true
ignore = [
“^vendor/.”,
“^var/.”,
“^node_modules/.”
]
さらに、CI/CD環境やローカルでの開発時には、デーモンの状態を常にクリーンに保つために以下のコマンドを活用せよ。
型チェッカーの状態がおかしい、あるいはインクリメンタルが効かなくなったと感じたら
hh_client restart
—
結び:型とは制約ではなく、高速化の武器である
多くのエンジニアは、「Hackの厳格な型付けは開発スピードを落とす足かせだ」と誤解している。それは間違いだ。
型が厳格であり、モジュール間の依存関係が美しく断絶されているからこそ、HHVMの型チェッカーは「どのファイルが安全で、どのファイルを再計算すべきか」をミリ秒単位で判断できる。
堅牢なコードを書くことと、爆速のビルド・チェック環境を手に入れることは、表裏一体なのだ。
次のコードレビューでは、ただ動くコードを書くだけでなく、「この変更はHHVMの依存グラフにどう波及するか?」という視点をぜひ持ってほしい。その意識の積み重ねこそが、君のチームを次の次元へと引き上げる。