※小説ではない※専門書 要約資料集 為替(換算)3.9万円でもらう 紐解集生成 専門 初入門 資料   作:{作者名}

318 / 382
# BOOK-0358f PC創造大全 第6部 — 関数を正式に組む

> 学問の宇宙・統合大型巻 BOOK-0358『PC創造大全』(全22部+総論・§16.22完全性優先)の第6部(物語脊椎f)。ガイド役: Fable 5 監修 / Sonnet 5 執筆(脚本班)。
> トーン規約: GAKUMON_UNIVERSE.md準拠。専門用語は初出で必ず説明する。誇大表現は避け、「必ず〜できる」でなく「〜できる場所まで行ける」という言葉づかいを守る。
> **安全枠(§16.18・絶対厳守)**: 本冊が示すコード例はすべて言語非依存の擬似コードか、外部ライブラリに依存しないJavaScriptに限定する。特定の開発環境の構築手順・特定製品の操作手順は扱わない。示す数値・実行結果はすべて本冊内で自己検算できる範囲にとどめる。
> 接続先: →BOOK-0184『関数パターン目録I』(関数の構造パターン200種・数学関数としての体系。本冊が扱う「関数」はプログラムの関数だが、恒等関数・合成といった基礎語彙は0184と共有する)、→BOOK-0341『自分の言語を作る』第1巻(構文解析・意味論という、関数が実行される仕組みの内側)、→BOOK-0343『情報構造の設計』(配列・木・グラフという、関数が受け取り・返すデータの入れ物)、→BOOK-0358e『熱と電源の解析』(前部・予約番号のみ確定)、→BOOK-0358g『OSを作る実践』(次部・予約番号のみ確定。本冊で学ぶ関数の契約・純粋性・モジュール化は、OSのカーネル関数を安全に書くための直接の土台になる)、→BOOK-0358k〜n『関数ノウハウ目録』(KNOW-F-0001〜0500・本部の考え方を五点セットの即戦力の型として展開する対の目録)。
> 水準: 五〜八(関数契約を正式な書式で書けるようになる入口から、純粋関数と副作用の切り分け、参照透過性と冪等性の実務利用、関数合成とモジュール化、境界値・回帰テストの設計、やってはいけない結合の診断までを扱う)。

---



# BOOK-0358f PC創造大全 第6部 — 関数を正式に組む

# BOOK-0358f PC創造大全 第6部 — 関数を正式に組む

 

> 学問の宇宙・統合大型巻 BOOK-0358『PC創造大全』(全22部+総論・§16.22完全性優先)の第6部(物語脊椎f)。ガイド役: Fable 5 監修 / Sonnet 5 執筆(脚本班)。

> トーン規約: GAKUMON_UNIVERSE.md準拠。専門用語は初出で必ず説明する。誇大表現は避け、「必ず〜できる」でなく「〜できる場所まで行ける」という言葉づかいを守る。

> **安全枠(§16.18・絶対厳守)**: 本冊が示すコード例はすべて言語非依存の擬似コードか、外部ライブラリに依存しないJavaScriptに限定する。特定の開発環境の構築手順・特定製品の操作手順は扱わない。示す数値・実行結果はすべて本冊内で自己検算できる範囲にとどめる。

> 接続先: →BOOK-0184『関数パターン目録I』(関数の構造パターン200種・数学関数としての体系。本冊が扱う「関数」はプログラムの関数だが、恒等関数・合成といった基礎語彙は0184と共有する)、→BOOK-0341『自分の言語を作る』第1巻(構文解析・意味論という、関数が実行される仕組みの内側)、→BOOK-0343『情報構造の設計』(配列・木・グラフという、関数が受け取り・返すデータの入れ物)、→BOOK-0358e『熱と電源の解析』(前部・予約番号のみ確定)、→BOOK-0358g『OSを作る実践』(次部・予約番号のみ確定。本冊で学ぶ関数の契約・純粋性・モジュール化は、OSのカーネル関数を安全に書くための直接の土台になる)、→BOOK-0358k〜n『関数ノウハウ目録』(KNOW-F-0001〜0500・本部の考え方を五点セットの即戦力の型として展開する対の目録)。

> 水準: 五〜八(関数契約を正式な書式で書けるようになる入口から、純粋関数と副作用の切り分け、参照透過性と冪等性の実務利用、関数合成とモジュール化、境界値・回帰テストの設計、やってはいけない結合の診断までを扱う)。

 

---

 

## 入口の物語 — 順番を入れ替えたら世界が壊れた日

 

ミナは自作のテキストゲームエディタに、プレイヤーが読む「事件簿」を追加しようとしていた。事件簿は複数の段落からなる長い記録で、実行時には各段落に対応する小さな処理――所持金を加算する、フラグを立てる、称号を付与する――が順番に呼び出される仕組みになっていた。ある日、読み物としての流れをよくしようと、ミナは段落の並び順を入れ替えた。文章としては前よりずっと自然に読めるようになった。

 

ところが、いざゲームを起動すると、称号画面に見覚えのない称号が出て、所持金は本来より多く増え、フラグの一部はまったく立っていなかった。ミナは自分が書いた処理コードを一つも変更していない。変えたのは段落の「順番」だけだった。デバッグを進めるうちに分かったのは、称号を付与する処理が「直前に所持金加算処理が実行されたかどうか」というグローバルな変数を勝手に覗いていたこと、所持金加算処理が「1回だけ呼ばれる」という前提のまま作られていて2回呼ばれると二重に加算されてしまうこと、フラグを立てる処理が「初期化処理が先に済んでいる」という誰にも書かれていない前提に依存していたことだった。どの処理も、単体でコードを読む限りは正しく見えた。しかし「順番」という、コードのどこにも書かれていない暗黙の契約に、三つの処理がそれぞれ別の形で依存していたのである。

 

先輩エンジニアに相談すると、こう言われた。「きみが書いた個々の処理は、関数としては未完成なんだ。関数というものは、本来『これを渡せば、これを返す』という約束だけで完結しているべきで、呼ばれる順番やタイミングに結果が左右されてはいけない。順番に依存する関数は、見た目は動いていても、実は土台のない家のようなものだ。土台がないから、家具(段落)の配置を変えただけで傾いてしまう」。

 

先輩はさらに続けた。「逆に言えば、関数を正式な作法で――入力と出力と前提条件と保証をはっきりさせて、隠れた依存を持たせずに――組めば、その関数が呼ばれる段落をどこに動かしても、結果は変わらないはずなんだ。文章の段落を入れ替えても意味が通じる文章があるように、正式に組まれた関数は、置かれる場所を入れ替えても意味が壊れない。これはたとえ話じゃなく、ちゃんと名前のついた設計原則の集まりでできている」。

 

この一言が、本冊の出発点になる。関数契約の正式な書き方から始めて、純粋関数と副作用の区別、参照透過性と冪等性という「順番に負けない」ための二本柱、関数合成とモジュール化という組み立て方、境界値・回帰テストという確かめ方、そして最後にやってはいけない結合(グローバル状態依存・隠れた順序依存)という落とし穴のカタログまで、一段ずつ積み上げていく。

 

---

 

## 解釈表 — ユーザー原文「段落を崩しても通る」を四つの構造原則に翻訳する

 

本大全の設計思想(CATALOG_PC創造大全.md §0請求項5「段落独立請求項」)は、ユーザーが最初に語った言葉を、関数専門書の言葉に翻訳する責任を本冊に置いている。翻訳の手順を誤ると、原文の意図を離れて別物になってしまうため、ここでは**原文を先に示し、解釈は必ずその後に置く**という順番を守る。

 

### 原文(要旨保存・そのまま引用)

 

> 「水準一から十二、即座にosを作れる、コード関数を正式に組める、構造の読み方、**段落を崩しても通る**、などの、関数専門書ノウハウ500+1000(5000kb)級上限なし、この一冊さえ読めば、足し算から、博士号(専門職)として、仕事ができる」

 

### 解釈の方針

 

「段落を崩しても通る」は、日常の言葉としては「文章の段落の並び順を変えても、読んで意味が通じる」ということを指しているように読める。しかし本冊はこの一文を、文章読本としてではなく関数専門書の一部として引き受ける。そこで「文章の段落」を「コードにおける関数・モジュールという一段落」に置き換え、「実行される順序に依存せずに、それでも全体として正しく動く構造」という設計原則の集合として翻訳する。この翻訳は、突き詰めると次の四つの概念に分解できる、というのが本冊の立場である。

 

| # | 原則名 | 一言の定義 | 「段落を崩しても通る」との対応 |

|---|---|---|---|

| 1 | 参照透過性(さんしょうとうかせい) | 同じ入力を渡せば、いつ・どこで・何回呼んでも常に同じ出力が返る性質 | どの「段落」(呼び出し箇所)に処理を置いても意味が変わらない |

| 2 | 冪等性(べきとうせい) | ある操作を1回行っても複数回行っても、結果として得られる状態が変わらない性質 | 「段落」の実行が重複しても、読み違えても壊れない |

| 3 | モジュール独立性 | あるモジュールの内部を変更しても、外部に約束した契約さえ破らなければ、他のモジュールに影響が及ばない性質 | 「段落」を差し替えても、隣の「段落」が無事でいられる |

| 4 | 宣言的記述(せんげんてききじゅつ) | 「どういう手順で行うか」でなく「何が成り立っていればよいか」という結果の性質を記述する書き方 | 「段落」の実行される順序そのものへの依存を、最初から作らない |

 

以下、四つの原則それぞれについて、定義・具体例・反例(やってはいけない書き方)を順に示す。四つの概念は本冊の第二章以降でも繰り返し立ち戻る幹であり、各章の冒頭でどの原則に対応する内容かを明示する(これも「前提リンクを明示すれば単独で読んでも成立する」という段落独立請求項の実践である)。

 

### 原則1 — 参照透過性

 

**定義**: **参照透過性(さんしょうとうかせい、水準五: ある式を、その式が計算した結果の値でそっくり置き換えても、プログラム全体の振る舞いが一切変わらない性質)**を持つ関数は、同じ入力に対して常に同じ出力を返し、しかも呼び出すこと自体が外の世界に何の影響も及ぼさない。

 

**例**: ゲーム内アイテムの合計金額を求める関数を考える。

 

```

関数 calcTotal(items):

合計 = 0

items の各要素 item について:

合計 = 合計 + item.price × item.qty

合計 を返す

 

入力: items = [{name:"回復薬", price:50, qty:3}, {name:"鍵", price:120, qty:1}]

出力: calcTotal(items) = 50×3 + 120×1 = 150 + 120 = 270

```

 

この関数は、`items`という同じ配列を渡す限り、1回目に呼んでも100回目に呼んでも、ゲームの他のどんな状態が変わっていようと、必ず270を返す。呼び出した瞬間に何かのファイルへ書き込む、画面を書き換える、といった外部への影響も一切ない。だからこの呼び出しは`calcTotal(items)`という式そのものを、計算済みの値`270`にそっくり置き換えても、プログラムの意味が変わらない。これが参照透過性である。

 

**反例**: 次の関数は参照透過ではない。

 

```

在庫割引率 = 0.9 ← モジュールの外側にある変数(グローバル状態)

 

関数 calcTotalBad(items):

合計 = 0

items の各要素 item について:

合計 = 合計 + item.price × item.qty × 在庫割引率

合計 を返す

```

 

`calcTotalBad(items)`は、渡す`items`が同じでも、`在庫割引率`という関数の外にある値がいつ書き換えられるかによって、返す値が変わってしまう。式`calcTotalBad(items)`を、ある時点で計算した値にそっくり置き換えてしまうと、後で`在庫割引率`が変わったときに結果が食い違う。これは参照透過性の欠如であり、この関数が呼ばれる「段落」の位置(在庫割引率が書き換えられる前か後か)によって結果が変わってしまう典型例である。

 

### 原則2 — 冪等性

 

**定義**: **冪等性(べきとうせい、水準五: ある操作を1回行った場合と、同じ操作をN回繰り返し行った場合とで、最終的に得られる状態が同一になる性質)**を持つ操作は、実行回数の数え間違いに強い。

 

**例**: セーブデータの保存フォルダを準備する関数を考える。

 

```

関数 ensureSaveFolder(path):

もし path にフォルダが存在しなければ:

フォルダを作成する

(存在すれば何もしない)

```

 

この関数は、1回呼んでも3回呼んでも、最終的な状態(そのフォルダが存在すること)は変わらない。うっかり同じ初期化処理を2回呼んでしまうという事故は、実務では珍しくないが、`ensureSaveFolder`が冪等であれば、その事故は実害を生まない。

 

**反例**: 次の関数は冪等ではない。

 

```

関数 grantWelcomeGold(player):

player.gold = player.gold + 100

```

 

この関数を1回呼べば所持金が100増えるが、誤って2回呼んでしまうと200増えてしまい、3回呼べば300増えてしまう。ミナの事件で実際に起きた「所持金が本来より多く増える」バグは、この種の非冪等な関数が、順番の入れ替えによって想定より多く(あるいは少なく)呼ばれたことが原因だった。

 

### 原則3 — モジュール独立性

 

**定義**: **モジュール独立性(水準六: あるモジュールが外部に公開した契約〈公開インターフェース〉さえ守っていれば、そのモジュールの内部実装をどのように変更しても、他のモジュールの正しさに影響が及ばない性質)**は、詳細を第六章で扱う。

 

**例**: 「割引計算モジュール」が`applyDiscount(items, rate)`という関数だけを公開し、内部でどのような計算式(単純な掛け算か、段階的な割引テーブルか)を使うかを外部に見せない設計にしておけば、内部の計算方式を後からより精密な方式に差し替えても、それを呼び出している他のすべてのコードは無傷のままである。

 

**反例**: 呼び出し側のコードが、割引計算モジュールの内部変数(たとえば割引テーブルの生の配列)を直接読みに行っていると、モジュールの内部実装を変更した瞬間に、呼び出し側のコードまで壊れてしまう。これは公開すべき契約と隠すべき内部実装の境界が曖昧になっている状態であり、「段落」(モジュール)を差し替えると隣の「段落」まで巻き添えで壊れる典型例である。

 

### 原則4 — 宣言的記述

 

**定義**: **宣言的記述(水準六: 処理の手順〈どういう順番で何をするか〉ではなく、成り立つべき結果の性質〈何が真であればよいか〉を記述する書き方)**は、命令的記述(手順を逐一書く書き方)と対をなす。

 

**例**: 「価格が100円以上のアイテムだけを取り出す」という処理を、次のように宣言的に書ける。

 

```

高額アイテム = items.filter(item => item.price >= 100)

```

 

この一行は「どうやって取り出すか」という手順(ループを回す、条件を判定する、配列に積む)を一切書いていない。「price >= 100を満たす要素の集まり」という結果の性質だけを書いている。だからこの一行がプログラムのどこに置かれていても、`items`さえ確定していれば結果は変わらない。

 

**反例**: 同じ処理を命令的に書くと、次のようになる。

 

```

高額アイテム = []

i = 0

items の要素数だけ繰り返す:

もし items[i].price >= 100 なら:

高額アイテム に items[i] を追加する

i = i + 1

```

 

これ自体は誤りではないが、この書き方は「ループ変数`i`が正しく初期化されているか」「途中で`i`を書き換える別の処理が紛れ込んでいないか」といった、手順の細部に依存する隠れた前提を増やしやすい。手順が増えるほど、順番への依存が忍び込む隙間も増える――これが、本冊が宣言的記述を推奨する理由である。

 

### よくある誤解 — 四原則それぞれについて

 

四つの原則は、名前だけを覚えると誤解しやすい。本冊が繰り返し立ち戻る誤解を、ここで先回りして正しておく。

 

**参照透過性についての誤解**: 「参照透過性とは、関数がとにかく何も出力しないことだ」という誤解がある。正しくは、関数が戻り値以外の経路(グローバル変数・ファイル・画面など)で外部に影響を与えないことであり、戻り値を返すこと自体は参照透過性を損なわない。`calcTotal`は270という戻り値を返すが、これは参照透過性と何ら矛盾しない。

 

**冪等性についての誤解**: 「冪等性とは、同じ結果を返す関数のことだ」という誤解がある。正しくは、冪等性は「戻り値」ではなく「操作後の状態」に注目する性質である(第四章コラムで詳述)。`GET`リクエストのように戻り値が毎回同じである必要はなく、状態を変える操作(`PUT`や`DELETE`)であっても、繰り返した後の最終状態が同じであれば冪等と呼べる。

 

**モジュール独立性についての誤解**: 「モジュール独立性とは、モジュール同士が一切やり取りしないことだ」という誤解がある。正しくは、モジュール同士は公開インターフェースを通じて自由にやり取りしてよく、独立性が求められるのは「内部実装の変更が、契約を越えて外部へ漏れ出さないこと」である。

 

**宣言的記述についての誤解**: 「宣言的記述は常に命令的記述より優れている」という誤解がある。正しくは、宣言的記述が有利なのは「結果の性質を書けば済み、手順そのものが本質的でない」場面に限られる。手順そのものが伝えたい主題である場面(第十四章の再帰の説明そのものなど)では、命令的・手続き的な記述の方が理解を助けることもある。

 

四つの原則がひととおり出そろった。ここから先の章は、この四原則を土台にしながら、関数を正式に組むための具体的な技術(契約の書式・純粋性の判定・合成・モジュール化・テスト・禁じ手の診断)を一段ずつ積み上げていく。

 

---

 

## 第一章: 関数契約を正式に書く(水準五)

 

> 対応する解釈表の原則: 全四原則の土台(契約が曖昧だと、参照透過性も冪等性も判定のしようがない)

 

### 契約という考え方

 

橋の設計者が「この橋は何トンまでの荷重に耐える」という仕様を明記するように、関数の作者も「この関数は何を受け取り、何を返し、何が満たされていれば正しく動き、何を約束するか」を明記できる。この考え方を**契約による設計(けいやくによるせっけい、Design by Contract、水準五: 関数やモジュールの振る舞いを、入力側が守るべき約束〈事前条件〉と、関数側が守る約束〈事後条件〉の対として明記する設計手法)**と呼ぶ。1980年代にスイスの計算機科学者バートランド・メイヤーがEiffelというプログラミング言語の設計と共に体系化して広めたとされる考え方であり、契約という言葉自体、法律や商取引における「守るべき約束」という比喩から来ている。

 

**関数契約(かんすうけいやく、水準五: ある関数について、入力・出力・前提条件・保証の四つを明記した約束の記述)**は、次の四要素で構成される。

 

1. **入力(にゅうりょく)**: 関数が受け取る値の名前と型(数値か、文字列か、配列か、といった値の種類)。

2. **出力(しゅつりょく)**: 関数が返す値の型。

3. **前提条件(ぜんていじょうけん、precondition)**: 関数を呼び出す側が、呼び出す前に満たしておくべき条件。守るのは呼び出し側の責任である。

4. **保証(ほしょう、postcondition、事後条件)**: 前提条件が満たされている限り、関数が呼び出された後に必ず成り立っていることを、関数の作者が約束する内容。守るのは関数側の責任である。

 

### 契約の正式な書式

 

本冊では、契約を次の形式で明記する。この書式は特定の言語の構文ではなく、どの言語でも読み替えられる言語非依存の記法である。

 

```

関数契約: calcTotal

入力 : items — {name: 文字列, price: 数値(0以上), qty: 整数(0以上)} の配列

出力 : 数値

前提条件: items の各要素の price は 0 以上、qty は 0 以上の整数であること

保証 : 戻り値は Σ(price × qty) に等しい。items が空配列なら 0 を返す

```

 

この書式が正式であることの意味は、書いた本人以外が読んでも、テストを書く人が読んでも、半年後の自分が読んでも、同じ理解に到達できる点にある。「前提条件」と「保証」を分けて書くことで、責任の所在――バグが起きたときに、呼び出し側の渡し方が悪かったのか、関数の内部実装が悪かったのか――を機械的に切り分けられるようになる。

 

### 検算1 — 前提条件違反と保証違反を区別する

 

`calcTotal`の契約に対して、二種類の失敗を考える。

 

```

ケースA: calcTotal([{name:"謎の巻物", price:-10, qq:2}]) を呼んだ

→ price が -10 で「0以上」という前提条件に違反している

→ これは呼び出し側の責任(前提条件違反)

 

ケースB: calcTotal([{name:"回復薬", price:50, qty:3}]) が 999 を返した

→ 前提条件は満たされているのに、保証(Σ(price×qty)=150)を満たしていない

→ これは関数の内部実装の責任(保証違反、すなわちバグ)

```

 

前提条件と保証を分けて書いていなければ、`calcTotal`が999を返したときに「呼び出し方が悪いのか、実装が悪いのか」を切り分けるのに時間がかかる。契約が正式に書かれていれば、ケースAとケースBは一目で区別できる。

 

### JavaScriptでの契約の表現(擬似コードとの対応)

 

本冊はコード例に外部ライブラリへ依存しないJavaScriptも用いる。契約をコメントとして明記したうえで、前提条件をコードの先頭で明示的に検査する書き方を示す。

 

```

// 関数契約: calcTotal

// 入力 : items — {name: string, price: number(>=0), qty: number(整数, >=0)} の配列

// 出力 : number

// 前提条件: 各要素の price は 0 以上、qty は 0 以上の整数

// 保証 : 戻り値は Σ(price × qty) に等しい。items が空配列なら 0 を返す

function calcTotal(items) {

for (const item of items) {

if (item.price < 0 || item.qty < 0) {

throw new Error("前提条件違反: price と qty は0以上である必要がある");

}

}

let total = 0;

for (const item of items) {

total = total + item.price * item.qty;

}

return total;

}

 

// 検算

const items = [{name:"回復薬", price:50, qty:3}, {name:"鍵", price:120, qty:1}];

console.log(calcTotal(items)); // 270

console.log(calcTotal([])); // 0

```

 

前提条件を関数の内部で検査して例外を投げる書き方は、「守られなかった契約をその場で報告する」という利点がある。ただし検査自体にも実行コストがかかるため、実務では「外部からの入力を受け取る境界(ゲームのセーブデータ読み込み、ユーザー入力の受付など)では検査を厳格に行い、内部で契約が保証済みの値だけをやり取りする箇所では検査を省略する」という使い分けが一般的である。

 

### コラム — 前提条件を弱く、保証を強く

 

契約設計には「前提条件はできるだけ弱く(呼び出しやすく)、保証はできるだけ強く(信頼しやすく)書く」という経験則がある。たとえば`calcTotal`が「itemsの要素数はちょうど10個でなければならない」という前提条件を課していたら、それは不必要に呼び出しにくい関数になる。前提条件を「itemsは配列であること」まで弱めれば、空配列でも100個の配列でも呼び出せる、より汎用性の高い関数になる。一方で保証は「戻り値がΣ(price×qty)に等しい」という具体的な計算式まで踏み込んで明記するほど、呼び出す側は関数の内部を読まずに安心して使える。

 

---

 

> **定着量の目安(第一章)**: 関数契約の四要素(入力・出力・前提条件・保証)を、与えられた関数の説明文から正しく抜き出せるようにするには、契約読解ドリル(短い関数の説明を読み、四要素に分解する)を20問程度こなすと、ほぼ定着すると見込まれる。

 

---

 

## 第二章: 純粋関数と副作用の区別(水準五)

 

> 対応する解釈表の原則: 1(参照透過性)

 

### 純粋関数の定義

 

**純粋関数(じゅんすいかんすう、水準五: 同じ入力に対して常に同じ出力を返し、かつ関数の外の世界〈変数・ファイル・画面・ネットワークなど〉に一切影響を与えない関数)**は、参照透過性(解釈表の原則1)を満たす関数の実務上の呼び名である。前章の`calcTotal`は純粋関数の例だった。純粋関数であるためには、次の二条件を両方満たす必要がある。

 

1. **決定性(けっていせい)**: 同じ入力なら常に同じ出力を返す(内部で乱数や現在時刻を使わない)。

2. **無副作用(むふくさよう)**: 関数の外にある状態を読み書きしない(グローバル変数の書き換え、ファイルへの書き込み、画面表示、ネットワーク通信をしない)。

 

### 副作用の定義とカタログ

 

**副作用(ふくさよう、水準五: 関数を呼び出したこと自体によって生じる、戻り値以外の外部世界への影響)**には、実務でよく出会う典型パターンがいくつかある。

 

```

副作用の型 具体例

------------------------------------------------------------

状態の書き換え グローバル変数・オブジェクトのプロパティの変更

入出力(I/O) ファイルの読み書き・画面への表示・音の再生

通信 ネットワーク経由でのデータ送受信

非決定性の混入 乱数生成・現在時刻の取得

例外の送出 契約違反時にエラーを投げる(第一章の前提条件検査も広義には副作用)

```

 

すべての副作用が「悪」というわけではない。ゲームがプレイヤーに何かを表示する以上、どこかで必ず画面への副作用が発生する。本冊が強調するのは、副作用そのものを禁止することではなく、**純粋な計算と副作用を持つ処理を意図的に分離し、副作用を持つ処理をできるだけ少数の場所に閉じ込める**という設計方針である。

 

### 検算2 — 純粋関数と不純な関数を見分ける

 

三つの関数を比較する。

 

```

関数A: function double(x) { return x * 2; }

→ 決定性あり、外部への影響なし → 純粋関数

 

関数B: let callCount = 0;

function logAndDouble(x) { callCount++; return x * 2; }

→ 呼ぶたびに callCount というモジュール外の変数を書き換える → 不純(状態の書き換え)

 

関数C: function randomBonus(x) { return x + Math.floor(Math.random() * 10); }

→ 同じxを渡しても呼ぶたびに違う値を返す → 不純(非決定性)

```

 

関数Bと関数Cは、どちらも「呼び出す場所(段落)を変えると結果に影響が出る」タイプの不純さを持つ。関数Bは呼び出し回数という隠れた状態に依存し、関数Cは呼び出したタイミングに依存する。

 

### 純粋な核と不純な殻 — サンドイッチ構造

 

実務でよく使われる設計方針に、**純粋な核と不純な殻(じゅんすいなかくとふじゅんなから、水準六: プログラムの中心的な計算ロジックを純粋関数として書き、ファイル入出力や画面表示といった副作用を持つ処理は、プログラムの入口と出口のごく薄い層に押し出す設計方針)**、通称「関数型コア・命令型シェル」と呼ばれる構造がある。

 

```

[不純な殻] セーブデータを読み込む(ファイルI/O)

↓

[純粋な核] calcTotal・applyDiscount・formatReceipt などの純粋関数だけで計算する

↓

[不純な殻] 結果を画面に表示する(画面I/O)

```

 

このサンドイッチ構造の利点は、核の部分(計算ロジック)がどれだけ複雑になっても、純粋関数である限り「同じ入力を渡せば同じ結果が返る」ことがテストで容易に確認できる点にある。殻の部分(I/O)は薄く保つことで、テストしにくい副作用のコードを最小限に抑えられる。

 

### コラム — 「副作用ゼロ」を目指しすぎない

 

まれに、副作用を完全にゼロにしようとして、画面表示やファイル保存までも過度に抽象化し、かえってコードが読みにくくなる例がある。本冊の立場は「副作用を根絶する」ことではなく「副作用の置き場所を意図的に選ぶ」ことである。ゲームの起動処理や終了処理のように、そもそも副作用そのものが目的である箇所まで純粋関数で書こうとするのは、契約の前提条件を不必要に強くしてしまうのと同じ種類の行き過ぎである。

 

---

 

> **定着量の目安(第二章)**: 与えられた短い関数を読んで「純粋/不純」を即座に判定し、不純と判定した場合はどの副作用の型(状態の書き換え・I/O・通信・非決定性・例外送出)に当たるかを言えるようにするには、判定ドリルを30問程度こなすと、ほぼ定着すると見込まれる。

 

---

 

## 第三章: 参照透過性を実務で使う — メモ化と遅延評価(水準六)

 

> 対応する解釈表の原則: 1(参照透過性)の応用

 

### なぜ参照透過性が最適化を安全にするのか

 

参照透過性を持つ関数は「同じ入力なら常に同じ出力」という性質のおかげで、一度計算した結果を保存しておき、次に同じ入力が来たら計算をやり直さずに保存した結果をそのまま返す、という最適化が安全に行える。この技法を**メモ化(めもか、memoization、水準六: 関数の呼び出し結果を、入力をキーとした表〈キャッシュ〉に保存しておき、同じ入力で再び呼ばれたときは再計算せずに保存済みの結果を返す最適化技法)**と呼ぶ。

 

不純な関数(第二章の関数B・関数Cのような)にメモ化を適用すると、呼び出し回数や呼び出しタイミングによって本来変わるはずの結果が、キャッシュのせいで固定されてしまい、誤動作の原因になる。メモ化が安全に使えるのは、参照透過性という土台があってこそである。

 

### 検算3 — メモ化の効果を測る

 

ゲーム内の装備品について、素材の組み合わせから合成後の強さを計算する、計算コストの高い純粋関数`calcSynthesisPower(materials)`があるとする。

 

```

// 関数契約: calcSynthesisPower

// 入力: materials — 文字列の配列(素材名の組み合わせ)

// 出力: 数値

// 前提条件: materials は1個以上の要素を持つ

// 保証 : 同じ組み合わせ(順序を無視した集合として同じ)には常に同じ数値を返す

 

キャッシュ = 空の表

 

関数 memoizedCalc(materials):

キー = materials をソートして連結した文字列

もし キー が キャッシュ に存在するなら:

キャッシュ[キー] を返す(計算をスキップ)

結果 = calcSynthesisPower(materials) ← 重い計算はここでのみ実行

キャッシュ[キー] = 結果

結果 を返す

```

 

仮に`calcSynthesisPower`1回あたりの計算に10ミリ秒かかり、同じ組み合わせが100回問い合わせられるゲーム画面があるとする。メモ化なしでは`10ミリ秒 × 100回 = 1,000ミリ秒`かかるが、メモ化ありでは初回の10ミリ秒だけで済み、残り99回はキャッシュ参照(仮に0.01ミリ秒とする)だけになるため、合計は`10 + 0.01×99 ≈ 10.99ミリ秒`となる。

 

```

検算: 1,000ミリ秒 ÷ 10.99ミリ秒 ≈ 91.0倍

```

 

およそ91倍の高速化になる計算である。この最適化が安全である根拠は、ひとえに`calcSynthesisPower`が参照透過だからであり、もしこの関数が乱数を含んでいたら、メモ化は「本来毎回変わるはずの抽選結果を固定してしまう」という重大なバグに直結する。

 

### 遅延評価との関係

 

**遅延評価(ちえんひょうか、水準六: 値が実際に必要になるまで、その計算を実行せずに先送りする評価戦略)**もまた、参照透過性を前提にした技法である。ある式の計算を後回しにできるのは、いつ計算しても結果が変わらない(参照透過である)という保証があるからこそである。副作用のある式を遅延評価すると、「いつ実行されるか予測できない」という新たな順序依存のバグを生む。ゲームのイベント処理で「本当は先に実行されるべきログ出力が、遅延評価によって後回しにされ、実際のログの時系列が狂う」といった事故は、この典型例である。

 

### コラム — キャッシュ無効化という難問

 

計算機科学には「名前付けとキャッシュ無効化とオフバイワンエラーだけが、計算機科学に残された二つの難しい問題である(named things, cache invalidation, off-by-one errors)」という有名な冗談があるとされる。メモ化は強力だが、元データ(たとえば素材の性能テーブル)が後から変更される場合、古いキャッシュをいつ・どう消すかという設計判断が別途必要になる。本冊の例のように、入力が変わらない限り出力も変わらないという契約が明記されていれば、キャッシュを消すべきタイミングは「その入力に対応する前提条件が変わったとき」だと機械的に判断できる。

 

---

 

> **定着量の目安(第三章)**: メモ化の効果(処理時間の短縮倍率)を、呼び出し回数と1回あたりのコストから計算するドリルを15問程度、参照透過でない関数にメモ化を適用した際に起こりうる不具合を指摘するドリルを10問程度こなすと、ほぼ定着すると見込まれる。

 

---

 

## 第四章: 冪等性の設計(水準六)

 

> 対応する解釈表の原則: 2(冪等性)

 

### 冪等性が守る場面

 

冪等性(解釈表の原則2)は、特に「同じ処理が意図せず複数回実行されるかもしれない」場面で威力を発揮する。ネットワーク越しの通信は途中で応答が届かず、送信側が「失敗したのかもしれない」と判断して同じ要求を再送する(リトライする)ことがある。この再送によって処理が2回実行されても安全であるためには、その処理が冪等でなければならない。

 

### HTTPという実例(通信規約に埋め込まれた冪等性の設計)

 

ウェブの通信規約であるHTTPには、動詞(メソッド)ごとに冪等性の設計が明記されている。

 

```

GET (取得) 冪等 — 同じ資源を何度取得しても状態は変わらない

PUT (置き換え) 冪等 — 同じ内容で何度上書きしても最終状態は同じ

DELETE(削除) 冪等 — 一度消えたものを何度削除要求しても最終状態(存在しない)は同じ

POST (追加・実行) 非冪等 — 同じ要求を2回送ると2件分の副作用(例: 注文が2件作られる)が起きうる

```

 

この設計は、通信が途中で切れて再送が必要になるという、ネットワークの現実的な不確実性を前提にしている。「再送しても安全な動詞(GET・PUT・DELETE)」と「再送に注意が要る動詞(POST)」を区別しておくことで、通信という「順序も回数も完全には制御できない」領域でも、正しさを保てる設計になっている。

 

### 検算4 — 非冪等な処理を冪等に直す

 

第二章で見た`grantWelcomeGold`(呼ぶたびに所持金が100増える、非冪等な関数)を、冪等な形に書き直す。

 

```

// 冪等化前(非冪等)

関数 grantWelcomeGold(player):

player.gold = player.gold + 100

 

// 冪等化後(冪等)

関数 grantWelcomeGoldOnce(player):

もし player.flags に "welcomeGoldGranted" が含まれていなければ:

player.gold = player.gold + 100

player.flags に "welcomeGoldGranted" を追加する

(すでに含まれていれば何もしない)

```

 

`grantWelcomeGoldOnce`を1回呼んでも100回呼んでも、`player.gold`の増分は必ず100だけである。鍵になっているのは「すでに実行済みかどうかを判定できる目印(フラグ)」を状態の側に持たせ、目印があれば処理を素通りさせるという構造である。この構造を**冪等キー(べきとうキー、水準六: 同じ操作が重複して実行されることを検知するために使う、操作ごとに一意な識別子)**と呼ぶこともある。

 

### 検算5 — 冪等でない初期化がもたらす実害

 

ミナの事件簿バグを、冪等性の観点から再検証する。フラグを立てる処理が「初期化処理が先に済んでいる」という前提に依存していたケースを、数値で確認する。

 

```

初期化処理init()を1回だけ呼ぶ設計だったところ、

段落の並び替えにより誤って2回呼ばれてしまったとする。

 

init()の中身: player.inventory = [] (所持品を空配列にリセットする)

 

1回目のinit()呼び出し: player.inventory を空にする(意図通り)

2回目のinit()呼び出し: player.inventory を再び空にする

→ 1回目のinit()の後に追加されていたアイテムが、2回目のinit()で消える

```

 

`init()`が「所持品を空配列にリセットする」という非冪等な操作を含んでいたため、意図しない2回目の呼び出しが、1回目と2回目の間に追加された内容を丸ごと消してしまった。これを冪等にするには、`init()`を「まだ初期化されていなければ空配列にする」という条件付きの形に直せばよい。

 

```

関数 initIdempotent(player):

もし player.inventory が未定義なら:

player.inventory = []

(すでに定義されていれば何もしない)

```

 

### コラム — 冪等性と参照透過性の違い

 

冪等性と参照透過性は似ているが同じではない。参照透過性は「戻り値」に注目する性質(同じ入力なら同じ戻り値)であるのに対し、冪等性は「操作後の状態」に注目する性質(何度実行しても最終状態が同じ)である。純粋関数(第二章)はそもそも外部状態を変更しないため、冪等性の議論自体が意味を持たない(変更する状態がないから、何回呼んでも状態は変わりようがない)。冪等性が本当に問題になるのは、副作用を持つ関数――状態を書き換える関数――に対してである。この違いを整理すると、次の表になる。

 

```

同じ入力なら同じ戻り値か 同じ操作を繰り返しても最終状態は同じか

純粋関数 常に成り立つ 議論の対象外(状態を変更しない)

冪等な副作用関数 必ずしも成り立たない 成り立つ

非冪等な副作用関数 必ずしも成り立たない 成り立たない

```

 

---

 

> **定着量の目安(第四章)**: 与えられた操作(HTTPの動詞・ゲーム内の処理)が冪等かどうかを判定するドリルを20問程度、非冪等な処理を冪等キーやフラグを使って冪等化する書き直しドリルを10問程度こなすと、ほぼ定着すると見込まれる。

 

---

 

## 第五章: 関数合成(水準六〜七)

 

> 対応する解釈表の原則: 1(参照透過性)を土台に、複数の純粋関数を組み立てる

 

### 関数合成という考え方

 

**関数合成(かんすうごうせい、function composition、水準六: ある関数の出力を、別の関数の入力にそのまま渡すことで、二つ以上の関数を一つの新しい関数として組み立てる操作)**は、記号では`(f∘g)(x) = f(g(x))`と書かれる。「gを実行した結果をfに渡す」という意味であり、右から左へ読む慣習に注意が必要である。

 

関数合成が「正式に組まれた関数」にとって重要なのは、合成される個々の関数がそれぞれ契約を持つ純粋関数であれば、合成した結果の関数もまた契約を持つ純粋関数になるという性質があるためである。個々の部品が正しければ、組み立てた全体も自動的に正しくなる――これは電子回路で正しく規格化された部品同士をつなぐ発想(→BOOK-0300第一章)と同型の考え方である。

 

### 検算6 — 三つの純粋関数を合成する

 

第一章から第四章までの例で使ってきた関数群を、次のように合成する。

 

```

関数契約: applyDiscount

入力: items(前章までと同じ型)、rate(数値、0以上1以下)

出力: items と同じ型の新しい配列(元のitemsは書き換えない)

前提条件: 0 <= rate <= 1

保証 : 戻り値の各要素の price は、元の price × (1 - rate) に等しい

 

関数契約: formatReceipt

入力: items(前章までと同じ型)

出力: 文字列

前提条件: なし(空配列も許容)

保証 : 各要素を「名前: 金額」の形式で改行区切りにした文字列を返す

 

合成: receipt = formatReceipt(applyDiscount(items, 0.1))

```

 

`items = [{name:"回復薬", price:50, qty:3}, {name:"鍵", price:120, qty:1}]`に対して`rate=0.1`(1割引)を適用すると、次のように計算できる。

 

```

applyDiscount後: [{name:"回復薬", price:45, qty:3}, {name:"鍵", price:108, qty:1}]

(50×(1-0.1)=45, 120×(1-0.1)=108)

 

formatReceipt後: "回復薬: 45\n鍵: 108"

```

 

`applyDiscount`と`formatReceipt`はそれぞれ独立に契約が確認できる純粋関数であり、その合成`formatReceipt(applyDiscount(items, 0.1))`もまた、同じ`items`と`0.1`を渡す限り、常に同じ文字列を返す純粋な計算になる。

 

### JavaScriptでのパイプライン記述

 

複数の関数を左から右へ順番に適用する書き方を**パイプライン(pipeline、水準七: 複数の関数を、前の関数の出力が次の関数の入力になるように鎖状につなげた処理の流れ)**と呼ぶ。次の例は、外部ライブラリに依存しない素朴な`pipe`関数を自作し、パイプラインとして関数合成を表現する。

 

```

function pipe(...fns) {

return function (initialValue) {

return fns.reduce((acc, fn) => fn(acc), initialValue);

};

}

 

const buildReceipt = pipe(

(items) => applyDiscount(items, 0.1),

formatReceipt

);

 

console.log(buildReceipt(items));

// "回復薬: 45\n鍵: 108"

```

 

`pipe`自体も純粋関数であり、渡された関数群を左から右へ順に適用する新しい関数を返すだけで、副作用を持たない。この`pipe`は高階関数(こうかいかんすう、水準六: 関数を引数として受け取る、または関数を戻り値として返す関数)の一例でもある。

 

### 恒等関数と結合律 — 合成の骨格

 

関数合成には代数的な性質がある。**恒等関数(こうとうかんすう、identity function、水準六: 受け取った値をそのまま返す関数。f(x)=x)**を任意の関数fと合成しても、結果はfのまま変わらない(→BOOK-0184理-KAN-001「恒等関数は合成の単位元である」)。

 

```

identity(x) = x

pipe(identity, f) の結果 = pipe(f, identity) の結果 = f と同じ

```

 

また、三つ以上の関数を合成するとき、どこから先に組み合わせても最終結果は変わらないという**結合律(けつごうりつ、associativity、水準七: (f∘g)∘h と f∘(g∘h) が同じ結果になる性質)**が成り立つ。

 

```

pipe(pipe(f, g), h) の結果 = pipe(f, pipe(g, h)) の結果

```

 

結合律が成り立つということは、三つの関数を「先にfとgをまとめてから、hをつなぐ」形で書いても、「先にgとhをまとめてから、fの後ろにつなぐ」形で書いても、結果が変わらないということである。これはまさに、合成される関数がすべて参照透過であれば、パイプラインという「段落」の組み立て方を変えても意味が壊れない、という段落独立請求項の具体的な現れである。

 

### map・filter・reduce — 配列を対象にした高階関数

 

配列を処理する高階関数として、`map`(各要素を変換する)・`filter`(条件を満たす要素だけを残す)・`reduce`(要素を1つの値に畳み込む)の三つが広く使われる。これらはいずれも宣言的記述(解釈表の原則4)の代表例でもある。

 

```

items.map(item => item.name)

→ ["回復薬", "鍵"] (各要素からnameだけを取り出す)

 

items.filter(item => item.price >= 100)

→ [{name:"鍵", price:120, qty:1}] (price>=100の要素だけ残す)

 

items.reduce((sum, item) => sum + item.price * item.qty, 0)

→ 270 (第一章のcalcTotalと同じ計算をreduceで書き直したもの)

```

 

第一章で手続き的なループとして書いた`calcTotal`は、`reduce`を使えば`items.reduce((sum, item) => sum + item.price * item.qty, 0)`という一行の宣言的記述に書き換えられる。この書き換え前後で契約(入力・出力・前提条件・保証)は一切変わらない――これは、同じ契約を満たす限り実装の書き方(命令的か宣言的か)を自由に選べるという、契約設計の柔軟さを示す好例である。

 

---

 

> **定着量の目安(第五章)**: 二つ以上の純粋関数を合成した結果を手計算で検算するドリルを20問程度、`map`・`filter`・`reduce`のいずれを使うべきかを処理内容から判定するドリルを15問程度こなすと、ほぼ定着すると見込まれる。

 

---

 

## 第六章: モジュール化と独立性(水準七)

 

> 対応する解釈表の原則: 3(モジュール独立性)

 

### モジュールとは何か

 

**モジュール(水準七: 関連する関数やデータをひとまとめにし、外部に公開する部分〈公開インターフェース〉と、外部から隠す部分〈内部実装〉を区別した、コードの独立した単位)**は、関数一つよりも大きな「段落」の単位である。第一章で扱った関数契約が個々の関数の約束であったのに対し、モジュールの契約は「このモジュールが外部に提供する関数群と、それぞれの契約一式」である。

 

### 公開インターフェースと内部実装の分離

 

モジュールを設計するときの核心は、**何を公開し、何を隠すか**を意図的に決めることにある。次の例は、割引計算モジュールを、内部実装を隠したかたちで設計したものである。

 

```

モジュール: DiscountModule

 

【公開インターフェース(契約として保証する部分)】

applyDiscount(items, rate) -> 新しいitems配列

前提条件: 0 <= rate <= 1

保証 : 各要素のpriceが (1-rate) 倍された新しい配列を返す。元のitemsは変更しない

 

【内部実装(外部に見せない部分・自由に変更してよい部分)】

roundToNearestYen(price) ← 端数処理の詳細

discountTable ← 将来、段階的割引に変える際に使う予定の内部テーブル

```

 

内部実装(端数処理の細かい方式や、将来の拡張のために用意した内部テーブル)は、公開インターフェースの契約さえ守っていれば、いつでも自由に書き換えてよい。この「隠す」という行為を**カプセル化(かぷせるか、encapsulation、水準七: モジュールの内部状態や実装の詳細を外部から直接見えないようにし、公開インターフェースを通してのみやり取りできるようにする設計技法)**と呼ぶ。

 

### 検算7 — カプセル化を破るとどうなるか

 

呼び出し側のコードが、公開インターフェースを無視して内部実装(`discountTable`)を直接読みに行く設計をしていたとする。

 

```

// 悪い例: 内部実装に直接依存する呼び出し側

const rawPrice = DiscountModule.discountTable[itemId] * originalPrice;

```

 

この状態で、`DiscountModule`の内部実装者が「discountTableの形式を配列からMapに変更する」というリファクタリング(公開インターフェースの契約は変えない、内部だけの変更)を行うと、公開インターフェースだけを使っている他の呼び出し側は無傷のまま動き続けるが、`discountTable`に直接依存していたこのコードだけが壊れる。これは第六章冒頭で述べた「モジュール独立性」がまさに破られた瞬間であり、モジュールという「段落」を差し替えたら隣の「段落」まで巻き添えで壊れる典型例である。

 

### 依存の向きと循環依存の禁止

 

複数のモジュールを組み合わせるとき、**依存関係(いぞんかんけい、水準七: あるモジュールが、別のモジュールの公開インターフェースを使っているという関係)**の向きを一方向に保つことが望ましい。

 

```

良い依存の向き(一方向・木構造に近い):

UI画面モジュール → 在庫計算モジュール → 基礎ユーティリティモジュール

 

悪い依存の向き(循環依存):

在庫計算モジュール → 割引モジュール → 在庫計算モジュール(!)

```

 

**循環依存(じゅんかんいぞん、水準七: 二つ以上のモジュールが、直接または間接に互いを参照し合っている状態)**が生じると、片方のモジュールを読むためにもう片方を先に理解しなければならず、「単独で読んでも成立する」というモジュール独立性の前提そのものが崩れる。循環依存は、コードを段落として独立に読めなくする、段落独立請求項の観点からは最も避けるべき結合である。

 

### インターフェース分離という指針

 

一つのモジュールが持つ公開インターフェースは、利用する側が本当に必要とする範囲だけに絞るべきだという指針を**インターフェース分離(interface segregation、水準七: 利用者ごとに必要な機能だけをまとめた小さな公開インターフェースに分割し、利用しない機能への依存を強制しない設計指針)**と呼ぶ。たとえば「アイテムを表示するだけの画面」に「割引率を変更する権限つきの巨大なインターフェース」を丸ごと渡してしまうと、表示だけしたいコードが誤って割引率変更の関数まで呼び出せてしまう余地を生む。表示に必要な`formatReceipt`だけを渡す小さなインターフェースにしておけば、誤用の余地そのものが構造的になくなる。

 

### コラム — モジュール分割の単位をどう決めるか

 

「何を1つのモジュールにまとめるべきか」という問いに唯一の正解はないが、実務でよく使われる指針に「一つのモジュールは一つの理由でだけ変更されるべきである(単一責任の原則)」という考え方がある。割引計算モジュールが「割引率の計算方式が変わったから」という理由と「画面の表示レイアウトが変わったから」という理由の両方で変更されるようなら、それは二つの異なる責任が一つのモジュールに同居しているサインである。

 

---

 

> **定着量の目安(第六章)**: 与えられたモジュール構成図から、循環依存が発生している箇所を指摘するドリルを15問程度、公開インターフェースと内部実装を分類するドリルを15問程度こなすと、ほぼ定着すると見込まれる。

 

---

 

## 第七章: 宣言的記述をさらに広げる(水準七〜八)

 

> 対応する解釈表の原則: 4(宣言的記述)

 

### 宣言的記述が現れる場所

 

解釈表の原則4で扱った宣言的記述は、関数の書き方だけの話ではない。実務で広く使われる仕組みの多くが、宣言的記述という同じ考え方の別の姿である。

 

```

仕組み 宣言する内容 隠れている手順

------------------------------------------------------------------------------

SQL(データベース問い合わせ) 「条件を満たす行がほしい」 探索アルゴリズム・実行計画

正規表現 「このパターンに合う文字列がほしい」 状態機械による文字列走査

設定ファイル(JSON/YAML等) 「こういう状態であってほしい」 設定を読み取って反映する処理

関数型プログラミングの合成 「この変換を順に適用した結果がほしい」 ループや再帰の詳細

```

 

これらに共通するのは、「何が成り立っていればよいか」を書く人が、「その状態をどう実現するか」という手順の実装を、宣言的記述を解釈する側(データベースエンジン・正規表現エンジン・設定読み込み処理・合成の実行系)に委ねている点である。手順の実装を委ねられた側は、宣言された結果さえ正しく実現すれば、内部でどんな順序で処理してもよい。この「実装側が順序を自由に選べる」という性質こそ、宣言的記述が段落の並び替えに強い理由である。

 

### 検算8 — 宣言的記述と命令的記述で結果を比較する

 

第五章で扱った`filter`の例を、命令的記述と宣言的記述の両方で書き、同じ結果になることを確かめる。

 

```

宣言的: items.filter(item => item.price >= 100)

 

命令的:

結果 = []

i = 0

items の要素数だけ繰り返す:

もし items[i].price >= 100 なら:

結果 に items[i] を追加する

i = i + 1

 

items = [{name:"回復薬", price:50, qty:3}, {name:"鍵", price:120, qty:1}]

→ どちらの書き方でも結果は [{name:"鍵", price:120, qty:1}] で一致する

```

 

二つの書き方は結果としては一致するが、命令的記述のほうは「ループ変数`i`の初期化」「ループの終了条件」「配列への追加操作」という3つの手順の正しさに個別に依存している。宣言的記述はこれらの手順をすべて`filter`という一語に委ね、書き手が誤る余地をその分だけ減らしている。

 

### 宣言的記述の限界

 

宣言的記述にも限界はある。極端に複雑な条件分岐や、外部の状態を段階的に読みながら判断を変えていくような処理は、無理に宣言的な一行に押し込めようとすると、かえって読みにくくなる。本冊の立場は「宣言的記述を使えるところでは積極的に使い、手順そのものが本質的に重要な場面(たとえばアルゴリズムの教材でアルゴリズムの手順自体を説明したい場合)では、命令的記述を隠さず示す」という使い分けである。これは第一章コラムで述べた「前提条件を弱く」という経験則と同じ発想であり、道具を無理に一種類に絞らないことが、正式に関数を組むうえでの成熟した判断である。

 

### コラム — 宣言的記述と契約の関係

 

宣言的記述と第一章の関数契約は、実は同じことを違う角度から見ている。契約の「保証」欄(戻り値が満たすべき性質)は、それ自体が宣言的な記述である。`calcTotal`の保証「戻り値はΣ(price×qty)に等しい」は、計算の手順(ループを回すのか、reduceを使うのか)には一切触れていない。契約を先に宣言的に書いてから、その保証を満たす実装(命令的でも宣言的でもよい)を後から選ぶ、という順番こそが、本冊が第一章から一貫して勧めている進め方である。

 

---

 

> **定着量の目安(第七章)**: 与えられた命令的な処理を宣言的記述(map・filter・reduce等)に書き換えるドリルを20問程度、宣言的記述が不向きな場面を指摘するドリルを10問程度こなすと、ほぼ定着すると見込まれる。

 

---

 

## 第八章: テストの書き方 — 境界値・回帰(水準八)

 

> 対応する解釈表の原則: 全四原則を検証可能にする(契約を書いても、確かめなければ約束は守られているか分からない)

 

### 契約からテストを導く

 

第一章で書いた関数契約は、そのままテストの設計図として使える。契約の「前提条件」は「どんな入力を試すべきか」を、「保証」は「どんな出力を期待すべきか」を教えてくれる。この対応関係を意識せずに思いつきでテストを書くと、本当に危険な入力(契約の境目)を見落としやすい。

 

### 境界値分析

 

**境界値分析(きょうかいちぶんせき、boundary value analysis、水準八: 前提条件が変わる境目の値〈境界値〉と、その直前・直後の値を重点的にテストする手法)**は、不具合が境目に集中しやすいという経験則にもとづく。第四章の`applyDiscount`の前提条件「0 <= rate <= 1」を例に取る。

 

```

境界値分析の対象:

rate = 0 (下限そのもの)

rate = -0.01 (下限のすぐ外側・前提条件違反となるはず)

rate = 1 (上限そのもの)

rate = 1.01 (上限のすぐ外側・前提条件違反となるはず)

rate = 0.5 (範囲の内側の代表値)

```

 

`rate=0`のとき`applyDiscount`は「割引なし」として元の価格をそのまま返すはずであり、`rate=1`のときは「全品無料」として価格が0になるはずである。この二つの境界は、実装のミス(たとえば`rate=1`のときに0除算が起きる、といった見落とし)が最も現れやすい場所である。

 

### 検算9 — 境界値でapplyDiscountを検算する

 

```

applyDiscount([{name:"回復薬", price:50, qty:3}], 0)

→ price: 50 × (1-0) = 50 (元の価格のまま)

 

applyDiscount([{name:"回復薬", price:50, qty:3}], 1)

→ price: 50 × (1-1) = 0 (全品無料)

 

applyDiscount([{name:"回復薬", price:50, qty:3}], 0.5)

→ price: 50 × (1-0.5) = 25 (半額)

```

 

三つとも保証(「priceが(1-rate)倍された新しい配列を返す」)を満たしている。境界値`rate=-0.01`と`rate=1.01`については、前提条件違反として例外が投げられることを確認する必要がある(第一章のJavaScript例のように、前提条件を関数内部で検査している場合)。

 

### 同値分割

 

**同値分割(どうちぶんかつ、equivalence partitioning、水準八: 入力の範囲を、同じ振る舞いが期待できるグループ〈同値クラス〉に分け、各グループから代表値を1つずつ選んでテストする手法)**は、境界値分析と組み合わせて使われることが多い。`rate`の場合、「0未満(無効)」「0以上1以下(有効)」「1超(無効)」という3つの同値クラスに分け、各クラスから1つずつ(たとえば-0.5、0.5、1.5)を選べば、境界値と合わせて効率よく網羅できる。

 

### 回帰テスト

 

**回帰テスト(かいきテスト、regression test、水準八: コードを変更した後にも、以前は正しく動いていた機能が壊れていないかを確認するために、過去のテストを再実行すること)**は、第六章のモジュール独立性を裏付ける仕組みでもある。モジュールの内部実装を変更したとき、公開インターフェースに対する契約テスト一式を再実行し、全て合格すれば「このモジュールを差し替えても他は無事だった」ことを確認できる。

 

```

回帰テストの典型的な流れ:

1. 変更前のコードで、既存のテスト一式(境界値・代表値を含む)を実行し、全て記録する

2. コードを変更する(内部実装の改善・バグ修正・機能追加)

3. 変更後のコードで、同じテスト一式を再実行する

4. 1と3の結果を比較し、意図した差分以外に食い違いがないか確認する

```

 

ミナの事件簿バグは、もし段落を並び替える前に境界値テスト(所持金加算を2回呼んだ場合のテスト、フラグが未初期化の状態でのテスト)が用意されていたら、並び替えた直後の回帰テストで即座に検出できたはずのバグだった。テストは、契約という「書かれた約束」が実際に守られているかを、変更のたびに機械的に確認する仕組みである。

 

### テストピラミッド

 

**テストピラミッド(水準八: 実行が速く対象範囲の狭い単体テストを土台に多く配置し、実行が遅く対象範囲の広い結合テスト・総合テストを頂点に少なく配置する、テストの構成比率の指針)**は、純粋関数を中心に据えた設計と相性がよい。純粋関数は入力と出力だけで検証できるため、単体テストが書きやすく実行も速い。副作用を持つコードを第二章の「純粋な核と不純な殻」構造で薄い殻に押し出しておけば、テストの大部分を土台の単体テストでまかない、実行の遅い結合テストは殻の部分だけに絞れる。

 

```

△ 総合テスト(少数・遅い・画面全体の動作確認)

△△ 結合テスト(中程度・複数モジュールの組み合わせ確認)

△△△ 単体テスト(多数・速い・個々の純粋関数の契約確認)

```

 

### コラム — テストが書きにくい関数は設計を疑う

 

ある関数のテストを書こうとして、モックやスタブ(本物の代わりに使う偽の依存先)を何段も用意しなければならない場合、それは多くの場合、その関数が副作用や隠れた依存を多く抱えすぎている合図である。「テストが書きにくい」という感覚は、契約が曖昧である、モジュールの独立性が破れている、といった設計上の問題を発見する実務上のセンサーとして機能する。

 

---

 

> **定着量の目安(第八章)**: 与えられた前提条件から境界値の候補を洗い出すドリルを20問程度、同値クラスへの分割ドリルを10問程度、回帰テストで検出できるはずの不具合を事例から見抜くドリルを10問程度こなすと、ほぼ定着すると見込まれる。

 

---

 

## 第九章: やってはいけない結合 — グローバル状態依存・隠れた順序依存(水準八)

 

> 対応する解釈表の原則: 四原則すべての違反パターンを診断可能な形でカタログ化する

 

### なぜ「禁止のカタログ」が必要か

 

第一章から第八章までは「どう組めば正式か」という前向きの技術を積み上げてきた。本章はその裏返しとして、「これをやると段落独立性が壊れる」という結合のパターンを、診断できる形でカタログ化する。ミナの事件簿バグは、実は次に挙げる二つのアンチパターンの組み合わせだった。

 

### アンチパターン1 — グローバル状態依存

 

**グローバル状態(ぐろーばるじょうたい、水準八: 特定の関数の内側に閉じておらず、プログラムのどこからでも読み書きできてしまう共有の変数や領域)**に依存する関数は、参照透過性(原則1)を構造的に持てない。

 

```

【診断チェックリスト・グローバル状態依存】

□ 関数の内部で、引数として渡されていない変数を読んでいないか

□ 関数の内部で、引数として渡されていない変数を書き換えていないか

□ 「この関数を呼ぶ前に、あの変数がこの値になっている必要がある」という

コメントやドキュメントが、コードの外に存在していないか(=書かれていない前提条件)

```

 

第二章の`calcTotalBad`(モジュール外の`在庫割引率`を読む関数)は、このアンチパターンの実例だった。グローバル状態依存を避ける最も直接的な処方箋は、外部から読みたい値をすべて引数として明示的に受け取ることである。これを**依存性の明示化(いぞんせいのめいじか)**と呼ぶこともある。

 

```

// 悪い例(グローバル状態依存)

let discountRate = 0.9;

function calcTotalBad(items) { /* discountRate をどこかで読む */ }

 

// 良い例(依存性を引数として明示)

function calcTotal(items, discountRate) { /* 引数として受け取る */ }

```

 

引数として渡すだけで、`calcTotal`の契約(第一章)には「discountRateを受け取る」という一行が入り、呼び出す側は自分がどんな値を渡しているかを常に把握できる。これだけで、関数がどの「段落」に置かれても、必要な情報はすべて引数という形でその場に揃っている状態になる。

 

### アンチパターン2 — 隠れた順序依存

 

**隠れた順序依存(かくれたじゅんじょいぞん、水準八: 「この処理は、あの処理より先に実行されていなければならない」という前提が、コードのどこにも明記されずに埋め込まれている状態)**は、グローバル状態依存としばしば併発する。

 

```

【診断チェックリスト・隠れた順序依存】

□ ある関数が正しく動くために、別の関数が「先に」呼ばれている必要はないか

□ その「先に呼ばれている必要」は、契約の前提条件として文書化されているか

□ 呼び出し元のコードを並べ替えたら、動作が変わる可能性はないか

□ 初期化処理(init系の関数)が、複数回呼ばれた場合の挙動を規定しているか(第四章の冪等性)

```

 

ミナの事件で、フラグを立てる処理が「初期化処理が先に済んでいる」という前提に依存していたのは、この隠れた順序依存の典型である。処方箋は二つある。第一に、順序への依存が本当に必要なら、それを契約の前提条件として正式に書く(隠さない)。第二に、可能であれば、順序に依存しない設計(たとえば初期化を冪等にする、必要な値を都度引数で渡す)に作り変え、そもそも順序依存を消してしまう。

 

### 検算10 — 二つのアンチパターンを両方取り除く

 

ミナの事件簿の処理を、本章までの技術で修正した最終形を示す。

 

```

// 修正前(グローバル状態依存 + 隠れた順序依存)

let initialized = false;

function initGame(player) { player.inventory = []; initialized = true; }

function grantWelcomeGold(player) { player.gold = player.gold + 100; }

function grantTitle(player) {

if (initialized) { player.titles.push("新人冒険者"); } // グローバル状態initializedに依存

}

 

// 修正後(依存性を明示 + 冪等化)

// 関数契約: initGameIdempotent

// 入力: player

// 出力: player(同じ参照。inventoryが未初期化なら空配列にする)

// 前提条件: player はオブジェクトである

// 保証 : 呼び出し後、player.inventory は配列である。複数回呼んでも既存の中身は保持される

function initGameIdempotent(player) {

if (player.inventory === undefined) { player.inventory = []; }

return player;

}

 

// 関数契約: grantWelcomeGoldOnce (第四章で既出)

// 関数契約: grantTitleOnce

// 入力: player、title(文字列)

// 出力: player

// 前提条件: player.titles は配列である

// 保証 : title が player.titles に含まれていなければ追加する。既に含まれていれば何もしない

function grantTitleOnce(player, title) {

if (!player.titles.includes(title)) { player.titles.push(title); }

return player;

}

```

 

修正後の三つの関数は、`initGameIdempotent`が冪等(第四章)、`grantWelcomeGoldOnce`が冪等(第四章)、`grantTitleOnce`がグローバル状態`initialized`に頼らず`player.titles`という渡された引数だけを見て判断する(依存性の明示化)ようになっている。この三つを呼び出す段落をどんな順序に並べ替えても、最終的な`player`の状態は変わらない。まさに「段落を崩しても通る」状態が、契約・純粋性・冪等性・依存性の明示化という技術の積み重ねによって実現されている。

 

### コラム — シングルトンという罠

 

グローバル状態の変種として、「アプリケーション全体で1つだけ存在するインスタンス」を提供する**シングルトン(singleton、水準八: あるクラスやモジュールのインスタンスが、プログラム全体を通じて常に1つだけ存在するように制限する設計パターン)**がある。一見便利だが、シングルトンは本質的にはグローバル状態の一種であり、複数のテストを並行して実行するときに互いの状態が干渉する、テストの順序によって結果が変わる、といった同種の問題を引き起こしやすい。本冊は「シングルトンを一切使うな」とは主張しないが、使う場合は「なぜグローバルな共有状態が必要なのか」を契約として明記し、その影響範囲を意識的に狭く保つことを勧める。

 

### 禁止パターンの総合チェックリスト

 

```

やってはいけない結合・診断チェックリスト(第九章まとめ)

[1] 関数が、引数に現れない外部の変数を読み書きしていないか(グローバル状態依存)

[2] 関数を呼ぶ順序について、書かれていない前提が存在しないか(隠れた順序依存)

[3] 同じ関数を2回呼んだときの挙動が、契約に明記されているか(冪等性の明記)

[4] モジュールの内部実装に、外部のコードが直接依存していないか(カプセル化の破れ)

[5] 循環依存が存在しないか(モジュール独立性の破れ)

[6] 契約の前提条件・保証が、境界値のテストで裏付けられているか(テストの裏付け)

```

 

このチェックリストの六項目は、そのまま解釈表の四原則(参照透過性・冪等性・モジュール独立性・宣言的記述)と、第一章の契約、第八章のテストへ一対一に対応している。本冊全体の技術は、最終的にこの六項目へ収束するように設計されている。

 

---

 

> **定着量の目安(第九章)**: 与えられたコード片からグローバル状態依存・隠れた順序依存を発見するドリルを25問程度、総合チェックリスト六項目を使って小さなコード例を総合診断するドリルを15問程度こなすと、ほぼ定着すると見込まれる。

 

---

 

## 第十章: エラー処理を契約に組み込む(水準七)

 

> 対応する解釈表の原則: 1(参照透過性)を、失敗が起こりうる現実の中でどう守るか

 

### 「失敗」は契約の外側の出来事ではない

 

第一章では、前提条件が破られた場合を「呼び出し側の責任」、保証が破られた場合を「関数側の責任」として整理した。しかし現実の関数には、前提条件も保証も正しく守られているのに、それでも失敗する場面がある。セーブファイルが破損している、ネットワークが切断されている、ディスクの空き容量が尽きている――こうした失敗は、関数の作者にも呼び出し側にも非がないにもかかわらず起こりうる。**エラー処理(えらーしょり、水準七: 関数が正常に処理を完了できなかった場合に、その事実を呼び出し側へ伝え、後続の処理が誤った前提で進まないようにする仕組み)**は、この「誰の責任でもない失敗」を契約の中に正式に組み込む技術である。

 

### 例外を投げる方式と、値として返す方式

 

エラーを伝える方式には大きく二つの系統がある。

 

```

方式A: 例外(exception)を投げる

正常時: 戻り値をそのまま返す

異常時: throw文などで例外を送出し、呼び出し元の通常の処理の流れを中断する

 

方式B: 失敗を値として返す(Result型・Either型などと呼ばれる)

正常時: {ok: true, value: 結果} のような成功を表す値を返す

異常時: {ok: false, error: エラー情報} のような失敗を表す値を返す

```

 

方式Aは、正常系のコードを読みやすく保てる一方、「どの関数がどんな例外を投げうるか」がコードの見た目だけでは分かりにくいという弱点がある。方式Bは、戻り値の型を見るだけで「この関数は失敗しうる」ということが一目で分かり、呼び出し側に失敗の処理を書くことを型のレベルで強制できるが、正常系のコードに`if (result.ok)`のような分岐が増える。本冊はどちらか一方を絶対視せず、契約にどちらの方式を採用したかを明記することを重視する。

 

### 検算11 — セーブデータ読み込み関数の契約と両方式の比較

 

```

関数契約: loadSaveData

入力: path(文字列、ファイルパス)

出力(方式A・例外): SaveData オブジェクト。読み込みに失敗した場合は例外 SaveLoadError を投げる

出力(方式B・Result型): {ok: true, value: SaveData} または {ok: false, error: 文字列}

前提条件: path は空文字列でないこと

保証(共通): 戻り値または成功時のvalueが得られた場合、そのSaveDataは

playerオブジェクトとinventory配列を必ず含む

```

 

方式Aの実装例(JavaScript)。

 

```

function loadSaveData(path) {

if (path === "") { throw new Error("前提条件違反: pathが空文字列"); }

if (!fileExists(path)) { throw new SaveLoadError("ファイルが見つからない: " + path); }

const raw = readFile(path);

return parseSaveData(raw); // 破損データならここでも例外を投げる想定

}

 

// 呼び出し側

try {

const data = loadSaveData("save1.json");

console.log(data.player.name);

} catch (e) {

console.log("読み込み失敗: " + e.message);

}

```

 

方式Bの実装例(JavaScript)。

 

```

function loadSaveDataSafe(path) {

if (path === "") { return { ok: false, error: "前提条件違反: pathが空文字列" }; }

if (!fileExists(path)) { return { ok: false, error: "ファイルが見つからない: " + path }; }

const raw = readFile(path);

const parsed = tryParseSaveData(raw);

if (parsed === null) { return { ok: false, error: "セーブデータの形式が不正" }; }

return { ok: true, value: parsed };

}

 

// 呼び出し側

const result = loadSaveDataSafe("save1.json");

if (result.ok) {

console.log(result.value.player.name);

} else {

console.log("読み込み失敗: " + result.error);

}

```

 

どちらの方式でも、契約に「失敗しうる」ことと「失敗した場合にどんな情報が得られるか」が明記されている点が重要である。契約に書かれていない失敗(たとえば`loadSaveData`が失敗時に何も返さず、呼び出し元が気づかないまま`undefined`を使い続けてしまう)こそが、本冊が避けたい「隠れた前提」の一種である。

 

### フェイルファストと縮退運転

 

失敗にどう対応するかにも設計方針がある。**フェイルファスト(fail fast、水準七: 異常を検知した時点で、できるだけ早く・明確に処理を停止させる設計方針)**は、契約違反をその場で例外として報告する第一章のスタイルと相性がよい。異常を握りつぶしたまま処理を続けると、原因から遠く離れた場所で別の不可解な不具合として表面化し、デバッグを著しく困難にする。一方、ゲームのようにプレイヤー体験を止めたくない場面では、**縮退運転(しゅくたいうんてん、graceful degradation、水準七: 一部の機能が失敗しても、致命的でない範囲であれば残りの機能を提供し続ける設計方針)**が選ばれることもある。たとえば実績(トロフィー)の記録に失敗しても、ゲーム本編の進行自体は止めない、といった判断である。どちらを選ぶかは契約の一部として明記し、暗黙のままにしない。

 

### コラム — エラーメッセージも契約の一部

 

エラーメッセージの文面や、エラー情報オブジェクトの形式が実行のたびに変わってしまうと、そのエラーを検知して自動的にリトライする、あるいはログを集計するといった後続の処理が、メッセージの文字列を頼りにした脆い実装になりがちである。契約による設計の考え方をエラー処理にまで徹底するなら、エラー情報にも「どんなフィールドを必ず含むか」という保証を与えるべきである。

 

---

 

> **定着量の目安(第十章)**: 与えられた関数の説明から、例外方式・Result型方式のどちらが採用されているかを判定するドリルを15問程度、契約にエラー処理の項目を追記するドリルを10問程度こなすと、ほぼ定着すると見込まれる。

 

---

 

## 第十一章: 不変性とデータの扱い(水準七)

 

> 対応する解釈表の原則: 1(参照透過性)を、データそのものの設計で支える

 

### ミュータブルとイミュータブル

 

**イミュータブル(immutable、水準七: 一度作られたら、その中身を後から書き換えられないデータ)**と、その反対の**ミュータブル(mutable、水準七: 作られた後でも中身を書き換えられるデータ)**という区別は、純粋関数を実現するうえで契約と同じくらい重要な土台になる。第五章の`applyDiscount`の契約は「元のitemsは変更しない」という保証を含んでいた。この保証は、関数の内部実装が新しい配列を作って返す(イミュータブルな扱いをする)ことによってのみ、確実に守られる。

 

### 検算12 — ミュータブルな実装がもたらす思わぬ副作用

 

`applyDiscount`を、うっかり元の配列を直接書き換える形(ミュータブルな実装)で書いてしまった場合を考える。

 

```

// 契約違反の実装(元のitemsを書き換えてしまう)

function applyDiscountMutating(items, rate) {

for (const item of items) {

item.price = item.price * (1 - rate); // 元のオブジェクトを直接書き換える

}

return items;

}

 

const original = [{name:"回復薬", price:50, qty:3}];

const discounted = applyDiscountMutating(original, 0.1);

 

console.log(original[0].price); // 45 ← 元のoriginalまで書き換わってしまっている!

console.log(discounted === original); // true(同じ配列を指している)

```

 

契約は「元のitemsは変更しない」と約束していたのに、実装は`item.price`を直接書き換えている。呼び出し側が「discountedだけが割引後の配列で、originalは元のままのはず」と信じてコードを書いていた場合、この契約違反は静かに、しかし深刻なバグを生む。もし`original`が他の複数の画面で同時に参照されているデータ(たとえばプレイヤーの実際の所持品)だったら、割引表示を計算しただけのつもりが、実際の所持品の価格まで書き換えてしまうという事故になる。

 

### イミュータブルな実装への直し方

 

```

// 契約を守る実装(新しい配列・新しいオブジェクトを作って返す)

function applyDiscount(items, rate) {

return items.map(item => ({

...item, // 元のオブジェクトの中身をコピーし

price: item.price * (1 - rate) // priceだけ新しい値に置き換えた、新しいオブジェクトを作る

}));

}

 

const original = [{name:"回復薬", price:50, qty:3}];

const discounted = applyDiscount(original, 0.1);

 

console.log(original[0].price); // 50(元のまま)

console.log(discounted[0].price); // 45(新しい配列の方だけ変わっている)

console.log(discounted === original); // false(別の配列)

```

 

`{...item, price: ...}`という書き方は、`item`の中身をすべてコピーした新しいオブジェクトを作りながら、`price`フィールドだけを新しい値に差し替えるという意味を持つ。`items.map`自体、元の配列を書き換えずに新しい配列を返す関数として設計されている(第五章)。この実装であれば、`applyDiscount`の契約(元のitemsは変更しない)は構造的に守られる――「守ろうと注意する」のではなく「書き換えようがない構造にする」という点が、イミュータブルなデータ設計の強みである。

 

### 構造共有という効率化

 

イミュータブルなデータは「毎回まるごとコピーするので遅いのではないか」という疑問を持たれやすい。実務では**構造共有(こうぞうきょうゆう、structural sharing、水準八: データの一部だけが変わったとき、変わっていない部分は複製せずに元のデータと共有し、変わった部分だけを新しく作る技法)**によって、この懸念の多くが解消される。第十一章の`applyDiscount`の例で言えば、`{...item, price: ...}`は`item`の`name`や`qty`といった変わらないフィールドの値そのもの(文字列や数値)を新しいオブジェクトにコピーしているが、これは値のコピーであって、巨大なデータ構造を丸ごと複製しているわけではない。より大きな木構造やオブジェクトになるほど、構造共有によって「変わった部分だけ新しく作り、変わらない部分は同じ参照を使い回す」という最適化が効いてくる(→BOOK-0343が扱う木構造・グラフの回でさらに深く扱う話題である)。

 

### コラム — 「凍結」で不変性を強制する

 

JavaScriptには`Object.freeze()`という、オブジェクトを実行時にイミュータブルな状態へ固定する仕組みがある。`Object.freeze(item)`されたオブジェクトのプロパティを書き換えようとすると、書き換えは黙って無視されるか(非厳格モード)、エラーになる(厳格モード)。これは「イミュータブルであるべき」という契約上の約束を、実行時に機械的にチェックする一つの手段であり、第一章で述べた「前提条件を関数内部で検査する」考え方の、データ側への応用である。ただし`Object.freeze()`は浅い(shallow)凍結であり、オブジェクトの中にネストした別のオブジェクトまでは凍結しない点に注意が必要である。

 

---

 

> **定着量の目安(第十一章)**: ミュータブルな実装とイミュータブルな実装を読み比べ、どちらが契約(元のデータを変更しない保証)を守っているかを判定するドリルを20問程度、`{...obj}`によるコピーを使った書き換えドリルを15問程度こなすと、ほぼ定着すると見込まれる。

 

---

 

## 第十二章: 状態機械としてのモジュール設計(水準八)

 

> 対応する解釈表の原則: 2(冪等性)と4(宣言的記述)を、順序そのものが本質的に重要な処理に適用する

 

### 順序をなくせない処理もある

 

本冊はここまで「順序への依存を減らす」ことを繰り返し勧めてきた。しかし、ゲームの進行(タイトル画面→冒険開始→エンディング、という遷移)のように、順序そのものが処理の本質である場合もある。このような場合に「順序依存を消す」ことは目的にならない。代わりに、**その順序を隠さず、宣言的な表として明記する**ことが、段落独立請求項の正しい適用になる。

 

### 状態機械という考え方

 

**状態機械(じょうたいきかん、state machine、水準八: システムが取りうる「状態」の集合と、ある状態から別の状態へ移る「遷移」の集合を、明確な表として定義する設計手法)**は、この目的のために使われる。→BOOK-0341が言語処理系の内部で構文解析に使う状態機械と、本章がモジュール設計に使う状態機械は、同じ数学的な道具を別の場所に応用したものである。

 

### 検算13 — ゲーム進行を状態機械として宣言する

 

```

状態機械契約: GameProgress

 

状態の集合: { "タイトル", "冒険中", "エンディング" }

初期状態 : "タイトル"

 

遷移表:

現在の状態 イベント 次の状態

---------------------------------------

タイトル "開始する" 冒険中

冒険中 "全滅する" タイトル

冒険中 "クリアする" エンディング

エンディング "タイトルへ戻る" タイトル

(表にない組み合わせは、無効な遷移としてエラーを返す)

```

 

この遷移表そのものが宣言的記述(解釈表の原則4)である。「タイトル画面の次に何をすべきか」という手順を書く代わりに、「どの状態でどのイベントが来たら、どの状態になるべきか」という結果の性質だけを、表として宣言している。

 

### JavaScriptでの実装

 

```

// 関数契約: transition

// 入力: currentState(文字列)、event(文字列)

// 出力: 次の状態を表す文字列

// 前提条件: currentState は状態の集合の要素である

// 保証 : 遷移表に定義されている組み合わせなら対応する次の状態を返す。

// 定義されていない組み合わせなら例外 InvalidTransitionError を投げる

const transitionTable = {

"タイトル": { "開始する": "冒険中" },

"冒険中": { "全滅する": "タイトル", "クリアする": "エンディング" },

"エンディング": { "タイトルへ戻る": "タイトル" }

};

 

function transition(currentState, event) {

const nextStates = transitionTable[currentState];

if (nextStates === undefined || nextStates[event] === undefined) {

throw new InvalidTransitionError(currentState + "状態で" + event + "は無効な遷移");

}

return nextStates[event];

}

 

// 検算

console.log(transition("タイトル", "開始する")); // "冒険中"

console.log(transition("冒険中", "クリアする")); // "エンディング"

```

 

`transition`自体は純粋関数である(第二章)。同じ`currentState`と`event`を渡せば常に同じ結果を返し、外部状態を書き換えない。「現在どの状態にいるか」という状態そのものは、この純粋関数の外側(呼び出し側)が保持する責任を負う――これも第二章で扱った「純粋な核と不純な殻」構造の一例であり、`transition`が核、実際に現在の状態を変数に保存して更新する処理が殻にあたる。

 

### 状態機械と冪等性の関係

 

状態機械として明記された遷移表は、冪等性の議論にも役立つ。たとえば「タイトル状態で開始するイベントが誤って2回送られてくる」という事故が起きても、1回目の`transition`呼び出しで状態は「冒険中」に変わり、2回目の`transition("冒険中", "開始する")`は遷移表に定義がないため`InvalidTransitionError`が送出される。これは「勝手に無視して黙って通す」のではなく「定義されていない遷移として正式にエラー報告する」という設計であり、フェイルファスト(第十章)の考え方とも合致する。曖昧なまま2回実行されて状態が壊れるより、エラーとして表面化させる方が、結果的に安全である。

 

### コラム — 状態機械図とモジュールの契約書式の関係

 

第一章で導入した契約の書式(入力・出力・前提条件・保証)と、本章の遷移表は、見た目は違うが同じ役割を果たしている。遷移表の「現在の状態」と「イベント」は契約の「入力」に、「次の状態」は契約の「出力」に、「表にない組み合わせはエラー」は契約の「前提条件」に、それぞれ対応づけられる。状態機械は、順序が本質的に重要な処理について、契約の考え方を絵と表という、より視覚的に把握しやすい形へ翻訳したものだと理解できる。

 

---

 

> **定着量の目安(第十二章)**: 与えられた状態遷移の説明文から遷移表を作成するドリルを15問程度、遷移表をもとに`transition`関数の呼び出し結果を検算するドリルを15問程度こなすと、ほぼ定着すると見込まれる。

 

---

 

## 第十三章: 型による契約の強化(水準八)

 

> 対応する解釈表の原則: 全四原則を、人間が読む契約から機械が確かめられる契約へ引き上げる

 

### 動的型付けと静的型付けの違い

 

JavaScriptのように、値の型(数値か文字列か配列かなど)を実行するまで確定させない言語の性質を**動的型付け(どうてきかたづけ、水準八: 変数や関数の引数がどんな型の値を持つかを、プログラムを実行する時点まで確定させない仕組み)**と呼ぶ。これに対し、コードを実行する前に型の整合性を検査する仕組みを**静的型付け(せいてきかたづけ、水準八: 変数や関数の引数がどんな型の値を持つかを、プログラムを実行する前〈コンパイルや型検査の段階〉に確定させ検査する仕組み)**と呼ぶ。第一章で契約の「入力」「出力」に型を書いてきたが、動的型付け言語では、その型注記はコードを実行しても自動的には検査されない、あくまで人間向けの注釈にとどまる。

 

### JSDocによる型注記 — 外部ライブラリなしで型を明記する

 

外部ライブラリに依存しないという安全枠(冒頭)を守りながら型情報を明記する方法として、JavaScriptの標準的なコメント記法である**JSDoc(じぇいえすどっく、水準八: 関数の引数・戻り値の型や説明を、`/** ... */`という決まった形式のコメントとして関数の直前に書く記法)**がある。

 

```

/**

* 関数契約: calcTotal

* @param {Array<{name: string, price: number, qty: number}>} items

* @returns {number}

* 前提条件: 各要素の price は 0 以上、qty は 0 以上の整数

* 保証 : 戻り値は Σ(price × qty) に等しい

*/

function calcTotal(items) {

return items.reduce((sum, item) => sum + item.price * item.qty, 0);

}

```

 

JSDocコメント自体はJavaScriptの実行に一切影響を与えない、純粋な注釈である。しかしエディタの多くはこの注記を読み取り、関数を呼び出そうとしたときに「引数の型が合っていない可能性がある」という警告を、コードを実行する前の段階で表示できる。これは第八章のテストが「実行してから確かめる」検証であるのに対し、「実行する前に確かめる」検証を部分的に提供する仕組みである。

 

### 検算14 — 型注記が捕まえる典型的な間違い

 

```

calcTotal([{name:"回復薬", price:"50", qty:3}])

```

 

この呼び出しでは`price`が数値の`50`ではなく文字列の`"50"`になっている。JavaScript自体はこの呼び出しをエラーにせず実行してしまい、`"50" × 3`という計算はJavaScriptの型変換規則によって`150`という数値になる(たまたま期待通りの値になる)ため、バグとして気づかれにくい。しかし`price`が`"50円"`のような文字列だった場合、`"50円" × 3`は`NaN`(Not a Number、数値として定義できないことを表す値)になり、契約の保証(戻り値はΣ(price×qty)に等しい)が静かに破られる。JSDocの型注記``@param {Array<{... price: number ...}>}``があれば、エディタは`price: "50"`を渡そうとした時点で型の不一致を警告でき、実行するまで気づけなかった問題を、書いている最中に発見できる可能性が高まる。

 

### Union型的な表現で「複数のありうる型」を明記する

 

第十章のResult型のように、戻り値が「成功時の型」か「失敗時の型」のどちらかになる場合、JSDocでは次のように書ける。

 

```

/**

* @param {string} path

* @returns {{ok: true, value: object} | {ok: false, error: string}}

*/

function loadSaveDataSafe(path) { /* ... */ }

```

 

`{ok: true, value: object} | {ok: false, error: string}`という記法は「戻り値はこの二つの形のどちらか一方である」ということを宣言的に表現している。呼び出し側がこの契約を読めば、`result.ok`を確認せずに`result.value`へ直接アクセスするコードが危険であることを、実装を読まずとも把握できる。

 

### 型は契約の一部であって全部ではない

 

型注記が保証できるのは「値の形」までである。「priceは0以上でなければならない」という前提条件は、`number`という型だけでは表現しきれない(負の数もJavaScriptの`number`型である)。型による検査と、第一章で導入した実行時の前提条件検査は、互いに補い合う関係にあり、どちらか一方だけで契約のすべてを機械的に保証することはできない。

 

---

 

> **定着量の目安(第十三章)**: 与えられた関数にJSDoc形式の型注記を書き加えるドリルを15問程度、型注記だけでは捕まえられない契約違反(前提条件の細かい制約)を指摘するドリルを10問程度こなすと、ほぼ定着すると見込まれる。

 

---

 

## 第十四章: 再帰による宣言的分解(水準七〜八)

 

> 対応する解釈表の原則: 4(宣言的記述)を、繰り返し処理の書き方にまで広げる

 

### 再帰という考え方

 

**再帰(さいき、recursion、水準七: 関数が自分自身を呼び出すことによって、大きな問題をより小さな同じ形の問題に分解して解く手法)**は、ループとは異なる形で繰り返しを表現する。再帰関数は必ず二つの部分からなる。

 

```

ベースケース(base case): それ以上分解せずに直接答えを返せる、最も単純な場合

再帰ケース(recursive case): 問題を一回り小さくして、自分自身を呼び出す場合

```

 

### 検算15 — calcTotalを再帰で書く

 

第一章の`calcTotal`をループでなく再帰で書き直す。

 

```

/**

* 関数契約: calcTotalRecursive(契約は第一章のcalcTotalと同一)

*/

function calcTotalRecursive(items) {

if (items.length === 0) {

return 0; // ベースケース: 空配列の合計は0

}

const [first, ...rest] = items; // 先頭の要素と、残りの配列に分ける

return first.price * first.qty + calcTotalRecursive(rest); // 再帰ケース

}

 

console.log(calcTotalRecursive([{name:"回復薬", price:50, qty:3}, {name:"鍵", price:120, qty:1}]));

// 150 + calcTotalRecursive([{name:"鍵", price:120, qty:1}])

// = 150 + (120 + calcTotalRecursive([]))

// = 150 + (120 + 0)

// = 270

```

 

`calcTotalRecursive`は`calcTotal`と同じ契約を満たしながら、ループ変数を一切使わずに実装されている。「配列の合計は、先頭要素の値と、残りの配列の合計を足したもの」という、問題そのものの構造をそのまま宣言的に表現している点が、再帰の特徴である。

 

### 末尾再帰

 

再帰には、関数の最後の操作が自分自身の呼び出しになっている**末尾再帰(まつびさいき、tail recursion、水準八: 再帰呼び出しが、その関数の最後に行われる処理であり、呼び出し元に戻ってから追加の計算を行わない再帰の形)**という特別な形がある。

 

```

// calcTotalRecursive は末尾再帰ではない

// (calcTotalRecursive(rest) の結果に対して、呼び出しが戻ってきた後に

// 「first.price * first.qty を足す」という追加の計算が必要なため)

 

// 末尾再帰に書き直した版(累積値accを引数として持ち回す)

function calcTotalTailRecursive(items, acc = 0) {

if (items.length === 0) {

return acc; // ベースケース: 累積値をそのまま返す

}

const [first, ...rest] = items;

return calcTotalTailRecursive(rest, acc + first.price * first.qty); // 末尾再帰

}

```

 

末尾再帰では、再帰呼び出しの結果を受け取った後に何も追加の計算をしないため、一部の言語・実行環境ではこの形を検出して、通常のループと同じ効率(呼び出しのたびにスタック領域を追加で消費しない)に変換する最適化を行う。この最適化を**末尾呼び出しの最適化(tail call optimization)**と呼ぶ。すべての言語・実行環境がこの最適化を行うわけではないため、契約に「大きな配列を渡しても安全に処理できるか」という性能上の保証を含める場合は、実際に使う実行環境で末尾呼び出しの最適化が働くかどうかを確認する必要がある。

 

### 相互再帰

 

二つ以上の関数が互いを呼び合う形を**相互再帰(そうごさいき、mutual recursion、水準八: 関数Aが関数Bを呼び出し、関数Bが関数Aを呼び出すというように、複数の関数が互いを呼び合うことで成立する再帰)**と呼ぶ。第十二章の状態機械のように「偶数個の要素が残っていれば処理Xへ、奇数個なら処理Yへ」というように役割を交互に切り替える処理は、相互再帰で自然に表現できることがある。ただし相互再帰は理解の難度が上がりやすいため、本冊は「再帰で書けるところはすべて再帰で書くべきだ」とは主張しない。宣言的記述(第七章)と同様に、問題の構造が再帰的である場合にのみ再帰を選ぶという判断が実務的である。

 

### コラム — 再帰とmap・filter・reduceの関係

 

第五章で扱った`reduce`は、内部的には再帰(あるいはそれと同等のループ)によって実装されている。`items.reduce((sum, item) => sum + item.price * item.qty, 0)`という宣言的な一行は、本章の`calcTotalTailRecursive`が明示的に書いていた「累積値を持ち回しながら先頭から処理する」という再帰の構造を、`reduce`という高階関数の内部に隠して提供したものである。再帰を理解しておくと、`map`・`filter`・`reduce`が「舞台裏で何をしているか」を見通せるようになり、これらの高階関数をどんな場面で信頼して使えるかの判断力が上がる。

 

---

 

> **定着量の目安(第十四章)**: 短い再帰関数のベースケースと再帰ケースを見分けるドリルを15問程度、再帰呼び出しの実行過程を手でトレースして最終結果を求めるドリルを15問程度こなすと、ほぼ定着すると見込まれる。

 

---

 

## 第十五章: 契約をどこまで書くか — 実務のコストと判断(水準八)

 

> 対応する解釈表の原則: 全四原則の実務適用における費用対効果の判断

 

### 契約疲れという現実

 

すべての関数に対して、第一章の四要素をフルに書き、第八章の境界値テストをすべて用意し、第十三章の型注記を隅々まで付けるとなると、書く量そのものが膨大になり、本来の開発速度を落としてしまう。この状態を実務では**契約疲れ(けいやくづかれ、contract fatigue、水準八: 契約や仕様の記述に費やす労力が過大になり、かえって開発や保守の効率を損なってしまう状態)**と呼ぶことがある。本冊は契約を「常に完全に書くべき義務」としてではなく、「壊れたときの被害の大きさに応じて濃淡をつける投資」として捉えることを勧める。

 

### 濃淡をつけるための判断軸

 

```

濃い契約(四要素すべて・境界値テスト・型注記まで)を書くべき対象:

- 外部からの入力を最初に受け取る境界(セーブデータ読み込み、ユーザー入力の受付)

- 複数のモジュールから広く呼ばれる共通関数(第六章の公開インターフェース)

- 過去に一度でも不具合を起こしたことがある関数

 

薄い契約(型と一言の説明程度)で足りることが多い対象:

- モジュール内部だけで使われる小さなヘルパー関数

- 呼び出し元が1箇所しかなく、その場ですぐに正しさを目視確認できる関数

- 試作段階(プロトタイプ)で、後から作り直すことが決まっている関数

```

 

この判断軸は、第一章コラムで述べた「前提条件を弱く、保証を強く」という原則とは別の軸であることに注意したい。あちらは「一つの契約をどう書くか」という質の指針、こちらは「どの関数にどれだけの契約を書くか」という配分の指針である。

 

### コードレビューにおける契約チェックリスト

 

チームでコードを読み合うコードレビューの場面では、契約が正しく機能しているかどうかを、次のような観点で確認できる。

 

```

コードレビュー用・契約チェックリスト

[1] 公開インターフェースの関数に、入力・出力・前提条件・保証が明記されているか

[2] 前提条件は、呼び出し側にとって不必要に厳しくなっていないか(第一章コラム)

[3] 保証は、呼び出し側が実装を読まずに信頼できるだけ具体的か

[4] 純粋関数であるべき箇所に、隠れた副作用が紛れ込んでいないか(第二章・第九章)

[5] 変更されたモジュールについて、境界値を含む回帰テストが再実行され合格しているか(第八章)

[6] 契約のコメントと、実装の実際の振る舞いが食い違っていないか(契約の腐敗の検出)

```

 

### 契約の腐敗を防ぐ

 

契約はコードとは別に書かれたコメントであることが多いため、実装だけが変更されて契約のコメントが更新されないまま放置される、という事態が起こりうる。この状態を**契約の腐敗(けいやくのふはい、documentation rot・contract driftとも呼ばれる、水準八: コードの実装が変更された後も、それを説明する契約やドキュメントの記述が古いまま取り残され、実態と食い違ってしまう状態)**と呼ぶ。契約の腐敗を防ぐ最も確実な方法は、第八章のテストを契約の記述内容と一致させ、契約が変わればテストも変わる、テストが失敗すれば契約と実装のどちらかがおかしいと即座に分かる、という状態を保つことである。契約とテストを一体として扱うことで、契約は「書いたら終わりの飾り」ではなく「変更のたびに検証され続ける生きた約束」になる。

 

### 段落独立請求項の総まとめ

 

本冊が第一章から積み上げてきた技術(契約・純粋関数・参照透過性・冪等性・合成・モジュール化・宣言的記述・テスト・禁止パターンの診断・エラー処理・不変性・状態機械・型・再帰・契約の運用)は、すべて「関数という段落を、どこに置いても、何度実行しても、誰が読んでも、同じ意味で通るようにする」という一つの目的へ収束している。ユーザー原文の「段落を崩しても通る」は、突き詰めれば、正式に組まれた関数の集まりが持つべき性質そのものを言い当てた言葉だったと、本冊は結論づける。

 

---

 

> **定着量の目安(第十五章)**: 与えられた関数について、濃い契約を書くべきか薄い契約で足りるかを判断軸に沿って判定するドリルを15問程度、コードレビュー用チェックリストの六項目を使って小さなコード例を総合診断するドリルを15問程度こなすと、ほぼ定着すると見込まれる。

 

---

 

## 第十六章: 副作用を閉じ込める設計パターン — 依存性注入(水準八)

 

> 対応する解釈表の原則: 3(モジュール独立性)を、テストのしやすさという観点まで押し広げる

 

### 副作用への依存を「注入」に変える

 

第二章では副作用を「純粋な核と不純な殻」構造で分離することを勧めた。この分離をさらに一歩進める技法が**依存性注入(いぞんせいちゅうにゅう、dependency injection、水準八: 関数やモジュールが必要とする外部の機能〈ファイル保存・現在時刻の取得・乱数生成など〉を、関数の内部で直接呼び出すのではなく、外部から引数として渡す設計技法)**である。第九章で「依存性の明示化」として簡単に触れた考え方を、副作用を持つ依存先にまで正式に広げたものである。

 

### 検算16 — 現在時刻という副作用を注入する

 

ゲーム内のイベントログに記録時刻を付与する関数を考える。

 

```

// 依存性注入をしない場合(不純)

function logEventBad(message) {

const timestamp = new Date(); // 関数の内部で現在時刻を直接取得している

return timestamp.toISOString() + ": " + message;

}

```

 

`logEventBad`は、呼ぶたびに`new Date()`という非決定的な値(第二章の関数Cと同じ種類の不純さ)を内部で取得しており、同じ`message`を渡しても呼び出すたびに違う結果を返す。この関数のテストを書こうとすると、「戻り値の文字列が特定の時刻と一致すること」を検証できず、テストが不安定になる。

 

```

// 依存性注入をした場合(純粋)

// 関数契約: logEvent

// 入力: message(文字列)、now(Dateオブジェクト。呼び出し側が用意する)

// 出力: 文字列

// 前提条件: なし

// 保証 : now.toISOString() + ": " + message を返す

function logEvent(message, now) {

return now.toISOString() + ": " + message;

}

 

// 検算(固定した時刻を注入することで、結果が完全に予測できる)

const fixedTime = new Date("2026-07-10T09:00:00.000Z");

console.log(logEvent("冒険開始", fixedTime));

// "2026-07-10T09:00:00.000Z: 冒険開始"

```

 

`logEvent`は`now`という引数を通じて時刻を受け取るだけの純粋関数になった。実際のゲーム実行時には呼び出し側(不純な殻)が`new Date()`を1回だけ呼んで`logEvent`に渡せばよく、テスト時には固定した時刻を注入すれば、戻り値を1文字も違わずに予測できる。

 

### テストにおける依存性注入の効果

 

依存性注入は、第八章のテストのしやすさに直結する。乱数・現在時刻・ファイルI/O・ネットワーク通信といった、テストのたびに結果が変わってしまう依存先を、すべて引数として外から差し込める形にしておけば、テストコードは「本物の乱数生成器」の代わりに「常に同じ値を返す偽の乱数生成器(テストダブル)」を注入するだけで、決定的な(何度実行しても同じ結果になる)テストを書けるようになる。

 

```

// 乱数依存も同様に注入できる

function rollDamageWithDice(baseDamage, rollDiceFn) {

const roll = rollDiceFn(); // rollDiceFn を呼び出し側が注入する

return baseDamage + roll;

}

 

// 本番: 本物の乱数生成器を注入

rollDamageWithDice(10, () => Math.floor(Math.random() * 6) + 1);

 

// テスト: 常に3を返す偽の関数を注入

rollDamageWithDice(10, () => 3); // 常に13を返す、予測可能なテストになる

```

 

### コラム — 依存性注入とモジュール独立性の関係

 

依存性注入は、第六章のモジュール独立性を「実行時の依存先」にまで広げたものだと理解できる。第六章では「公開インターフェースさえ守れば内部実装を変更してよい」という独立性を扱ったが、依存性注入は「実行のたびに、どの実装を差し込むかを外から選べる」という、さらに強い独立性を関数に与える。本物のファイル保存機能と、テスト用の偽のファイル保存機能を、関数側のコードを一切変更せずに差し替えられるのは、この設計の直接の恩恵である。

 

---

 

> **定着量の目安(第十六章)**: 関数内部に直接埋め込まれた副作用依存(時刻・乱数・I/O)を見つけ、引数として注入する形に書き直すドリルを20問程度こなすと、ほぼ定着すると見込まれる。

 

---

 

## 第十七章: 並行処理と隠れた順序依存(水準八)

 

> 対応する解釈表の原則: 1(参照透過性)と2(冪等性)を、複数の処理が同時に進む場面へ適用する

 

### 並行処理という新しい種類の「順序」

 

ここまでの章は、一つの処理が順番に実行される前提で「順序への依存」を扱ってきた。しかし現代のプログラムの多くは、複数の処理が同時に(あるいは順序不定のタイミングで)進行する**並行処理(へいこうしょり、concurrency、水準八: 複数の処理が、互いに完全な完了を待たずに進行しうる実行の形)**を含んでいる。JavaScriptにおける非同期処理(`Promise`・`async`/`await`)は、この並行処理の一種であり、複数の非同期処理がどちらが先に完了するかは、実行のたびに変わりうる。

 

### 検算17 — 実行順序が不定な非同期処理の危険

 

```

// 危険な例: 二つの非同期保存処理が、どちらが先に終わるか分からない

let saveCount = 0;

 

async function saveSlot(slotId, data) {

await writeFileAsync("save" + slotId + ".json", data); // 完了までの時間は毎回変わりうる

saveCount = saveCount + 1; // グローバル状態への書き込み(第九章)

}

 

saveSlot(1, dataA); // 呼び出しはすぐ次の行へ進む(完了を待たない)

saveSlot(2, dataB); // 呼び出しはすぐ次の行へ進む(完了を待たない)

console.log(saveCount); // 0(両方ともまだ完了していない可能性が高い)

```

 

`saveSlot(1, dataA)`と`saveSlot(2, dataB)`は、どちらの保存が先に完了するか、実行するたびに変わりうる。`saveCount`というグローバル状態への書き込みも重なるタイミング次第で結果が変わる余地があり、これは第九章の「グローバル状態依存」が、並行処理という新しい文脈で再び現れた形である。

 

### 対処1 — 完了を明示的に待つ

 

```

async function saveAllSlots(saves) {

// Promise.all は、渡されたすべての非同期処理の完了を待ってから次に進む

await Promise.all(saves.map(s => writeFileAsync("save" + s.slotId + ".json", s.data)));

return saves.length; // すべて完了した後の状態が保証されている

}

```

 

`Promise.all`は「渡された非同期処理がすべて完了するまで待つ」という宣言的な記述であり、個々の保存処理がどんな順序で完了するかを気にする必要がなくなる。これは第七章の宣言的記述の考え方を、並行処理の待ち合わせにまで拡張した例である。

 

### 対処2 — 純粋な計算と非同期のI/Oを分離する

 

```

// 純粋な部分(第二章の「純粋な核」)を先にすべて計算しておき、

// 非同期I/O(不純な殻)はその計算結果を保存するだけにする

function buildSaveData(player) { // 純粋関数

return { player: player, savedAt: null };

}

 

async function persistSaveData(slotId, saveData, now) { // 不純(I/O)

const finalData = { ...saveData, savedAt: now.toISOString() };

await writeFileAsync("save" + slotId + ".json", finalData);

}

```

 

計算(`buildSaveData`)を非同期処理の外に出し、純粋関数として先に完了させておくことで、非同期I/Oの完了順序が計算結果に影響を与える余地そのものをなくしている。これは第十六章の依存性注入とも通じる考え方であり、「いつ終わるか分からない処理」の影響範囲を、可能な限り薄い殻の中に押し込める設計である。

 

### 競合状態という言葉

 

複数の処理が共有する状態に同時に書き込もうとして、実行順序次第で結果が変わってしまう状況を**競合状態(きょうごうじょうたい、race condition、水準八: 複数の処理が共有の状態に同時にアクセスし、実行される順序によって最終結果が変わってしまう不具合の状態)**と呼ぶ。競合状態は、本冊が第一章から扱ってきた「隠れた順序依存」の、並行処理における現れ方である。対処の骨格は同じであり、共有状態への依存をなくす(純粋関数化・依存性注入)か、共有状態への書き込みを冪等にする(第四章)か、順序を明示的に宣言する(`Promise.all`のような待ち合わせ)かのいずれかに帰着する。

 

---

 

> **定着量の目安(第十七章)**: 与えられた非同期処理のコードから、競合状態が起こりうる箇所を指摘するドリルを15問程度、`Promise.all`等を使って安全な待ち合わせに書き直すドリルを10問程度こなすと、ほぼ定着すると見込まれる。

 

---

 

## 第十八章: 総合演習 — セーブ&ロードシステムを正式に組み立てる(水準五〜八・総合)

 

> 対応する解釈表の原則: 四原則すべての統合

 

### 演習の設計

 

本章では、本冊全体で積み上げてきた技術を使い、小さなセーブ&ロードシステムを最初から組み立てる。目的は「段落を崩しても通る」システムを、実際に手を動かして確かめることである。扱う機能は、プレイヤーの状態(所持金・所持品・称号)を更新する三つの処理と、それらを保存・復元する処理である。

 

### 部品1 — 純粋なドメインロジック(第二章・第十一章)

 

```

// 関数契約: addGold

// 入力: player、amount(数値、0以上)

// 出力: player と同じ形の新しいオブジェクト

// 前提条件: amount は 0 以上

// 保証 : 戻り値の gold は player.gold + amount に等しい。元の player は変更しない

function addGold(player, amount) {

if (amount < 0) { throw new Error("前提条件違反: amountは0以上"); }

return { ...player, gold: player.gold + amount };

}

 

// 関数契約: addTitleOnce(第九章のgrantTitleOnceを不変性を守る形に書き直したもの)

// 入力: player、title(文字列)

// 出力: player と同じ形の新しいオブジェクト

// 前提条件: player.titles は配列である

// 保証 : titleがすでに含まれていれば元のplayerと同じ内容の新オブジェクトを、

// 含まれていなければtitlesにtitleを追加した新オブジェクトを返す

function addTitleOnce(player, title) {

if (player.titles.includes(title)) { return { ...player }; }

return { ...player, titles: [...player.titles, title] };

}

 

// 関数契約: addItem

// 入力: player、item({name, price, qty}の形)

// 出力: player と同じ形の新しいオブジェクト

// 前提条件: item.qty は 0 以上の整数

// 保証 : 戻り値の inventory は、元の inventory に item を1件追加した配列に等しい

function addItem(player, item) {

if (item.qty < 0) { throw new Error("前提条件違反: qtyは0以上"); }

return { ...player, inventory: [...player.inventory, item] };

}

```

 

三つの関数はすべて純粋であり、契約が明記され、元の`player`を書き換えず新しいオブジェクトを返す(イミュータブル、第十一章)。

 

### 検算18 — 段落を入れ替えても結果が変わらないことを確認する

 

初期状態`player0 = { gold: 0, titles: [], inventory: [] }`に対し、三つの関数を異なる順序で適用する。

 

```

順序パターンA: addGold → addTitleOnce → addItem

player1 = addGold(player0, 100)

player2 = addTitleOnce(player1, "新人冒険者")

player3 = addItem(player2, {name:"回復薬", price:50, qty:1})

結果: { gold: 100, titles: ["新人冒険者"], inventory: [{name:"回復薬", price:50, qty:1}] }

 

順序パターンB: addItem → addGold → addTitleOnce

player1 = addItem(player0, {name:"回復薬", price:50, qty:1})

player2 = addGold(player1, 100)

player3 = addTitleOnce(player2, "新人冒険者")

結果: { gold: 100, titles: ["新人冒険者"], inventory: [{name:"回復薬", price:50, qty:1}] }

```

 

`gold`・`titles`・`inventory`はそれぞれ独立したフィールドを更新しており、三つの関数の間には依存関係がない(第六章のモジュール独立性)。したがって順序パターンAとBは、適用する関数の順番を入れ替えたにもかかわらず、最終的な`player3`の中身は完全に一致する。これが本冊の言う「段落を崩しても通る」の、最も具体的な実演である。

 

### 部品2 — 不純な殻(第二章・第十六章・第十七章)

 

```

// 依存性注入により、時刻とファイル保存を外部から渡す(第十六章)

async function saveGame(player, slotId, now, writeFileFn) {

const saveData = { player: player, savedAt: now.toISOString() };

await writeFileFn("save" + slotId + ".json", saveData); // 冪等(第四章): 同じ内容を2回書いても最終状態は同じ

}

 

// 状態機械による進行管理(第十二章)との組み合わせ例

async function saveAllSlotsIfReady(slots, now, writeFileFn) {

await Promise.all( // 完了を明示的に待つ(第十七章)

slots.map(s => saveGame(s.player, s.slotId, now, writeFileFn))

);

}

```

 

`saveGame`は、ファイルへの上書き保存という操作自体が「同じ内容を書けば最終状態は同じ」という意味で冪等(第四章)であるため、リトライによって複数回呼ばれても安全である。

 

### 総合チェックリストとの照合

 

本章の演習を、第九章の禁止パターン総合チェックリストに照らすと、次のようになる。

 

```

[1] グローバル状態依存 → なし(すべて引数として player・slotId・now・writeFileFn を受け取る)

[2] 隠れた順序依存 → なし(検算18で順序入れ替えの無害性を確認済み)

[3] 冪等性の明記 → saveGame は冪等であることを本文中に明記済み

[4] カプセル化の破れ → なし(呼び出し側は関数の戻り値だけを使い、内部実装を直接読まない)

[5] 循環依存 → なし(ドメインロジック→保存処理という一方向)

[6] 契約の境界値テストの裏付け → amount=0・qty=0 等の境界値を前提条件検査で確認可能

```

 

六項目すべてを満たした状態が、本冊が最初から目指してきた「正式に組まれた関数」の到達点である。

 

---

 

> **定着量の目安(第十八章)**: 本章の三つのドメイン関数について、演習で示した以外の順序パターン(6通りある順列のうち残り4通り)でも最終結果が一致することを、自分の手で検算するドリルを1セットこなすと、段落独立性という性質を体感として定着させられると見込まれる。

 

---

 

## 第十九章: 契約の互換性とバージョニング(水準八)

 

> 対応する解釈表の原則: 3(モジュール独立性)を、時間軸(過去のコードとの互換性)に広げる

 

### 契約は一度書いたら終わりではない

 

これまでの章は、契約を「ある時点で正しく書く」ことに焦点を当ててきた。しかし実務のソフトウェアは長期間にわたって保守され、契約そのものが後から変更される。第六章で「モジュールの内部実装は、契約を破らなければ自由に変えてよい」と述べたが、契約そのものを変える必要が生じたとき、それが呼び出し側にどんな影響を与えるかを整理する枠組みが必要になる。

 

### 契約の変更を三種類に分類する

 

```

変更の種類 呼び出し側への影響 具体例

--------------------------------------------------------------------------

後方互換な追加 既存の呼び出しは無傷 戻り値のオブジェクトに新しいフィールドを追加する

後方互換な緩和 既存の呼び出しは無傷 前提条件を弱める(第一章コラム)

破壊的変更(breaking) 既存の呼び出しが壊れる可能性 引数の型を変える・前提条件を厳しくする・関数名を変える

```

 

**後方互換(こうほうごかん、backward compatible、水準八: 変更後のバージョンでも、変更前のバージョンを前提に書かれた既存のコードがそのまま動き続ける性質)**な変更は、第六章のモジュール独立性が正しく機能していれば自由に行ってよい。一方、**破壊的変更(はかいてきへんこう、breaking change、水準八: 変更後のバージョンでは、変更前のバージョンを前提に書かれた既存のコードが動かなくなる可能性がある変更)**は、呼び出し側のコードすべてに影響が及ぶ可能性があるため、慎重な取り扱いが要る。

 

### 検算19 — applyDiscountの契約変更を三種類に分類する

 

```

変更A: applyDiscountの戻り値に、新しく totalSaved(節約額の合計)フィールドを追加した

→ 既存の呼び出し側は totalSaved を読まないだけで、price フィールドへのアクセスは無傷

→ 後方互換な追加

 

変更B: applyDiscountの前提条件を「0 <= rate <= 1」から「-1 <= rate <= 1」に緩めた

(rateが負の値なら値上げとして扱う、という機能拡張)

→ 既存の呼び出し側(常に0以上のrateしか渡していない)は無傷

→ 後方互換な緩和

 

変更C: applyDiscountの引数の順序を (items, rate) から (rate, items) に入れ替えた

→ 既存のすべての呼び出し側で、渡す引数の順序が逆転し、誤った結果を返すようになる

→ 破壊的変更

```

 

変更Cのような破壊的変更が本当に必要な場合、実務では新しい契約を持つ別名の関数(たとえば`applyDiscountV2`)を追加し、古い関数は当面残しつつ「非推奨」であることを明記する、という移行の手順がよく使われる。

 

### 非推奨という宣言

 

**非推奨(ひすいしょう、deprecated、水準八: ある関数やインターフェースについて、将来的に削除される予定であることを、削除前の段階で呼び出し側に知らせる印)**を付けることは、破壊的変更を一段階に分けて行うための技法である。

 

```

/**

* @deprecated applyDiscountV2 を使用すること。この関数は次の大きな更新で削除予定。

* 関数契約: applyDiscount(旧版・第五章と同一)

*/

function applyDiscount(items, rate) { /* ... */ }

```

 

非推奨の印を付けてから実際に削除するまでの猶予期間を設けることで、呼び出し側のコードを書いている人たちに、移行のための時間を与えられる。これは、契約が「ある関数一つの中だけで完結する約束」ではなく、「その関数を信頼して使っている、まだ見ぬすべての呼び出し側との約束」であるという、モジュール独立性(第六章)の本質を思い出させる仕組みである。

 

### コラム — 意味的バージョニング

 

ソフトウェアのバージョン番号を「メジャー番号.マイナー番号.パッチ番号」の形で管理し、破壊的変更を行うときは必ずメジャー番号を上げる、後方互換な追加を行うときはマイナー番号を上げる、という取り決めを**意味的バージョニング(いみてきばーじょにんぐ、semantic versioning、水準八: バージョン番号自体に「どの種類の変更が含まれているか」という意味を持たせる番号付けの規約)**と呼ぶ。この規約に従っていれば、呼び出し側は「メジャー番号が上がっていなければ、契約は壊れていないはずだ」と、バージョン番号を見ただけで信頼できるようになる。これも、契約という約束を、時間軸に沿って正式に運用するための工夫の一つである。

 

---

 

> **定着量の目安(第十九章)**: 与えられた契約の変更が、後方互換な追加・後方互換な緩和・破壊的変更のどれに当たるかを分類するドリルを20問程度こなすと、ほぼ定着すると見込まれる。

 

---

 

## 第二十章: 契約から自動生成するテスト — プロパティベーステスト(水準八)

 

> 対応する解釈表の原則: 1(参照透過性)と契約(第一章)を、テストの自動生成にまで結びつける

 

### 具体例を並べるテストの限界

 

第八章の境界値分析・同値分割は、人間が「ここが危ない」と判断した具体的な入力値を選んでテストする手法だった。この方式は効果的だが、人間が思いつかなかった入力値に潜むバグは見逃しやすいという弱点がある。**プロパティベーステスト(property-based testing、水準八: 具体的な入力値を人間が一つずつ選ぶのではなく、契約の「保証」が満たすべき性質〈プロパティ〉を記述し、ランダムに生成した大量の入力に対してその性質が常に成り立つかを機械的に検証するテスト手法)**は、この弱点を補う手法として実務で使われている。

 

### 検算20 — calcTotalのプロパティを記述する

 

第一章の`calcTotal`の保証「戻り値はΣ(price×qty)に等しい。items が空配列なら 0 を返す」から、次のようなプロパティを導ける。

 

```

プロパティ1(空配列則): calcTotal([]) は常に 0 である

プロパティ2(単調性): items に qty>0 の要素を1件追加すると、calcTotal の戻り値は

追加前の値以上になる(価格が0以上である前提条件があるため)

プロパティ3(結合則): calcTotal(itemsA と itemsB を連結した配列) は、

calcTotal(itemsA) + calcTotal(itemsB) に等しい

```

 

プロパティ3は特に強力である。これは`calcTotal`という関数が、配列を「連結してから計算する」のと「別々に計算してから足す」のとで同じ結果になる、という構造的な性質(第五章で扱った結合律と同じ種類の性質)を主張している。プロパティベーステストの道具は、ランダムな`items`の組を何百通りも自動生成し、この主張がすべての組み合わせで成り立つかどうかを機械的に検査する。

 

```

擬似コード:

100回繰り返す:

itemsA = ランダムなアイテム配列を生成する(要素数0〜10、価格0〜1000、個数0〜10)

itemsB = ランダムなアイテム配列を生成する(要素数0〜10、価格0〜1000、個数0〜10)

もし calcTotal(itemsA.concat(itemsB)) != calcTotal(itemsA) + calcTotal(itemsB) なら:

失敗を報告し、その時のitemsAとitemsBを記録する(反例)

```

 

もし`calcTotal`の実装に不具合(たとえば途中でオーバーフローする、負の数の扱いを誤る、といった見落とし)があれば、100回のランダム生成のどこかでプロパティ3が破られ、その時の`itemsA`・`itemsB`が反例として記録される。人間が境界値として思いつかなかった組み合わせでも、ランダム生成であれば偶然その組み合わせに行き当たる可能性がある。

 

### 縮小(シュリンキング)という便利な機能

 

プロパティベーステストの道具の多くは、反例を見つけた後、その反例をさらに小さく単純な形へ自動的に絞り込む**縮小(しゅくしょう、shrinking、水準八: 見つかった反例を、性質が破られたままより小さく・単純な形に自動的に絞り込んでいく機能)**という機能を備えている。要素数10個のランダムな配列で見つかった反例が、縮小によって「要素数2個の、特定の価格の組み合わせ」まで絞り込まれれば、原因の特定が大幅に容易になる。

 

### プロパティベーステストと契約の関係

 

プロパティベーステストが有効に機能する条件は、契約の保証が「具体的な一つの数値」ではなく「入力によらず成り立つ性質」として書かれていることである。第一章コラムで述べた「保証はできるだけ強く、具体的に書く」という指針は、プロパティベーステストの土台としても機能する。逆に言えば、契約の保証を性質として明確に書けない関数は、プロパティベーステストの対象にもしにくく、これは契約の質を見直す一つの診断材料にもなる。

 

### コラム — 境界値テストとプロパティベーステストの使い分け

 

本冊は「プロパティベーステストがあれば境界値分析は不要になる」とは主張しない。境界値分析(第八章)は、人間が持つドメイン知識(「rate=0とrate=1が特に危ない」という直感)を直接テストに反映できる強みがあり、プロパティベーステストは、人間が気づかない組み合わせを広く浅く探索する強みがある。実務では両者を併用し、境界値テストで「分かっている危険地帯」を確実に押さえ、プロパティベーステストで「気づいていない危険地帯」を広く探る、という役割分担がよく行われる。

 

---

 

> **定着量の目安(第二十章)**: 与えられた関数契約の保証から、プロパティ(結合則・単調性・空入力則など)を書き出すドリルを15問程度、プロパティベーステストと境界値テストのどちらを使うべきかを場面から判断するドリルを10問程度こなすと、ほぼ定着すると見込まれる。

 

---

 

## 付録: 関数ノウハウ目録KNOW-Fの五点セット見本(水準五〜八)

 

本大全のノウハウ目録(→BOOK-0358k〜n、KNOW-F-0001〜0500)は、各項目を①名称 ②構造の型 ③使いどころ ④組合せ例 ⑤確認事項〈罠と検算・安全注意〉という五点セットで記述する(CATALOG_PC創造大全.md §1・BOOK-0184方式)。本冊の内容がどのように五点セットへ翻訳されるかを、代表的な四項目の見本として示す。これらは目録の正式なID割り当てに先立つ見本であり、正式な項目番号は→BOOK-0358k〜nの側で確定する。

 

### 見本1 — 関数契約の記述(第一章対応)

 

```

①名称: 契約明記型

②構造の型:

関数契約: <関数名>

入力 : <型>

出力 : <型>

前提条件: <呼び出し側が守る条件>

保証 : <関数側が守る条件>

③使いどころ: 複数のモジュールから呼ばれる公開関数、外部入力を受け取る境界の関数

④組合せ例: エラー処理型(見本4)と組み合わせ、保証欄に「前提条件が破られた場合は

例外を投げる」旨を明記すると、契約とエラー処理が一つの記述に統合される

⑤確認事項: 前提条件を不必要に強くしていないか(第一章コラム)。

保証が具体的な計算式にまで踏み込んでいるか。検算: calcTotal([{price:50,qty:3}])=150

```

 

### 見本2 — 純粋な核と不純な殻(第二章対応)

 

```

①名称: サンドイッチ構造型

②構造の型:

[不純な殻] 入力を取得する(ファイル読込・ユーザー入力)

↓

[純粋な核] 契約明記型の純粋関数だけで計算する

↓

[不純な殻] 出力する(画面表示・ファイル保存)

③使いどころ: I/Oと計算ロジックが混在しがちな処理全般(セーブ&ロード・画面更新・通信)

④組合せ例: 依存性注入型(見本3)と組み合わせ、不純な殻が使う副作用(時刻・乱数・I/O)を

関数の外から差し込む形にすると、核だけでなく殻の一部までテスト可能になる

⑤確認事項: 核の内部に副作用が紛れ込んでいないか(第九章チェックリスト[1])。

罠: 殻を薄く保つつもりが、殻の中に条件分岐ロジックまで書き込んでしまい、

殻自体が肥大化する事故がよく起きる

```

 

### 見本3 — 依存性注入型(第十六章対応)

 

```

①名称: 依存性注入型

②構造の型:

関数(本来の入力..., 外部依存を表す引数...) -> 出力

例: logEvent(message, now) ではなく logEvent(message) の内部で new Date() を呼ばない

③使いどころ: 時刻・乱数・ファイルI/O・ネットワーク通信など、実行のたびに結果が

変わりうる依存先を持つ関数

④組合せ例: 純粋な核と不純な殻(見本2)の「殻」側の関数に本技法を適用すると、

本番用の実装とテスト用の偽実装(テストダブル)を、関数のコードを変更せずに

差し替えられるようになる

⑤確認事項: 検算: rollDamageWithDice(10, () => 3) は常に13を返すか(第十六章検算16)。

罠: 注入する依存の型(関数か、値か、オブジェクトか)を契約に明記し忘れると、

呼び出し側が誤った形の依存を注入してしまう

```

 

### 見本4 — 状態機械型(第十二章対応)

 

```

①名称: 遷移表型

②構造の型:

状態の集合: { S1, S2, ... }

遷移表: (現在の状態, イベント) -> 次の状態

表にない組み合わせ -> エラー

③使いどころ: 順序そのものが本質的に重要な処理(ゲームの進行・UI画面遷移・

通信プロトコルの手順)

④組合せ例: 契約明記型(見本1)と組み合わせ、遷移表の「現在の状態」「イベント」を

契約の「入力」に、「次の状態」を「出力」に、「表にない組み合わせはエラー」を

「前提条件」に、それぞれ対応づけて記述する

⑤確認事項: 検算: transition("タイトル","開始する")="冒険中"か(第十二章検算13)。

罠: 遷移表に載っていない組み合わせを、エラーにせず黙って無視すると、

不正な状態遷移が検出されないまま進行してしまう

```

 

四つの見本はいずれも、本冊が第一章から第十九章まで積み上げてきた技術の一部を、単独で参照できる五点セットの形に圧縮したものである。これは「各KNOW項目・各章は前提リンクを明示すれば単独で読んでも成立する」という段落独立請求項(CATALOG_PC創造大全.md §0-5)を、目録形式でも実践した具体例である。

 

---

 

## よくある質問(本冊の範囲と使い方)

 

**Q. なぜコード例に擬似コードとJavaScriptの両方を使うのか。**

A. 擬似コードは特定の言語の文法知識を前提とせず、契約の考え方そのものを読み取れるようにするために使う。JavaScriptは、実際に動く形で検算を確認できるようにするために、外部ライブラリに依存しない範囲(安全枠)で併用している。→BOOK-0358g『OSを作る実践』では、より低水準な言語での実装に踏み込む予定である。

 

**Q. 本冊で学んだ考え方は、オブジェクト指向プログラミングとどう関係するか。**

A. 本冊は関数を中心に据えて説明してきたが、契約(第一章)・カプセル化(第六章)・不変性(第十一章)といった考え方は、オブジェクト指向プログラミングにおけるクラス設計にもそのまま応用できる。クラスのメソッドも「入力(引数とオブジェクトの内部状態)を受け取り、出力(戻り値と、場合によっては状態の変化)を返す」という意味では、関数契約の枠組みで捉え直せる。

 

**Q. 本冊のすべての技術を、すべての関数に適用すべきか。**

A. 第十五章で述べた通り、答えは否である。契約の濃淡・純粋関数化の徹底度・テストの手厚さは、その関数が壊れたときの被害の大きさに応じて調整すべきものであり、本冊はそのための判断軸を示すことを目的としている。

 

**Q. 「段落を崩しても通る」は、どんな処理にも当てはまる理想なのか。**

A. 第十二章・第十七章で述べた通り、順序そのものが本質的に重要な処理(ゲームの進行、通信プロトコルの手順)は存在する。そうした処理では「順序依存をなくす」ことではなく「順序を隠さず宣言的な表として明記する」ことが、段落独立請求項の正しい適用になる。

 

**Q. 本冊の範囲外は何か。**

A. 特定のプログラミング言語の文法網羅・特定の開発環境の構築手順・分散システムにおける合意アルゴリズムのような高度な並行処理理論は、本冊の範囲外である。これらは→BOOK-0358g以降の後続部、および関数ノウハウ目録(→BOOK-0358k〜n)で個別に深掘りされる予定である。

 

---

 

## 終章 — 本冊の階段を振り返る

 

ミナは最終的に、事件簿の各段落に対応する処理を、本冊で積み上げた技術で書き直した。契約を明記し、純粋関数と副作用を分離し、参照透過性と冪等性を確認し、合成とモジュール化で組み立て、宣言的記述とエラー処理と不変性で足場を固め、状態機械・型・再帰・依存性注入・並行処理という応用技術を通し、境界値と回帰のテストを揃え、禁止パターンのチェックリストで総点検し、最後に契約のバージョニングという時間軸まで視野に入れた。書き直した後は、実際に段落の並び順を入れ替えても、称号もフラグも所持金も、意図した通りの結果を返し続けた(第十八章検算18)。「段落を崩しても通る」というユーザー原文の一言は、こうして十九章分の具体的な技術へと翻訳された。

 

```

[水準八] 契約の互換性とバージョニング

後方互換・破壊的変更・非推奨・意味的バージョニング(検算19)

▲

│ 契約を時間軸に沿って正式に運用する

[水準五〜八] 総合演習 — セーブ&ロードシステム

三つの純粋関数の順序入れ替え検証(検算18)・六項目チェックリスト照合

▲

│ すべての技術を一つの小さな系として統合する

[水準八] 並行処理と隠れた順序依存

競合状態・Promise.allによる待ち合わせ(検算17)

▲

│ 「順序」を、同時進行の処理にまで拡張する

[水準八] 依存性注入

時刻・乱数という副作用を引数として注入する(検算16)

▲

│ 副作用への依存を、外から選べる形に変える

[水準八] やってはいけない結合の診断

グローバル状態依存・隠れた順序依存・

六項目チェックリストへの収束(検算10)

▲

│ 禁止パターンをカタログ化して裏返す

[水準八] テストの書き方(境界値・回帰)

契約から境界値を導く(検算9)・テストピラミッド

▲

│ 書いた契約を機械的に確かめる

[水準八] 型による契約の強化/再帰による宣言的分解/契約運用の判断

JSDoc型注記(検算14)・末尾再帰(検算15)・契約疲れと濃淡の判断

▲

│ 契約を機械可読にし、繰り返しを宣言的に表し、費用対効果で運用する

[水準七〜八] 宣言的記述の広がり/エラー処理/不変性/状態機械

SQL・正規表現との同型性(検算8)・Result型(検算11)・

構造共有(検算12)・遷移表(検算13)

▲

│ 「何が成り立つか」を書き、「どう実現するか」を委ねる

[水準七] モジュール化と独立性

公開インターフェースと内部実装の分離(検算7)・循環依存の禁止

▲

│ 関数の集まりを、独立して読める単位にまとめる

[水準六〜七] 関数合成

f∘g・恒等関数・結合律・パイプライン(検算6)

▲

│ 正しい部品同士をつないでも正しさが保たれる

[水準六] 冪等性の設計

HTTPの動詞・冪等キー・非冪等の冪等化(検算4・5)

▲

│ 同じ操作の重複実行に強くする

[水準六] 参照透過性の実務利用

メモ化91倍検算(検算3)・遅延評価

▲

│ 「同じ入力なら同じ出力」を最適化に使う

[水準五] 純粋関数と副作用の区別

決定性・無副作用・純粋な核と不純な殻

▲

│ 参照透過性を関数の性質として判定する

[水準五] 関数契約を正式に書く

入力・出力・前提条件・保証の四要素(検算1・2)

▲

│ 契約を土台に置く

[冒頭] 解釈表 — 「段落を崩しても通る」を四原則へ翻訳

参照透過性・冪等性・モジュール独立性・宣言的記述

```

 

### 横の広がり — 同じ水準にある姉妹概念

 

水準五(契約・純粋関数)では「事前条件⇄事後条件」という対が、水準六(参照透過性・冪等性)では「値の同一性⇄状態の同一性」という対が、水準七(合成・モジュール化・宣言的記述の入口)では「組み立てる⇄分離する」という対が、水準八(エラー処理・不変性・状態機械・型・再帰・テスト・禁止パターン・依存性注入・並行処理・バージョニング)では「確かめる⇄禁じる」という対と「今の正しさ⇄未来の互換性」という対が、それぞれの階に姉妹概念として存在する。四つの水準すべてを貫く軸が、解釈表の四原則(参照透過性・冪等性・モジュール独立性・宣言的記述)であり、水準が上がるごとに、この四原則が扱う対象が「一つの関数の中」から「複数のモジュールの間」へ、さらに「実行時間・並行処理・将来のバージョン」へと広がっていく構造になっている。

 

## 接続先の再掲

 

本冊が扱った「関数を正式に組む」という技能は、単体で完結する話ではない。→BOOK-0184『関数パターン目録I』の200種の構造パターンは、本冊の契約という枠組みに具体的な形を与える語彙集として機能する(たとえば理-KAN-001の恒等関数は、本冊第五章の合成の単位元として直接使われた)。→BOOK-0341『自分の言語を作る』は、本冊が「関数」と呼んできたものが、実際にはどのような構文解析・意味論の仕組みの上で実行されているかを扱い、第十二章の状態機械はその内部で使われる道具と同型である。→BOOK-0343『情報構造の設計』は、本冊の関数群が受け取り・返してきた配列という入れ物の、より進んだ形(木・グラフ)を扱い、第十一章の構造共有はその発展形の理解に直結する。→BOOK-0358e『熱と電源の解析』(前部)は、本冊が電子回路(→BOOK-0300)からの類推で語ってきた「規格化された部品同士をつなぐ」という発想の、電力設計における姿を扱う。→BOOK-0358g『OSを作る実践』(次部)では、本冊の契約・純粋性・モジュール化・状態機械がそのままカーネル関数やブートシーケンスの設計原則として引き継がれる。→BOOK-0358k〜n『関数ノウハウ目録』では、本冊の考え方が命名・引数・戻り値・エラー処理・制御と分岐・データ変換・合成と堅牢化という五点セットの即戦力の型として、500項目に展開される。

 

## 参照文献(定番教科書・一般に知られる出典)

 

1. バートランド・メイヤーによる契約による設計(Design by Contract)の体系化。Eiffelというプログラミング言語の設計を通じて1980年代に広めたとされる考え方(第一章)。

2. HTTPの仕様書群における冪等性(idempotent)の定義。GET・PUT・DELETEを冪等、POSTを非冪等な操作として区別する取り決め(第四章)。

3. 関数型プログラミングの標準的な教科書群で扱われる関数合成・高階関数・純粋関数・不変性・構造共有の基礎概念(第二章・第五章・第十一章)。

4. ソフトウェアテストの標準的な教科書群で扱われる境界値分析・同値分割・回帰テスト・テストピラミッドの基礎概念(第八章)。

5. 単一責任の原則・インターフェース分離といったモジュール設計の指針、および依存性注入の設計パターン(第六章・第十六章)。

6. 計算機科学の標準的な教科書群で扱われる状態機械・再帰・末尾呼び出しの最適化の基礎概念(第十二章・第十四章)。

7. 意味的バージョニングの規約、および後方互換性・破壊的変更・非推奨という運用上の取り決め(第十九章)。

 

---

 

(本冊子は現代学問宇宙図鑑シリーズ BOOK-0358f。統合大型巻『PC創造大全』(BOOK-0358・全22部+総論)の物語脊椎・第6部(関数を正式に組む)。前部BOOK-0358e(熱と電源の解析)・次部BOOK-0358g(OSを作る実践)はいずれも予約番号のみ確定。対応する目録BOOK-0358k〜n(関数ノウハウKNOW-F-0001〜0500)も予約番号のみ確定。CATALOG_PC創造大全.md §0請求項5「段落独立請求項」に定める解釈表は本冊冒頭に設置済み。GAKUMON_UNIVERSE.md進捗台帳を参照。)

 

---

 

# 終章 — 階段図(GAKUMON_UNIVERSE.md規約準拠・4要素)

 

> 本節は第15波の棚卸し(2026-07-10/11)で「軽微欠落: f階段図」と記録された欠落分の補完である。本文(第一章〜第二十章・終章・見本・よくある質問・接続先の再掲・参照文献)は一切変更せず、末尾への追記のみで完結させる。既出の「終章 — 本冊の階段を振り返る」「横の広がり」の内容と重複する部分があるが、GAKUMON_UNIVERSE.md「終章の階段図規約」が定める4要素(縦の階段/横の広がり/現在のフロンティア/次の冊子への矢印)を、この形式で独立して機械検査できるようにするために正式な節として設置する。

 

## 1. 縦の階段(水準五〜八・検算可能な具体例つき)

 

```

水準八 契約の互換性とバージョニング ▲ 変更C(引数順序の入替)→破壊的変更/変更A(フィールド追加)→後方互換な追加(検算19)

│

水準五〜八 総合演習 — セーブ&ロードシステム ▲ 三つの純粋関数の呼び出し順序を入れ替えても結果は同一(検算18)

│

水準八 並行処理と隠れた順序依存 ▲ Promise.allで完了を待ち合わせ、競合状態を防ぐ(検算17)

│

水準八 依存性注入 ▲ rollDamageWithDice(10, () => 3) は常に13を返す(検算16)

│

水準八 やってはいけない結合の診断 ▲ グローバル状態依存+隠れた順序依存を六項目チェックリストで検出(検算10)

│

水準八 テストの書き方(境界値・回帰) ▲ applyDiscount(items,1)→price=0、applyDiscount(items,0)→price=50(検算9)

│

水準七〜八 型による契約の強化・再帰・契約運用 ▲ JSDoc型注記(検算14)・末尾再帰calcTotalTailRecursive(検算15)

│

水準七〜八 宣言的記述の広がり・エラー処理・不変性・状態機械 ▲ transition("タイトル","開始する")="冒険中"(検算13)

│

水準七 モジュール化と独立性 ▲ DiscountModuleの公開applyDiscountと内部discountTableの分離(検算7)

│

水準六〜七 関数合成 ▲ formatReceipt(applyDiscount(items,0.1))="回復薬:45\n鍵:108"(検算6)

│

水準六 冪等性の設計 ▲ grantWelcomeGoldOnceは1回でも100回でも所持金+100(検算4・5)

│

水準六 参照透過性の実務利用 ▲ メモ化で1,000ミリ秒→10.99ミリ秒へ約91倍高速化(検算3)

│

水準五 純粋関数と副作用の区別 ▲ double(x)=x*2は純粋、logAndDoubleはcallCountを書き換え不純(検算2)

│

水準五 関数契約を正式に書く ▲ calcTotal([{price:50,qty:3},{price:120,qty:1}])=270(検算1)

[冒頭] 解釈表 — 「段落を崩しても通る」を四原則へ翻訳

参照透過性・冪等性・モジュール独立性・宣言的記述

```

 

## 2. 横の広がり(同じ水準にある姉妹概念)

 

- **水準五**: 契約の「前提条件」(呼び出し側が守る約束)←(事前⇄事後の対)→契約の「保証」(関数側が守る約束)。純粋関数の二条件である「決定性」←(二条件の対)→「無副作用」も、同じ水準に並ぶ姉妹概念である。

- **水準六**: 参照透過性が支える「値の同一性」に基づく最適化(メモ化・遅延評価)←(同じ「同じなら省略してよい」という発想の兄弟)→冪等性が支える「状態の同一性」に基づく安全性(冪等キー・HTTPのGET/PUT/DELETE)。

- **水準七**: 関数合成による「組み立てる」(pipe・恒等関数・結合律)←(組み立てと分離という逆方向の操作の対)→モジュール化による「分離する」(公開インターフェースと内部実装の分離・循環依存の禁止)。

- **水準八**: テストによる「確かめる」(境界値分析・プロパティベーステスト)←(検証と禁止という対)→やってはいけない結合の診断による「禁じる」(六項目チェックリスト)。契約の互換性(検算19)が扱う「今の正しさ」←(時間軸に沿った対)→意味的バージョニングが扱う「未来の互換性」。

 

## 3. 現在のフロンティア(年号つき)

 

- **契約による設計の言語機能化**: バートランド・メイヤーが1980年代にEiffelで体系化した契約の考え方(第一章)は、2014年に提案されたPEP 484によるPythonの型ヒントや、2012年に公開されたTypeScriptのような、より軽量な形で主流言語に取り込まれつつあるが、実行時の前提条件・事後条件検査までを標準機能として持つ主流言語は、本書執筆時点〈2026年〉でも依然として少数派にとどまる、とされる。

- **プロパティベーステストの実務浸透**: Claessen & HughesによるQuickCheck(2000年発表)に始まる手法(第二十章)は、2020年代には主要言語向けの実装が広く整備されているが、「良いプロパティをどう見つけるか」という設計の勘所そのものは自動化しにくく、依然として書き手の経験に依存する部分が大きいとされる。

- **並行処理における隠れた順序依存の静的検出**: 競合状態(第十七章)を実行前に機械的に検出する型システム・所有権管理の研究は進んでいるが、既存の大規模なコードベース全体を後から安全に検証しきる決定的な手法は、本書執筆時点でもなお発展途上とされる。

 

## 4. 次の冊子への矢印

 

```

次の冊子(水準七〜十): 第7部 OSを作る実践(BOOK-0358g) ─────▶

主題: ブートローダ・CPUモードの切り替え・カーネルへの橋渡し・

メモリ管理・割込とタイマ・簡易ファイルシステム・

プロセスとスケジューラ・簡易シェルを、エミュレータ上で

実際に「たまごOS」として一段ずつ組み立てる実装続篇

```

 

BOOK-0358g(第7部・OSを作る実践)は本節作成時点でディスク上に本文が現存する巻であり(→「未着手」ではない)、上記の主題はその本文冒頭の接続先・章立てに実在する記述に基づく。本冊f側の接続先(冒頭・よくある質問・接続先の再掲)がg を「予約番号のみ確定」と記す表現は本文無傷維持の方針により本節では訂正せず、事実確認の結果のみをここに註記する。

 

---

 

 

 




# BOOK-0358f PC創造大全 第6部 — 関数を正式に組む
  1. 目次
  2. 小説情報
  3. 縦書き
  4. しおりを挟む
  5. お気に入り登録
  6. 評価
  7. 感想
  8. ここすき
  9. 誤字
  10. 閲覧設定