例外という名の「暗黙の破壊」を断つ:HackによるResult型への移行戦術
PHPの時代、我々は「例外(Exception)」という名の爆弾をコードの至る所に埋め込んできた。関数が何を投げ、どこでキャッチされるべきか。そのシグネチャからは何も読み取れず、実行するまでスタックトレースが破裂するかどうかすら不明なコード。
Hackの静的型システムを扱う以上、そのような「不確実性」は撲滅すべき技術的負債だ。本稿では、PHP的な例外駆動開発を卒業し、`Result
—
1. なぜ「例外」はHackの思想に反するのか
例外は「非局所的な制御フロー」を生む。関数 `A` が `B` を呼び、`B` が `C` を呼ぶ時、`C` が投げる例外を `A` が補足することを期待する設計は、型システムによる検証をすり抜ける。
Hackの型チェッカーは「関数が何を返すか」を厳格に追跡する。しかし、例外は型シグネチャに現れない。つまり、例外に依存するコードは、型システムの保護範囲外にある「未定義の挙動」を抱えているのと同義だ。
2. Result型による「失敗の明示化」
我々が目指すのは、エラーを「異常系」として隠蔽するのではなく、「値」として扱うことだ。以下のパターンを見てほしい。
namespace App;
use HH\Result;
/
- 従来のPHP: throw new Exception(“User not found”)
- Hackの設計: 失敗を戻り値の型として定義する
/
function findUserById(int $id): Result
$user = User::fetch($id);
if ($user === null) {
// 失敗を値としてラップして返す
return Result::failure(new UserNotFoundError($id));
}
return Result::success($user);
}
この設計により、呼び出し元は「成功した場合」と「失敗した場合」の両方を強制的にハンドリングしなければならない。これを怠れば、型チェッカーが静的にエラーを吐く。これが「バグの起きないコード」への第一歩だ。
3. 実践:HSLを活用した堅牢なパイプライン
HSL(Hack Standard Library)の力を借りれば、エラーハンドリングはよりエレガントになる。
use HH\Result;
final class UserAccountService {
public function processUserUpdate(int $id, string $newName): void {
// 成功時のみ処理を継続するパイプライン
$result = $this->findUserById($id)
->flatMap($user ==> $this->validateName($newName))
->map($validatedName ==> $this->updateUser($id, $validatedName));
// 最終的な結果のパターンマッチング
if ($result->isFailure()) {
$error = $result->getFailure();
// ログ出力やエラーハンドリングの集約
Log::error($error->getMessage());
return;
}
Log::info(“Update success for user: {$id}”);
}
}
なぜこれが「速い」のか?
HHVMのJITコンパイラは、型が確定しているコードに対して最適化をかける。例外が発生すると、スタックの巻き戻し(Unwinding)という重い処理が走る。対して、`Result` は単なるデータ構造の受け渡しであり、通常の関数呼び出しと変わらないオーバーヘッドで済む。パフォーマンスと保守性は、ここではトレードオフではなく「両立」する。
—
4. 移行のための「3つの鉄則」
既存のPHPコードベースをHackへ移行する際、以下のルールを徹底せよ。
1. ドメイン境界での例外変換: サードパーティのライブラリやレガシーなコードが投げる例外は、呼び出しの境界(Infrastructure層)で確実にキャッチし、`Result` 型に変換して内部へ渡すこと。
2. `Result` を引数にしない: `Result` は関数の戻り値として使うものだ。これを引数に渡す設計は、関数の責務を曖昧にする。
3. 網羅性の強制: `match` 式を活用し、すべてのエラーケースを処理するように記述する。これにより、将来的にエラータイプを増やした際に、コンパイラが「処理漏れ」を即座に指摘してくれる。
—
最後に:型は「ドキュメント」以上の存在である
多くのエンジニアは「型を書くのが面倒だ」と言う。だが、型とはコードに対する「契約」だ。例外という曖昧な契約を捨て、`Result` という明確な契約を結ぶこと。それが、HHVM上でプロダクション環境を運用するエンジニアが備えるべき唯一の作法である。
次にコードを書くとき、その `throws` アノテーションを削除し、戻り値の型を `Result` に書き換えてみよ。コンパイラが赤線で示した警告こそが、君のコードがより強固になった証だ。
さあ、コードをリファクタリングしよう。その先には、深夜の障害対応から解放された、静かな夜が待っているはずだ。