破壊的変更という「時限爆弾」を葬り去る:Hack `readonly` プロパティの真髄
PHPのレガシーなコードベースをHackへと移行する際、最も多くのエンジニアが直面する悪夢は「意図しない副作用(Side Effects)」だ。
参照渡し(reference)や、共有されたオブジェクトのプロパティが、コードのあちこちで書き換えられる。これこそが、非同期処理や複雑なコンポーネント設計におけるバグの温床だ。HHVMのアーキテクチャを知る者であれば、メモリ上のオブジェクトがどのスタックフレームから変更可能であるかを制御することが、どれほど堅牢性に直結するかを理解しているはずだ。
今日は、Hackが提供する `readonly` プロパティを使い、副作用を排除した「沈黙するほど堅牢な」設計パターンを伝授する。
—
1. なぜ `readonly` なのか:メモリの安定性と計算コスト
PHPにおけるオブジェクトの可変性(Mutability)は、一見すると利便性が高いように思える。しかし、大規模システムでは、あるサービスが保持するDTO(Data Transfer Object)が、別のコンポーネントで書き換えられ、予期せぬ不整合を引き起こす。
Hackの `readonly` プロパティは、単なるシンタックスシュガーではない。HHVMの型チェッカー(HackC)が、コンパイル時にメモリの書き込み権限を静的に検証する。つまり、実行時のオーバーヘッドなしに、書き込み違反を未然に防ぐことができるのだ。
ダメな設計:PHP的思考の残滓
// 何が起きるか予測不能な危険なコード
class UserProfile {
public function __construct(
public string $email, // 書き換え可能
) {}
}
function process(UserProfile $user): void {
// 外部からの副作用で $user->email が変えられる可能性がある
$user->email = ‘hacked@example.com’;
}
—
2. 実務で即戦力となる「イミュータブル・パターン」
コンポーネント設計において、データを持ち回る際は「不変(Immutable)」であることを保証する。以下のコードは、HSL(Hack Standard Library)を活用した、副作用のないクリーンな設計例だ。
namespace App;
// readonly プロパティを付与し、不変性を強制する
final class UserSession {
public function __construct(
public readonly string $userId,
public readonly DateTimeImmutable $loginAt,
) {}
// 状態を変更したい場合は、新しいインスタンスを返す(Witherパターン)
public function withLoginAt(DateTimeImmutable $newTime): this {
return new self($this->userId, $newTime);
}
}
function authenticate(UserSession $session): void {
// $session->userId = ‘new’; // ここで静的エラーが発生。コンパイルを通さない。
$newSession = $session->withLoginAt(new DateTimeImmutable());
// 変更が必要な場合は、新しいオブジェクトを生成して伝播させる。
// これにより、元の $session は保護される。
}
—
3. パフォーマンスとアーキテクチャ上の注意点
「オブジェクトを新しく作り直すとメモリ効率が悪くなるのでは?」と懸念する諸君がいるかもしれない。だが、HHVMの「Immutable Object Optimization」を信じてほしい。
- HHVMの最適化: HHVMは、不変なオブジェクトに対しては非常に効率的な内部表現を用いる。頻繁なオブジェクト生成であっても、現代のメモリ管理とJIT(Just-In-Time)コンパイルの恩恵により、可変オブジェクトによる不整合のデバッグコストよりも遥かに安上がりだ。
- 非同期APIとの相性: 非同期処理(Awaitable)において、共有オブジェクトが不変であることは、競合状態(Race Condition)を論理的に排除する。ロック(mutex)を管理する煩わしさから解放されたければ、まずはデータを `readonly` にすることから始めよ。
—
4. チーフアーキテクトからの助言:移行の戦略
PHPからHackへ移行する際、すべてを一気に `readonly` にするのは現実的ではない。以下のステップで進めるのが、コードベースを破壊しない唯一の道だ。
1. DTOの特定: まずは外部入力を受け取るDTOから `readonly` を適用する。
2. HSLの活用: 配列の操作には `vec`, `dict` を使い、HSLの不変コレクションへ置き換える。
3. コンストラクタによる注入: プロパティを直接公開せず、必ずコンストラクタで初期化する設計を徹底する。
最後に
Hackの `readonly` は、あなたの書くコードを「守られるべき資産」に変える。レビューで「なぜここを `readonly` にしなかったのか?」と問えるエンジニアであれ。
型システムは、あなたの「意図」をコンパイラに伝えるための最強のドキュメントだ。そのドキュメントが嘘をつかないようにすることこそ、真にプロフェッショナルな設計である。
さあ、コードを書き換えろ。そして、バグの影を消し去るのだ。