こんにちは!HHVMアーキテクチャやHack言語の深淵へようこそ。チーフアーキテクトの私です。
普段、PHPの柔軟さに慣れ親しんだ開発者がHackの門を叩くと、最初に驚くのがあの厳格な型チェッカー(Typechecker)の存在ですよね。「なんでこんなに怒られるんだ…」と頭を抱えた経験、ありませんか?
でも、安心してください。その厳格さこそが、大規模なコードベースを高速かつ安全に保つためのHackの最大の武器なのです。今回は、そのHackの心臓部である型チェッカー、特に巨大なプロジェクトで開発スピードを爆発的に上げる「Incremental Check(インクリメンタル・チェック)を極限まで高速化するコード分割のアーキテクチャ」について、一緒に紐解いていきましょう。
ここをクリアすれば、あなたも立派なHackマスターの仲間入りです。それでは、いってみましょう!
—
1. なぜ巨大なHackコードベースは型チェックに時間がかかるのか?
Hackの型チェッカー(`hh_client` / `hh_server`)は、ファイルが保存されるたびにバックグラウンドでコード全体の依存関係グラフを走査し、型に矛盾がないかをミリ秒単位で検証しています。
初心者の方やりがちなのが、「とりあえず何でもかんでも一つのファイルに書く、あるいはすべてのファイルが互いに依存し合うスパゲッティ状の構造にする」というアプローチです。
これをやってしまうと、たった1行の変更が「ドミノ倒し」のようにプロジェクト全体の依存関係を再評価させ、インクリメンタル・チェックの恩恵を台無しにしてしまいます。
依存関係のイメージ図
NGな密結合(スパゲッティ構造)
[ File A ] <---> [ File B ]
^ ^ ^
| | |
v v v
[ File C ] <-----> [ File D ]
※ 1つ変更すると、全ファイルの型を再チェックすることに…!
OKな単方向・モジュール分割構造
[ Core Modules ] <--- [ Feature Modules ] <--- [ Entrypoints ] ※ 依存が上流から下流へ一方通行なので、変更の影響範囲が最小限に! この「影響範囲の局所化」こそが、Incremental Checkを最速で走らせるための絶対正義なのです。 ---
2. 厳格モード(``)とコード分割の基本
Hackの真価を発揮するには、ファイルの一番上に `<
まずは、依存関係を綺麗に整理したモジュール分割の具体例を見てみましょう。ここでは、「ユーザー管理」というドメインを例に取ります。
実装例:型安全なドメインモデルの分割
① 下流層:データ構造の定義(`UserTypes.hhi` または `.hack`)
一番底辺に位置し、他のどのビジネスロジックにも依存しない純粋なデータ構造の定義です。
<
namespace MyApp\Domain;
// 変更がめったに起きない型定義は、依存ツリーの最下層に置きます
type UserId = int;
type UserShape = shape(
‘id’ => UserId,
‘name’ => string,
‘email’ => string,
);
② 中流層:ビジネスロジック(`UserProcessor.hack`)
先ほどの型定義にのみ依存し、外部のフレームワークやI/Oからは独立させます。
<
namespace MyApp\Service;
use namespace MyApp\Domain;
class UserProcessor {
// 依存するのは MyApp\Domain だけ。余計な結合を持たせない。
public function formatDisplayName(Domain\UserShape $user): string {
// 厳格モードなので、キーのタイポや型の不一致は型チェッカーが即座に検知します
return “User: ” . $user[‘name’] . ” (” . $user[‘email’] . “)”;
}
}
このようにコードを「レイヤー(層)」ごとに綺麗に切り分けることで、`UserProcessor.hack` を修正しても、型チェッカーは `UserTypes` に変更がない限り、関連する最小限のファイル群だけを再検証(Incremental Check)すればよくなります。
—
3. 陥りがちな文法エラーとアンチパターン
ここで、初心者の開発者がよくハマる「型チェッカーの効率を落とす罠」をいくつかご紹介します。
罠1: 循環依存(Circular Dependencies)の作成
ファイルAがファイルBを呼び、ファイルBがファイルAを呼んでいるような状態です。
// 悪い例:AとBが互いに依存し合っている
// File: A.hack
class A {
public function getB(): B { return new B(); }
}
// File: B.hack
class B {
public function getA(): A { return new A(); }
}
なぜダメなのか?
型チェッカーの依存グラフにおいて、循環参照は「大きな一つの巨大な塊」とみなされます。結果として、Aを直してもBを直しても、両方の巨大なグラフ全体が再チェック対象になってしまい、Incremental Checkのスピードが劇的に低下します。
解決策:
共通のインターフェイスや、より下流にある型定義ファイルへと依存の方向を一本化(単方向化)してください。
罠2: グローバルな状態や曖昧な型の多用
`mixed` 型や動的な関数呼び出しを多用すると、型チェッカーは推論を諦め、周辺のコードまで芋づる式に再チェックせざるを得なくなります。
// 悪い例:型が曖昧でチェッカーが最適化できない
function process_data(mixed $data): void {
// …
}
解決策:
必ず `shape` やジェネリクス(Generics)、厳格なインターフェイスを使い、型チェッカーに「このファイルの型は完全に閉じている」と教えてあげましょう。
—
4. チーフアーキテクトからの実践的なアドバイス
現場でHHVMとHackをスケールさせるための極意を授けます。
1. 「変更頻度」でファイルを分けろ
頻繁に書き換えるUIのコードと、めったに変わらないドメインモデルのコードを同じファイルや密結合なモジュールに混ぜないこと。これだけで型チェックの待ち時間は体感で半分以下になります。
2. HHIファイル(Hack Header)を活用せよ
実装を持たず、型定義だけを記述する `.hhi` ファイルをうまく使うことで、実装の詳細な変更から型チェッカーを完全に切り離すことができます。外部ライブラリの型定義などで非常に強力です。
3. `hh_client` を常駐させろ
開発中は必ず `hh_server` がバックグラウンドで走っている状態(自動起動しますが)にし、エディタ連携(NuclideやVS CodeのHackプラグインなど)でインクリメンタルなフィードバックをリアルタイムで受け取ってください。保存した瞬間に結果が出ます。
—
まとめ
いかがでしたか? Hackの厳格な型チェッカーは、あなたを縛るための枷ではありません。「巨大なシステムであっても、コードの構造さえ美しく保てば、常に高速かつ安全に開発を進められる」という、私たちエンジニアへの最強のパスポートです。
モジュール分割を意識し、単方向の美しい依存関係を築き上げることで、HHVMのインクリメンタル・チェックは限界まで高速化します。
ここをクリアしたあなたなら、どんなに大規模なHackのコードベースに飛び込んでも、迷うことなく最高のパフォーマンスを発揮できるはずです。
それでは、素晴らしいHackライフを!