【実務・中級編】Hackの『Attribute』を活用したカスタム静的解析ルールの作成 – Hack言語 コア・静的型システムとHHVMのアーキテクチャ解析バイブル

Hackを掌握する極限の知見:カスタムAttributeと静的解析による「コンパイル時・契約駆動設計」の極意

テックリードの私たちがコードレビューで最も時間を使うのは、変数名やフォーマットの指摘ではない。「ビジネスロジックの不整合が、なぜ型システムやランタイムの防壁をすり抜けて本番環境に到達してしまったのか」というアーキテクチャの根本的な欠陥に対するデバッグだ。

HHVMのJITコンパイルと洗練された型チェッカー(`hh_client`)を持つHack言語は、標準でも十分に堅牢である。しかし、プロジェクトが巨大化するにつれ、「特定の機密データを扱うメソッドには必ず監査ログのラッパーを通す」「非同期APIクライアントを呼び出すメソッドにはタイムアウト値の明示を強制する」といった、ドメイン固有の制約を静的に強制したくなるはずだ。

今回は、Hackの `<>` と、HHVMの型チェッカー/静的解析パイプラインをハックし、「人間ではなくコードにルールを強制させる」ための極限の設計パターンを授けよう。

—

1. なぜ「型チェッカーの枠を超える」必要があるのか

HackのStrictモード(`<<__Strict>>`)は、不安全なキャストや曖昧な型推論を許さない。しかし、型の整合性と「ビジネス要件の整合性」は別問題だ。

例えば、以下のような要件を考えてみてほしい。

  • 「決済処理を行うすべてのpublicメソッドには、`<>` 属性が付与されていなければならない」
  • 「外部APIを叩くサービス層のクラスは、名前に `Client` を含み、かつ全てのメソッドが非同期(`async`)でなければならない」

これらを従来のコードレビューや、実行時(Runtime)の例外処理に頼るのは怠慢だ。ランタイムエラーはコストが高い。私たちは「ビルド(型チェック)の段階でコンパイルエラーとして検出する」べきなのだ。

Hackでは、ユーザー定義属性(User-defined Attributes)を付与したメタデータに対して、独自の静的解析レイヤーを組み合わせることで、この理想郷をコードベースに具現化できる。

—

2. 実装:カスタム属性を用いた「契約強制」アーキテクチャ

ここでは、特定のセキュリティ要件を満たすべきメソッドに `<>` 属性を強制し、それが正しく付与されているか、あるいは違反していないかを静的に検証する設計パターンを示す。

以下のプロダクションコードは、そのまま君たちのプロジェクトのアーキテクチャテストや静的解析パイプラインに組み込めるクオリティに仕上げてある。

<<__24小时__>> // 冗談だ。厳格なStrictモードを指定する
<<__STRICT__>>
namespace HackExpert\Architecture\Security;

use namespace HH\Lib\C;
use namespace HH\Lib\Str;
use ReflectionClass;
use ReflectionMethod;

/

  • 独自属性の定義
  • この属性が付与されたクラス、またはメソッドは、
  • セキュリティ監査チームのレビュー済みであることを静的・動的に示す。

/
<<__Attribute( // クラスとメソッドの両方に付与可能とする \Attribute::TARGET_CLASS | \Attribute::TARGET_METHOD, // 同一ターゲットに複数付与は不可 \Attribute::IS_REPEATABLE === false, )] final class RequireSecurityContext implements \HH\ClassAttribute, \HH\MethodAttribute { public function __construct( public string $classificationLevel, public bool $requiresTwoFactor = true, ) {} } /

  • 【検証対象のドメインサービス例】
  • 厳格な金融系APIを模したクラス。
  • アーキテクチャルールにより、決済処理メソッドには必ず
  • `RequireSecurityContext` の付与が義務付けられているとする。

/
final class PaymentProcessor {

<>
public async Task executeSecureTransactionAsync(
string $accountId,
float $amount,
): Awaitable {
// 実際の決済ロジック
return true;
}

/

  • 【アンチパターン例】
  • 属性が付与されていないため、静的解析ツールによって検知されるべきメソッド。

/
public async Task executeUnsafeLegacyTransactionAsync(
string $accountId,
float $amount,
): Awaitable {
return false;
}
}

/

  • 【静的/動的検証エンジンの実装】
  • 通常、これはCIパイプラインやカスタムLinter(hhast等)の拡張として実行される。

/
final class SecurityArchitectureEnforcer {

/

  • 指定されたクラス群をスキャンし、セキュリティポリシーの違反を検出する。
  • 違反があった場合は例外をスローし、CIを即座に落とす。

/
public static function enforce(vec $classNames): void {
foreach ($classNames as $className) {
$reflector = new ReflectionClass($className);

// クラス名が Processor で終わるものは厳格な監査対象とする
if (Str\ends_with($reflector->getName(), “Processor”)) {
self::validateProcessorMethods($reflector);
}
}
}

private static function validateProcessorMethods(ReflectionClass $reflector): void {
foreach ($reflector->getMethods() as $method) {
// パブリックメソッドのみを対象とする
if (!$method->isPublic()) {
continue;
}

$methodName = $method->getName();
// コンストラクタや特殊メソッドはスキップ
if (Str\starts_with($methodName, “__”)) {
continue;
}

// 属性の取得
$attributes = $method->getAttributes();
$hasSecurityContext = C\any(
$attributes,
($attr) => $attr->getName() === RequireSecurityContext::class,
);

if (!$hasSecurityContext) {
// プロダクションコードではここでビルドエラー、またはLinterエラーとして処理する
throw new \InvariantException(
\Str\format(
“Architecture Violation: Method ‘%s::%s’ handles sensitive operations ” .
“but lacks the mandatory <> attribute.”,
$reflector->getName(),
$methodName
)
);
}
}
}
}

—

3. パフォーマンス上の注意点とHHVMアーキテクチャの理解

上記のコードを見て、「リフレクション(Reflection)を毎リクエスト実行するのはオーバーヘッドになるのでは?」と直感したエンジニアは、非常に優秀だ。

1. リフレクションとHHVMのJIT最適化

HHVMは、C++で書かれた優れた仮想マシンであり、型情報やクラス構造の多くをネイティブのメタデータとしてキャッシュする。そのため、PHP 7/8時代のような「リフレクション=極端に遅い」という神話はHack/HHVMの世界では半分間違いだ。

しかし、本番のリクエストライフサイクル中に動的なリフレクションを行うべきではない。

【正しい実務アプローチ:ビルド時(CI時)の検証】

カスタム属性の検証は、HTTPリクエストの処理中ではなく、CIパイプライン(静的解析フェーズ)またはコンパイル時に一度だけ実行されるべきである。

HHVMのAST(抽象構文木)を直接操作するLinterツールである HHAST(Hack AST) を用いることで、コードを実行することなく、ソースコードの構文木レベルでカスタム属性の付与漏れを静的に検知し、`hh_client` のビルドプロセスの一部として組み込むことが可能だ。

—

4. テックリードからの実務アドバイス:保守性の高い設計にするために

1. 属性自体にビジネスロジックを持たせない
Attributeは、あくまで「メタデータのコンテナ」であるべきだ。データ(引数)を持ち、構造を定義することに集中させ、検証や振る舞いは専用の Enforcer(検証クラス)や Linter に分離せよ。

2. 例外メッセージは「どう直すべきか」を指示せよ
先ほどのコードの `InvariantException` のように、「何がダメで、どの属性をどう付ければ解決するのか」をエラーメッセージに含めること。これだけで、ジュニアエンジニアや他のチームメンバーがコードレビューを待たずに自律的に修正できるようになる。

3. HHVMのバージョン追従
Hack言語の仕様やAttributeのターゲット指定(`\Attribute::` の定数など)は進化している。プロジェクトの `hhvm.to_string` や厳格モードの設定と併せて、常に最新の言語仕様に最適化されたメタデータ設計を心がけよう。

—

結び

Hackの静的型システムとカスタム属性を組み合わせることで、私たちのコードベースは単なる「動くプログラム」から、「ルールに違反することが物理的(コンパイル的)に不可能な自己防衛システム」へと進化する。

型チェッカーが守るのはコードの「構造」だが、カスタム属性と解析ロジックが守るのは君たちの「ビジネスの信頼性」だ。
妥協のないコードレビューと堅牢な設計で、次のデプロイを完璧なものにしてほしい。

タイトルとURLをコピーしました