【入門編】【中級者向け】`throws`アノテーションの設計と運用:例外処理を型システムの一部として組み込む – Hack言語 コア・静的型システムとHHVMのアーキテクチャ解析バイブル

こんにちは!フルスタックエンジニアの先輩です。今日は、Hack言語における極めて強力な武器、`throws`アノテーションのお話をしちゃいますね。

他の言語(例えばPHPやPythonなど)からHackの世界に飛び込んできたとき、こんなモヤモヤを抱えたことはありませんか?
「あれ、この関数を呼び出したときに、どんな例外(Exception)が飛んでくるのか、コードを見ただけじゃ分からないぞ……?」
「うっかり例外のキャッチを忘れて、本番環境で予期せぬクラッシュを起こしちゃった……」

そう、一般的なプログラミング言語では、例外の発生は「実行時(ランタイム)のサプライズ」になりがちです。しかし、厳格な静的型システムを誇るHackは違います。例外の発生すらも型システムの一部としてコンパイル時に完全に統御してしまう、それが`throws`アノテーションなんです。

ここをクリアすれば、あなたの書くコードの堅牢性は劇的に跳ね上がります。さあ、一緒にHackの深淵を覗いてみましょう!

—

1. なぜ「例外」を型で縛る必要があるのか?

まずは、私たちが普段やりがちな「危ういコード」をイメージ図で見てみましょう。

[呼び出し元 (Caller)]
│
├─► 例外の存在を知らずに呼び出す!
│
▼
[危険な関数 (Callee)] ──(予期せぬ例外バースト!💥)──> [本番環境でクラッシュ🔥]

従来の動的言語や緩やかな型付けの世界では、関数がどんなエラーを投げるかは、ドキュメント(あるいは実装者の記憶)頼りでした。これでは、コードが肥大化したときに破綻するのは目に見えていますよね。

HackのStrict Mode(厳格モード)では、「この関数は、こういう例外を投げる可能性がありますよ」という未来を、型チェッカーに対してあらかじめ宣言する義務があります。これが`throws`アノテーションです。

—

2. 基本的な使い方:`throws`の構文をマスターする

百聞は一見にしかず。実際のHackコードで書き方を確認してみましょう。

<<__Strict>>
namespace HackExemplar;

// 独自例外の定義
class InsufficientFundsException extends \Exception {}

class BankAccount {
private float $balance = 100.0;

// 【重要】throws アノテーションで「この例外を投げる」と宣言する
<<__Rx, __Atomics>> // ※HHVMの最適化アノテーション文脈は一旦置いておき、シグネチャに注目
public function withdraw(float $amount): void
throws InsufficientFundsException {

if ($amount > $this->balance) {
// 宣言した例外をスロー
throw new InsufficientFundsException(“残高が足りません!”);
}

$this->balance -= $amount;
}
}

コードの解説

  • `<<__Strict>>`: ファイル全体をHackの厳格モードに指定しています。甘えは一切許されません。
  • `throws InsufficientFundsException`: このシグネチャが今回の主役です。「私はこの例外を外側に放り投げますよ」と型チェッカーに宣言しています。

もし、この `throws` 宣言を書き忘れて関数内で `throw` しようものなら……? 型チェッカーが即座に「おっと、宣言されていない例外が投げられていますよ!」とコンパイルエラーを叩きつけてくれます。最高に頼もしいですよね。

—

3. 呼び出し側の義務:例外は必ず「処理」か「伝播」させる

`throws` で守られた関数を呼び出す側(Caller)にも、当然ルールがあります。投げられる可能性のある例外は、無視することができません。

次の2つの選択肢のどちらかを必ず取る必要があります。

1. `try-catch` で確実に捕捉して処理する
2. 自分自身も `throws` をつけて、さらに上位へ例外を伝播させる

<<__Strict>>
namespace HackExemplar;

class TransactionProcessor {
public function process(BankAccount $account): void {
try {
// 1. 呼び出し:例外をキャッチする構えを見せる
$account->withdraw(150.0);
} catch (InsufficientFundsException $e) {
// 2. 確実にエラーハンドリングを行う
\C\io_print(“エラーを検知しました: “.$e->getMessage().”\n”);
// 必要であればリカバリ処理を書く
}
}
}

もし `try-catch` で囲わずにそのまま呼び出そうとすると、型チェッカーは容赦なくエラーを出します。「おいおい、爆弾(例外)が飛んでくるかもしれないのに、防護服を着ていないじゃないか!」と型チェッカーが怒ってくれるわけです。

—

4. 現場で陥りがちな文法エラーとアンチパターン

ここで、実務でやりがちなハマりポイントをいくつか共有しておきますね。

① `\Exception` を何でもかんでも `throws` しない

「とりあえず `\Exception` を投げます!」という雑な設計は、型システムの恩恵を台無しにします。どのレイヤーで、どんなドメインエラー(残高不足、ユーザー不在など)が起き得るのかを細かくクラス分けし、それぞれの具象例外を `throws` に指定するのがプロの作法です。

② 親クラス・子クラスの例外の扱い

例外クラスにも継承関係があります。例えば、`DatabaseException` が `SystemException` を継承している場合、より抽象度の高い親クラスで `throws` を宣言することも可能です。ただし、呼び出し側の精緻なハンドリングを考慮すると、できる限り具体的(Specific)な例外を型として定義・宣言する方が、保守性がグッと上がりますよ。

—

まとめ:例外処理を「型」で支配する快感

いかがでしたでしょうか?
Hackの `throws` アノテーションを活用することで、例外処理は「運任せのランタイムエラー」から「コンパイル時に完全に予測・制御可能なドメインロジック」へと生まれ変わります。

最初は「型チェッカーに怒られてばかりで面倒だな……」と感じるかもしれませんが、それを乗り越えた先にあるのは、「絶対に予期せぬ例外でクラッシュしない、極限まで堅牢なコードベース」という圧倒的な安心感です。

ここをクリアすれば、あなたのHackの基本はもうバッチリマスターできていますよ!自信を持って、美しい型の世界を突き進んでいきてくださいね。それでは、次のコードレビューでお会いしましょう!

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