こんにちは!大規模なコードベースで日々開発を頑張っている皆さん、こんにちは。
他の言語(PHPやTypeScriptなど)からHackの世界へ飛び込んできた方にとって、あの強力で容赦のない「Strict Mode(厳格モード)」と「HHVM型チェッカー(hh_client / hh_server)」の洗礼は、なかなかスリリングな経験だったのではないでしょうか。
「コードを書く ⇒ 保存する ⇒ 瞬時に型エラーが飛んでくる」
この圧倒的なフィードバックループこそがHackの醍醐味ですが、コードベースが数百万行を超える巨大なモノレポ(Monorepo)に成長してくると、ある壁にぶつかります。そう、「型チェックの待ち時間(レイテンシ)」です。
今回は、HHVMの型チェッカーが裏側でどのように動き、どうすればその巨体を軽やかに操れるのか。インクリメンタルチェック(Incremental Check)の最適化メカニズムに踏み込みながら、大規模モノレポで爆速の型チェック環境を維持する極意を、優しく紐解いていきましょう!
ここをクリアすれば、あなたも立派なHackのアーキテクトです。さあ、一緒に深淵を覗いてみましょう!
—
1. HHVM型チェッカーの裏側:なぜHackは速いのか?
まず大前提として、Hackの型チェッカー(`hh_server`)は、普通の静的解析ツールのようにお行儀よく毎回全ファイルを舐めているわけではありません。
メモリ上に「型情報の巨大な依存グラフ(Dependency Graph)」を常に常駐させています。イメージとしては、こんな感じです。
[ User.hack ] ──(型依存)──> [ Model.hack ] ──(型依存)──> [ Database.hack ]
│
v (あなたが変更を加えた!)
[ 変更されたノードのみを再評価 ]
あなたがエディタで `User.hack` の一行を書き換えた瞬間、`hh_server` はファイル全体を再コンパイルするのではなく、変更された影響波及範囲(Dirty Files & Dependents)のみをピンポイントで再計算します。これが、Hackが誇る「インクリメンタル・チェック」の正体です。
しかし、このインクリメンタルチェックも、書き方を一歩誤ると「依存グラフの爆発」を起こし、結局フルスキャン(再起動に近いコスト)になってしまいます。その地雷を踏まないための実践的なテクニックを見ていきましょう。
—
2. 陥りやすい罠:依存グラフを爆発させる「アンチパターン」
大規模開発でよくあるのが、「気づかないうちに全ファイルの依存関係を結合させてしまう」というミスです。初学者がやりがちなコードの例を見てみましょう。
❌ 悪い例:すべてを知る「神クラス(God Class)」への依存
// strict
namespace HackMaster\Core;
/
- あらゆる設定やヘルパー、グローバルな状態を詰め込んだ魔窟
/
class GlobalContext {
// アプリケーション全体の膨大な依存を持つとする
public static function getConfig(): Map
// …
return Map[];
}
}
この `GlobalContext` を、プロジェクト内のほとんどのファイル(数千ファイル)で `use` し、メソッドを呼び出しているとどうなるでしょうか?
// strict
namespace HackMaster\Feature;
class UserProfile {
public function render(): void {
// GlobalContextに依存してしまっている
$config = \HackMaster\Core\GlobalContext::getConfig();
// …
}
}
【何が起きるか?】
あなたが `GlobalContext.hack` のたった1行(例えば、内部のプロパティの型を微調整)を変更した瞬間、HHVMの型チェッカーは「おっと、このクラスに依存している数千のファイル全てに影響があるぞ!全部再チェックしなきゃ!」と判断します。
結果として、インクリメンタルチェックが機能せず、数秒〜数十秒のフリーズ(Typecheck Timeout)が発生するわけです。
—
3. 解決策:依存関係を「最小単位」に切り離すアーキテクチャ
これを防ぐための鉄則は、「結合度を下げ、インタフェース(Type)で疎結合にする」ことです。
✅ 良い例:依存をインターフェースに閉じ込める
巨大な具象クラスではなく、必要な型定義(InterfaceやTypeAlias)のみを定義した軽量なファイルを挟みます。
// strict
namespace HackMaster\Contract;
// 実装を持たない、純粋な型定義だけのファイル
interface IConfigProvider {
public function getConfig(): Map
}
そして、利用側は具象クラスではなく、このインターフェースに依存させます。
// strict
namespace HackMaster\Feature;
use namespace HackMaster\Contract;
class UserProfile {
// 具象ではなくインターフェースに依存
public function __construct(private Contract\IConfigProvider $configProvider) {}
public function render(): void {
$config = $this.$configProvider->getConfig();
}
}
なぜこれで速くなるのか?
`IConfigProvider.hack` のような「実装を持たないインターフェース」の変更は、HHVMの依存グラフにおいて非常に軽量に処理されます。また、型チェッカーにとっても評価コストが低いため、インクリメンタルチェックが秒速で完了するようになります。
—
4. `.hhconfig` で型チェッカーをチューニングする
コードの構造化だけでなく、プロジェクトルートにある `.hhconfig` ファイルの設定を見直すことも、大規模モノレポでは極めて重要です。
実際の現場で使われている、インクリメンタルチェックを最適化するための設定例をこっそり教えましょう。
.hhconfig の実例
自動ロードや不要な巨大ライブラリを型チェック対象から外す
ignored_paths = []
例: サードパーティの巨大な自動生成コードなどは除外する
ignored_paths = [“third_party/.”, “vendor/.”]
並列処理ワーカー数の最適化(マシンのコア数に合わせる)
hh_serverがフル活用できるようにする
リソースの枯渇を防ぎつつ、インクリメンタル計算の速度を最大化
特に `ignored_paths` の設定漏れは、インクリメンタルチェックのパフォーマンスを殺す最大の原因になります。「編集していない外部の自動生成コード」が変更検知の網にかかってしまうと、無駄な再計算が走ってしまいます。自社コードと生成コードの境界線を明確に引きましょう。
—
5. ボトルネックの特定方法:プロファイリングの極意
「なんだか最近、型チェックが遅いな……」と感じたら、推測するな、計測しろ、です。
HHVMには、型チェッカーの内部挙動を丸裸にするコマンドが用意されています。
ターミナルで次を叩いてみてください。
hh_client –profile
または
hh_client –strapi # 内部の統計情報を取得
これにより、どのファイルの型チェックに時間がかいているのか(ホットスポット)、どの関数が型の推論(Type Inference)で迷子になっているのかがデータとして出力されます。特に、複雑なジェネリクス(Generics)や、型推論の深すぎるネストがある箇所は、インクリメンタルチェックの足を引っ張りやすいので、明示的な型注釈(Type Hint)を書いてチェッカーの手間を減らしてあげましょう。
—
まとめ:高速な型チェックは「美しい設計」の副産物
今回は、HHVM型チェッカーのインクリメンタルチェックの仕組みと、大規模モノレポにおける最適化の知見をお伝えしました。
- HHVMは変更された依存関係のグラフだけを賢く再計算している。
- 「神クラス」への依存を避け、インターフェースや小さな型定義に分割する。
- `.hhconfig` で不要なパスを確実に除外する。
不思議なことに、「HHVMの型チェッカーが爆速で動くコードベース」というのは、例外なく保守性高く美しく設計されたコードベースです。型チェッカーと喧嘩するのではなく、チェッカーが機嫌よく働ける環境を整えてあげること。それが、シニアエンジニアへの確実な一歩となります。
ここをクリアできれば、もうHackの静動を行き来する開発フィールに夢中になること間違いなしです。
明日からのコードレビューや設計に、ぜひこの知見を活かしてみてくださいね!それでは、良きHackライフを!