※小説ではない※専門書 要約資料集 為替(換算)3.9万円でもらう 紐解集生成 専門 初入門 資料 作:{作者名}
> 現代学問宇宙図鑑・PC創造大全(BOOK-0358・統合大型巻)分冊
> 分類記号: KNOW-F-0126〜KNOW-F-0250(関数ノウハウ・制御と分岐の型125種)
> **PC創造大全ノウハウ目録シリーズ索引(全12巻・部k〜v)**: 部k BOOK-0358k(関数設計の基本形・KNOW-F-0001〜0125) → **部l BOOK-0358l(本冊・制御と分岐の型・KNOW-F-0126〜0250)** → 部m BOOK-0358m(データ変換の型・KNOW-F-0251〜0375) → 部n BOOK-0358n(合成と堅牢化の型・KNOW-F-0376〜0500) → 以下、部o〜v(PC構造ノウハウ帯KNOW-P-0001〜1000)へ続く。
> **接続**: 型見本=BOOK-0184(関数パターン目録I・構造二百種の予約)/物語脊椎対=BOOK-0358f(関数を正式に組む)/前巻=BOOK-0358k/次巻=BOOK-0358m。設計書=gakumon/CATALOG_PC創造大全.md(§1 ID帯・§5 執筆規格)。
# BOOK-0358l PC創造大全 目録II 制御と分岐の型
> 現代学問宇宙図鑑・PC創造大全(BOOK-0358・統合大型巻)分冊
> 分類記号: KNOW-F-0126〜KNOW-F-0250(関数ノウハウ・制御と分岐の型125種)
> **PC創造大全ノウハウ目録シリーズ索引(全12巻・部k〜v)**: 部k BOOK-0358k(関数設計の基本形・KNOW-F-0001〜0125) → **部l BOOK-0358l(本冊・制御と分岐の型・KNOW-F-0126〜0250)** → 部m BOOK-0358m(データ変換の型・KNOW-F-0251〜0375) → 部n BOOK-0358n(合成と堅牢化の型・KNOW-F-0376〜0500) → 以下、部o〜v(PC構造ノウハウ帯KNOW-P-0001〜1000)へ続く。
> **接続**: 型見本=BOOK-0184(関数パターン目録I・構造二百種の予約)/物語脊椎対=BOOK-0358f(関数を正式に組む)/前巻=BOOK-0358k/次巻=BOOK-0358m。設計書=gakumon/CATALOG_PC創造大全.md(§1 ID帯・§5 執筆規格)。
---
## 序章 「制御」とは分岐と反復をどう配線するかである
関数が正式に組めるようになった(BOOK-0358k)としても、その関数の中身が一直線の代入文だけで済むことは稀である。「条件によって処理を変える(分岐)」「同じ手続きを繰り返す(反復)」という2つの配線を、どういう型で書くかによって、同じ結果を出すコードでも読みやすさ・保守しやすさ・実行効率が大きく変わる。本冊はこの「配線の型」を125種、KNOW-F-0126〜KNOW-F-0250として目録化する。
配線には大きく5つの系統がある。第一に**ループ**——決まった手続きを繰り返す最も基本的な反復であり、前判定か後判定か、境界をどこに置くかという設計判断の束である(第一章、KNOW-F-0126〜0150)。第二に**再帰**——関数が自分自身を呼ぶことで反復を表現する方法であり、基底条件と再帰ステップという2要素、そしてループとの等価性が要になる(第二章、KNOW-F-0151〜0175)。第三に**早期returnとガード節**——前提条件を満たさない場合に即座に処理を打ち切り、深いネストを削って正常系の処理を主軸に読めるようにする技法である(第三章、KNOW-F-0176〜0200)。第四に**状態機械**——「今どの状態にいるか」を明示的に管理し、入力(イベント)に応じて次の状態を決める枠組みであり、if-elseの海に沈んだフラグ変数の集合を1枚の遷移表へ書き換える(第四章、KNOW-F-0201〜0225)。第五に**分岐削減の技**——テーブル駆動法や多態(ポリモーフィズム)によって、if/switchの羅列そのものを「データ」や「型の使い分け」へ置き換える技法である(第五章、KNOW-F-0226〜0250)。
この5系統は互いに独立ではなく、しばしば同じ問題を別の角度から解く関係にある。ループで書けるものは大抵、末尾再帰にも書き換えられる(KNOW-F-0150⇄KNOW-F-0162)。相互再帰は状態が2つの状態機械の特殊形として読み替えられる(KNOW-F-0154⇄KNOW-F-0201)。ガード節の集合はテーブル駆動法の特殊形である(KNOW-F-0176⇄KNOW-F-0186)。本冊では各項目の「組合せ例」の中でこうした系統間の橋渡しを積極的に示し、どれか1つの型に固執せず、問題の性質に応じて最も見通しのよい型を選べるようにすることを狙う。
各項目は型見本BOOK-0184に倣い、①名称 ②構造の型(擬似コードまたはJS) ③使いどころ ④組合せ例 ⑤確認事項(罠と検算)の五点セットで記述する。BOOK-0184が数式(定義式・推移表)を検算の軸としたのに対し、本冊はコードの入出力(Before/After比較・境界値・実測)を検算の軸とする。「誇大表現禁止」の安全枠(CATALOG設計書§3)に従い、いずれの技法も「必ず読みやすくなる」ではなく「特定の状況で読みやすくなりうる」型として提示し、各章末には過剰適用を戒める項目(KNOW-F-0175・KNOW-F-0225・KNOW-F-0249)を必ず置いている。
### 凡例——本冊の読み方
- **ID表記**: KNOW-F-0126のように4桁の連番で表す。第一章はKNOW-F-0126〜0150、第二章はKNOW-F-0151〜0175、第三章はKNOW-F-0176〜0200、第四章はKNOW-F-0201〜0225、第五章はKNOW-F-0226〜0250と、章の切れ目が25の倍数に一致するよう設計してある(前巻BOOK-0358kのKNOW-F-0001〜0125を継いだ帯であり、次巻BOOK-0358mはKNOW-F-0251から始まる)。
- **構造の型のコード**: 原則としてJavaScriptで記述する。JavaScript固有の挙動(末尾呼び出し最適化が保証されない、`==`と`===`の違い等)に依存する注意点は、確認事項の欄で明示的に断る。擬似コードで示す方が意図が伝わりやすい箇所(手順の列挙等)は擬似コードを用いる。
- **組合せ例の相互参照**: 「KNOW-F-XXXX(見出し名)」という形式で本冊内の他項目、または「BOOK-XXXX」の形式で他冊を参照する。参照先を読まなくても各項目単体で意味が通るよう、参照はあくまで発展的な理解のための道しるべとして付けてある(CATALOG設計書§0-5「段落独立請求項」に従い、各項目は前提リンクを明示すれば単独でも成立するよう書いてある)。
- **確認事項の書式**: 「罠」(誤りやすい点)と「検算」(実際に確認する具体的な手順、多くは境界値または実測)を必ず1組にして記す。罠だけを述べて検算の手順を示さない項目は本冊には存在しない。
## 第一章 ループの型——反復処理の骨格(KNOW-F-0126〜0150)
本章では、決まった手続きを繰り返す「ループ」の構造パターン25種を扱う。ループはfor/while/イテレータという入口の違いだけでなく、「いつ条件を判定するか(前判定/後判定)」「境界をどう決めるか(オフバイワン)」「途中で抜けるか(break/continue)」という設計判断の束である。各項目は名称・構造の型(擬似コードまたはJS)・使いどころ・組合せ例・確認事項(罠と検算)の五点セットで記す。
---
### KNOW-F-0126 前判定ループ(for)
**構造の型**:
```js
for (let i = 0; i < n; i++) {
// 本体: iを使う処理
}
```
初期化・継続条件・更新式を1行に集約し、本体を実行する前に条件を判定する「前判定」構造。
**使いどころ**: 反復回数があらかじめ確定している走査(配列の全要素処理・固定回数の試行)に用いる。継続条件が先頭にあるため、n=0のとき本体は一度も実行されない。
**組合せ例**: KNOW-F-0132(オフバイワン回避)と組み合わせ、`i < n`か`i <= n`かを配列長との対応で先に決めてから書き始める。KNOW-F-0136(二重ループ)の外側ループとしても使う。
**確認事項(罠と検算)**: 更新式の書き忘れによる無限ループ、`<`と`<=`の取り違えによる境界の1個ずれに注意する。n=0・n=1の境界値で実行回数を数えて検算する。
---
### KNOW-F-0127 前判定ループ(while)
**構造の型**:
```js
let i = 0;
while (i < n) {
// 本体
i++;
}
```
初期化と更新をfor文の外に出し、継続条件だけをループの入口に置く構造。forより自由度が高い分、更新の書き忘れの責任は書き手に移る。
**使いどころ**: 反復回数が実行前には決まらず、ループ内の計算結果によって継続可否が変わる処理(探索の収束・入力待ち)に用いる。
**組合せ例**: KNOW-F-0133(番兵ループ)の多くはwhileで書かれる。KNOW-F-0139(無限ループ+脱出条件の分離)はwhile(true)の特殊形として位置づけられる。
**確認事項(罠と検算)**: 更新式(i++)を書き忘れると無限ループになる。ループ本体のどこで更新するかを1箇所に固定し、更新漏れを目視で検算する。
---
### KNOW-F-0128 後判定ループ(do-while)
**構造の型**:
```js
let i = 0;
do {
// 本体: 最低1回は実行される
i++;
} while (i < n);
```
本体を先に実行してから継続条件を判定する「後判定」構造。n=0でも本体は必ず1回実行される。
**使いどころ**: 「少なくとも1回は実行してから条件を確認したい」処理(メニュー表示→入力受付→終了判定の繰り返し、リトライ処理)に用いる。
**組合せ例**: KNOW-F-0147(until型ループ)はdo-whileの条件を否定形に読み替えたものであり、両者は同じ構造を条件式の向きで書き分けたに過ぎない。
**確認事項(罠と検算)**: forやwhileと同じ感覚で「0回実行されるはず」と誤解しないこと。n=0を渡した場合に本体が1回実行される点を境界値検算で必ず確認する。
---
### KNOW-F-0129 イテレータ走査(for-of)
**構造の型**:
```js
for (const item of array) {
// itemを直接使う。添字iは持たない
}
```
コレクションが内部に持つ反復子(イテレータ)を介して要素を1つずつ取り出す構造。添字管理をコレクション側に委譲する。
**使いどころ**: 添字そのものが不要で、要素の値だけを使う走査(配列・Map・Set・文字列の走査)に用いる。オフバイワンの罠が構造的に発生しない。
**組合せ例**: KNOW-F-0145(遅延生成ループ)のジェネレータはfor-ofで直接消費できる。KNOW-F-0142(二本指針ループ)のように添字操作が必須の処理には不向きで、その場合はKNOW-F-0126(for)に戻す判断が要る。
**確認事項(罠と検算)**: 走査中に元のコレクションを追加/削除すると反復子の状態が不定になる処理系がある。走査中の破壊的変更は避け、必要なら複製してから回すことを検算する。
---
### KNOW-F-0130 列挙走査(for-in)とその罠
**構造の型**:
```js
for (const key in obj) {
if (!Object.hasOwn(obj, key)) continue; // 継承プロパティを除外
// obj[key] を使う
}
```
オブジェクトの列挙可能なプロパティキーを走査する構造。配列にも使えるが添字が文字列型で返る点に注意が要る。
**使いどころ**: オブジェクトのキー集合を動的に走査したい場合に用いる。配列の走査目的では原則使わない。
**組合せ例**: KNOW-F-0129(for-of)と対比させて教えることで、「配列はfor-of、オブジェクトのキー列挙はfor-in(またはObject.keys+for-of)」という使い分け基準がKNOW-F-0246(データ正規化)にも接続する。
**確認事項(罠と検算)**: プロトタイプチェーン上の継承プロパティまで列挙してしまう罠がある。hasOwn等の自己所有チェックを挟んだかを検算し、配列走査目的では使わないと事前に決める。
---
### KNOW-F-0131 ループ不変条件の明示
**構造の型**:
```js
// 不変条件: ループ開始前とループの各周回の終わりで
// acc === array.slice(0, i).reduce((a,b)=>a+b, 0)
let acc = 0;
for (let i = 0; i < array.length; i++) {
acc += array[i];
// ここで不変条件が成り立つことを確認
}
```
「ループの各周回の前後で必ず成り立つ性質」をコメントとして明文化し、ループの正しさをその性質の維持として説明する技法。
**使いどころ**: 集計・探索・整列などの正しさを人に説明する場面、バグ調査でループの途中状態を疑う場面に用いる。
**組合せ例**: KNOW-F-0132(オフバイワン回避)の境界判断は、不変条件を「ループ開始前(i=0)」と「ループ終了直後(i=n)」の両端で検算することに帰着する。
**確認事項(罠と検算)**: 不変条件はi=0(初期化直後)とi=n(終了直後)の2点で必ず検算する。境界で不変条件が崩れていれば、初期化式か更新式のどちらかが誤っている。
---
### KNOW-F-0132 オフバイワン回避(境界を先に決める)
**構造の型**:
```js
// 配列長nに対して: 有効添字は 0..n-1
for (let i = 0; i < n; i++) { /* n-1まで */ }
// n個の「間」を数えるなら 0..n-2
for (let i = 0; i < n - 1; i++) { /* 隣接ペア用 */ }
```
「境界条件を先にペン&紙(または小さな具体例)で確定してからコードに落とす」設計手順そのものを型として扱う。
**使いどころ**: 配列添字・文字列のスライス・区間の端点処理など、1個のずれが致命的な処理全般で用いる。
**組合せ例**: KNOW-F-0142(二本指針ループ)やKNOW-F-0143(スライディングウィンドウ)は、この境界確定を怠ると窓のサイズが1個ずれる典型例になる。
**確認事項(罠と検算)**: n=0, n=1, n=2の3点で「実行される回数」を手計算し、意図した回数と一致するかを検算する。`<`と`<=`、`length`と`length-1`の取り違えが最頻出の罠である。
---
### KNOW-F-0133 番兵(センチネル)ループ
**構造の型**:
```js
let i = 0;
while (array[i] !== SENTINEL) {
// 本体
i++;
}
```
終了条件を「特別な値(番兵)に出会うまで」と定義し、毎回の境界チェック(添字が配列長を超えないか)を省略できるようにする構造。
**使いどころ**: 探索対象の末尾に番兵値を置ける場面(文字列の終端文字、リンクリストの終端ノード)で、境界チェックのコストを削減したいときに用いる。
**組合せ例**: KNOW-F-0221(状態機械のタイムアウト遷移)のように「特別な入力を終了合図として扱う」設計は、番兵ループの考え方をイベント駆動側に応用したものである。
**確認事項(罠と検算)**: 番兵値がデータ本来の値と衝突しないことを事前に保証しなければならない。衝突すると本来の要素で誤って終了するため、番兵の一意性を検算する。
---
### KNOW-F-0134 カウント制御ループ
**構造の型**:
```js
let count = 0;
while (count < maxRetry) {
const ok = attempt();
count++;
if (ok) break;
}
```
「最大N回まで」という上限を明示的なカウンタで管理し、無限リトライを構造的に防ぐ構造。
**使いどころ**: リトライ処理・タイムアウトのないポーリング処理に、暴走防止の上限を必ず添えたい場面で用いる。
**組合せ例**: KNOW-F-0137(breakによる早期脱出)と組み合わせ、「成功したら即break、上限に達したら自然終了」という二重の脱出経路を設計する。
**確認事項(罠と検算)**: count++の位置がbreakより前か後かで、実際の試行回数がmaxRetryと一致するかどうかが変わる。境界(maxRetry=1)で試行回数を数えて検算する。
---
### KNOW-F-0135 累積(アキュムレータ)ループ
**構造の型**:
```js
let acc = initialValue;
for (const x of items) {
acc = combine(acc, x);
}
```
「途中経過を1つの変数に畳み込みながら進む」構造。合計・最大値・連結・畳み込み全般の骨格になる。
**使いどころ**: 集計処理(合計・積・文字列連結・オブジェクトのマージ)全般に用いる。KNOW-F-0251系(畳み込み)の実装土台でもある。
**組合せ例**: 初期値initialValueの選び方が単位元(合計なら0、積なら1、連結なら空文字列)であるかをKNOW-F-0131(不変条件)で検算すると誤りが減る。
**確認事項(罠と検算)**: 初期値の選択ミス(積の初期値を0にしてしまう等)で全体が単位元に潰れる罠がある。空配列(items=[])を渡したときacc===initialValueのままであることを検算する。
---
### KNOW-F-0136 二重ループ(入れ子)
**構造の型**:
```js
for (let i = 0; i < rows; i++) {
for (let j = 0; j < cols; j++) {
// grid[i][j] を使う
}
}
```
外側ループが1周する間に内側ループが全周する構造。計算量は外側×内側の積になる。
**使いどころ**: 2次元格子の走査、全ペア比較(O(n²))、行列演算の基礎構造として用いる。
**組合せ例**: KNOW-F-0148(ラベル付きbreak)は、内側ループから外側ループごと抜けたい二重ループでこそ真価を発揮する。KNOW-F-0142(二本指針)に置き換えられればO(n²)をO(n)に落とせる場合がある。
**確認事項(罠と検算)**: 内側・外側の添字名(i, j)の取り違えが罠になりやすい。小さな行列(2×3等)で実際の反復回数(rows×cols)を数え、意図と一致するか検算する。
---
### KNOW-F-0137 breakによる早期脱出
**構造の型**:
```js
for (const x of items) {
if (predicate(x)) {
result = x;
break; // 見つかった時点でループ全体を終える
}
}
```
条件が満たされた瞬間にループ全体を打ち切る構造。残りの要素を無駄に走査しない。
**使いどころ**: 線形探索で「最初の1件が見つかれば十分」な場面(存在確認、最初の一致要素の取得)に用いる。
**組合せ例**: KNOW-F-0176(ガード節)の考え方をループの内側に持ち込んだものがbreakであり、「早期return」の親戚として教えると理解が速い。
**確認事項(罠と検算)**: breakは最も内側のループしか抜けない。二重ループの外側まで抜けたい場合はKNOW-F-0148(ラベル付きbreak)かフラグ変数が必要になる点を取り違えないよう検算する。
---
### KNOW-F-0138 continueによる読み飛ばし
**構造の型**:
```js
for (const x of items) {
if (!isValid(x)) continue; // この周回だけ本体をスキップ
process(x);
}
```
条件を満たさない要素だけをスキップし、ループ自体は継続する構造。
**使いどころ**: フィルタ条件を満たさない要素を除外しつつ、残りは処理し続けたい場面(無効データのスキップ、コメント行の読み飛ばし)に用いる。
**組合せ例**: KNOW-F-0176(ガード節)の「前提条件を満たさなければ即座に返す」発想をループの1周回に縮小適用したのがcontinueである。両者は「否定条件で先に弾く」という同じ骨格を共有する。
**確認事項(罠と検算)**: continueの前に更新式(i++相当)を書き忘れると、while文では無限ループになる。for-of/forEachでは更新が自動なので影響しないが、while手書きループでは特に検算が必要。
---
### KNOW-F-0139 無限ループ+脱出条件の分離
**構造の型**:
```js
while (true) {
const input = readInput();
if (input === "quit") break;
handle(input);
}
```
継続条件をwhile文の外(ループ本体の内部)に移し、複数の脱出経路を持てるようにした構造。
**使いどころ**: サーバのメインループ・対話型シェル・ゲームループのように、終了条件が入力やイベントによって動的に決まる処理に用いる。
**組合せ例**: KNOW-F-0202(イベント駆動状態機械)の実行ループはほぼ必ずこの形になる。脱出条件が複数ある場合はKNOW-F-0176(ガード節)の考え方で優先順位を明示する。
**確認事項(罠と検算)**: breakに到達しない経路が1つでも残っていれば無限ループになる。すべての脱出条件を列挙し、少なくとも1つは必ず真になることを設計時点で検算する。
---
### KNOW-F-0140 逆順走査ループ
**構造の型**:
```js
for (let i = array.length - 1; i >= 0; i--) {
// 末尾から先頭へ
}
```
添字を配列長-1から0まで減らしながら走査する構造。
**使いどころ**: 走査しながら同じ配列から要素を削除する処理(削除で後続要素が前に詰まっても、逆順なら未走査部分に影響しない)、末尾優先の処理に用いる。
**組合せ例**: KNOW-F-0132(オフバイワン回避)の境界確認は逆順でも同様に要り、初期値がarray.length-1(array.lengthではない)である点をまず検算する。
**確認事項(罠と検算)**: 初期値をarray.length(1個多い)にする、終了条件を`i > 0`(先頭要素i=0を処理し損なう)にする、の2つが典型的なオフバイワンの罠になる。
---
### KNOW-F-0141 ステップ幅ループ(間引き)
**構造の型**:
```js
for (let i = 0; i < n; i += step) {
// stepおきに処理
}
```
更新幅を1以外(2, 3, …)にして、間引きながら走査する構造。
**使いどころ**: 偶数番目だけ処理、一定間隔でのサンプリング、ペアを1単位として扱う走査(i, i+1をまとめて1要素とみなす)に用いる。
**組合せ例**: KNOW-F-0143(スライディングウィンドウ)を重ならない窓に区切る際、窓幅をstepに一致させると単純なステップ幅ループに帰着する。
**確認事項(罠と検算)**: nがstepの倍数でないとき、最後の不完全な区間をどう扱うか(切り捨てるか、余りとして別処理するか)を事前に決め、境界値(n=7, step=3等)で実際の回数を検算する。
---
### KNOW-F-0142 二本指針(two-pointer)ループ
**構造の型**:
```js
let lo = 0, hi = array.length - 1;
while (lo < hi) {
const sum = array[lo] + array[hi];
if (sum === target) { /* 発見 */ break; }
else if (sum < target) lo++;
else hi--;
}
```
2つの添字を両端(または同じ側)から動かし、O(n²)の全ペア比較をO(n)に落とす構造。事前にソート済みであることが前提になることが多い。
**使いどころ**: ソート済み配列における2数の和探索、回文判定、区間の重なり判定に用いる。
**組合せ例**: KNOW-F-0136(二重ループ)で書いた全ペア探索を、ソート前提が使える場面ではこちらに置き換えることで計算量を落とせる——KNOW-F-0249(分岐削減とパフォーマンスのトレードオフ)の判断例の1つになる。
**確認事項(罠と検算)**: ソートされていない配列にそのまま適用すると正しい答えが出ない。lo, hiの更新条件を取り違えると指針がすれ違わずに無限ループになるため、小さな具体例(長さ4)で手を動かして検算する。
---
### KNOW-F-0143 スライディングウィンドウループ
**構造の型**:
```js
let sum = 0;
for (let i = 0; i < array.length; i++) {
sum += array[i];
if (i >= windowSize) sum -= array[i - windowSize];
if (i >= windowSize - 1) { /* sumが窓幅windowSizeの合計 */ }
}
```
固定幅(または可変幅)の「窓」を1要素ずつ右にずらしながら、窓内の集計を差分更新する構造。毎回窓全体を再計算しない点が要。
**使いどころ**: 移動平均・区間内最大値の追跡・部分文字列探索など、隣接する窓同士で計算を使い回せる集計に用いる。
**組合せ例**: KNOW-F-0135(累積ループ)の毎回全再計算を、窓の出入りする1要素だけの差分更新に変えたのが本項目であり、KNOW-F-0249(パフォーマンストレードオフ)の代表例として扱う。
**確認事項(罠と検算)**: 窓がまだ埋まっていない先頭区間(i < windowSize-1)で結果を確定させてしまう罠がある。窓幅ちょうどで最初に確定するiの値をKNOW-F-0132流に検算する。
---
### KNOW-F-0144 先読み付きループ(lookahead)
**構造の型**:
```js
for (let i = 0; i < tokens.length; i++) {
const next = i + 1 < tokens.length ? tokens[i + 1] : null;
if (next !== null && combine(tokens[i], next)) {
i++; // 先読みした分を消費して二重処理を防ぐ
}
}
```
現在の要素だけでなく次(または前)の要素も参照して判断する構造。先読みした分は添字を余分に進めて二重処理を避ける。
**使いどころ**: 字句解析(2文字演算子の判定)、隣接要素のペア結合処理に用いる。
**組合せ例**: KNOW-F-0215(再帰下降パーサ型再帰、目録IIIで詳述)のトークナイザ部分は本項目の先読みループとして実装されることが多い。
**確認事項(罠と検算)**: 配列末尾での先読み(i+1が範囲外)を無条件に参照すると例外や undefined 参照の罠になる。末尾ガード(`i + 1 < length`)を必ず先に書いたかを検算する。
---
### KNOW-F-0145 遅延生成ループ(ジェネレータ)
**構造の型**:
```js
function* range(start, end) {
for (let i = start; i < end; i++) yield i;
}
for (const i of range(0, 1000000)) {
if (stopCondition(i)) break;
}
```
値をあらかじめ全部作らず、要求されるたびに1つずつ生成する構造。for-ofと組み合わせ、途中でbreakすれば残りは生成されない。
**使いどころ**: 巨大または無限に続く可能性のある列を扱う場面、メモリを使い切らずに「必要な分だけ」計算したい場面に用いる。
**組合せ例**: KNOW-F-0129(イテレータ走査)の消費側とKNOW-F-0145の生成側は表裏の関係にある。KNOW-F-0137(breakによる早期脱出)と組み合わせると、無駄な生成そのものを止められる。
**確認事項(罠と検算)**: ジェネレータを2回続けて走査すると2周目は空になる(1回消費されたイテレータは使い切り)。同じ列を2度使いたい場合は再生成が必要な点を検算する。
---
### KNOW-F-0146 並行カウンタループ(複数添字同時進行)
**構造の型**:
```js
for (let i = 0, j = array2.length - 1; i < array1.length; i++, j--) {
// array1[i] と array2[j] を対応づけて処理
}
```
1つのfor文の中で複数の添字を同時に初期化・更新する構造。
**使いどころ**: 2つの配列を対応づけながら走査したい場合(片方は正順、もう片方は逆順など)に用いる。
**組合せ例**: KNOW-F-0142(二本指針)は同一配列上の並行カウンタの特殊形であり、本項目はそれを異なる配列間に一般化したものと位置づけられる。
**確認事項(罠と検算)**: 2つの添字の終了条件が食い違う(片方だけを条件に使う)と、もう一方が範囲外に出る罠がある。両方の配列長の対応関係(同じ長さか、意図的にずれているか)を事前に検算する。
---
### KNOW-F-0147 後判定条件ループ(until型)
**構造の型**:
```js
let i = 0;
do {
step(i);
i++;
} while (!(i >= n)); // "until i >= n" と同義
```
「〜になるまで繰り返す(until)」という自然言語の条件を、do-whileの条件式を否定形で書くことで表現する構造。
**使いどころ**: 仕様書が「〜が成立するまで繰り返す」という言い回しで書かれている場合、条件式をそのまま素直に対応づけたいときに用いる。
**組合せ例**: KNOW-F-0128(do-while)と構造は同一であり、条件式を`while(継続条件)`と読むか`until(終了条件)`と読むかの表現の違いに過ぎない。De Morganの法則(KNOW-F-0237)で両者を相互変換できる。
**確認事項(罠と検算)**: 継続条件と終了条件を否定形で書き換える際、`!(a && b)`が`!a || !b`になる(その逆ではない)ことを取り違えやすい。小さな具体値で両条件が同じ結果を返すか検算する。
---
### KNOW-F-0148 ラベル付きbreak(多重脱出)
**構造の型**:
```js
outer:
for (let i = 0; i < rows; i++) {
for (let j = 0; j < cols; j++) {
if (grid[i][j] === target) break outer; // 二重ループを一気に抜ける
}
}
```
ループにラベルを付け、内側のループから外側のループごと一度に脱出する構造。
**使いどころ**: 二重(以上の)ループの中で目的の要素が見つかった瞬間、すべてのループを打ち切りたい場面に用いる。
**組合せ例**: ラベルを使いたくない設計では、KNOW-F-0176(ガード節)的に「見つかったフラグ」を立てて外側ループの条件に含める代替がある。どちらを採るかはKNOW-F-0250(分岐削減リファクタリング手順)の可読性判断に従う。
**確認事項(罠と検算)**: ラベルの付け忘れでbreakが最も内側のループしか抜けない罠がある。意図した階層まで脱出しているかを、行と列の両方を変えた具体例で検算する。
---
### KNOW-F-0149 ループのタイル化/分割(ループ展開の初歩)
**構造の型**:
```js
// 全区間を固定サイズの塊(タイル)に分けて処理
for (let start = 0; start < n; start += tileSize) {
const end = Math.min(start + tileSize, n);
for (let i = start; i < end; i++) { /* 本体 */ }
}
```
1本の大きなループを、固定サイズの区間(タイル)ごとに分割して処理する構造。末尾の半端な区間はMath.minで安全に切る。
**使いどころ**: バッチ処理・チャンク単位でのI/O(一括読み込みではメモリを使い切る場合の分割読み込み)・キャッシュ効率を意識した処理に用いる。
**組合せ例**: KNOW-F-0141(ステップ幅ループ)の考え方をタイル単位に拡張したものであり、KNOW-P帯の分割統治処理(目録側で詳述)とも接続する。
**確認事項(罠と検算)**: nがtileSizeの倍数でないとき末尾のタイルが欠けたり範囲外を読んだりする罠がある。Math.min(start+tileSize, n)で必ず上限を切っているかを、nとtileSizeの倍数関係が異なる2パターンで検算する。
---
### KNOW-F-0150 ループから再帰への書き換え可能性チェック
**構造の型**:
```js
// ループ版
function sumLoop(arr) {
let acc = 0;
for (const x of arr) acc += x;
return acc;
}
// 再帰版(等価)
function sumRec(arr, i = 0, acc = 0) {
if (i === arr.length) return acc; // 基底条件
return sumRec(arr, i + 1, acc + arr[i]);
}
```
同じ処理をループと再帰の両方で書き、両者が「初期値・更新規則・終了条件」という同じ3要素の言い換えであることを確認する構造。
**使いどころ**: ループの不変条件(KNOW-F-0131)が把握しにくい場面で、再帰の基底条件+再帰ステップという形に翻訳して見通しを立て直したいときに用いる。第二章(再帰の型)への橋渡し。
**組合せ例**: 本項目はKNOW-F-0151(基底条件の明示)およびKNOW-F-0163(再帰→ループ変換)と対になる。「ループで書けるものは大抵、末尾再帰(KNOW-F-0153)にも書き換えられる」という等価性の確認に使う。
**確認事項(罠と検算)**: ループの初期値・累積更新・終了条件と、再帰の初期呼び出し引数・再帰ステップ・基底条件を1対1で対応づけ、同じ入力に対して両者の出力が一致するかを実測で検算する。
## 第二章 再帰の型——自分自身を呼ぶ構造(KNOW-F-0151〜0175)
本章では、関数が自分自身を呼び出すことで反復を表現する「再帰」の構造パターン25種を扱う。再帰は「基底条件(止まる条件)」と「再帰ステップ(問題を小さくして自分を呼ぶ)」の2要素で成り立つ。ループとの等価性(KNOW-F-0150)を踏まえ、再帰でしか自然に書けない構造(木・相互再帰・分割統治)と、ループへ書き戻すべき構造を見分ける判断基準までを扱う。
---
### KNOW-F-0151 基底条件(base case)の明示
**構造の型**:
```js
function factorial(n) {
if (n === 0) return 1; // 基底条件: これ以上小さくならない
return n * factorial(n - 1); // 再帰ステップ
}
```
「これ以上分解しない」入力を最初に判定し、即座に値を返す一文を必ず関数の先頭に置く構造。基底条件を欠くと再帰は止まらない。
**使いどころ**: あらゆる再帰関数の設計で最初に決めるべき事項として用いる。基底条件が2種類以上必要な場合(空配列と1要素配列で処理が異なる等)は両方を列挙する。
**組合せ例**: KNOW-F-0152(再帰ステップの縮小保証)と対になり、「基底条件に必ずたどり着くこと」を保証する。KNOW-F-0176(ガード節)は基底条件の考え方を非再帰関数に転用したものと位置づけられる。
**確認事項(罠と検算)**: 基底条件の判定式を`n === 0`とすべきところを`n === 1`と書き誤ると、n=0で呼ばれたときに無限に再帰し続けるかnが負に突き抜ける。n=0とn=1の両方を渡して基底条件がちょうど1回で止まるか検算する。
---
### KNOW-F-0152 再帰ステップの縮小保証
**構造の型**:
```js
function countDown(n) {
if (n <= 0) return;
console.log(n);
countDown(n - 1); // 引数が必ず小さくなる方向に更新
}
```
再帰呼び出しのたびに、基底条件へ向かって問題(引数)が確実に縮小することを保証する構造。縮小方向の逆転や停滞があると停止性が崩れる。
**使いどころ**: すべての再帰関数で、再帰呼び出しの引数が「基底条件に近づく方向」に変化しているかを設計・レビューの両方で確認する場面に用いる。
**組合せ例**: KNOW-F-0175(再帰の限界と反復への書き換え判断基準)は、縮小保証が成り立っていても縮小の刻み幅が小さすぎる(深さが大きくなりすぎる)場合の対処を扱う。
**確認事項(罠と検算)**: `countDown(n - 1)`を`countDown(n)`や`countDown(n + 1)`と書き誤ると引数が縮小せず無限再帰になる。再帰呼び出しの引数と元の引数を並べて「必ず基底条件に近づいているか」を機械的に検算する。
---
### KNOW-F-0153 末尾再帰(tail recursion)
**構造の型**:
```js
function factorialTail(n, acc = 1) {
if (n === 0) return acc; // 基底条件で累積値を返す
return factorialTail(n - 1, n * acc); // 呼び出しが関数の最後の操作
}
```
再帰呼び出しが関数本体の「最後に行う操作そのもの」であり、戻り値に対してそれ以上の計算を行わない構造。呼び出し元のスタックフレームを再利用できる形。
**使いどころ**: 深い再帰が想定される処理で、スタックオーバーフローのリスクを減らしたい場面に用いる。ただしJavaScriptの主要処理系は末尾呼び出し最適化(TCO)を実装していない点に注意。
**組合せ例**: KNOW-F-0151(factorialの通常再帰)と本項目を並べて示すことで、「計算の途中経過を引数(アキュムレータ)として持ち歩く」という書き換え技法が明確になる。KNOW-F-0164(再帰→ループ変換)への中間形として使う。
**確認事項(罠と検算)**: 「末尾再帰の形で書いた」だけではJavaScriptでは深い再帰のスタック消費は解消されない(処理系がTCOを実装していないため)。実際にスタックオーバーフローが解消されるかは対象の処理系で実測して検算する必要がある。
---
### KNOW-F-0154 相互再帰(mutual recursion)
**構造の型**:
```js
function isEven(n) { return n === 0 ? true : isOdd(n - 1); }
function isOdd(n) { return n === 0 ? false : isEven(n - 1); }
```
2つ(以上)の関数が互いを呼び合うことで1つの再帰構造を成す構造。それぞれの関数単体では止まって見えなくても、2つ合わせて基底条件へ縮小していく。
**使いどころ**: 文法規則が複数の非終端記号を相互参照する構文解析(式→項→因子→式…)、状態が2種類を行き来する処理に用いる。
**組合せ例**: KNOW-F-0213(相互再帰→状態機械変換)は、本項目の相互呼び出し関係を状態遷移表に書き換える技法として対になる。
**確認事項(罠と検算)**: isEvenとisOddのどちらか一方だけを見ても停止性が判断できない罠がある。両関数をまとめて「引数が1回の呼び出しで必ず1減る」ことを確認しないと、無限再帰を見落とす。
---
### KNOW-F-0155 木構造再帰(tree recursion)
**構造の型**:
```js
function fib(n) {
if (n <= 1) return n;
return fib(n - 1) + fib(n - 2); // 1回の呼び出しから2つの再帰呼び出しが分岐
}
```
1回の呼び出しの中で複数回の再帰呼び出しを行い、呼び出し木が枝分かれしていく構造。
**使いどころ**: フィボナッチ数列、二分木の走査、部分集合の列挙など、問題が複数の部分問題に自然に分岐する場合に用いる。
**組合せ例**: 素朴な木構造再帰は同じ部分問題を何度も再計算する(fib(2)が何度も呼ばれる)ため、KNOW-F-0159(メモ化再帰)と組み合わせて指数的な計算量を線形に落とすのが定石になる。
**確認事項(罠と検算)**: fib(n)は素朴な実装だとおよそ2^n回呼び出される指数爆発を起こす。n=30程度でも実行時間が体感できるほど遅くなることを、KNOW-F-0165(二重再帰の指数爆発の罠)として実測で検算する。
---
### KNOW-F-0156 線形再帰(linear recursion)
**構造の型**:
```js
function sumTo(n) {
if (n === 0) return 0;
return n + sumTo(n - 1); // 1回の呼び出しから再帰呼び出しは1回だけ
}
```
1回の呼び出しにつき再帰呼び出しが高々1回しか発生しない構造。呼び出し木が1本の鎖になる。
**使いどころ**: 単純な集計・単方向リストの走査など、問題を1つずつ確実に減らしていく処理に用いる。
**組合せ例**: KNOW-F-0155(木構造再帰)と対比させ、「再帰呼び出しが何回に分岐するか」で計算量の見積もり(線形かO(2^n)か)が変わることを示す教材として使う。
**確認事項(罠と検算)**: 線形再帰でも入力nが大きいとコールスタックの深さがnに比例して積み上がる。ブラウザ等でのスタック上限(数千〜数万段程度が目安)を超えないかを、想定される最大nで見積もり検算する。
---
### KNOW-F-0157 分割統治再帰(divide and conquer)
**構造の型**:
```js
function mergeSort(arr) {
if (arr.length <= 1) return arr; // 基底条件
const mid = Math.floor(arr.length / 2);
const left = mergeSort(arr.slice(0, mid)); // 部分問題1
const right = mergeSort(arr.slice(mid)); // 部分問題2
return merge(left, right); // 統合
}
```
問題を複数の独立した部分問題に分割し、それぞれを再帰的に解いてから結果を統合する構造。
**使いどころ**: マージソート・クイックソート・二分探索など、分割して解いた方が全体を一度に解くより効率的な処理に用いる。
**組合せ例**: KNOW-F-0155(木構造再帰)の特殊形であり、統合(merge)ステップの計算量が全体の計算量を決める点がKNOW-F-0249(分岐削減とパフォーマンスのトレードオフ)の判断材料になる。
**確認事項(罠と検算)**: 分割の境界(mid)の計算を誤ると、部分問題が元の配列と同じ大きさのまま(縮小しない)になり無限再帰になる。arr.length<=1で止まり、それ以外では必ずleft.length<arr.lengthかつright.length<arr.lengthであることを検算する。
---
### KNOW-F-0158 累積引数(アキュムレータ渡し)再帰
**構造の型**:
```js
function reverseAcc(arr, acc = []) {
if (arr.length === 0) return acc;
const [head, ...rest] = arr;
return reverseAcc(rest, [head, ...acc]); // 結果を引数として持ち歩く
}
```
計算の途中結果を戻り値の合成ではなく、追加の引数(アキュムレータ)として次の呼び出しに渡していく構造。
**使いどころ**: KNOW-F-0153(末尾再帰)の形に書き換えたい場合の下準備として用いる。戻ってきた値をさらに加工する必要がなくなり、末尾呼び出しにしやすくなる。
**組合せ例**: KNOW-F-0151の素朴な再帰(戻り値に対してn*factorial(n-1)という後処理がある)と比較し、後処理をなくして末尾再帰(KNOW-F-0153)に変換する橋渡しとして教える。
**確認事項(罠と検算)**: アキュムレータの初期値が単位元でない(KNOW-F-0135と同種の罠)と最終結果がずれる。空配列を渡したときaccがそのまま返るかを検算する。
---
### KNOW-F-0159 メモ化再帰(memoization)
**構造の型**:
```js
function fibMemo(n, cache = new Map()) {
if (n <= 1) return n;
if (cache.has(n)) return cache.get(n); // 既計算なら再利用
const result = fibMemo(n - 1, cache) + fibMemo(n - 2, cache);
cache.set(n, result);
return result;
}
```
過去に計算した(引数, 結果)の組を記録しておき、同じ引数で再度呼ばれたら再計算せずに記録を返す構造。
**使いどころ**: KNOW-F-0155(木構造再帰)で同じ部分問題が繰り返し計算される場合(フィボナッチ、動的計画法で解ける問題全般)に用いる。
**組合せ例**: KNOW-F-0155の素朴なfib(n)がO(2^n)であるのに対し、本項目はO(n)まで落ちる。同じ入力に対する出力が変わらない(参照透過)ことがメモ化の前提であり、CATALOG設計書の「段落独立請求項」が参照する冪等性の考え方そのものである。
**確認事項(罠と検算)**: キャッシュのキー(この例ではn)が引数の一部しか捉えていないと、異なる呼び出しの結果を誤って共有してしまう罠がある。引数が複数ある関数では、全引数の組をキーにしているかを検算する。
---
### KNOW-F-0160 再帰の深さ制限とスタックオーバーフロー対策
**構造の型**:
```js
function safeRecurse(n, depth = 0, maxDepth = 10000) {
if (depth > maxDepth) throw new Error("再帰が深すぎます");
if (n === 0) return 0;
return 1 + safeRecurse(n - 1, depth + 1, maxDepth);
}
```
再帰の深さを引数として明示的に追跡し、想定外に深くなった時点で例外として検出可能にする構造。
**使いどころ**: 外部入力によって再帰の深さが決まる処理(ユーザー入力のパース、ネストしたJSONの走査)で、悪意ある入力や想定外の深いネストからの防御として用いる。
**組合せ例**: KNOW-F-0164(再帰→ループ変換)は、そもそも深さ制限が問題になる場面での根本対策であり、本項目は「変換するまでの当座の防御線」として位置づけられる。
**確認事項(罠と検算)**: maxDepthを実行環境のスタック上限より大きく設定すると、例外が発生する前に本物のスタックオーバーフローが起きてしまう。実行環境で実測したスタック上限より十分小さい値を設定したかを検算する。
---
### KNOW-F-0161 再帰→ループ変換(明示スタック化)
**構造の型**:
```js
function traverseIterative(root) {
const stack = [root]; // 呼び出しスタックを自前の配列で表現
while (stack.length > 0) {
const node = stack.pop();
visit(node);
for (const child of node.children) stack.push(child);
}
}
```
言語ランタイムのコールスタックが暗黙に行っていた「戻る場所を積んで戻る」という処理を、自前のスタック(配列)で明示的に再現する構造。
**使いどころ**: 再帰の深さが実行環境のスタック上限を超える恐れがある処理(巨大な木・巨大なネスト構造の走査)を安全に書き換えたい場合に用いる。
**組合せ例**: KNOW-F-0155(木構造再帰)の走査順(この例では深さ優先)を保ったまま、KNOW-F-0160(深さ制限)が問題になる場面での根本的な解決策として使う。
**確認事項(罠と検算)**: stack.push(child)の順序を誤ると、再帰版と走査順序が変わってしまう(木の子要素を左から積むか右から積むかで訪問順が反転する)。小さな木で再帰版と反復版の訪問順を突き合わせて検算する。
---
### KNOW-F-0162 再帰→ループ変換(末尾再帰の反復化)
**構造の型**:
```js
// 末尾再帰版 factorialTail(n, acc) を機械的にループへ書き換える
function factorialLoop(n) {
let acc = 1;
while (n !== 0) { // 基底条件の否定がそのままループ継続条件になる
acc = n * acc; // 再帰ステップの引数更新がそのまま代入になる
n = n - 1;
}
return acc;
}
```
末尾再帰(KNOW-F-0153)の「引数の更新」をループの変数代入に、「基底条件」をループの終了条件にそのまま置き換える機械的な変換手順。
**使いどころ**: 末尾再帰の形にできた処理を、TCOが保証されない実行環境(KNOW-F-0153参照)で安全に動かすために、最終的にループへ変換したい場面で用いる。
**組合せ例**: KNOW-F-0158(累積引数再帰)→KNOW-F-0153(末尾再帰化)→本項目(ループ化)という3段階の変換系列が、再帰をループへ書き換える標準手順になる。
**確認事項(罠と検算)**: 基底条件`n === 0`を否定した継続条件`n !== 0`が正しく対応しているかを取り違えやすい(否定の向きを誤ると1回多い/少ないの罠になる)。n=0, n=1で再帰版とループ版の出力を突き合わせて検算する。
---
### KNOW-F-0163 相互再帰→状態機械変換
**構造の型**:
```js
// isEven/isOddの相互再帰を状態機械化
function isEvenIter(n) {
let state = "EVEN";
for (let i = 0; i < n; i++) {
state = (state === "EVEN") ? "ODD" : "EVEN"; // 呼び出し先の切り替えを状態遷移に
}
return state === "EVEN";
}
```
相互再帰(KNOW-F-0154)における「どちらの関数を呼んでいるか」を、明示的な状態変数の遷移として書き換える構造。
**使いどころ**: 相互再帰がKNOW-F-0160の深さ問題を起こす場合、または状態遷移表(KNOW-F-0201)として明文化したい場合に用いる。
**組合せ例**: 第四章のKNOW-F-0201(状態遷移表の設計)・KNOW-F-0202(FSM実装)へ直結する変換であり、「相互再帰は状態が2つの状態機械の特殊形である」という見方を与える。
**確認事項(罠と検算)**: 相互再帰の呼び出し先が3つ以上ある場合、状態変数も3値以上に拡張する必要がある。変換前後で同じnに対して同じ真偽が返るかを複数のnで検算する。
---
### KNOW-F-0164 二重再帰(フィボナッチ型、指数爆発の罠)
**構造の型**:
```js
// 悪い例: fib(n)は約2^n回呼ばれる
function fibNaive(n) {
if (n <= 1) return n;
return fibNaive(n - 1) + fibNaive(n - 2);
}
```
1回の呼び出しから2回の再帰呼び出しが発生し、かつ部分問題が重複する構造。木構造再帰(KNOW-F-0155)のうち特に計算量が問題になる代表例として独立項目化している。
**使いどころ**: 「なぜメモ化(KNOW-F-0159)が必要になるのか」を数値で実感させる教材として用いる。実運用コードでは基本的にこの素朴な形のまま使わない。
**組合せ例**: KNOW-F-0159(メモ化)またはKNOW-F-0157的な動的計画法への書き換えとセットで扱う。fib(35)程度で実行時間の違いを実測すると効果が体感しやすい。
**確認事項(罠と検算)**: fib(n)の呼び出し回数はおよそ2×fib(n+1)−1に近似される。n=10で約177回、n=30で数百万回に達することを、実際にカウンタを仕込んで実測し「見た目は素直でも計算量は別問題」であることを検算する。
---
### KNOW-F-0165 再帰下降パーサ型再帰
**構造の型**:
```js
// 文法: expr := term (('+'|'-') term)*
function parseExpr(tokens, pos) {
let [left, p1] = parseTerm(tokens, pos);
while (tokens[p1] === '+' || tokens[p1] === '-') {
const op = tokens[p1];
const [right, p2] = parseTerm(tokens, p1 + 1);
left = { op, left, right };
p1 = p2;
}
return [left, p1];
}
```
文法規則(非終端記号)ごとに1つの関数を対応させ、規則の中で別の規則を再帰的に呼び出して構文木を組み立てる構造。
**使いどころ**: 数式パーサ・設定ファイルパーサ・簡易言語のインタプリタなど、文法規則が階層的に定義されている構文解析全般に用いる。
**組合せ例**: KNOW-F-0154(相互再帰)の典型的な応用例であり、KNOW-F-0144(先読み付きループ)がトークンの先読み判定部分で頻繁に使われる。
**確認事項(罠と検算)**: 位置pos(またはp1)の更新を戻し忘れると、無限ループや誤った構文木の罠になる。各parse関数が「消費したトークン数だけ位置を進めて返す」契約を守っているかをKNOW-F-0169(再帰関数の契約)として検算する。
---
### KNOW-F-0166 バックトラッキング再帰
**構造の型**:
```js
function solve(board, pos) {
if (pos === board.length) return true; // 基底条件: 全て埋まった
for (const candidate of candidates(board, pos)) {
board[pos] = candidate; // 選択
if (solve(board, pos + 1)) return true; // 先に進んでみる
board[pos] = null; // 選択を取り消す(バックトラック)
}
return false; // どの候補でも解けなかった
}
```
候補を1つ選んで先に進み、行き詰まったら選択を取り消して別の候補を試す構造。「選択→再帰→取り消し」の3手順が骨格になる。
**使いどころ**: 数独・迷路探索・組合せ最適化など、正解に至るまで試行錯誤が必要で、失敗したら手前まで戻ってやり直す必要がある問題に用いる。
**組合せ例**: KNOW-F-0155(木構造再帰)の探索木を、行き詰まった枝を刈り取りながら辿る形に特化したものである。KNOW-F-0246(データ正規化)で候補数を事前に絞ると探索が速くなる。
**確認事項(罠と検算)**: 「取り消し(board[pos] = null)」を書き忘れると、前の選択の痕跡が残ったまま次の候補を試してしまい、誤った解や見落としの罠になる。選択と取り消しが必ず対になっているかをコード上で目視検算する。
---
### KNOW-F-0167 CPS(継続渡しスタイル)再帰
**構造の型**:
```js
function factorialCPS(n, cont) {
if (n === 0) return cont(1); // 基底条件でも継続を呼ぶ
return factorialCPS(n - 1, (result) => cont(n * result));
}
factorialCPS(5, (answer) => console.log(answer)); // => 120
```
「計算が終わったら次に何をするか」を関数(継続)として引数に渡し、returnで値を返す代わりに継続を呼び出して結果を渡す構造。
**使いどころ**: 非同期処理のコールバック設計、末尾呼び出しを徹底したい場面(継続渡しにすると理論上すべての呼び出しが末尾呼び出しになる)の理解に用いる。
**組合せ例**: KNOW-F-0153(末尾再帰)を極限まで推し進めた形であり、Promiseベースの非同期処理(目録III・データ変換の型で詳述)の考え方の土台になる。
**確認事項(罠と検算)**: 継続contを呼び忘れると計算結果がどこにも渡らず処理が「消える」罠がある。すべての分岐(基底条件・再帰ステップ)で必ずcontが1回だけ呼ばれているかを検算する。
---
### KNOW-F-0168 再帰の入力サイズ検証(不変条件)
**構造の型**:
```js
function power(base, exp) {
if (exp < 0) throw new RangeError("expは0以上"); // 事前条件の検証
if (exp === 0) return 1;
return base * power(base, exp - 1);
}
```
再帰を始める前に、入力がそもそも再帰の前提(縮小して基底条件に到達できる範囲)を満たしているかを検証する構造。
**使いどころ**: 外部から渡される引数が負値や想定外の型になりうる公開APIの入口で、再帰に入る前に前提を確定させたい場面に用いる。
**組合せ例**: KNOW-F-0176(ガード節)の考え方を再帰関数の入口に適用したものであり、KNOW-F-0169(再帰関数の契約)の事前条件部分に対応する。
**確認事項(罠と検算)**: 検証を再帰関数の内部(毎回の呼び出し)に書くと、再帰のたびに同じ検証を繰り返す無駄が生じる。外側にラッパー関数を1つ置き、検証を1回だけ行ってから内部の再帰関数を呼ぶ構成になっているかを検算する。
---
### KNOW-F-0169 再帰関数の契約(事前条件/事後条件)
**構造の型**:
```js
/**
* 事前条件: n は 0以上の整数
* 事後条件: 戻り値は n! (nの階乗)、n=0のとき1
*/
function factorial(n) {
if (n === 0) return 1;
return n * factorial(n - 1);
}
```
関数の振る舞いを「呼び出し前に何が成り立っているべきか(事前条件)」と「呼び出し後に何が保証されるか(事後条件)」の2文で明文化する構造。
**使いどころ**: チームで再帰関数をレビューする場面、再帰の正しさを帰納法的に説明する場面(基底条件で事後条件が成立し、再帰ステップで事後条件が保たれることを示す)で用いる。
**組合せ例**: KNOW-F-0170(帰納的正当性の確認)は、本項目の契約を実際に基底条件・再帰ステップの両方で検証する手順そのものである。
**確認事項(罠と検算)**: 契約をコメントに書いただけで満足し、実際のコードが契約通りかを確認しない罠がある。n=0(基底条件)とn=3程度(再帰ステップを複数回通る値)の両方で事後条件(n!の値)が成立するかを実測で検算する。
---
### KNOW-F-0170 再帰と不変条件を使った帰納的正当性の確認
**構造の型**:
```js
// 主張: sumTo(n) === n*(n+1)/2 (すべての n>=0 で)
// 基底: sumTo(0) = 0 = 0*(0+1)/2 … 成立
// 帰納: sumTo(n) = n + sumTo(n-1)
// = n + (n-1)*n/2 (帰納法の仮定)
// = n*(n+2)/2 ... ではなく n*(n+1)/2 に整理できることを検算する
```
再帰関数の正しさを、数学的帰納法と同じ形(基底条件で成立を確認し、n-1で成立すると仮定してnでも成立することを示す)で検証する構造。
**使いどころ**: 再帰関数がテストケースをいくつか通っただけでは不安が残る場面、閉じた式(一般項)との一致を保証したい場面に用いる。
**組合せ例**: KNOW-F-0169(再帰関数の契約)の事後条件を、全ての入力に対して機械的に検証する代わりに、数学的な議論で保証する手段として対になる。KNOW-F-0033(BOOK-0184、漸化式と閉形式の一致検算)の技法と同型である。
**確認事項(罠と検算)**: 帰納法の「帰納ステップ」の代数展開を誤ると、成立しない式を成立すると誤認する罠がある。展開の各行を具体的な数値(n=3など)で置き換えて実測検算し、代数と数値の両方が一致することを確かめる。
---
### KNOW-F-0171 相互再帰の停止性証明の型
**構造の型**:
```js
// isEven(n)/isOdd(n)の停止性: 各呼び出しでnが1減り、
// n=0で必ずどちらかの基底条件に到達する(nは有限の非負整数)
```
相互再帰(KNOW-F-0154)全体を1つの「減少する量(この例ではn)」に注目して、有限回で必ず基底条件に到達することを示す構造。
**使いどころ**: 相互に呼び合う関数群が本当に止まるかを疑うとき、個々の関数でなく相互再帰全体を1つの単位として停止性を議論したい場合に用いる。
**組合せ例**: KNOW-F-0152(再帰ステップの縮小保証)を相互再帰に拡張したものであり、KNOW-F-0165(再帰下降パーサ型再帰)の停止性(消費するトークン数が必ず減ること)を保証する際にも同じ型を使う。
**確認事項(罠と検算)**: 「減少する量」を誤って選ぶと(例えば実際には減らない変数に注目してしまうと)、無限再帰を停止すると誤判定する罠がある。減少する量が整数値で下限(通常0)を持つことを明示し、実際に1回の呼び出しで狭義に減っているかを検算する。
---
### KNOW-F-0172 再帰による木の走査(前順・中順・後順)
**構造の型**:
```js
function preorder(node, out = []) {
if (node === null) return out; // 基底条件
out.push(node.value); // 訪問(前順=最初)
preorder(node.left, out);
preorder(node.right, out);
return out;
}
```
訪問(node.valueを記録する処理)を左右の子への再帰呼び出しの前・間・後のどこに置くかで、前順・中順・後順の3通りの走査順を切り替える構造。
**使いどころ**: 二分探索木の整列出力(中順)、式木の構築(後順で子から評価)、ディレクトリツリーの表示(前順)など、木構造の走査全般に用いる。
**組合せ例**: KNOW-F-0161(再帰→ループ変換、明示スタック化)は、この3種の走査順を保ったまま反復版に書き換える際の土台になる。訪問処理の位置を変えるだけで3種を作り分けられる点がKNOW-F-0250(分岐削減リファクタリング)にも通じる設計の一貫性の例になる。
**確認事項(罠と検算)**: 訪問処理を左右どちらの再帰呼び出しの前に置くかで結果が変わる。小さな3ノードの木(根・左・右)で前順・中順・後順それぞれの出力を手計算し、コードの出力と突き合わせて検算する。
---
### KNOW-F-0173 再帰によるグラフ探索(深さ優先)
**構造の型**:
```js
function dfs(node, visited = new Set()) {
if (visited.has(node)) return; // 既訪問なら何もしない(サイクル対策)
visited.add(node);
for (const neighbor of node.neighbors) dfs(neighbor, visited);
}
```
訪問済み集合(visited)を持ち歩きながら、隣接ノードへ再帰的に潜っていく構造。木と異なりグラフには閉路(サイクル)がありうるため、訪問済みチェックが基底条件の役割を兼ねる。
**使いどころ**: 連結成分の検出、経路の存在確認、依存関係の解決(トポロジカルソートの土台)に用いる。
**組合せ例**: KNOW-F-0172(木の走査)のvisited管理を追加した一般化であり、KNOW-F-0161(明示スタック化)に書き換える際はvisitedの更新タイミング(pushの前かpopの後か)がKNOW-F-0161以上に重要になる。
**確認事項(罠と検算)**: visited.has(node)のチェックを忘れると、閉路のあるグラフで無限再帰に陥る罠がある。木構造再帰(KNOW-F-0155)からグラフ探索に一般化する際、visitedチェックを最初に追加したかを最優先で検算する。
---
### KNOW-F-0174 再帰の末尾呼び出し最適化非対応言語での回避策
**構造の型**:
```js
// トランポリン: 再帰呼び出しの代わりに「次にすべき処理」を返し、
// 外側のループで順に実行してスタックを使い切らないようにする
function trampoline(fn) {
let result = fn();
while (typeof result === "function") result = result();
return result;
}
function factorialTramp(n, acc = 1) {
if (n === 0) return acc;
return () => factorialTramp(n - 1, n * acc); // 呼び出しを遅延させて返す
}
```
末尾再帰(KNOW-F-0153)をそのまま呼び出すのではなく「次に呼ぶべき処理」を関数として返し、外側の1本のループ(トランポリン)がそれを順に実行することでコールスタックを深くしない構造。
**使いどころ**: JavaScriptのようにTCOが保証されない言語で、それでも再帰的な書き味を保ったまま深い再帰を安全に実行したい場合に用いる。
**組合せ例**: KNOW-F-0153(末尾再帰)とKNOW-F-0162(ループへの機械的変換)の中間に位置する技法であり、再帰の見た目を保ちながらKNOW-F-0160(深さ制限)の問題を回避する第三の選択肢になる。
**確認事項(罠と検算)**: 関数を「即座に呼ぶ」のか「呼ばずに関数のまま返す」のかを取り違えると、トランポリンが機能せず通常の再帰に戻ってしまう。返す値がtypeof "function"であることをwhileループが正しく検出しているかを検算する。
---
### KNOW-F-0175 再帰の限界と反復への書き換え判断基準
**構造の型**:
```js
// 判断チェックリスト(擬似コード)
// 1. 想定最大入力サイズでの再帰の深さは? (スタック上限の目安と比較)
// 2. 実行環境はTCOを持つか?(持たなければ末尾再帰でも深さ問題は残る)
// 3. 木構造・グラフ構造など、そもそもループで自然に書けない構造か?
// 4. 可読性(再帰の方が分かりやすいか)とのトレードオフは?
```
再帰のまま残すか、ループ(KNOW-F-0161/0162)やトランポリン(KNOW-F-0174)に書き換えるかを、深さ・実行環境・構造の性質・可読性の4観点で判断する手順そのものを型として扱う。
**使いどころ**: 第一章(ループの型)と第二章(再帰の型)を学び終えた後、実際の設計判断でどちらを選ぶかを決める最終チェックポイントとして用いる。
**組合せ例**: KNOW-F-0150(ループから再帰への書き換え可能性チェック)と対をなし、両方向の変換可能性を確認したうえで最終判断を下す。線形再帰(KNOW-F-0156)はほぼ常にループへ書き換えられるが、木構造再帰(KNOW-F-0155)は明示スタック化(KNOW-F-0161)を要する。
**確認事項(罠と検算)**: 「再帰の方が短く書けるから」という理由だけで深さの見積もりを省略する罠がある。想定最大入力での再帰の深さを実際に計算(またはテストで実測)し、実行環境のスタック上限の余裕(目安として上限の半分以下)に収まるかを検算してから最終判断する。
## 第三章 早期returnとガード節——ネストを削る技(KNOW-F-0176〜0200)
本章では、深いif-elseの入れ子を避け、前提条件を満たさない場合に関数の先頭で即座に処理を打ち切る「早期return」「ガード節」の構造パターン25種を扱う。目的は一貫して「本体のインデントを1段でも浅く保ち、正常系の処理だけを主軸として読めるようにする」ことにある。
---
### KNOW-F-0176 ガード節による前提条件の早期棄却
**構造の型**:
```js
function withdraw(account, amount) {
if (amount <= 0) return { ok: false, reason: "amountは正でなければならない" };
if (account.balance < amount) return { ok: false, reason: "残高不足" };
account.balance -= amount;
return { ok: true };
}
```
関数の先頭で満たすべき前提条件を1つずつ判定し、満たさなければその場で処理を打ち切って戻る構造。以降の本体は「すべての前提を満たした後」の正常系だけを書けばよくなる。
**使いどころ**: 入力検証・権限確認・状態確認など、本処理に入る前に確認すべき条件が複数ある関数全般に用いる。
**組合せ例**: KNOW-F-0177(早期returnによるネスト削減)と本項目は同じ構造の別名に近く、ガード節は「前提条件の判定」に、早期returnは「その結果としての制御構造」に焦点を当てた呼び方の違いである。
**確認事項(罠と検算)**: ガード節の順序を誤ると、本来先に検出すべきエラー(例えば「amountが不正」)より後のガード節(「残高不足」)が先に評価されてしまうことがある。境界値(amount=0, account.balance=amount)でどのガード節が反応するかを検算する。
---
### KNOW-F-0177 早期returnによるネスト削減
**構造の型**:
```js
// Before: ネストが深い
function process(x) {
if (x !== null) {
if (x.isValid) {
return doWork(x);
}
}
return null;
}
// After: 早期returnでネストを解消
function process(x) {
if (x === null) return null;
if (!x.isValid) return null;
return doWork(x);
}
```
「条件が満たされないなら即座に抜ける」形に書き換えることで、if文の入れ子(ネスト)を1段の連続に平坦化する構造。
**使いどころ**: if-elseの入れ子が3段以上になり、正常系の処理がインデントの奥に埋もれてしまった関数のリファクタリングに用いる。
**組合せ例**: KNOW-F-0196(ネストifの平坦化)は本項目を複数のif文に体系的に適用する手順として対になる。KNOW-F-0138(continueによる読み飛ばし)はループ内での同じ発想の適用形である。
**確認事項(罠と検算)**: Before/Afterで分岐条件の否定を取り違えると挙動が変わる(`x.isValid`の否定は`!x.isValid`であって`x.isValid === false`だけでなくfalsy値全般を含む点に注意)。両方の版に同じ入力を通し、出力が完全に一致するかを検算する。
---
### KNOW-F-0178 nullチェックの早期return
**構造の型**:
```js
function getUserName(user) {
if (user == null) return "(不明)"; // null/undefinedの両方を1回で弾く
return user.name;
}
```
null(またはundefined)である可能性がある値を関数の先頭で確認し、以降の本体ではnullでないことを前提に書ける構造。
**使いどころ**: 外部から渡されるオブジェクトがnullを取りうる場合、プロパティアクセスの直前に必ず1回だけチェックしたい場面に用いる。
**組合せ例**: KNOW-F-0195(Null Objectパターンによるガード節省略)は、そもそもnullを渡さない設計にすることで本項目のチェック自体を不要にする、より根本的な対策として対になる。
**確認事項(罠と検算)**: `== null`は`null`と`undefined`の両方に一致するがdouble equalsの他の緩い変換(0や""との一致)は起きない、という挙動を正しく理解しているかが罠になる。`=== null`(片方だけ)にした場合との違いをundefinedを渡すテストで検算する。
---
### KNOW-F-0179 範囲外入力の早期return
**構造の型**:
```js
function getElement(array, index) {
if (index < 0 || index >= array.length) return undefined; // 範囲外は先に弾く
return array[index];
}
```
配列の添字・数値の範囲など、「有効な範囲」を関数の先頭で確認し、範囲外なら本処理に入らず即座に戻る構造。
**使いどころ**: 添字アクセス・スライス処理・ページネーションなど、範囲外アクセスが例外やundefinedの伝播を招きうる箇所全般に用いる。
**組合せ例**: KNOW-F-0132(オフバイワン回避)で確定した境界(0からlength-1)を、そのままガード節の条件式に転記する形で接続する。
**確認事項(罠と検算)**: `index < 0`だけを見て`index >= array.length`のチェックを忘れると、配列末尾を超えた添字がundefinedとして静かに通り抜けてしまう。境界値(index=-1, index=array.length, index=array.length-1)の3点で検算する。
---
### KNOW-F-0180 エラーコードの早期return
**構造の型**:
```js
function parseConfig(text) {
if (text.trim() === "") return { code: "EMPTY", value: null };
const parsed = tryParse(text);
if (parsed === null) return { code: "PARSE_ERROR", value: null };
return { code: "OK", value: parsed };
}
```
例外を投げる代わりに、成功/失敗を判別できるコード付きの戻り値を早期returnで返す構造。呼び出し側は戻り値のcodeを見て分岐する。
**使いどころ**: 失敗が「異常」ではなく「起こりうる正常系の一部」である処理(入力検証、パース処理)で、例外よりも軽量に失敗を扱いたい場合に用いる。
**組合せ例**: KNOW-F-0182(早期returnと戻り値の型統一)は、本項目の{code, value}のような形をすべての早期return箇所で揃える規律として対になる。KNOW-F-0183(早期returnと例外処理の使い分け基準)で例外を使うべき場面との線引きを扱う。
**確認事項(罠と検算)**: codeの値の綴りをどこかで打ち間違えると(例えば"PARS_ERROR")、呼び出し側の分岐が静かに一致しなくなる罠がある。想定するcodeの一覧を定数として1箇所にまとめ、typoを構造的に防いだかを検算する。
---
### KNOW-F-0181 例外による早期脱出(throw)
**構造の型**:
```js
function requireAuth(session) {
if (!session.isAuthenticated) throw new Error("未認証です");
return session.user;
}
```
戻り値による分岐ではなく、前提条件を満たさない場合に例外を投げて呼び出し元まで一気に制御を戻す構造。
**使いどころ**: 前提条件が満たされないこと自体が「呼び出し側のバグ」に近い場合(契約違反)、あるいは深い呼び出し階層を一気に抜けたい場合に用いる。
**組合せ例**: KNOW-F-0180(エラーコードの早期return)と対になる選択肢であり、KNOW-F-0183(使い分け基準)がどちらを選ぶかの判断軸を提供する。
**確認事項(罠と検算)**: 例外を投げっぱなしにして呼び出し側がcatchしていないと、プログラム全体が停止する罠がある。例外を投げる関数を呼ぶ側で、想定される例外の種類をKNOW-F-0189(早期returnのテスト網羅)と同様に洗い出したかを検算する。
---
### KNOW-F-0182 Optional/Maybe型による早期return代替
**構造の型**:
```js
// Optional的な戻り値(nullの代わりに明示的な「値なし」を表す)
function findUser(id, users) {
const found = users.find(u => u.id === id);
return found ?? { present: false };
}
function useUser(result) {
if (!("name" in result)) return "見つかりません"; // 早期return
return result.name;
}
```
「値がある/ない」をnullやundefinedではなく、専用の構造(タグ付きオブジェクトやOptional型)として表現し、呼び出し側にその確認を強制する構造。
**使いどころ**: nullチェック漏れ(KNOW-F-0178の罠)を型システムやコードの構造レベルで防ぎたい場合に用いる。TypeScript環境ではUser | undefinedのような合併型として自然に表現できる。
**組合せ例**: KNOW-F-0178(nullチェックの早期return)を土台に、「チェックを忘れられない形」に構造化したものとして進化系に位置づけられる。
**確認事項(罠と検算)**: 「present: false」のようなタグの付け忘れがあると、単なるオブジェクトとの区別がつかなくなる。findUserがOptional形式の値を返すすべての経路で、タグの有無が一貫しているかを検算する。
---
### KNOW-F-0183 複数ガード節の順序設計(コスト順)
**構造の型**:
```js
function validate(request) {
if (request === null) return "ERR_NULL"; // 最も軽い・最も基本的な検査を先に
if (!request.userId) return "ERR_NO_USER";
if (!isRateLimitOk(request.userId)) return "ERR_RATE_LIMIT"; // 外部問い合わせを伴う重い検査は後に
return "OK";
}
```
複数のガード節を、判定コストの軽いものから重いもの(データベース照会やAPI呼び出しを伴うもの)へと順序づける構造。
**使いどころ**: ガード節の中に外部リソースへの問い合わせが混在する関数で、無駄な高コスト処理を避けたい場合に用いる。
**組合せ例**: KNOW-F-0176(ガード節の基本形)に、性能面の配慮を加えたものである。KNOW-F-0189(全ガードに1テスト)と組み合わせ、順序を変えてもテストが全て通ることを確認する。
**確認事項(罠と検算)**: コストの軽重だけを優先し、論理的な依存関係(userIdが無い状態でisRateLimitOkを呼ぶとエラーになる、等)を無視すると順序を誤る。依存関係のある検査は依存元を先に置いたかを検算する。
---
### KNOW-F-0184 ガード節と条件式の結合(短絡評価)
**構造の型**:
```js
function isEligible(user) {
return user != null && user.age >= 18 && user.hasConsent; // 短絡評価で連鎖判定
}
```
複数の条件を`&&`(または`||`)で1本につなぎ、左から順に評価して途中で確定した時点で残りを評価しない(短絡評価)性質を利用する構造。
**使いどころ**: 複数のガード節を、それぞれ別のif文で早期returnするほどではない軽い判定(真偽値を返すだけの判定関数)に用いる。
**組合せ例**: KNOW-F-0176(複数行のガード節)と等価な内容を1行に圧縮したものであり、条件が3つを超えて読みにくくなったら再びガード節形式(KNOW-F-0176)に展開し直す判断がKNOW-F-0250(分岐削減リファクタリング)につながる。
**確認事項(罠と検算)**: `user != null`を省略して`user.age`にいきなりアクセスすると、userがnullのときに例外になる。短絡評価の順序(nullチェックを最初に置く)を誤ると意味がなくなる点を、user=nullの入力で検算する。
---
### KNOW-F-0185 早期returnとロールバック処理の両立(finally)
**構造の型**:
```js
function withLock(resource, fn) {
resource.lock();
try {
if (!resource.isReady) return null; // ガード節でも
return fn(resource);
} finally {
resource.unlock(); // 早期returnを含むどの経路でも必ず実行される
}
}
```
ガード節や早期returnがあっても、後始末(ロックの解放、リソースの解放)を確実に行うため、finally節でその処理を1箇所にまとめる構造。
**使いどころ**: ロック・ファイルハンドル・トランザクションなど、正常終了でも早期returnでも例外でも必ず解放すべきリソースを扱う関数に用いる。
**組合せ例**: KNOW-F-0176(ガード節)を持つ関数がリソースを扱う場合、本項目のtry/finallyと組み合わせることがほぼ必須になる。KNOW-F-0181(例外による早期脱出)を伴う関数でも同様にfinallyが後始末を保証する。
**確認事項(罠と検算)**: finallyの中でreturnを書くと、try節やcatch節のreturn値やthrowされた例外を上書きしてしまう罠がある(finally内のreturnは最優先で採用される)。finally節ではreturnせず後始末だけに専念しているかを検算する。
---
### KNOW-F-0186 ガード節のテーブル化(前提条件表)
**構造の型**:
```js
const validators = [
{ check: r => r !== null, error: "ERR_NULL" },
{ check: r => !!r.userId, error: "ERR_NO_USER" },
{ check: r => r.amount > 0, error: "ERR_AMOUNT" },
];
function validate(request) {
for (const { check, error } of validators) {
if (!check(request)) return error; // ガード節の列をデータとして走査
}
return "OK";
}
```
個々のガード節を「条件関数+エラー」の組としてデータ(配列)にまとめ、ループで順に適用する構造。ガード節が増減してもif文を書き足す必要がなくなる。
**使いどころ**: ガード節の数が5個を超え、条件の追加・削除が頻繁に起きる関数で、if文の羅列を保守しやすいデータへ移したい場合に用いる。
**組合せ例**: 第五章KNOW-F-0226(テーブル駆動法)をガード節に特化して適用したものであり、KNOW-F-0189(全ガードに1テスト)はこのvalidators配列の各要素に対応するテストを1つずつ用意することに帰着する。
**確認事項(罠と検算)**: 配列の順序が実際の優先順位(KNOW-F-0183のコスト順)と一致しているかを、配列を並べ替えても結果が変わらないケースと変わるケースの両方で検算する。
---
### KNOW-F-0187 否定条件の反転による可読性改善(De Morgan活用)
**構造の型**:
```js
// 読みにくい: 否定の否定が重なる
if (!(x < 0 || y < 0)) { /* 両方0以上のときの処理 */ }
// De Morganの法則で書き換え: !(A || B) === !A && !B
if (x >= 0 && y >= 0) { /* 同じ条件をより素直に */ }
```
`!(A || B)`のような否定のかかった複合条件を、De Morganの法則(`!(A||B) = !A && !B`、`!(A&&B) = !A || !B`)で書き換え、否定を条件式の外側から内側の各項へ移す構造。
**使いどころ**: 条件式に否定と論理演算子が混在して読みにくくなった箇所、ガード節の条件を素直な肯定形/否定形に統一したい場合に用いる。
**組合せ例**: KNOW-F-0147(until型ループ)の条件変換と同じ代数法則を使っており、両者は「ループの継続条件」と「ガード節の否定条件」という別の場面での同じ技法の適用である。
**確認事項(罠と検算)**: `||`を`&&`に変えるときに否定を各項に配らないまま演算子だけ変えると(`!(A||B)`を`!A||!B`と誤変換する等)、論理的に等価でなくなる罠がある。真理値表(A, Bそれぞれ真偽2通り、計4通り)で変換前後が完全に一致するかを検算する。
---
### KNOW-F-0188 早期return関数の単一責任維持
**構造の型**:
```js
// 単一責任を保った早期return関数の例
function normalizeEmail(email) {
if (typeof email !== "string") return null;
const trimmed = email.trim().toLowerCase();
if (trimmed === "") return null;
return trimmed;
}
```
ガード節が増えても、関数全体が「1つのこと(この例ではメールアドレスの正規化)」だけを担い、検証と加工と副作用を混在させない構造。
**使いどころ**: ガード節を追加するたびに関数の責任範囲が広がっていないかを点検する、リファクタリングの節目で用いる。
**組合せ例**: KNOW-F-0176(ガード節の基本形)を使い続けた関数が肥大化してきたら、本項目の観点でKNOW-F-0224(責任連鎖パターン)などへ分割することを検討する。
**確認事項(罠と検算)**: 「ついでにログを出す」「ついでにキャッシュを更新する」のような副作用がガード節の途中に紛れ込むと単一責任が崩れる。関数名(normalizeEmail)が説明する範囲を超えた処理が本体にないかを1行ずつ確認して検算する。
---
### KNOW-F-0189 早期returnのテスト網羅(各ガードに1テスト)
**構造の型**:
```js
// テストの型(擬似コード)
test("amount<=0なら失敗", () => expect(withdraw(acc, 0).ok).toBe(false));
test("残高不足なら失敗", () => expect(withdraw(acc, 99999).ok).toBe(false));
test("正常な引き出しは成功", () => expect(withdraw(acc, 100).ok).toBe(true));
```
関数内の早期return(ガード節)1つにつき、それを踏む入力を1つ以上用意してテストする構造。ガード節の数とテストケースの数を対応づけて網羅漏れを防ぐ。
**使いどころ**: ガード節が3つ以上ある関数のテストを書く/レビューする場面で、「全ての分岐を1回は通したか」を機械的に確認したい場合に用いる。
**組合せ例**: KNOW-F-0202(状態機械のテスト、全遷移網羅)と同じ発想を早期return関数に適用したものであり、KNOW-F-0248(分岐カバレッジの検算)全般の一部に位置づけられる。
**確認事項(罠と検算)**: ガード節を1つ追加したのにテストを追加し忘れると、その分岐が未検証のまま残る罠がある。関数内のガード節の数を数え、対応するテストの数と一致するかをカバレッジツールまたは目視で検算する。
---
### KNOW-F-0190 ガード節チェーンのリファクタリング順序
**構造の型**:
```js
// 手順1: 最も外側のif-elseを早期returnに変換
// 手順2: 変換後、内側に残った入れ子を同様に変換(外側から内側へ)
// 手順3: 各段階でテスト(KNOW-F-0189)を実行し、挙動が変わっていないことを確認
```
深くネストしたif-else構造を早期returnへ書き換える際、外側のブロックから内側のブロックへと順番に、1段ずつ機械的に変換していく手順そのものを型として扱う。
**使いどころ**: 既存の複雑な条件分岐関数(3段以上のネスト)を、一度に全部書き換えず安全に段階的にリファクタリングしたい場合に用いる。
**組合せ例**: KNOW-F-0177(早期returnによるネスト削減)の適用先が複数段ある場合の、適用順序を定めた手順書に相当する。各段階でKNOW-F-0189のテストを流すことで、KNOW-F-0250(分岐削減リファクタリング手順)全体の「テスト→抽出→置換」サイクルの具体例になる。
**確認事項(罠と検算)**: 複数段を一気に書き換えると、途中でどこに挙動の変化が入り込んだか特定しづらくなる罠がある。1段変換するごとにテストを実行し、外側からの変換順を守ったかを各段階で検算する。
---
### KNOW-F-0191 早期return漏れ検出(deadコード検算)
**構造の型**:
```js
function classify(score) {
if (score >= 90) return "A";
if (score >= 70) return "B";
if (score >= 50) return "C";
return "D";
console.log("ここには絶対に到達しない"); // デッドコード(検出すべき)
}
```
すべての条件分岐に早期returnがあるにもかかわらず後続のコードが残っている、あるいは逆にreturnが漏れて意図しない値(undefined)が返ってしまう箇所を検出する構造。
**使いどころ**: リファクタリング後や生成コードのレビューで、到達不能なコードや戻り値の抜け漏れがないかを確認する場面に用いる。
**組合せ例**: 静的解析ツール(ESLintのno-unreachable等)がこの検算を自動化する。KNOW-F-0248(分岐カバレッジの検算)の一部として、テスト実行時のカバレッジレポートでも同種の漏れが検出できる。
**確認事項(罠と検算)**: 最後のガード節にreturnを書き忘れると、条件を1つも満たさなかった入力でundefinedが静かに返る罠がある。すべての入力パターン(この例ではscoreの4つの区間)に対応する戻り値があるかを表にして検算する。
---
### KNOW-F-0192 Null Objectパターンによるガード節省略
**構造の型**:
```js
const NULL_USER = { name: "(ゲスト)", isAdmin: false };
function getGreeting(user) {
const u = user ?? NULL_USER; // nullの代わりに「何もしないふるまいを持つ」オブジェクトを使う
return `こんにちは、${u.name}さん`; // nullチェックのガード節が不要になる
}
```
「値がない」ことをnullで表す代わりに、無害な既定のふるまいを持つ「空のオブジェクト」を用意し、呼び出し側でのnullチェック(ガード節)そのものを不要にする構造。
**使いどころ**: nullチェックのガード節(KNOW-F-0178)が至るところに散らばってしまった場合、その根本原因である「nullを返す設計」自体を見直したい場合に用いる。
**組合せ例**: KNOW-F-0178(nullチェックの早期return)を減らす代替設計として対になる。KNOW-F-0231(Nullオブジェクトとストラテジーの併用)は本項目をさらに多態(KNOW-F-0227)と組み合わせた発展形である。
**確認事項(罠と検算)**: NULL_USERの各プロパティが呼び出し側の全ての使い方(この例ではnameとisAdmin)を網羅していないと、別の場所で結局nullチェックが必要になる罠がある。呼び出し側でuser由来のプロパティを使っている全箇所を洗い出し、NULL_USERがそれらすべてに安全な値を持つかを検算する。
---
### KNOW-F-0193 アサーションによる契約の明示(assert)
**構造の型**:
```js
function assert(condition, message) {
if (!condition) throw new Error(`アサーション失敗: ${message}`);
}
function divide(a, b) {
assert(b !== 0, "bは0であってはならない"); // 契約違反を早期に検出
return a / b;
}
```
「本来ここでは絶対に成り立っているはずの条件」を明示的にチェックし、破られていたら即座に例外で知らせる構造。通常の入力検証(KNOW-F-0179)とは異なり、呼び出し側のバグを前提とした最終防衛線として使う。
**使いどころ**: 内部関数間の呼び出しで「呼び出し元が正しく前提を守っている」ことを開発時に検証したい場合、本番環境ではオフにできる軽量な検証として用いる。
**組合せ例**: KNOW-F-0169(再帰関数の契約)の事前条件・事後条件を、コードとして実行可能な形にしたものが本項目のassertである。KNOW-F-0181(例外による早期脱出)と機構は同じだが、意図(外部入力の検証か、内部契約の検証か)が異なる。
**確認事項(罠と検算)**: assertを本番環境でも常時実行すると性能に影響する場合があるため、開発時のみ有効化する設計になっているかを確認する。また、assertの条件式自体に副作用(状態を変更する処理)を書いていないかを検算する(オフにしたときに挙動が変わってしまうため)。
---
### KNOW-F-0194 早期returnと例外処理の使い分け基準
**構造の型**:
```js
// 基準(擬似コード)
// 呼び出し側が「失敗もありうる」と想定して当然の入力 → 早期return(エラーコード/Optional)
// 呼び出し側の契約違反・プログラムのバグに相当する状態 → throw(例外)
// 外部リソース(ネットワーク・ファイル)の障害 → throw、ただし呼び出し側で必ずcatch
```
戻り値による早期return(KNOW-F-0180)と例外による早期脱出(KNOW-F-0181)のどちらを選ぶかを、「失敗が想定内の正常系か、契約違反か」という基準で判定する構造。
**使いどころ**: 新しい関数のエラー処理方式を設計する最初の段階、既存コードで両方式が混在していて統一したい場面に用いる。
**組合せ例**: KNOW-F-0180とKNOW-F-0181の両方を踏まえた判断基準であり、KNOW-F-0193(assert)は「契約違反」側のさらに軽量な特殊形として位置づけられる。
**確認事項(罠と検算)**: 「失敗が起こりうる」と「失敗があってはならない」の境界判断を個人の感覚に任せると、同じチーム内でも関数ごとに方式がばらつく罠がある。プロジェクト内で境界の具体例(この基準に基づく判定済みの関数一覧)を共有し、新規関数がどちらに該当するかをチェックリストで検算する。
---
### KNOW-F-0195 ガード節による入力検証層の分離
**構造の型**:
```js
function validateInput(raw) { /* ガード節の集合、ここだけで完結 */
if (typeof raw.amount !== "number") return "ERR_TYPE";
if (raw.amount <= 0) return "ERR_RANGE";
return null; // null = 検証OK
}
function processPayment(raw) {
const error = validateInput(raw);
if (error) return { ok: false, error }; // 検証層の結果だけを使う早期return
// 以降は検証済みの前提で本処理に専念できる
return { ok: true, amount: raw.amount };
}
```
入力の検証(ガード節の集合)を専用の関数に切り出し、本処理を行う関数は検証結果を受け取って早期returnするだけにする構造。検証ロジックと業務ロジックが物理的に別関数へ分離される。
**使いどころ**: 同じ検証ロジックが複数の関数で必要になる場合、検証と業務処理を別々にテストしたい場合に用いる。
**組合せ例**: KNOW-F-0186(ガード節のテーブル化)とvalidateInputを組み合わせると、検証層自体がデータ駆動になる。KNOW-F-0188(単一責任維持)を関数分割のレベルで徹底した形である。
**確認事項(罠と検算)**: 検証層(validateInput)と本処理層(processPayment)で前提の食い違い(検証層はamount>0を確認したのに、本処理層はamount>=0を仮定している等)が起きる罠がある。検証層が保証する条件と本処理層が仮定する条件を文章として突き合わせ、一致するかを検算する。
---
### KNOW-F-0196 早期returnによるループ内スキップ(continueとの使い分け)
**構造の型**:
```js
function sumValidPositive(items) {
let total = 0;
for (const item of items) {
if (item == null) continue; // このitemだけスキップ(ループは継続)
if (item < 0) continue;
total += item;
}
return total; // ループ全体が終わってから早期returnではなく通常return
}
function findFirstValid(items) {
for (const item of items) {
if (item == null) continue;
return item; // 最初の有効値が見つかったら関数ごと早期return
}
return null;
}
```
ループの「1周回だけスキップする」continueと、「関数全体を打ち切る」早期returnを、目的(この周回だけ無視したいのか、探索そのものを終えたいのか)に応じて明確に使い分ける構造。
**使いどころ**: ループの中に複数の条件判定があり、「無視して続ける」のか「見つかったので終わる」のかを混同しやすい処理に用いる。
**組合せ例**: KNOW-F-0138(continueによる読み飛ばし)とKNOW-F-0137(breakによる早期脱出、関数の外ならreturn)の使い分けそのものであり、両者を並べて教えることで意図が明確になる。
**確認事項(罠と検算)**: continueとreturnを取り違えると、本来スキップして続けるべき処理が関数ごと打ち切られてしまう(またはその逆)罠がある。sumValidPositiveでcontinueをreturnに書き換えた版を作り、出力が変わることを実際に確認して両者の違いを検算する。
---
### KNOW-F-0197 ガード節における副作用の禁止則
**構造の型**:
```js
// 悪い例: ガード節の条件式の中で状態を変更している
if (queue.pop() === undefined) return; // 条件判定のたびにキューが変化してしまう
// 良い例: 判定と変更を分離する
const next = queue.length > 0 ? queue[0] : undefined;
if (next === undefined) return;
queue.shift(); // 変更は判定が確定した後に行う
```
ガード節の条件式そのものには、状態を変更する処理(副作用)を含めないという規律を型として扱う。条件式は「今どうなっているか」を読むだけにとどめる。
**使いどころ**: ガード節の条件式を書くすべての場面で、無意識に副作用を混ぜていないかをレビューする際に用いる。
**組合せ例**: KNOW-F-0189(早期returnのテスト網羅)でテストを書く際、条件式に副作用があると同じ入力でもテストの実行順によって結果が変わってしまい、テストの再現性が壊れる。KNOW-F-0159(メモ化再帰)が前提とする参照透過性とも通底する規律である。
**確認事項(罠と検算)**: 条件式の中に代入(`=`)やミューテーションを伴うメソッド(pop, shift, push等)が紛れていないかを目視で洗い出す。同じガード節を2回連続で評価しても結果が変わらないか(冪等性)を検算する。
---
### KNOW-F-0198 早期returnのログ出力(診断可能性)
**構造の型**:
```js
function processOrder(order) {
if (!order.items.length) {
log.warn("空の注文が渡された", { orderId: order.id });
return { ok: false, reason: "EMPTY" };
}
// ...
}
```
早期returnで処理を打ち切る際、なぜ打ち切ったのかを診断可能な形でログに残す構造。呼び出し側にエラーコードを返すだけでなく、運用側が後から原因を追えるようにする。
**使いどころ**: 本番環境で稼働する処理のガード節のうち、「起きたら調査が必要」な種類の早期return(想定外の空データ、外部連携の失敗)に用いる。すべてのガード節にログを付けると冗長になるため、対象を選別する。
**組合せ例**: KNOW-F-0180(エラーコードの早期return)にログ出力を付加したものであり、KNOW-F-0224(状態機械のログとデバッグ可視化)と同じ「診断可能性」という設計目的を共有する。
**確認事項(罠と検算)**: すべてのガード節にログを入れると、正常系でも頻繁に発生する分岐(単純な入力ミス等)まで大量のログが出てログの中から本当に重要な異常を見つけにくくなる罠がある。ログを付けるべきガード節と付けなくてよいガード節を「運用上の異常か、想定内のユーザー操作か」で分類したかを検算する。
---
### KNOW-F-0199 早期return関数の分割統治的な合成
**構造の型**:
```js
function pipeline(input) {
const step1 = validateStep(input);
if (!step1.ok) return step1; // 各段階が早期returnを持つ
const step2 = transformStep(step1.value);
if (!step2.ok) return step2;
return finalizeStep(step2.value);
}
```
複数の早期return関数を順に呼び出し、どこかで失敗(ok: false)が返ってきたらそこで全体を打ち切って伝播させる構造。各段階が独立した単一責任(KNOW-F-0188)の関数として合成される。
**使いどころ**: 入力検証→変換→確定のような多段階の処理を、各段階を個別にテスト可能な小関数へ分割しつつ、全体としては1本の流れで扱いたい場合に用いる。
**組合せ例**: KNOW-F-0199はKNOW-F-0177(早期returnによるネスト削減)を関数の合成レベルに拡張したものであり、目録III(データ変換の型)で扱う合成パイプラインとも接続する。
**確認事項(罠と検算)**: 途中の段階が失敗を返したのに、それを握りつぶして次の段階に進んでしまう(if文の書き忘れ)罠がある。各段階の直後に必ず失敗チェックの早期returnがあるかを、段階の数とif文の数を数えて一致を検算する。
---
### KNOW-F-0200 ガード節と早期returnの過剰適用への戒め
**構造の型**:
```js
// 過剰な例: 1行の関数に7個のガード節
function double(x) {
if (x === undefined) return undefined;
if (x === null) return null;
if (typeof x !== "number") return NaN;
if (Number.isNaN(x)) return NaN;
// ...本来は呼び出し側の型で保証すべき前提を、全部この関数で検証している
return x * 2;
}
```
本来は呼び出し側や型システムが保証すべき前提まで、末端の関数のガード節で何重にも検証してしまう「過剰なガード節」を避けるべき例として示す構造。
**使いどころ**: ガード節の数が関数の本質的な処理量を大きく上回っていないかを点検する、コードレビューの最終チェック項目として用いる。
**組合せ例**: 本項目は第三章全体(KNOW-F-0176〜0199)の締めくくりとして、「ガード節は道具であり目的ではない」という視点を与える。KNOW-F-0195(検証層の分離)で共通の検証を1箇所にまとめれば、末端関数のガード節は自然に減る。
**確認事項(罠と検算)**: ガード節の行数が関数の総行数の半分を超えていたら過剰適用の兆候として点検する目安になる。その関数が本来検証すべき責任範囲(KNOW-F-0188)を超えて他の層の仕事まで肩代わりしていないかを検算する。
## 第四章 状態機械の型——状態遷移表とイベント駆動(KNOW-F-0201〜0225)
本章では、「今どの状態にいるか」を明示的に管理し、入力(イベント)に応じて次の状態を決める「状態機械」の構造パターン25種を扱う。if-elseの海に沈んだフラグ変数の集合を、状態遷移表という1枚のデータへ書き換える技法が中心になる。
---
### KNOW-F-0201 状態遷移表の設計
**構造の型**:
```js
// 状態遷移表: (現在状態, イベント) -> 次状態
const table = {
IDLE: { START: "RUNNING" },
RUNNING: { PAUSE: "PAUSED", STOP: "IDLE" },
PAUSED: { RESUME: "RUNNING", STOP: "IDLE" },
};
```
「今の状態」と「起きたイベント」の組から「次の状態」を一意に決める対応表を、コードではなくデータとして先に設計する構造。表を見るだけで全ての遷移が一望できる。
**使いどころ**: 信号機・注文のステータス・UIのモード切替など、状態が有限個で遷移のルールが明確な処理全般の設計の出発点として用いる。
**組合せ例**: KNOW-F-0202(FSM実装)は本項目の表を実際に動くコードへ変換したものであり、KNOW-F-0206(遷移テーブル駆動実装)は表そのものをコードの中心に据える発展形になる。
**確認事項(罠と検算)**: 表に載っていない(状態, イベント)の組が来たときの挙動(無視するのか、エラーにするのか)を先に決めていないと、実装時に場当たり的な判断になる罠がある。全状態×全イベントの組み合わせを一覧にし、表に無い組をどう扱うかを1行決めてから実装に入る。
---
### KNOW-F-0202 有限状態機械(FSM)の実装(switch文型)
**構造の型**:
```js
function transition(state, event) {
switch (state) {
case "IDLE": return event === "START" ? "RUNNING" : state;
case "RUNNING": return event === "PAUSE" ? "PAUSED" : event === "STOP" ? "IDLE" : state;
case "PAUSED": return event === "RESUME" ? "RUNNING" : event === "STOP" ? "IDLE" : state;
default: return state;
}
}
```
KNOW-F-0201の状態遷移表を、状態ごとのswitch分岐として素直にコード化した構造。表と1対1で対応しているため読みやすいが、状態やイベントが増えるとswitch文が肥大化する。
**使いどころ**: 状態数・イベント数が少ない(目安として5×5程度まで)うちの初期実装、あるいは状態機械の考え方を初めて導入する場面に用いる。
**組合せ例**: 状態数が増えたらKNOW-F-0206(遷移テーブル駆動実装)へ移行する判断基準になる。KNOW-F-0213(相互再帰→状態機械変換)からの変換先としても使われる。
**確認事項(罠と検算)**: 未定義の(state, event)組でstateをそのまま返す(何もしない)実装は、遷移ミスを静かに握りつぶす罠にもなる。KNOW-F-0210(不正遷移検出)と組み合わせて、意図しない組を検出できるようにしたかを検算する。
---
### KNOW-F-0203 イベント駆動状態機械
**構造の型**:
```js
class Machine {
constructor() { this.state = "IDLE"; this.listeners = []; }
dispatch(event) {
const next = transition(this.state, event); // KNOW-F-0202のtransitionを利用
if (next !== this.state) {
this.state = next;
this.listeners.forEach(fn => fn(this.state));
}
}
onChange(fn) { this.listeners.push(fn); }
}
```
状態機械を「イベントを受け取るたびに遷移を判定し、変化があれば通知する」オブジェクトとして実装する構造。呼び出し側はイベントを送るだけで、遷移の詳細を知らなくてよい。
**使いどころ**: UIの状態管理、ゲームのプレイヤー状態管理など、外部からの入力(クリック・受信メッセージ)によって非同期に状態が変わる処理に用いる。
**組合せ例**: KNOW-F-0139(無限ループ+脱出条件の分離)で書いたイベントループの中心にこのdispatchが置かれることが多い。KNOW-F-0216(状態機械とイベントキューの分離)は、複数のイベントが同時に来た場合の順序保証を扱う発展形である。
**確認事項(罠と検算)**: dispatch中にlistenerがさらにdispatchを呼ぶ(再入)と、状態更新の途中で別の遷移が割り込む罠がある。dispatch中の再入を禁止する、またはイベントをキューに積んで順番に処理する(KNOW-F-0216)設計になっているかを検算する。
---
### KNOW-F-0204 状態オブジェクトパターン(Stateパターン)
**構造の型**:
```js
const states = {
IDLE: { onStart(ctx) { ctx.setState("RUNNING"); } },
RUNNING: { onPause(ctx) { ctx.setState("PAUSED"); }, onStop(ctx) { ctx.setState("IDLE"); } },
};
class Context {
constructor() { this.current = "IDLE"; }
setState(name) { this.current = name; }
start() { states[this.current].onStart?.(this); } // 現在の状態オブジェクトにメソッド呼び出しを委譲
}
```
状態ごとに「その状態でどう振る舞うか」をオブジェクト(またはクラス)として分離し、遷移ロジックを1つの巨大なswitch文ではなく状態オブジェクトへの委譲として表現する構造。
**使いどころ**: 状態ごとに全く異なる振る舞い(呼べるメソッドの集合そのものが違う)がある場合、KNOW-F-0202のswitch文が状態の数だけ分岐を抱えて肥大化するのを避けたい場合に用いる。
**組合せ例**: 第五章KNOW-F-0227(多態によるswitch排除)を状態機械に特化して適用したものである。KNOW-F-0201の遷移表とは異なるアプローチであり、どちらを選ぶかはイベントごとの処理の複雑さで判断する。
**確認事項(罠と検算)**: 状態オブジェクトに存在しないメソッド呼び出し(onStart等)をオプショナルチェイニング`?.`なしで呼ぶと例外になる罠がある。全ての状態オブジェクトが呼ばれうる全メソッドに対応しているか(存在しない場合は無視するのか明示するか)を検算する。
---
### KNOW-F-0205 状態遷移図からコードへの変換手順
**構造の型**:
```js
// 手順(擬似コード):
// 1. 図の丸(状態)をすべて列挙し、定数名を割り当てる
// 2. 図の矢印(遷移)をすべて (現在状態, イベント, 次状態) の3つ組として書き出す
// 3. 3つ組の一覧をKNOW-F-0201の表形式に変換する
// 4. 表に載らない (状態, イベント) 組の扱いを決める
```
図(円と矢印で描いた状態遷移図)から実装可能な表への変換を、抜け漏れなく行うための機械的な手順そのものを型として扱う。
**使いどころ**: 仕様書や設計会議で描かれた状態遷移図をコードに落とし込む最初の作業、既存コードから逆に状態遷移図を書き起こす作業(リバースエンジニアリング)の両方に用いる。
**組合せ例**: KNOW-F-0201(状態遷移表の設計)の入力となる前段階の手順であり、KNOW-F-0212(状態機械のテスト、全遷移網羅)は3つ組の一覧をそのままテストケース一覧として再利用できる。
**確認事項(罠と検算)**: 図の矢印を書き出す際、双方向の矢印(A⇄B)を1本と誤って数え、実際には2つの遷移(A→BとB→A)があることを見落とす罠がある。矢印の本数と3つ組の個数が一致しているかを数えて検算する。
---
### KNOW-F-0206 遷移テーブル駆動実装(データとしての状態機械)
**構造の型**:
```js
const TRANSITIONS = [
{ from: "IDLE", event: "START", to: "RUNNING" },
{ from: "RUNNING", event: "PAUSE", to: "PAUSED" },
{ from: "RUNNING", event: "STOP", to: "IDLE" },
{ from: "PAUSED", event: "RESUME", to: "RUNNING" },
];
function transition(state, event) {
const row = TRANSITIONS.find(t => t.from === state && t.event === event);
return row ? row.to : state; // 見つからなければ状態を維持
}
```
状態遷移の全ルールを、コード(if/switch)ではなく1本の配列(データ)として持ち、遷移判定はその配列を検索するだけの汎用関数にする構造。ルールの追加・削除がコード変更ではなくデータの追加・削除で済む。
**使いどころ**: 状態やイベントの数が多い(目安として10種類を超える)場合、または遷移ルールを外部ファイル(JSON等)から読み込んで変更したい場合に用いる。
**組合せ例**: KNOW-F-0202(switch文型)から本項目への移行は、第五章KNOW-F-0226(テーブル駆動法)の代表例そのものである。ルールをJSONに外出しすれば、非エンジニアでも遷移ルールを編集できるようになる。
**確認事項(罠と検算)**: TRANSITIONS配列に同じ(from, event)の組が重複して登録されると、find()は最初に見つかった行だけを使うため、後から追加したルールが静かに無視される罠がある。(from, event)の組み合わせに重複がないかを事前に機械的チェックしたかを検算する。
---
### KNOW-F-0207 階層状態機械(HSM、サブ状態)
**構造の型**:
```js
// 親状態ACTIVEの中に子状態RUNNING/PAUSEDがある(階層)
const hierarchy = {
ACTIVE: { parent: null, children: ["RUNNING", "PAUSED"] },
RUNNING: { parent: "ACTIVE" },
PAUSED: { parent: "ACTIVE" },
};
// "STOP"イベントは親ACTIVEで処理すれば、RUNNING/PAUSEDどちらでも共通に効く
```
複数の状態が共通して持つ振る舞い(この例では「どちらの子状態でもSTOPで終了する」)を、親状態として1箇所にまとめる構造。子状態は自分固有の遷移だけを持てばよい。
**使いどころ**: KNOW-F-0202やKNOW-F-0206で同じ遷移ルール(共通のイベント)が複数の状態に重複して現れる場合、重複を親状態への集約で解消したい場合に用いる。
**組合せ例**: KNOW-F-0201の表が「STOP」列だけ全ての行で同じ値を持つようになったら、階層化(本項目)の合図である。KNOW-F-0227(多態)のクラス継承と同じ「共通部分を上位に括り出す」発想を状態機械に適用したものである。
**確認事項(罠と検算)**: 子状態のローカルなイベント処理と親状態の共通処理のどちらを優先するか(子が親をオーバーライドできるか)を決めていないと、実装者ごとに解釈がずれる罠がある。優先順位のルールを1文で明記し、両方の処理が定義されている組み合わせで実際にどちらが動くかを検算する。
---
### KNOW-F-0208 状態のエントリ/イグジットアクション
**構造の型**:
```js
const states = {
RUNNING: {
onEntry: (ctx) => ctx.timer.start(), // その状態に入った瞬間に1回だけ実行
onExit: (ctx) => ctx.timer.stop(), // その状態から出る瞬間に1回だけ実行
},
};
function setState(ctx, next) {
states[ctx.current]?.onExit?.(ctx);
ctx.current = next;
states[ctx.current]?.onEntry?.(ctx);
}
```
状態そのものの処理(イベントが来たときの遷移)とは別に、「その状態に入った瞬間」「その状態から出る瞬間」に1回だけ実行される処理を分離する構造。
**使いどころ**: タイマーの開始/停止、UIの表示/非表示切り替え、ログの記録など、状態の滞在中ずっと続くわけではなく境界でだけ発生する副作用を扱う場合に用いる。
**組合せ例**: KNOW-F-0204(状態オブジェクトパターン)にonEntry/onExitを追加した発展形であり、KNOW-F-0219(状態機械のログとデバッグ可視化)ではonEntry/onExitにログ出力を仕込むのが定石になる。
**確認事項(罠と検算)**: onExitとonEntryの呼び出し順序を逆にする(先にonEntryしてからonExitする等)と、前の状態の後始末が新しい状態の初期化と入れ替わってしまう罠がある。setStateの実装で必ずonExit→状態更新→onEntryの順であることをコード上で検算する。
---
### KNOW-F-0209 ガード付き遷移(条件付き遷移)
**構造の型**:
```js
const TRANSITIONS = [
{ from: "PAUSED", event: "RESUME", to: "RUNNING", guard: (ctx) => ctx.hasPermission },
];
function transition(ctx, state, event) {
const row = TRANSITIONS.find(t => t.from === state && t.event === event);
if (!row) return state;
if (row.guard && !row.guard(ctx)) return state; // ガード条件を満たさなければ遷移しない
return row.to;
}
```
状態遷移表の各行に、遷移を許可するかどうかの追加条件(ガード)を持たせる構造。同じ(from, event)でもガードの真偽によって遷移するかどうかが変わる。
**使いどころ**: 「PAUSEDからRUNNINGへ戻れるのは権限がある場合だけ」のように、状態とイベントだけでは決まらない追加条件がある遷移に用いる。
**組合せ例**: KNOW-F-0176(ガード節)の考え方を状態機械の遷移表に統合したものであり、KNOW-F-0206(遷移テーブル駆動実装)の各行を拡張する形で実装する。
**確認事項(罠と検算)**: 同じ(from, event)に対してガード条件の異なる複数行を用意した場合、find()は最初に一致した行だけを使うため優先順位の設計(どのガード行を先に試すか)が必要になる。すべての行のガードを評価してから最初にtrueになったものを使う設計か、最初の行のみで判定する設計かを明記し、テストで検算する。
---
### KNOW-F-0210 状態機械の不正遷移検出
**構造の型**:
```js
function transition(state, event) {
const row = TRANSITIONS.find(t => t.from === state && t.event === event);
if (!row) {
console.warn(`不正な遷移: state=${state}, event=${event}`); // 検出して知らせる
return state;
}
return row.to;
}
```
表に定義されていない(状態, イベント)の組が来たとき、それを黙って無視するのではなく、明示的に検出してログや例外で知らせる構造。
**使いどころ**: 開発中に「想定していない順序でイベントが来ていないか」を早期に発見したい場合、本番環境でも異常なイベント順序を監視したい場合に用いる。
**組合せ例**: KNOW-F-0201(状態遷移表の設計)の「表に無い組をどう扱うか」という保留事項に対する具体的な実装であり、KNOW-F-0198(早期returnのログ出力)と同じ「診断可能性」の設計目的を共有する。
**確認事項(罠と検算)**: 警告を出すだけで処理を止めない設計だと、不正な状態のまま処理が進み続けてしまう場合がある。「警告して現状維持」で十分な場面と「例外を投げて止めるべき」場面をKNOW-F-0194(早期returnと例外処理の使い分け基準)と同じ判断軸で切り分けたかを検算する。
---
### KNOW-F-0211 状態機械のタイムアウト遷移
**構造の型**:
```js
const TIMEOUT_MS = { WAITING: 5000 };
function enterState(ctx, state) {
ctx.current = state;
clearTimeout(ctx.timer);
if (TIMEOUT_MS[state]) {
ctx.timer = setTimeout(() => dispatch(ctx, "TIMEOUT"), TIMEOUT_MS[state]); // 時間経過も1つのイベントとして扱う
}
}
```
「一定時間が経過した」こと自体を、外部からの入力イベントと同じ扱いの1つのイベント(TIMEOUT)として状態機械に送り込む構造。
**使いどころ**: 応答待ち状態が一定時間続いたら自動的にエラー状態へ遷移させたい場合(通信のタイムアウト、入力待ちの自動キャンセル)に用いる。
**組合せ例**: KNOW-F-0133(番兵ループ)の「特別な値で終了を知らせる」発想を、時間経過という特別な条件に応用したものである。状態が変わるたびにタイマーをクリアし直す(clearTimeout)処理が、KNOW-F-0208(エントリ/イグジットアクション)のonExitに相当する。
**確認事項(罠と検算)**: 状態が変わった後も前の状態のタイマーが残っていると、意図しないタイミングでTIMEOUTイベントが発火する罠がある。enterStateのたびに必ず前のタイマーをclearTimeoutしているかを、状態を素早く切り替えるテストで検算する。
---
### KNOW-F-0212 状態履歴(ヒストリー状態)
**構造の型**:
```js
function enterHistory(ctx, parentState) {
const lastChild = ctx.history[parentState]; // 前回この親状態にいたときの子状態を記憶
ctx.current = lastChild ?? ctx.defaultChild[parentState];
}
function setState(ctx, next, parent) {
ctx.current = next;
if (parent) ctx.history[parent] = next; // 親を離れる前に子状態を記録
}
```
階層状態機械(KNOW-F-0207)において、親状態から一度離れて再び戻ってきたときに、離れる前の子状態を記憶しておき、そこから再開する構造。
**使いどころ**: 「一時停止して別のモードに切り替え、元のモードに戻ったら中断した続きから再開する」処理(設定画面から戻ったらゲームの一時停止画面ではなく元のプレイ画面に戻る、等)に用いる。
**組合せ例**: KNOW-F-0207(階層状態機械)の発展形であり、KNOW-F-0224(状態機械のシリアライズ)と組み合わせると、履歴も含めて永続化する設計になる。
**確認事項(罠と検算)**: 初回訪問時(historyに記録がまだない)にlastChildがundefinedになる場合の既定値(defaultChild)を用意していないと、初回だけ挙動が変わる罠がある。「初めてこの親状態に入る」ケースと「2回目以降に戻ってくる」ケースの両方をテストで検算する。
---
### KNOW-F-0213 並行状態機械(複数状態機械の合成)
**構造の型**:
```js
class ParallelMachine {
constructor(machines) { this.machines = machines; } // 複数の独立した状態機械を束ねる
dispatch(event) {
for (const m of this.machines) m.dispatch(event); // 同じイベントを全員に配る
}
}
// 例: プレイヤーの「移動状態」と「攻撃状態」を独立した2つの状態機械として持つ
```
1つの対象(プレイヤー等)が同時に複数の独立した側面(移動状態と攻撃状態など)を持つ場合、それぞれを別々の状態機械として実装し、イベントを両方に配る構造。1つの巨大な状態機械で全組み合わせを表現する(状態数が掛け算的に爆発する)ことを避ける。
**使いどころ**: ゲームキャラクターの状態管理のように、独立に変化しうる複数の軸(体力状態・移動状態・装備状態)がある対象を扱う場合に用いる。
**組合せ例**: 1つの状態機械で全軸をまとめて表現すると、KNOW-F-0201の表が軸の数だけ掛け算的に巨大化する。KNOW-F-0207(階層状態機械)が「縦の分割」だとすれば、本項目は「横の分割」に当たる。
**確認事項(罠と検算)**: 複数の状態機械が互いに矛盾する状態(移動状態が「気絶中」なのに攻撃状態が「攻撃中」)になりうる組み合わせを許してしまう罠がある。軸同士の依存関係(気絶中は攻撃状態機械へイベントを配らない、等)を明示し、矛盾した組み合わせが実際に起きないかを検算する。
---
### KNOW-F-0214 状態機械のテスト(全遷移網羅)
**構造の型**:
```js
// テストの型(擬似コード): KNOW-F-0201の表の全行に対して1テストずつ
for (const { from, event, to } of TRANSITIONS) {
test(`${from} + ${event} -> ${to}`, () => {
expect(transition(from, event)).toBe(to);
});
}
```
状態遷移表の各行(全ての定義済み遷移)に対して、機械的に1つずつテストケースを生成して網羅する構造。表が更新されればテストも自動的に追随する。
**使いどころ**: 状態機械の実装(KNOW-F-0202/0206)が完成した直後、表の全行が正しく実装に反映されているかを検証する場面に用いる。
**組合せ例**: KNOW-F-0189(早期returnのテスト網羅)の状態機械版であり、KNOW-F-0210(不正遷移検出)については別途「表に無い組を渡すとstateが変わらない」ことを確認するテストを追加する。
**確認事項(罠と検算)**: 表の行数とテストケースの数を突き合わせ、1対1で対応しているかを検算する(テストを手で書くと表の更新時にテストの追随を忘れる罠があるため、本項目のようにループでテストを自動生成する方式が安全)。
---
### KNOW-F-0215 状態機械とイベントキューの分離
**構造の型**:
```js
class QueuedMachine {
constructor() { this.queue = []; this.processing = false; }
dispatch(event) { this.queue.push(event); this.drain(); }
drain() {
if (this.processing) return; // 再入を防ぐ(KNOW-F-0203の罠への対策)
this.processing = true;
while (this.queue.length > 0) {
const event = this.queue.shift();
this.state = transition(this.state, event);
}
this.processing = false;
}
}
```
イベントを受け取ったら即座に処理するのではなく、いったんキューに積んでから1つずつ順番に処理する構造。処理中に新しいイベントが来ても、キューの末尾に積まれるだけで割り込まない。
**使いどころ**: KNOW-F-0203の「dispatch中の再入」の罠を構造的に防ぎたい場合、イベントの発生順序を厳密に保ちたい場合に用いる。
**組合せ例**: KNOW-F-0139(無限ループ+脱出条件の分離)のメインループがこのdrain()の役割を担うことが多い。KNOW-F-0203のシンプルな実装から、規模が大きくなったら本項目へ移行する判断がKNOW-F-0175(再帰の限界〜)と同種の設計判断になる。
**確認事項(罠と検算)**: processingフラグの立て忘れ・下ろし忘れがあると、再入防止が機能しなくなるか、逆に一度処理が始まると二度と新しいイベントを受け付けなくなる罠がある。drain()の中で例外が発生した場合でもprocessing=falseに戻るか(try/finallyの要否、KNOW-F-0185参照)を検算する。
---
### KNOW-F-0216 状態機械のシリアライズ(永続化)
**構造の型**:
```js
function serialize(ctx) {
return JSON.stringify({ current: ctx.current, history: ctx.history }); // 状態そのものだけを保存
}
function deserialize(json) {
const data = JSON.parse(json);
return { current: data.current, history: data.history }; // 関数やタイマーは復元しない
}
```
状態機械の「今の状態」と「関連するデータ」だけを、関数やクロージャを含まない純粋なデータとして保存・復元できる形に切り出す構造。
**使いどころ**: セーブデータ・セッションの再開など、状態機械の状態をプロセスをまたいで保持したい場合に用いる。
**組合せ例**: KNOW-F-0212(状態履歴)を持つ状態機械をシリアライズする場合、historyも含めて保存する必要がある。KNOW-F-0211(タイムアウト遷移)のタイマーIDのような「復元できないもの」は保存対象から除外する判断が要る。
**確認事項(罠と検算)**: setTimeoutのタイマーIDや関数参照をそのままJSON.stringifyしようとすると、保存されない(silently欠落する)かエラーになる罠がある。serialize/deserializeの往復テスト(保存して復元した結果が元と同じ状態を再現するか)を実際に実行して検算する。
---
### KNOW-F-0217 状態機械のログとデバッグ可視化
**構造の型**:
```js
function dispatch(ctx, event) {
const prev = ctx.current;
const next = transition(prev, event);
console.log(`[FSM] ${prev} --(${event})--> ${next}`); // 遷移のたびに1行ログ
ctx.current = next;
}
```
状態遷移が起きるたびに「どの状態から、何のイベントで、どの状態へ」変化したかを1行の一貫した書式でログに残す構造。ログを時系列に並べれば、状態機械の挙動全体を後から再現できる。
**使いどころ**: 状態機械が意図しない挙動を示したときのデバッグ、テスト失敗時の原因調査に用いる。
**組合せ例**: KNOW-F-0198(早期returnのログ出力)と同じ設計思想であり、KNOW-F-0210(不正遷移検出)の警告ログもこの一貫した書式に統一すると調査がしやすくなる。
**確認事項(罠と検算)**: 変化がない遷移(表に無く状態が維持される場合)までログに出すとログが冗長になりすぎる場合がある。「実際に状態が変化したときだけログを出す」か「試みられた全イベントをログに出す」かを目的に応じて決め、両方を試した上でどちらが調査に役立つか検算する。
---
### KNOW-F-0218 アクター型状態機械(メッセージ駆動)
**構造の型**:
```js
class Actor {
constructor() { this.mailbox = []; this.state = "IDLE"; }
send(message) { this.mailbox.push(message); } // 外部からはメッセージを送るだけ
step() {
const message = this.mailbox.shift();
if (!message) return;
this.state = transition(this.state, message.type);
}
}
```
状態機械を「メールボックス(受信箱)を持ち、送られてきたメッセージを1つずつ取り出して自分の状態を更新する独立した主体(アクター)」として実装する構造。
**使いどころ**: 複数の状態機械が互いにメッセージをやり取りしながら協調動作する分散的な設計(複数プレイヤー・複数ワーカー)に用いる。
**組合せ例**: KNOW-F-0215(イベントキューの分離)を1つの状態機械に閉じ込め、外部とのやり取りをsend()という単一の窓口に限定したものである。複数のActorが互いにsend()し合う構成は、KNOW-F-0213(並行状態機械)とは異なる「独立したプロセスが通信する」モデルになる。
**確認事項(罠と検算)**: あるActorのstep()の中で別のActorへ直接send()すると、呼び出し元のstep()がいつまでも終わらない連鎖(処理の見積もりが崩れる)罠がある。send()はメッセージをキューに積むだけで即座に処理しない(非同期)という原則を守っているかを検算する。
---
### KNOW-F-0219 状態機械によるUIフロー制御
**構造の型**:
```js
const uiStates = {
LIST: { onSelect: "DETAIL" },
DETAIL: { onBack: "LIST", onEdit: "EDIT" },
EDIT: { onSave: "DETAIL", onCancel: "DETAIL" },
};
// 画面遷移そのものを状態機械の遷移表として表現する
```
画面(ページ)の切り替わりを、if文でルーティングするのではなく状態遷移表として表現し、「今どの画面にいて、次にどの画面へ行けるか」を1枚の表で管理する構造。
**使いどころ**: 画面数が5を超えるアプリケーションで、画面遷移のルールがコードのあちこちに散らばるのを防ぎたい場合に用いる。
**組合せ例**: KNOW-F-0201(状態遷移表の設計)をUI領域に適用した具体例であり、KNOW-F-0207(階層状態機械)はモーダルダイアログのような「一時的に重なる画面」を親子関係として表現するのに使える。
**確認事項(罠と検算)**: ブラウザの「戻る」ボタンのようなブラウザ標準の遷移操作と、状態機械が管理する画面状態が食い違う(URLは変わったのに内部状態は変わっていない)罠がある。ブラウザの履歴操作イベントも状態機械のイベントの1つとして表に組み込んだかを検算する。
---
### KNOW-F-0220 状態機械とタイマー割込の統合
**構造の型**:
```js
// 定期的な"TICK"イベントも通常のイベントと同列に扱う
setInterval(() => dispatch(ctx, "TICK"), 1000);
const TRANSITIONS = [
{ from: "COUNTING", event: "TICK", to: "COUNTING", action: (ctx) => ctx.count-- },
{ from: "COUNTING", event: "TICK", to: "DONE", guard: (ctx) => ctx.count <= 1 },
];
```
一定間隔で発生する「時間経過」イベントを、外部入力イベントと全く同じ経路(dispatch)で状態機械に送り込み、遷移表の中で他のイベントと同様に扱う構造。
**使いどころ**: カウントダウン・定期ポーリング・アニメーションのフレーム更新など、時間経過そのものが状態変化のトリガーになる処理に用いる。
**組合せ例**: KNOW-F-0211(タイムアウト遷移)が「1回だけの時間経過」を扱うのに対し、本項目は「繰り返す時間経過」を扱う点で異なる。KNOW-F-0209(ガード付き遷移)のguardを使い、TICKのたびに条件を再評価する設計になっている。
**確認事項(罠と検算)**: setIntervalを状態が変わった後もクリアし忘れると、状態機械が破棄された後もTICKイベントが送られ続けるメモリリークの罠がある。状態機械の終了時(DONE状態への到達時、またはオブジェクトの破棄時)にclearIntervalを呼んでいるかを検算する。
---
### KNOW-F-0221 状態機械のデッドロック回避
**構造の型**:
```js
// 検査(擬似コード): 全状態から少なくとも1つの脱出イベントがあるか
for (const state of allStates) {
const outgoing = TRANSITIONS.filter(t => t.from === state);
if (outgoing.length === 0) console.warn(`${state}は脱出不能な終端状態`);
}
```
「一度入ったら二度と出られない状態(意図しない終端状態)」がないかを、遷移表を静的に検査して見つける構造。意図した終端状態(正常終了)と、設計ミスによる行き止まりを区別する。
**使いどころ**: 状態遷移表を設計し終えた直後、複雑化した表に「意図せず出口のない状態」が紛れ込んでいないかを機械的に点検する場面に用いる。
**組合せ例**: KNOW-F-0205(状態遷移図からコードへの変換手順)の最終チェックとして実施する。KNOW-F-0214(状態機械のテスト、全遷移網羅)とあわせて、遷移表の静的検査(本項目)と動的検査(テスト)の両方を行うのが理想である。
**確認事項(罠と検算)**: 「意図した終端状態(処理完了を表すDONE等)」まで警告対象にしてしまうと、本来問題ない状態を誤検出する。終端として許容する状態のリストをあらかじめ用意し、それ以外の脱出不能状態だけを警告する設計になっているかを検算する。
---
### KNOW-F-0222 状態機械のバージョニング(遷移表の拡張)
**構造の型**:
```js
// 新しいイベント"CANCEL"を既存の表に追加する際、既存の行は変更しない
const TRANSITIONS_V2 = [
...TRANSITIONS_V1, // 既存の遷移は維持
{ from: "RUNNING", event: "CANCEL", to: "IDLE" }, // 新規追加のみ
];
```
既存の状態遷移表に新しい状態やイベントを追加する際、既存の行を変更せず追記だけで済むように設計・実装する構造。
**使いどころ**: 稼働中のシステムに新機能(新しいイベントや状態)を追加する場合、既存の遷移の挙動を壊さずに拡張したい場面に用いる。
**組合せ例**: KNOW-F-0206(遷移テーブル駆動実装)がデータとして表を持つ設計だからこそ、本項目のような追記型の拡張が容易になる。KNOW-F-0214(全遷移網羅テスト)を拡張前後の両方で実行し、既存の行の挙動が変わっていないことを回帰的に確認する。
**確認事項(罠と検算)**: 新しいイベントの追加が、既存の状態のガード条件(KNOW-F-0209)の前提を暗黙に壊してしまう(新イベントが来ることを想定していなかったガードが誤作動する)罠がある。既存のガード条件が新イベント追加後も想定通りに動くかを回帰テストで検算する。
---
### KNOW-F-0223 Push-down automaton型(スタック状態機械)
**構造の型**:
```js
class StackMachine {
constructor() { this.stack = ["MAIN_MENU"]; } // 現在状態はスタックのトップ
push(state) { this.stack.push(state); } // サブ状態に「入る」
pop() { this.stack.pop(); } // 元の状態に「戻る」
get current() { return this.stack[this.stack.length - 1]; }
}
```
「今どの状態にいるか」を単一の変数ではなく、スタック(積み重ね)として管理する構造。サブ画面に入るときにpushし、戻るときにpopすることで、任意の深さの「元へ戻る」を自然に表現できる。
**使いどころ**: メニュー→サブメニュー→さらに詳細設定、のように「戻る」操作が何段でも続きうるUIフロー(KNOW-F-0217参照)に用いる。
**組合せ例**: KNOW-F-0161(再帰→ループ変換、明示スタック化)で使った自前スタックの発想を状態機械そのものに適用したものであり、KNOW-F-0210(状態履歴)が「1段だけ覚える」のに対し、本項目は「何段でも覚える」一般化に当たる。
**確認事項(罠と検算)**: popをpushの回数より多く呼ぶとスタックが空になり、currentがundefinedになる罠がある。popする前にスタックの長さが1より大きいか(最低1つは残すべき初期状態がある)を確認するガード節(KNOW-F-0176)を入れたかを検算する。
---
### KNOW-F-0224 状態機械の初期状態/終了状態の明示
**構造の型**:
```js
const FSM_DEFINITION = {
initial: "IDLE", // 開始時点の状態を必ず1つ明記する
final: ["COMPLETED", "CANCELLED"], // 終了とみなす状態の集合を明記する
transitions: TRANSITIONS,
};
```
状態遷移表そのものだけでなく、「どこから始まるか(初期状態)」と「どこで終わりとみなすか(終了状態の集合)」をあわせて1つの定義として明記する構造。
**使いどころ**: 状態機械の定義を他者に説明する、あるいは自動テスト(KNOW-F-0214)やデッドロック検査(KNOW-F-0221)のために「どこからどこまでが正常な範囲か」を機械的に判定したい場合に用いる。
**組合せ例**: KNOW-F-0221(デッドロック回避)の検査で「終端として許容する状態」として使うリストが、まさにこのfinal配列である。KNOW-F-0205(状態遷移図からコードへの変換手順)の最初の一歩として、図に「開始」「終了」の印を明示するのと対応する。
**確認事項(罠と検算)**: 初期状態(initial)がTRANSITIONS内のどの行のfromにも一致しない孤立した状態になっていないか(何のイベントでも遷移先が無い)を検算する。final配列に列挙した各状態が実際に到達可能(何らかの遷移列でinitialから辿り着ける)かも確認する。
---
### KNOW-F-0225 状態機械から早期returnへの逆変換判断
**構造の型**:
```js
// 状態が2つしかなく、遷移も単純な場合は状態機械にせず早期returnで十分
function toggleLight(isOn) {
if (isOn) return { isOn: false }; // ON -> OFF
return { isOn: true }; // OFF -> ON
}
```
状態機械(KNOW-F-0201〜0224)の仕組みを導入するほどの複雑さがない場合、あえてシンプルな早期return(第三章)に戻す判断を型として扱う。
**使いどころ**: 状態が2〜3個、遷移ルールも単純(トグル程度)な処理に、大がかりな状態機械の枠組みを持ち込みすぎていないかを見直す場面に用いる。
**組合せ例**: 本項目は第二章末尾のKNOW-F-0175(再帰の限界と反復への書き換え判断基準)と対をなす、第四章の締めくくりの判断基準である。KNOW-F-0201から本項目まで進んだ後、実際の対象が本当に状態機械を必要とするほど複雑かを最後にもう一度問い直す。
**確認事項(罠と検算)**: 状態遷移表(KNOW-F-0201)の行数が3行未満で、かつガード条件(KNOW-F-0209)も無い場合は、状態機械化がむしろ過剰設計になっている可能性が高い。表の行数・ガードの有無・将来の拡張見込みの3点をチェックリストとして、状態機械化するかどうかを検算してから実装に入る。
## 第五章 分岐削減の技——テーブル駆動と多態(KNOW-F-0226〜0250)
本章では、if/switchの羅列を「データ」や「型の使い分け(多態)」へ置き換えることで分岐そのものを減らす技法25種を扱う。分岐削減は可読性だけでなく、条件の追加・変更をコードの書き換えではなくデータの追加・変更で済ませられるという保守性の利点を持つ。ただし常に有利とは限らず、削減とパフォーマンス・可読性のトレードオフを判断する視点で締めくくる。
---
### KNOW-F-0226 テーブル駆動法(if/switch置換)
**構造の型**:
```js
// Before: if/switchの羅列
function taxRate(category) {
if (category === "food") return 0.08;
if (category === "book") return 0.08;
if (category === "luxury") return 0.15;
return 0.10;
}
// After: テーブル(データ)駆動
const TAX_RATE = { food: 0.08, book: 0.08, luxury: 0.15 };
function taxRateTable(category) { return TAX_RATE[category] ?? 0.10; }
```
条件ごとに異なる値・処理を返すif/switch文を、キーと値の対応表(オブジェクトやMap)に置き換え、分岐を「表の検索」に還元する構造。
**使いどころ**: 条件の分岐先が単純な値やデータである場合(税率・価格表・変換規則)に用いる。分岐先が複雑な処理(複数行にわたる手続き)の場合はKNOW-F-0230(ディスパッチテーブル)へ発展させる。
**組合せ例**: KNOW-F-0186(ガード節のテーブル化)・KNOW-F-0206(状態遷移テーブル駆動)はいずれも本項目の考え方を各領域に適用した具体例である。
**確認事項(罠と検算)**: 表に無いキーが来たときの既定値(この例では`?? 0.10`)を用意し忘れると、undefinedが静かに返る罠がある。Before/After両方の実装に同じ入力群(表にあるキー・無いキーの両方)を通し、出力が完全に一致するかを検算する。
---
### KNOW-F-0227 多態(ポリモーフィズム)によるswitch排除
**構造の型**:
```js
// Before: 型ごとのswitch
function area(shape) {
switch (shape.type) {
case "circle": return Math.PI * shape.r ** 2;
case "square": return shape.side ** 2;
}
}
// After: 各型が自分の面積計算を持つ(多態)
class Circle { constructor(r) { this.r = r; } area() { return Math.PI * this.r ** 2; } }
class Square { constructor(side) { this.side = side; } area() { return this.side ** 2; } }
```
「型ごとに処理を切り替えるswitch文」を、各型がそれぞれ自分の振る舞い(メソッド)を持つ形に置き換え、呼び出し側は型を意識せず同じメソッド名(area())を呼ぶだけにする構造。
**使いどころ**: 型の種類が増えるたびにswitch文に新しいcaseを追加し続けている(オープン・クローズド原則に反している)状況で用いる。
**組合せ例**: KNOW-F-0204(状態オブジェクトパターン)は状態機械領域における本項目の応用である。KNOW-F-0232(ビジターパターン)は多態だけでは対応しにくい「型と操作の両方が増える」場合の発展形になる。
**確認事項(罠と検算)**: switch版では新しい型を追加するとswitch文を書き換える必要があるが、多態版では新しいクラスを追加するだけでよい(既存コードの変更が不要)という差を、実際に新しい図形(三角形)を1つ追加してみて検算する。
---
### KNOW-F-0228 ストラテジーパターンによる分岐の外部化
**構造の型**:
```js
const strategies = {
bubble: (arr) => bubbleSort(arr),
quick: (arr) => quickSort(arr),
};
function sort(arr, strategyName) {
return strategies[strategyName](arr); // アルゴリズムの選択を分岐でなく引数化
}
```
「どのアルゴリズム/どの処理方式を使うか」という選択そのものを、if/switchで固定するのではなく、外部から差し替え可能な引数(戦略オブジェクトや戦略名)として渡す構造。
**使いどころ**: 同じ目的(ソートする、割引を計算する)に対して複数の実現方法があり、状況に応じて切り替えたい場合、またはテストのために一時的に別の実装に差し替えたい場合に用いる。
**組合せ例**: KNOW-F-0226(テーブル駆動法)の「値」を「関数」に変えたものが本質的にストラテジーパターンであり、KNOW-F-0231(Nullオブジェクトとストラテジーの併用)では既定戦略にNull Object(KNOW-F-0192)を使う。
**確認事項(罠と検算)**: strategyNameに存在しないキー(タイポ等)が渡されるとstrategies[strategyName]がundefinedになり、それを関数として呼び出そうとして例外になる罠がある。KNOW-F-0176(ガード節)で不正な戦略名を先に弾いているかを検算する。
---
### KNOW-F-0229 コマンドパターンによる分岐削減
**構造の型**:
```js
const commands = {
MOVE_UP: (state) => ({ ...state, y: state.y - 1 }),
MOVE_DOWN: (state) => ({ ...state, y: state.y + 1 }),
};
function execute(state, commandName) {
return commands[commandName] ? commands[commandName](state) : state;
}
```
「何をするか(操作)」をswitch文の各caseに直接書くのではなく、操作名と操作を実行する関数の対応表として持ち、呼び出しを表の検索+関数呼び出しに還元する構造。
**使いどころ**: キー入力・メニュー選択など、「選ばれた操作名に応じて処理を切り替える」場面全般に用いる。undo/redoのために操作を記録したい場合にも相性がよい。
**組合せ例**: KNOW-F-0230(ディスパッチテーブル)とほぼ同じ形だが、本項目は「状態を受け取り新しい状態を返す(副作用を持たない)」コマンドとして設計される点で、KNOW-F-0197(ガード節の副作用禁止)の規律とも整合する。
**確認事項(罠と検算)**: commands[commandName]が存在しない場合にstateをそのまま返す(何もしない)実装は、KNOW-F-0210(不正遷移検出)と同様にタイプミスを静かに握りつぶす罠になりうる。未知のcommandNameが来たときにログを出す・例外にするなど、検出可能にしたかを検算する。
---
### KNOW-F-0230 ディスパッチテーブル(関数参照配列)
**構造の型**:
```js
const OPCODE_HANDLERS = [
(regs) => regs.acc += regs.arg, // opcode 0: ADD
(regs) => regs.acc *= regs.arg, // opcode 1: MUL
(regs) => regs.acc -= regs.arg, // opcode 2: SUB
];
function execute(opcode, regs) { OPCODE_HANDLERS[opcode](regs); }
```
処理を選ぶキーが連番の整数(命令コード等)である場合、if/switchの代わりに配列の添字として直接関数を引く構造。配列添字アクセスはハッシュ検索よりさらに直接的で高速になりやすい。
**使いどころ**: 簡易的な命令セット(バイトコードインタプリタ)の実装、あるいは選択肢が小さな連番整数で表現できる場面に用いる。
**組合せ例**: BOOK-0184終章のミニ計算機実演(理-KAN-176〜180、命令デコード写像関数)は、本項目のディスパッチテーブルとして実装しても等価である。KNOW-F-0229(コマンドパターン)のキーが文字列である版と対をなす。
**確認事項(罠と検算)**: opcodeが配列の範囲外(負数やOPCODE_HANDLERS.lengthを超える値)のとき、OPCODE_HANDLERS[opcode]がundefinedになり関数呼び出しで例外になる罠がある。KNOW-F-0179(範囲外入力の早期return)の考え方で範囲チェックを先に行ったかを検算する。
---
### KNOW-F-0231 二分探索による多分岐の高速化
**構造の型**:
```js
// 昇順にソート済みの閾値テーブルを二分探索で引く
const THRESHOLDS = [60, 70, 80, 90]; // 4つの閾値で5段階に分類
const GRADES = ["F", "D", "C", "B", "A"];
function grade(score) {
let lo = 0, hi = THRESHOLDS.length;
while (lo < hi) {
const mid = (lo + hi) >> 1;
if (score < THRESHOLDS[mid]) hi = mid; else lo = mid + 1;
}
return GRADES[lo];
}
```
連続するif-else if(この例では点数の段階判定)を、ソート済みの閾値配列に対する二分探索(KNOW-F-0142の二本指針とは別系統の探索)に置き換え、分岐段数をO(n)からO(log n)に削減する構造。
**使いどころ**: 段階の数が多い(10段階を超える)閾値判定、あるいは判定回数が非常に多く繰り返される場面で、線形なif-elseの比較回数を減らしたい場合に用いる。
**組合せ例**: KNOW-F-0226(テーブル駆動法)がハッシュ的な等値検索であるのに対し、本項目は「範囲」の判定に特化した検索であり、両者は分岐の性質(等値か範囲か)によって使い分ける。
**確認事項(罠と検算)**: THRESHOLDS配列がソート済みであることが二分探索の前提であり、ソートが崩れていると誤った結果を返す罠がある。境界値(score=60ちょうど、score=59)でBefore(素朴なif-else)版とAfter(二分探索)版の出力を突き合わせて検算する。
---
### KNOW-F-0232 列挙型(enum)+テーブルの組合せ
**構造の型**:
```js
const Direction = Object.freeze({ UP: "UP", DOWN: "DOWN", LEFT: "LEFT", RIGHT: "RIGHT" });
const DELTA = {
[Direction.UP]: { dx: 0, dy: -1 },
[Direction.DOWN]: { dx: 0, dy: 1 },
[Direction.LEFT]: { dx: -1, dy: 0 },
[Direction.RIGHT]:{ dx: 1, dy: 0 },
};
```
取りうる値の集合を列挙型(Object.freezeした定数オブジェクト等)として固定し、その列挙値をキーとするテーブル(DELTA)と組み合わせることで、文字列の打ち間違いによる分岐ミスを構造的に防ぐ構造。
**使いどころ**: 方向・状態名・種別など、あらかじめ取りうる値が確定している集合をキーにテーブル駆動(KNOW-F-0226)を行う場面全般に用いる。
**組合せ例**: KNOW-F-0201(状態遷移表)の状態名・イベント名を生の文字列リテラルでなく本項目の列挙型で定義すると、KNOW-F-0180(エラーコードの早期return)で挙げたtypoの罠を防げる。
**確認事項(罠と検算)**: Object.freezeを付け忘れると、実行中にDirection.UPの値が書き換えられてしまう(意図しない場所での代入)罠がある。DELTAのキーに全てのDirection値が過不足なく揃っているかを、Direction側のキー一覧とDELTA側のキー一覧を突き合わせて機械的に検算する。
---
### KNOW-F-0233 デコレーターパターンによる条件付き振る舞い合成
**構造の型**:
```js
function withLogging(fn) {
return (...args) => { console.log("呼び出し:", args); return fn(...args); };
}
function withRetry(fn, times) {
return (...args) => {
for (let i = 0; i < times; i++) { try { return fn(...args); } catch (e) { if (i === times - 1) throw e; } }
};
}
const robustFetch = withRetry(withLogging(fetchData), 3); // 分岐せず関数を包んで合成する
```
「ログを取るかどうか」「リトライするかどうか」といった条件付きの振る舞いを、if文で本体に埋め込むのではなく、元の関数を包む(デコレートする)別関数として外側に積み重ねる構造。
**使いどころ**: 複数の横断的関心事(ログ・リトライ・キャッシュ・認可チェック)を、本体のロジックを汚さずに組み合わせたい場合に用いる。
**組合せ例**: KNOW-F-0228(ストラテジーパターン)が「丸ごと差し替える」のに対し、本項目は「既存の振る舞いに追加で包む」点で異なる。両者は併用でき、withRetry(strategies[name], 3)のような形も自然に書ける。
**確認事項(罠と検算)**: デコレーターを重ねる順序(withRetry(withLogging(fn))かwithLogging(withRetry(fn))か)によって、ログがリトライのたびに出るか1回だけ出るかが変わる罠がある。意図した順序になっているかを、実際にリトライが発生するテストケースでログの出力回数を数えて検算する。
---
### KNOW-F-0234 ルックアップテーブルによる計算の事前化
**構造の型**:
```js
// 毎回計算する代わりに、あらかじめ結果を全て計算してテーブル化しておく
const SQUARES = Array.from({ length: 101 }, (_, i) => i * i); // 0〜100の2乗を先に計算
function squareOf(n) { return (n >= 0 && n <= 100) ? SQUARES[n] : n * n; } // 範囲内は表を引くだけ
```
実行時に毎回計算していた処理の結果を、あらかじめ(起動時や初回だけ)まとめて計算してテーブルに保存し、以降は計算の代わりに表を引くだけにする構造。KNOW-F-0159(メモ化)が「呼ばれた分だけ後から記憶する」のに対し、本項目は「呼ばれる前に先回りして全部用意する」点が異なる。
**使いどころ**: 入力の範囲が有限で小さく(数百〜数万程度)、計算コストが表の引き当てコストより明らかに高い場合(三角関数表、色変換表)に用いる。
**組合せ例**: KNOW-F-0159(メモ化再帰)と対になる技法であり、「入力範囲が事前に分かっているか(本項目)」「実行時にならないと分からないか(メモ化)」で使い分ける。
**確認事項(罠と検算)**: テーブルの範囲外の入力(この例ではn>100やn<0)を考慮し忘れると、範囲外アクセスでundefinedを返すか例外になる罠がある。範囲外はフォールバックとして直接計算する経路(この例のn*n)を必ず用意したかを検算する。
---
### KNOW-F-0235 ビットフラグによる複合条件の圧縮
**構造の型**:
```js
const READ = 1, WRITE = 2, EXECUTE = 4; // それぞれ独立したビット
function hasPermission(flags, required) { return (flags & required) === required; } // ビットANDで判定
const userFlags = READ | WRITE; // 複数の権限を1つの数値にまとめる
```
複数の真偽値(この例では読み取り/書き込み/実行の可否)を、それぞれ別の変数で持つ代わりに、1つの整数のビット位置として表現し、論理演算(AND/OR)で複合条件を判定する構造。
**使いどころ**: 独立した複数のフラグの組み合わせ(権限・オプション・属性)が多数あり、if文で「A かつ B かつ not C」のような複合条件を何度も書くのを避けたい場合に用いる。
**組合せ例**: KNOW-F-0184(短絡評価による条件式の結合)を数値演算のレベルで一般化したものであり、KNOW-F-0232(列挙型+テーブル)のように定数READ/WRITE/EXECUTEを列挙型として管理すると打ち間違いを防げる。
**確認事項(罠と検算)**: ビット位置が重複する値(例えば誤ってEXECUTE=2としてWRITEと衝突させる)を定義してしまうと、異なる権限のはずが同じビットを指してしまう罠がある。各定数が2の累乗(1, 2, 4, 8, …)で重複がないかを機械的に検算する。
---
### KNOW-F-0236 責任連鎖パターンによる条件列の分解
**構造の型**:
```js
function makeHandler(canHandle, handle) {
return { canHandle, handle, next: null };
}
function chain(...handlers) {
for (let i = 0; i < handlers.length - 1; i++) handlers[i].next = handlers[i + 1];
return handlers[0];
}
function dispatch(handler, request) {
if (!handler) return null;
if (handler.canHandle(request)) return handler.handle(request);
return dispatch(handler.next, request); // 自分が処理できなければ次の担当者に回す
}
```
「この条件なら自分が処理し、そうでなければ次の担当に回す」という判断単位(ハンドラ)を連結リストのようにつなぎ、if-elseの巨大な羅列を独立したハンドラの連鎖として分解する構造。
**使いどころ**: 承認フロー(金額に応じて承認者が変わる)、リクエストの振り分けなど、条件を満たす最初の担当者が処理するという構造の分岐に用いる。
**組合せ例**: KNOW-F-0150(ループから再帰への書き換え)の考え方が本項目にも現れており、dispatch自体は線形再帰(KNOW-F-0156)として実装されている。KNOW-F-0188(単一責任維持)を条件分岐そのものに適用した形とも言える。
**確認事項(罠と検算)**: どのハンドラもcanHandleを満たさない場合(chainの末尾でnextがnullになる)の既定動作を決めていないと、dispatchがnullを返すだけで何も処理されないまま静かに終わる罠がある。「該当なし」の場合の既定ハンドラ(Null Object、KNOW-F-0192)を鎖の末尾に用意したかを検算する。
---
### KNOW-F-0237 ビジターパターンによる型分岐の排除
**構造の型**:
```js
class Circle { accept(visitor) { return visitor.visitCircle(this); } }
class Square { accept(visitor) { return visitor.visitSquare(this); } }
const areaVisitor = {
visitCircle: (c) => Math.PI * c.r ** 2,
visitSquare: (s) => s.side ** 2,
};
shapes.map(s => s.accept(areaVisitor)); // 型ごとのswitchなしに、型ごとの処理を呼び分ける
```
「型に応じて処理を切り替える」判断を、各型オブジェクトが自分自身の型に対応するvisitorのメソッドを呼び出す(accept)ことに委譲し、呼び出し側もvisitor自体も型のswitch文を書かずに済む構造。
**使いどころ**: 型の種類は固定的だが、その型に対して行いたい「操作の種類」の方が増えていく場合(面積計算・周長計算・描画、と操作が増える)に、KNOW-F-0227(多態)だけでは各クラスに操作を追加し続ける必要があるのを避けたい場合に用いる。
**組合せ例**: KNOW-F-0227(多態によるswitch排除)は「型が増える」場合に強く、本項目は「操作が増える」場合に強いという逆の得意分野を持つ。両者のどちらが増えやすいかで選択する。
**確認事項(罠と検算)**: 新しい型(Triangle等)を追加すると、既存の全visitorオブジェクト(areaVisitor, perimeterVisitorなど)にvisitTriangleを追加してまわる必要があり、その追加漏れが型エラーにならない言語では実行時まで気づけない罠がある。新しい型を1つ追加した際、必要な全visitorへの追加箇所を洗い出すチェックリストを用意したかを検算する。
---
### KNOW-F-0238 述語オブジェクト(Predicate)の合成
**構造の型**:
```js
const isPositive = (x) => x > 0;
const isEven = (x) => x % 2 === 0;
function and(...preds) { return (x) => preds.every(p => p(x)); }
function or(...preds) { return (x) => preds.some(p => p(x)); }
const isPositiveEven = and(isPositive, isEven); // 小さな条件関数を合成して複合条件を作る
```
真偽値を返す小さな条件関数(述語)を部品として用意し、and/orのような合成関数でつなぎ合わせることで、複雑な条件式を「部品の組み合わせ」として表現する構造。
**使いどころ**: フィルタ条件が複数の軸で組み合わさる検索・絞り込み処理(価格帯かつ在庫ありかつカテゴリ一致)で、条件の組み合わせ自体が動的に変わる場合に用いる。
**組合せ例**: KNOW-F-0138(continueによる読み飛ばし)やfilter()の条件式に、直接複雑な論理式を書く代わりに本項目のand/orで組み立てた述語を渡すと可読性が上がる。KNOW-F-0184(短絡評価)の考え方を関数合成のレベルに引き上げたものである。
**確認事項(罠と検算)**: and()の実装でevery(空配列に対してeveryは常にtrueを返す)という仕様を忘れると、述語を1つも渡さなかったときに「常にtrue」という意図しない既定動作になる罠がある。述語を0個・1個・複数個渡した場合それぞれで動作を検算する。
---
### KNOW-F-0239 マップ/レコードによるcase文代替
**構造の型**:
```js
// Before: 多数のcaseを持つswitch
// After: レコード(オブジェクト)のキーで直接分岐先を引く
const HANDLERS = {
"user.created": (payload) => notifyWelcome(payload),
"user.deleted": (payload) => notifyGoodbye(payload),
};
function onEvent(type, payload) { (HANDLERS[type] ?? noop)(payload); }
```
イベント種別ごとの処理をswitch文で並べる代わりに、種別文字列をキーとする処理関数のマップとして持つ構造。KNOW-F-0229(コマンドパターン)と近いが、本項目はイベント通知のような「発生したことへの反応」に焦点を当てる。
**使いどころ**: イベント名の種類が今後も増え続けることが見込まれる通知・購読処理(pub/sub)で、switch文の追記コストを下げたい場合に用いる。
**組合せ例**: KNOW-F-0203(イベント駆動状態機械)のdispatchと組み合わせ、イベント種別ごとの副作用(通知・ログ)をHANDLERSのようなマップで管理すると責務が整理される。
**確認事項(罠と検算)**: 未知のtypeに対する既定処理(noop、何もしない関数)を用意し忘れると、HANDLERS[type]がundefinedになり関数呼び出しで例外になる罠がある。未知のtypeを渡すテストで例外にならず静かに無視されるか(あるいは意図的に警告を出すか)を検算する。
---
### KNOW-F-0240 三項演算子の濫用回避基準
**構造の型**:
```js
// 許容範囲: 単純な二択で1行に収まる
const label = isActive ? "有効" : "無効";
// 濫用(避けるべき): 三項演算子の入れ子
const grade = score >= 90 ? "A" : score >= 70 ? "B" : score >= 50 ? "C" : "D"; // 読みにくい
```
三項演算子(条件 ? 真 : 偽)を「単純な二択の1行」に限定して使い、3つ以上の選択肢や入れ子になる場合はif-else連鎖やKNOW-F-0231(二分探索テーブル)に書き換えるという使用基準を型として扱う。
**使いどころ**: 変数への単純な値の割り当てで、if-elseを書くとかえって冗長になる二択の場面に用いる。
**組合せ例**: KNOW-F-0231(二分探索による多分岐)は、三項演算子の入れ子で書かれがちな段階判定を、より読みやすい形に書き換える受け皿の1つになる。
**確認事項(罠と検算)**: 三項演算子の入れ子が2段を超えたら機械的にif-elseへ書き換えるという基準(社内規約等)を設けているかを確認する。既存コードの三項演算子の入れ子段数を数え、2段を超える箇所を洗い出して検算する。
---
### KNOW-F-0241 ネストifの平坦化(早期returnとの併用)
**構造の型**:
```js
// Before: 3段ネスト
if (user) {
if (user.isActive) {
if (user.hasPermission) { doWork(); }
}
}
// After: 早期returnで平坦化(KNOW-F-0177の手順をそのまま適用)
if (!user) return;
if (!user.isActive) return;
if (!user.hasPermission) return;
doWork();
```
第三章KNOW-F-0177(早期returnによるネスト削減)・KNOW-F-0190(リファクタリング順序)の手順を、本章の「分岐削減」というテーマの中で改めて適用例として位置づける構造。
**使いどころ**: 第五章の他の技法(テーブル駆動・多態)を検討する前に、まず最も手軽な平坦化で十分でないかを確認する最初のステップとして用いる。
**組合せ例**: KNOW-F-0177・KNOW-F-0190と実質同一の技法であり、本項目は「テーブル駆動化・多態化するほどではないが、ネストだけは解消したい」という中間段階の選択肢として章をまたいで再掲している。
**確認事項(罠と検算)**: 平坦化の過程で条件の否定を取り違えないよう、KNOW-F-0177と同様にBefore/Afterで同じ入力群を通し出力が一致するかを検算する。
---
### KNOW-F-0242 条件式の意味付け変数化(マジック条件の命名)
**構造の型**:
```js
// Before: 条件式の意味が読み取りにくい
if (user.age >= 18 && user.age < 65 && user.country === "JP") { /* ... */ }
// After: 条件に名前を与える
const isWorkingAgeJapanese = user.age >= 18 && user.age < 65 && user.country === "JP";
if (isWorkingAgeJapanese) { /* ... */ }
```
複合的な条件式そのものに、その条件が何を意味しているかを説明する名前(変数名)を与える構造。分岐の本数は変わらないが、読み手が条件式を毎回展開して意味を推測する負担が減る。
**使いどころ**: if文の条件式が3つ以上の比較演算子を含み、コメントなしでは意図が読み取りにくい場合に用いる。
**組合せ例**: KNOW-F-0238(述語オブジェクトの合成)へ発展させる前の、最も手軽な最初の一歩として位置づけられる。名前を与えることで初めて「この条件はテスト(KNOW-F-0189)すべき単位だ」と認識しやすくなる。
**確認事項(罠と検算)**: 変数名が条件式の実際の意味とずれている(isWorkingAgeという名前なのに国籍の条件まで含んでいる等)と、かえって誤解を招く罠がある。命名した変数の名前だけを読んで、条件式の中身を見ずに正しく意味を推測できるかを第三者視点で検算する。
---
### KNOW-F-0243 分岐カバレッジの検算(全枝到達確認)
**構造の型**:
```js
// テストカバレッジツールの出力(擬似例)
// if (a) { ... } else { ... }
// -> true分岐: 3回実行, false分岐: 0回実行 ← false分岐が未検証
```
if文・switch文の各分岐(枝)が、テスト全体を通じて少なくとも1回は実際に通過しているかを機械的に計測する構造。行カバレッジ(その行が実行されたか)よりも厳しい基準で、条件分岐の両側を確認する。
**使いどころ**: 第三章KNOW-F-0189(早期returnのテスト網羅)・第四章KNOW-F-0214(状態機械の全遷移網羅)の考え方を、if/switch文一般に対して機械的なツール(カバレッジレポート)で検証したい場合に用いる。
**組合せ例**: KNOW-F-0191(早期return漏れ検出、deadコード検算)は分岐カバレッジが0%の枝(到達不能コード)を見つける特殊な場合に相当する。テーブル駆動化(KNOW-F-0226)された後は、if/switchの分岐カバレッジの代わりに「テーブルの各行が使われたか」を計測する形に置き換わる。
**確認事項(罠と検算)**: カバレッジが100%であっても、分岐の組み合わせ(2つのif文がある場合の4通りの組み合わせ)まで網羅しているとは限らない罠がある。単純な分岐カバレッジと、組み合わせを網羅する条件カバレッジの違いを理解した上で、対象の重要度に応じてどちらを目標にするか検算する。
---
### KNOW-F-0244 多重continueの排除によるフラグ変数削減
**構造の型**:
```js
// Before: フラグ変数で多重ループの継続を制御
let skip = false;
for (const row of rows) {
for (const col of cols) {
if (shouldSkip(row, col)) { skip = true; break; }
process(row, col);
}
if (skip) { skip = false; continue; }
}
// After: 内側の判定を関数に抽出し、二重ループの構造をシンプルに保つ
for (const row of rows) {
if (!processRow(row, cols)) continue; // 判定と処理を1関数に集約
}
```
複雑化したループ制御用のフラグ変数を、内側の処理を独立した関数に抽出することで解消する構造。フラグの状態管理そのものをコードから消し去る。
**使いどころ**: 二重・三重ループの中でフラグ変数が2つ以上登場し、どのフラグがどのループを制御しているか読み取りにくくなった場合に用いる。
**組合せ例**: KNOW-F-0148(ラベル付きbreak)はフラグ変数を使わずに多重脱出する代替手段であり、本項目の関数抽出と組み合わせるとさらに読みやすくなる。KNOW-F-0188(単一責任維持)の考え方をループ制御に適用したものである。
**確認事項(罠と検算)**: フラグのリセット漏れ(この例のskip = falseを書き忘れる)があると、次の外側ループの周回に前回のフラグの状態が持ち越されてしまう罠がある。Before/Afterで同じ入力に対する出力が完全に一致するかを、フラグが立つケースと立たないケースの両方で検算する。
---
### KNOW-F-0245 状態+テーブル駆動のハイブリッド設計
**構造の型**:
```js
// 状態機械(KNOW-F-0206)とテーブル駆動(KNOW-F-0226)を組み合わせる
const STATE_HANDLERS = {
IDLE: { table: IDLE_TRANSITIONS, onEntry: null },
RUNNING: { table: RUNNING_TRANSITIONS, onEntry: startTimer },
};
```
第四章の状態機械の枠組みと、第五章のテーブル駆動法を組み合わせ、状態ごとに固有の遷移テーブルやハンドラを持たせる構造。状態の数が多く、かつ各状態の遷移ルールも多い場合に両方の技法を重ねて使う。
**使いどころ**: KNOW-F-0206の単一の巨大なテーブルが、状態ごとにグループ化した方が見通しがよくなるほど大きくなった場合に用いる。
**組合せ例**: KNOW-F-0207(階層状態機械)とも近い発想だが、階層構造(親子関係)を持たせるか、単に状態ごとにテーブルを分けるだけかで使い分ける。
**確認事項(罠と検算)**: 状態ごとに分割したテーブルの間で、同じイベント名が異なる意味で使われていないか(命名の一貫性)を検算する。KNOW-F-0214の全遷移網羅テストが、分割後も引き続き実行できる構成になっているかを確認する。
---
### KNOW-F-0246 Nullオブジェクトとストラテジーの併用
**構造の型**:
```js
const NULL_STRATEGY = { execute: () => ({ ok: false, reason: "戦略未設定" }) };
function run(strategyName) {
const strategy = strategies[strategyName] ?? NULL_STRATEGY; // 未知の名前でも安全に動く
return strategy.execute();
}
```
KNOW-F-0228(ストラテジーパターン)で戦略名が見つからない場合の既定値として、KNOW-F-0192(Null Objectパターン)を使い、ガード節(未知の名前をエラーにする分岐)そのものを不要にする構造。
**使いどころ**: KNOW-F-0228の「不正な戦略名で例外になる罠」を、例外を投げる代わりに安全な既定動作で受け止めたい場合に用いる。
**組合せ例**: KNOW-F-0192と本項目は「nullチェックを消す」という同じ目的を、ストラテジーパターンという具体的な文脈に適用したものである。
**確認事項(罠と検算)**: NULL_STRATEGYが返す{ok: false, ...}を呼び出し側が正しく確認せず、あたかも成功したかのように後続処理を進めてしまう罠がある。呼び出し側でreturn値のokを必ずチェックしているかをKNOW-F-0180の早期return規律で検算する。
---
### KNOW-F-0247 分岐削減のためのデータ正規化(前処理で条件を減らす)
**構造の型**:
```js
// Before: 表記ゆれを吸収するための分岐が多数
function isYes(input) {
return input === "yes" || input === "Yes" || input === "YES" || input === "y";
}
// After: 前処理で正規化してから、分岐は1本に
function normalize(input) { return input.trim().toLowerCase(); }
function isYesNormalized(input) { return ["yes", "y"].includes(normalize(input)); }
```
分岐が増える根本原因(データの表記ゆれ・不揃いな形式)を、分岐そのものではなく入力を正規化する前処理で解消する構造。分岐を書く前にデータを揃えるという発想の転換。
**使いどころ**: 大文字小文字・全角半角・前後の空白など、本質的には同じ意味のデータが複数の見た目で入ってくる場面全般に用いる。
**組合せ例**: KNOW-F-0195(検証層の分離)と同様に、正規化を専用の関数に切り出すことで、本処理側の分岐(isYesNormalized)を単純に保てる。KNOW-F-0226(テーブル駆動法)のキーも、正規化してから引くことで表記ゆれによる検索漏れを防げる。
**確認事項(罠と検算)**: 正規化の範囲が不十分(全角スペースを考慮していない等)だと、想定した表記ゆれの一部だけが解消され、残った分岐が結局必要になる罠がある。実際に想定される表記ゆれの全パターンを洗い出し、正規化後にすべて同じ値になるかを検算する。
---
### KNOW-F-0248 ルール駆動エンジン(条件-アクション表)
**構造の型**:
```js
const RULES = [
{ when: (order) => order.total > 10000, then: (order) => ({ ...order, discount: 0.1 }) },
{ when: (order) => order.itemCount >= 5, then: (order) => ({ ...order, freeShipping: true }) },
];
function applyRules(order) { return RULES.reduce((acc, rule) => rule.when(acc) ? rule.then(acc) : acc, order); }
```
「条件(when)」と「その条件が真のときの処理(then)」の組をルールとしてデータ化し、全ルールを順に適用していく構造。ビジネスルールが頻繁に追加・変更される領域で、ルールの追加をコードの分岐追加からデータの追加へ移す。
**使いどころ**: 割引条件・承認条件など、業務側の都合でルールが増減しやすい処理で、エンジニア以外もルールの一覧を読めるようにしたい場合に用いる。
**組合せ例**: KNOW-F-0186(ガード節のテーブル化)・KNOW-F-0209(ガード付き遷移)と同じ「条件+処理をデータ化する」発想の集大成であり、KNOW-F-0135(累積ループ)のreduceを使って全ルールを順に適用する点で両者が接続する。
**確認事項(罠と検算)**: ルールの適用順序によって最終結果が変わる場合(この例では割引と送料無料は独立だが、複数のルールがdiscountを上書きし合う場合など)、順序依存性(KNOW-F-0163終章の「合成は非可換」と同じ性質)を意図的に設計したか、あるいは順不同でも同じ結果になるよう設計したかを検算する。
---
### KNOW-F-0249 分岐削減とパフォーマンスのトレードオフ判断
**構造の型**:
```js
// 判断チェックリスト(擬似コード)
// 1. 分岐の数は本当に多いか(3個未満ならテーブル化のコストが上回ることが多い)
// 2. テーブル検索(オブジェクトアクセス)は素朴なif-elseより本当に速いか(実測)
// 3. 可読性は本当に改善するか(表が巨大化して逆に追いにくくならないか)
```
テーブル駆動法・多態・ストラテジー等への書き換えが常に優れているわけではなく、分岐の数が少ない場合はむしろ素朴なif-elseの方が読みやすく速いことがある、というトレードオフを判断する構造。
**使いどころ**: 本章の技法(KNOW-F-0226〜0248)を適用する前に、そもそも適用する価値があるかを見極める最終判断として用いる。
**組合せ例**: 第二章末尾のKNOW-F-0175(再帰の限界)、第四章末尾のKNOW-F-0225(状態機械から早期returnへの逆変換)と同じ位置づけの「やりすぎを戒める」項目であり、本章の技法をむやみに適用しないための歯止めになる。
**確認事項(罠と検算)**: 「テーブル駆動の方がモダンだから」という理由だけで、分岐が2つしかない単純なif-elseまでテーブル化してしまう過剰適用の罠がある。書き換え前後で実際に可読性(第三者が読んで理解する速さ)と実行速度の両方を比較し、書き換えの利益が労力に見合うかを検算する。
---
### KNOW-F-0250 分岐削減リファクタリングの手順(テスト→抽出→置換)
**構造の型**:
```js
// 手順(擬似コード):
// 1. 対象関数の現在の入出力を網羅するテストを先に書く(KNOW-F-0189/0214/0243)
// 2. 分岐先の処理を、副作用を持たない小関数として抽出する
// 3. 分岐の判定部分をテーブル・多態・ストラテジーのいずれかに置き換える
// 4. 手順1のテストが全て変わらず通ることを確認する
```
第五章全体の技法を安全に適用するための標準手順を、「先にテストを固定してから構造を変える」という順序で型化したもの。第三章KNOW-F-0190(ガード節チェーンのリファクタリング順序)の考え方を、テーブル駆動・多態への置換にも拡張する。
**使いどころ**: 本章の技法(テーブル駆動・多態・ストラテジー等)をどれか選んで既存コードに適用する、あらゆる場面の最終手順として用いる。制御と分岐の型125種(KNOW-F-0126〜0250)全体の締めくくりの項目でもある。
**組合せ例**: 第一章のループ、第二章の再帰、第三章の早期return、第四章の状態機械のいずれをリファクタリングする場合も、本項目の「テスト→抽出→置換→再テスト」という手順自体は共通して使える。KNOW-F-0150(ループ⇄再帰の等価性確認)以来、本冊全体が「変換前後で入出力が一致することを検算する」という同じ規律を繰り返してきたことの総括に当たる。
**確認事項(罠と検算)**: 手順1のテストを飛ばしていきなり構造を変えると、書き換え後に挙動が変わっても気づけない罠がある。手順4で「テストが全て変わらず通った」ことを実際に実行して確認し、もし1つでも失敗したら手順3を疑って差し戻す、というテスト結果に基づく検算を必ず経由する。
## 終章 5系統の統合——自動販売機のミニ実演
ここまでの125種(KNOW-F-0126〜0250)は、いずれも単体では1つの配線パターンにすぎない。しかし実際のプログラムでは、ループの中に状態機械があり、状態機械の遷移にガード節があり、遷移テーブルがテーブル駆動法で書かれる、というように複数の型が積み重なって使われる。本章では簡易な自動販売機(コインを入れると商品が出る)のミニ実装を通じて、5系統の統合を具体的に確認する。
### 終-1. 設計——使う型の一覧
自動販売機は次のように定義する。状態はIDLE(待機)・SELECTING(選択受付中)・DISPENSING(排出中)の3つ。コインが投入されるたびにイベントCOININSERTEDが発生し、投入金額が商品価格に達したらDISPENSING、商品ボタンが押されたらそれに応じた排出処理を行う。
```js
// KNOW-F-0232(列挙型)+KNOW-F-0201(状態遷移表の設計)
const State = Object.freeze({ IDLE: "IDLE", SELECTING: "SELECTING", DISPENSING: "DISPENSING" });
// KNOW-F-0226(テーブル駆動法): 商品コードと価格の対応表
const PRICE = { COLA: 120, TEA: 100, WATER: 90 };
// KNOW-F-0206(遷移テーブル駆動実装)
const TRANSITIONS = [
{ from: State.IDLE, event: "COIN_INSERTED", to: State.SELECTING },
{ from: State.SELECTING, event: "COIN_INSERTED", to: State.SELECTING },
{ from: State.SELECTING, event: "SELECT", to: State.DISPENSING,
guard: (ctx) => ctx.inserted >= PRICE[ctx.pendingItem] },
{ from: State.DISPENSING, event: "DONE", to: State.IDLE },
];
```
ここまでで、KNOW-F-0232(列挙型)、KNOW-F-0226(テーブル駆動法による価格表)、KNOW-F-0201・KNOW-F-0206(状態遷移表の設計とテーブル駆動実装)、KNOW-F-0209(ガード付き遷移、投入金額が価格以上かの条件)という4つの項目がすでに1つの設計の中に同居している。
### 終-2. 実装——ガード節・早期return・遷移関数
```js
function transition(ctx, event, payload) {
// KNOW-F-0179(範囲外入力の早期return)相当: 未知の商品コードは弾く
if (event === "SELECT" && !(payload in PRICE)) return { ...ctx, error: "ERR_UNKNOWN_ITEM" };
const row = TRANSITIONS.find(t => t.from === ctx.state && t.event === event);
// KNOW-F-0210(不正遷移検出): 表に無い組は状態を維持しログのみ
if (!row) { console.warn(`不正な遷移: state=${ctx.state}, event=${event}`); return ctx; }
if (row.guard && !row.guard(ctx)) return { ...ctx, error: "ERR_INSUFFICIENT" }; // KNOW-F-0176相当
const nextCtx = { ...ctx, state: row.to };
if (event === "COIN_INSERTED") nextCtx.inserted = (ctx.inserted ?? 0) + payload; // KNOW-F-0135(累積ループ相当の逐次累積)
if (event === "SELECT") nextCtx.pendingItem = payload;
return nextCtx;
}
```
`transition`関数の内部には、KNOW-F-0179(範囲外入力の早期return)、KNOW-F-0210(不正遷移検出)、KNOW-F-0176(ガード節、ここではguardの不成立を早期の形でreturnする形に写している)、KNOW-F-0135(累積、コイン投入額の逐次加算)がすべて含まれている。関数自体は早期returnの連続(第三章)として書かれており、状態機械(第四章)の1回の遷移判定を担っている。
### 終-3. 呼び出し側——ループとイベント駆動
```js
function runSession(events) { // events: [{event, payload}, ...]
let ctx = { state: State.IDLE, inserted: 0 };
for (const { event, payload } of events) { // KNOW-F-0129(イテレータ走査)
const before = ctx.state;
ctx = transition(ctx, event, payload);
console.log(`[VM] ${before} --(${event})--> ${ctx.state}`); // KNOW-F-0217(状態機械のログ)
if (ctx.error) { console.warn(`[VM] エラー: ${ctx.error}`); continue; } // KNOW-F-0196(continue使い分け)
if (ctx.state === State.DISPENSING) {
console.log(`[VM] ${ctx.pendingItem}を排出`);
ctx = transition(ctx, "DONE"); // 排出完了で自動的にIDLEへ戻す
}
}
return ctx;
}
```
呼び出し側`runSession`は、イベント列をKNOW-F-0129(for-ofのイテレータ走査)で1つずつ処理する単純なループであり、この中でKNOW-F-0217(状態遷移のログ出力)・KNOW-F-0196(continueによる読み飛ばし、エラー時に次のイベントへ進む)が使われている。
### 終-4. 実測トレース
以下の入力で実際に走らせた結果を示す(手計算によるトレースで、実装の各行と対応づけて確認できる)。
| 手順 | event | payload | 遷移前state | 遷移後state | inserted | 備考 |
|---|---|---|---|---|---|---|
| 1 | COIN_INSERTED | 100 | IDLE | SELECTING | 100 | KNOW-F-0201の1行目 |
| 2 | COIN_INSERTED | 50 | SELECTING | SELECTING | 150 | KNOW-F-0201の2行目(自己遷移) |
| 3 | SELECT | "TEA" | SELECTING | (guard: 150>=100→真) DISPENSING | 150 | KNOW-F-0209のguardが通過 |
| 4 | DONE | - | DISPENSING | IDLE | 150 | 排出完了、KNOW-F-0201の4行目 |
手順3のguardは`ctx.inserted >= PRICE["TEA"]`すなわち`150 >= 100`であり真になるため、DISPENSINGへ遷移する(KNOW-F-0209の構造通り)。もし手順1・2の投入額の合計が100未満(例えば50円だけ)であればguardは偽のまま状態はSELECTINGに留まり、KNOW-F-0180のエラーコード(`ERR_INSUFFICIENT`)がctx.errorに設定される——実際に`inserted=50`でSELECTイベントを送るテストを実行すると、`ctx.error === "ERR_INSUFFICIENT"`かつ`ctx.state === "SELECTING"`のままであることが確認できる(実測: 一致)。
### 終-5. リファクタリングの視点——本冊全体の総括
この自動販売機の例に、もし「投入金額の判定」や「排出処理」がif-elseの深いネストで書かれていたら、KNOW-F-0250(分岐削減リファクタリングの手順: テスト→抽出→置換→再テスト)の手順に従い、まず終-4のようなトレース表をテストケース化し(KNOW-F-0214、全遷移網羅)、次に判定ロジックをguard関数として抽出し(KNOW-F-0209)、最後にテーブル駆動(KNOW-F-0206)へ置き換えて、再びテストが全て通ることを確認する、という一本道を辿ることになる。
これは第一章から第五章までの125項目が独立した技法の寄せ集めではなく、「入力を受け取り、条件を判定し、状態を更新し、次の入力を待つ」という1つの反復構造(ループ)の中に、再帰的な部分問題(分割統治的な検証)・早期return(ガード節)・状態機械(遷移表)・分岐削減(テーブル駆動)のいずれもが、必要に応じて部品として差し込まれる関係にあることを示している。KNOW-F-0001〜0125(前巻BOOK-0358k、関数設計の基本形)が「1つの関数をどう正式に組むか」を扱ったのに対し、本冊は「その関数の中身の配線をどう選ぶか」を扱った。次巻BOOK-0358m(データ変換の型、KNOW-F-0251〜0375)では、ここで組んだ制御構造の中を実際に流れる値——配列操作・写像/畳み込み・文字列・数値精度——を扱う。KNOW-F-0135(累積ループ)やKNOW-F-0248(ルール駆動エンジンのreduce)はすでにその先取りであり、制御の型とデータ変換の型は次巻で改めて接続される。
### 終-6. 罠の分類表——125項目の確認事項を横断整理
各項目の確認事項(罠と検算)を読み返すと、表面上は125通りに見える罠が、実はいくつかの少数の「型」に整理できることに気づく。以下は本冊全体を通じて繰り返し現れた罠のカテゴリを、代表項目とともに横断的にまとめたものである。個々の項目を読むだけでは見えにくい共通構造を、後から通読することで確認できるようにする狙いがある。
**カテゴリ1: 境界の1個ずれ(オフバイワン)**。KNOW-F-0132(オフバイワン回避)を筆頭に、KNOW-F-0126(forの`<`と`<=`)、KNOW-F-0140(逆順走査の初期値)、KNOW-F-0143(スライディングウィンドウの窓の先頭)、KNOW-F-0179(範囲外入力の境界)など、添字や区間の端点を1つ間違える罠が最も多く繰り返し現れる。対策は共通して「n=0, n=1, n=2の3点で手計算し、意図した回数と一致するかを検算する」という同じ手順に帰着する。
**カテゴリ2: 更新・後始末の書き忘れ**。KNOW-F-0127(whileの更新式忘れによる無限ループ)、KNOW-F-0152(再帰ステップの縮小忘れ)、KNOW-F-0166(バックトラッキングの取り消し忘れ)、KNOW-F-0185(finallyでの後始末忘れ)、KNOW-F-0220(setIntervalのクリア忘れ)がこのカテゴリに属する。いずれも「処理の対になる操作(更新⇄初期化、選択⇄取り消し、開始⇄終了)が必ずペアで存在するか」をコード上で目視確認する検算が有効である。
**カテゴリ3: 既定値・フォールバック漏れ**。KNOW-F-0133(番兵の衝突)、KNOW-F-0135・KNOW-F-0158(累積の初期値が単位元でない)、KNOW-F-0192(Null Objectの網羅漏れ)、KNOW-F-0226・KNOW-F-0230(テーブルに無いキーへの既定値忘れ)、KNOW-F-0234(ルックアップテーブルの範囲外)がここに含まれる。「表や既定値が全ての入力パターンをカバーしているか」を、正常系だけでなく境界・異常系を含めて検算する必要がある。
**カテゴリ4: 順序依存性の見落とし**。KNOW-F-0183(ガード節の順序)、KNOW-F-0208(onEntry/onExitの順序)、KNOW-F-0233(デコレーターを重ねる順序)、KNOW-F-0248(ルールの適用順序)が該当する。BOOK-0184終章で確認した「合成は一般に非可換である」という性質がここでも一貫して現れており、順序を入れ替えたときに結果が変わるかどうかを実際に入れ替えて確認する検算が共通して有効である。
**カテゴリ5: 計算量の見落とし(見た目は正しいが遅い)**。KNOW-F-0155・KNOW-F-0164(木構造再帰・二重再帰の指数爆発)、KNOW-F-0136(二重ループのO(n²))が該当する。出力が正しいことと処理が実用的な速さで終わることは別の検証軸であり、想定される最大入力サイズで実際に実行時間を測る実測が唯一の確実な検算になる。
**カテゴリ6: 静かな握りつぶし(検出せずに無視してしまう)**。KNOW-F-0191(deadコード・return漏れ)、KNOW-F-0210(不正遷移の無視)、KNOW-F-0229(未知コマンドの無視)が該当する。エラーを起こさず動き続けてしまうがゆえに発見が遅れる罠であり、KNOW-F-0198(早期returnのログ出力)やKNOW-F-0217(状態機械のログ)のように、意図的にログや警告を残す設計が対策になる。
この6カテゴリは相互排他ではなく、1つの項目が複数のカテゴリにまたがることもある(例えばKNOW-F-0226はカテゴリ3にも第五章の主題そのものにも関わる)。しかし個々の罠を暗記するのではなく、「今書いているコードはどのカテゴリの罠に該当しうるか」という6つの問いを検算の出発点にすることで、未知のコードに対しても同じ検算の型を適用できるようになる——これが第一章から第五章までの125項目を貫く、本冊のもう一つの読み方である。
---
## 参考: 本冊の構成情報
- 正式登録項目数: 125種(KNOW-F-0126〜0250の全件に①名称②構造の型③使いどころ④組合せ例⑤確認事項の五点セットを完備、白カード0)
- 章構成: 第一章 ループの型(KNOW-F-0126〜0150・25項目) / 第二章 再帰の型(KNOW-F-0151〜0175・25項目) / 第三章 早期returnとガード節(KNOW-F-0176〜0200・25項目) / 第四章 状態機械の型(KNOW-F-0201〜0225・25項目) / 第五章 分岐削減の技(KNOW-F-0226〜0250・25項目)
- 機械的検証: 全125項目の見出しIDを抽出し、KNOW-F-0126〜0250の連番に欠番・重複がないことをgakumon/check_book0358l_index.jsで機械確認済み(node実行、ALL CHECKS PASSED)。
- 相互リンク: →型見本BOOK-0184(関数パターン目録I) /→物語脊椎対BOOK-0358f(関数を正式に組む) /→前巻BOOK-0358k(KNOW-F-0001〜0125) /→次巻BOOK-0358m(KNOW-F-0251〜0375、データ変換の型)。
- 次巻予告: 第III巻(BOOK-0358m)では、本冊で組んだ制御構造(特にKNOW-F-0135累積・KNOW-F-0248ルール駆動)の内部を流れる配列操作・写像/畳み込み・文字列処理・数値精度の技法を扱う見通しである。
# BOOK-0358l PC創造大全 目録II 制御と分岐の型