PHPで行き先の多い分岐を扱う際、未定義の分岐先(あるいは入ることのない分岐先)に例外を仕込んでおくと後の改修作業が少し楽になります。この記事ではその実装方法と例を紹介します。
プログラムを書く過程で状態遷移など複数の分岐が生じる瞬間がしばしばあります。これはとにかく今コードを動かす目的だけならば、次のようにシンプルに書くことができます:
if($state === State::INIT){
// 初期状態
} else if($state === State::ACTIVE) {
// 活動状態
} else {
// 終了状態
}
このコードは期待通りに動作しますが、将来的に状態が増えた場合にこのコードが更新されないと新しい状態も終了状態とみなされてしまう危険性があります。そこで以下のように書くことで起きるやもしれない改修に備えます。
if($state === State::INIT){
// 初期状態
} else if($state === State::ACTIVE) {
// 活動状態
} else if($state === State::END) {
// 終了状態
} else {
throw new \LogicException('考慮されていないパターンです');
}
この方法ではコード量が増えますが状態が増えた時に未改修の箇所に気づきやすくなります(TypeScriptの型機能とかがあれば静的解析で動かす前に気づけるようになる気もしますが)。経験則ですが、多くの分岐が存在する箇所では、更に分岐が増加し複雑になりやすいです。このため少々余分なコードでもこういったものを書いておくのは案外役に立ちます。
上記の例はif文ですがswitchやmatchでもdefaultに例外を投げさせることで同様の効果を得ることができます。例えばswitchでは以下のように書けます。
switch ($state) {
case State::INIT:
// 初期状態
break;
case State::ACTIVE:
// 活動状態
break;
case State::END:
// 終了状態
break;
default:
throw new \LogicException('考慮されていないパターンです');
}
matchでは次のように書けます。
$result = match ($state) {
State::INIT => // 初期状態の処理,
State::ACTIVE => // 活動状態の処理,
State::END => // 終了状態の処理,
default => throw new \LogicException('考慮されていないパターンです'),
};
とはいえ、実はmatchの方はこういったものを書かなくてもいい感じに動作してくれます。これはmatchではdefaultが未定義の状態で既存の条件に当てはまらない場合は Uncaught UnhandledMatchError: Unhandled match caseとエラーを投げてくれるためです。改修すべき部分が改修されていなくても、それがmatchの部分ならすぐ気づけるわけです。
読みやすいコードという観点からは少し遠ざかったコードになりますが、コードを改造する際にどこをどう触ればいいのかという手がかりのために使わないフローについてのコードやコメントを書いておくのは結構役に立ちます。