# AI時代のITエンジニアの生存戦略：自動化の上限を定量的に見積もる

> LLMによるコード生成が開発工程のどこを速くし、どこを速くしないのかを、アムダール則・生成検証サイクル・仕様の情報量下界・ライスの定理を使って定量的に分析し、何を学ぶべきかを導きます。
> https://rikai.mugen-giken.com/computer-science/ai-era/engineer-survival-strategy

## 0. この記事の要点

- LLM によるコード生成が速くするのは開発工程のうち「実装」だけです。アムダール則から、実装が全体の割合 $p$ を占めるとき、実装が無限に速くなっても全体の高速化は $1/(1-p)$ 倍で頭打ちになります（<Ref to="cor-amdahl-limit" />）。
- 生成が安くなると、相対的に高くなるのは**検証**です。生成コストを $0$ にしても、期待総コストは検証コストを受理確率で割った値より下がりません（<Ref to="cor-verify-floor" />）。ここが「レビューできる人」の価値の源泉です。
- 検証を機械に丸投げすることは原理的にできません。プログラムの意味に関する非自明な性質はすべて決定不能だからです（<Ref to="thm-rice" />）。
- 「何を作るか」を曖昧にしたまま生成させることもできません。$N$ 通りの候補から 1 つを選ばせるには、少なくとも $\log_2 N$ ビットの指示が要ります（<Ref to="prop-spec-bound" />）。プロンプトエンジニアリングの正体はこの符号化です。
- アーキテクチャの価値も定量化できます。$n$ 個の要素を適切に $m$ 個のモジュールに分けると、検査すべき相互作用の数は $\Theta(n^2)$ から $\Theta(n^{4/3})$ に落ちます（<Ref to="prop-modularity" />）。
- 結論として学ぶべきものは、課題設定・アーキテクチャ・検証能力の 3 つと、それらを支える低レイヤーと数学です。

## 1. 動機：コード生成は何を変えたのか

2021 年に OpenAI が Codex の評価論文を公開したとき、多くのエンジニアが受けた衝撃は「自然言語で書いた関数の説明から、動くコードが出てくる」という一点にありました [5]。その後 GitHub Copilot をはじめとする補完ツールが実務に入り、2023 年には無作為化比較試験（RCT）による生産性の測定結果も出ています [6]。

こうした変化に対する反応は、おおむね両極に分かれました。一方は「エンジニアの仕事はなくなる」、もう一方は「所詮おもちゃで、本質は何も変わらない」です。どちらも根拠が薄いと私は考えます。前者は測定された数字を全体に外挿しすぎていますし、後者は測定された数字を無視しています。

この記事で取る立場は違います。**変化の大きさを、工程ごとに分けて見積もる**というものです。幸いなことに、ソフトウェア工学には「一部だけ速くなったとき全体はどれだけ速くなるか」を答える古典的な道具があります。1967 年の Amdahl の議論 [1] と、1987 年の Brooks による本質的複雑さと偶有的複雑さの区別 [2] です。Brooks の言葉を借りれば、コード生成が攻めているのは主として偶有的複雑さ（本来の問題そのものではなく、それを機械で表現するために生じる面倒）の側です。本質的複雑さ、すなわち「何を作るべきかを決めること」は、道具が変わっても残ります。

とはいえ「本質は残る」と唱えるだけでは、何を学べばよいかは決まりません。そこでこの記事では、次の 4 つの問いに数式で答えます。

1. 実装が速くなると、全体はどれだけ速くなるのか。
2. 生成が安くなると、何が相対的に高くなるのか。
3. その「高くなるもの」は自動化できるのか。
4. 高くならないようにするには、何を設計すればよいのか。

## 2. 準備：開発を工程に分解する

議論の対象をはっきりさせます。ソフトウェアを作る仕事を、次の 6 工程に分けます。工程の呼び名は現場によって違いますが、順序と役割はおおむね共通です。

<Definition id="def-pipeline" title="開発工程と自動化率">
ソフトウェア開発の全作業時間 $T$ が、次の 6 工程の時間の和として書けるとします。

$$
T = T_{\text{課題}} + T_{\text{仕様}} + T_{\text{設計}} + T_{\text{実装}} + T_{\text{検証}} + T_{\text{運用}}
$$

ここで、課題設定は「そもそも何を解くか」を決める工程、仕様化は解くべきことを曖昧さなく記述する工程、設計は解の構造（モジュール分割・インタフェース・データ表現）を決める工程、実装は設計をコードに落とす工程、検証はコードが仕様を満たすことを確かめる工程、運用は稼働後の監視・修正の工程です。

ある道具によって速くなる工程の集合を $A$ とし、その合計時間が全体に占める割合

$$
p = \frac{1}{T} \sum_{i \in A} T_i \in [0, 1]
$$

を、その道具に対する**自動化率**と呼びます。
</Definition>

<Figure caption="開発工程と、コード生成が直接効く範囲">
<Mermaid code={`flowchart LR
  A["課題設定<br/>何を解くか"] --> B["仕様化<br/>曖昧さを消す"]
  B --> C["設計<br/>分割と界面"]
  C --> D["実装<br/>コードを書く"]
  D --> E["検証<br/>仕様を満たすか"]
  E --> F["運用<br/>監視と修正"]
  E -.->|不合格なら戻る| D
  D:::auto
  classDef auto stroke-width:3px`} />
</Figure>

図で太枠にした「実装」が、コード生成が直接速くする工程です。破線の矢印は、検証に落ちたときに実装へ戻るループを表します。このループが後で効いてきます（<Ref to="prop-retry" />）。

<Aside type="note">
工程を時間で分けるこの見方は粗い近似です。実際には課題設定と設計は往復しますし、実装しながら仕様の穴に気づくこともあります。ただし「どこが速くなったか」を数えるには、この程度の粗さで十分に有効な結論が出ます。
</Aside>

## 3. 自動化の上限（アムダール則）

まず、いちばん素朴な問いに答えます。実装が $s$ 倍速くなったら、全体は何倍速くなるのでしょうか。

<Proposition id="prop-amdahl" title="一般化アムダール則">
全作業時間を $T > 0$ とし、そのうち割合 $p \in [0,1]$ の部分が速度倍率 $s \ge 1$ で高速化され、残りの割合 $1-p$ は変化しないとします。このとき高速化後の全体時間 $T'$ と全体の速度向上比 $S(p, s) = T / T'$ は

$$
T' = (1-p)T + \frac{p}{s}T, \qquad S(p, s) = \frac{1}{(1-p) + \dfrac{p}{s}}
$$

で与えられます。
</Proposition>

<Proof of="prop-amdahl">
高速化される部分の所要時間は $pT$ です。速度が $s$ 倍になるとは、同じ仕事を $1/s$ の時間で行うことですから、その部分の時間は $pT/s$ になります。高速化されない部分の時間は $(1-p)T$ のまま変わりません。両者は逐次に実行されるので加算でき、

$$
T' = (1-p)T + \frac{p}{s}T = T\left((1-p) + \frac{p}{s}\right)
$$

を得ます。$s \ge 1$ かつ $p \le 1$ より $(1-p) + p/s > 0$（$p=1, s=\infty$ を除く）なので、$T' > 0$ であり、

$$
S(p,s) = \frac{T}{T'} = \frac{1}{(1-p) + \dfrac{p}{s}}
$$

が従います。
</Proof>

ここから、この記事でいちばん大事な帰結が出ます。

<Corollary id="cor-amdahl-limit" title="自動化の上限">
<Ref to="prop-amdahl" /> の設定で $0 < p < 1$ とすると、速度倍率をいくら大きくしても

$$
\lim_{s \to \infty} S(p, s) = \frac{1}{1-p}
$$

であり、かつ任意の $s \ge 1$ に対して $S(p,s) < 1/(1-p)$ です。
</Corollary>

<Proof of="cor-amdahl-limit">
$s \to \infty$ のとき $p/s \to 0$ なので、<Ref to="prop-amdahl" /> の分母は $(1-p)$ に収束します。$p < 1$ より $1-p > 0$ なので、極限の商は $1/(1-p)$ です。

また $s \ge 1$ が有限なら、$p > 0$ より $p/s > 0$ なので分母は $1-p$ より真に大きく、したがって $S(p,s) < 1/(1-p)$ です。（$p = 0$ のときは両辺とも $1$ になり、不等式は等号になります。この場合は自明なので $p > 0$ を仮定しました。）
</Proof>

言い換えると、**速くならない工程の割合が、そのまま全体の伸びしろの逆数を決めます**。実装が全体の 3 割なら、実装時間がゼロになっても全体は $1/0.7 \approx 1.43$ 倍にしかなりません。残りの 7 割に手を付けない限り、それ以上は原理的に出ないのです。

<Example id="ex-copilot-amdahl" title="Copilot の RCT の数字を全体に外挿する">
Peng らの RCT [6] では、HTTP サーバを JavaScript で実装するという課題で、GitHub Copilot を使った群は使わなかった群より 55.8% 短い時間で完了しました。これを速度倍率に直すと

$$
s = \frac{1}{1 - 0.558} = \frac{1}{0.442} \approx 2.26
$$

です。ここで注意すべきは、この課題が**実装工程だけを切り出したもの**だという点です。課題設定も仕様化も、実験者があらかじめ与えています。

そこで、実務での実装工程の割合を仮に $p = 0.3$ とおいて <Ref to="prop-amdahl" /> を適用します（この $0.3$ は測定値ではなく仮定です）。

$$
S(0.3,\ 2.26) = \frac{1}{0.7 + \dfrac{0.3}{2.26}} = \frac{1}{0.7 + 0.1327} = \frac{1}{0.8327} \approx 1.20
$$

全体では約 1.20 倍（所要時間にして約 17% の短縮）です。仮に $p = 0.5$ でも

$$
S(0.5,\ 2.26) = \frac{1}{0.5 + 0.2212} = \frac{1}{0.7212} \approx 1.39
$$

で 1.39 倍にとどまります。「実装が 2.26 倍速くなった」という事実と、「開発が 2.26 倍速くなった」という主張のあいだには、これだけの隔たりがあります。
</Example>

次の図は、速度倍率 $s = 2.26$ の場合と $s = \infty$ の場合について、自動化率 $p$ に対する全体の速度向上比を描いたものです。

<Figure caption="自動化率 p と全体の速度向上比。実線は s → ∞（実装が瞬時に終わる理想）、破線は s = 2.26。">
<svg viewBox="0 0 640 380" width="100%" role="img" aria-label="自動化率に対する全体速度向上比のグラフ">
  <g stroke="currentColor" fill="none" stroke-width="1.5">
    <line x1="70" y1="330" x2="612" y2="330" />
    <line x1="70" y1="330" x2="70" y2="30" />
  </g>
  <g stroke="currentColor" stroke-width="1" opacity="0.35">
    <line x1="70" y1="272" x2="612" y2="272" />
    <line x1="70" y1="214" x2="612" y2="214" />
    <line x1="70" y1="156" x2="612" y2="156" />
    <line x1="70" y1="98" x2="612" y2="98" />
    <line x1="70" y1="40" x2="612" y2="40" />
  </g>
  <g fill="currentColor" font-size="13" text-anchor="end">
    <text x="62" y="335">1</text>
    <text x="62" y="277">2</text>
    <text x="62" y="219">3</text>
    <text x="62" y="161">4</text>
    <text x="62" y="103">5</text>
    <text x="62" y="45">6</text>
  </g>
  <g fill="currentColor" font-size="13" text-anchor="middle">
    <text x="70" y="350">0</text>
    <text x="176" y="350">0.2</text>
    <text x="282" y="350">0.4</text>
    <text x="388" y="350">0.6</text>
    <text x="494" y="350">0.8</text>
    <text x="600" y="350">1.0</text>
  </g>
  <text x="341" y="372" fill="currentColor" font-size="14" text-anchor="middle">自動化率 p</text>
  <text x="20" y="180" fill="currentColor" font-size="14" text-anchor="middle" transform="rotate(-90 20 180)">速度向上比 S</text>
  <polyline points="70,330 176,315.5 282,291.3 335,272 388,243 441,194.7 494,98 511,40"
            fill="none" stroke="var(--sl-color-accent)" stroke-width="2.5" />
  <polyline points="70,330 123,326.6 176,322.7 229,318.3 282,313.4 335,307.6 388,300.8 441,292.9 494,283.3 547,271.6 600,256.9"
            fill="none" stroke="currentColor" stroke-width="2" stroke-dasharray="6 4" />
  <text x="520" y="60" fill="var(--sl-color-accent)" font-size="13">s → ∞</text>
  <text x="520" y="248" fill="currentColor" font-size="13">s = 2.26</text>
</svg>
</Figure>

グラフの左半分、つまり $p$ が $0.5$ 以下の領域では、$s = \infty$ という非現実的な理想でも速度向上比は $2$ 倍を超えません。**自動化への投資の効果は、自動化率そのものに支配されます。**

## 4. 生成と検証：安くなるものと高くなるもの

<Ref to="cor-amdahl-limit" /> は「他の工程が伸びしろを決める」と言っています。では、生成が安くなったとき、どの工程が相対的に重くなるのでしょうか。答えは検証です。それを見るために、生成と検証のループをモデル化します。生成コストと検証コストを別々に数えるこの見方は、<Ref to="computer-science/ai-era/llm-and-programming#def-delegation-cost" text="委譲コストモデル" /> と同じ枠組みを、1 単位の作業に絞って使うものです。

<Definition id="def-generate-verify" title="生成検証サイクル">
ある単位の作業（1 つの関数、1 つの変更）について、次を仮定します。

- 1 回の生成にかかるコストを $g \ge 0$ とする。
- 生成物が仕様を満たすかどうかを判定するコストを $v > 0$ とする。
- 各回の生成が仕様を満たす確率は $q \in (0, 1]$ であり、各回は独立とする。
- 検証は正しく判定する（見逃しも誤検出もない）とする。
- 満たさないと判定されたら、生成をやり直す。

このとき、最初に受理されるまでの総コストの期待値を $E(g, v, q)$ と書きます。
</Definition>

<Proposition id="prop-retry" title="生成検証サイクルの期待コスト">
<Ref to="def-generate-verify" /> の仮定のもとで、受理までの試行回数 $N$ は成功確率 $q$ の幾何分布に従い、$\mathbb{E}[N] = 1/q$、したがって

$$
E(g, v, q) = \frac{g + v}{q}
$$

が成り立ちます。
</Proposition>

<Proof of="prop-retry">
各試行は独立に確率 $q$ で成功するので、$N = k$ となるのは最初の $k-1$ 回が失敗し $k$ 回目が成功する場合で、

$$
\Pr[N = k] = (1-q)^{k-1} q \qquad (k = 1, 2, \ldots)
$$

です。$x = 1 - q \in [0, 1)$ とおくと、期待値は

$$
\mathbb{E}[N] = \sum_{k=1}^{\infty} k\, q\, x^{k-1} = q \sum_{k=1}^{\infty} k\, x^{k-1}
$$

です。ここで $|x| < 1$ のとき等比級数 $\sum_{k=0}^{\infty} x^k = 1/(1-x)$ を $x$ で項別微分できて

$$
\sum_{k=1}^{\infty} k\, x^{k-1} = \frac{d}{dx}\left(\frac{1}{1-x}\right) = \frac{1}{(1-x)^2}
$$

となります（べき級数は収束半径の内部で項別微分できるため、この操作は正当です）。$1 - x = q$ を代入して

$$
\mathbb{E}[N] = q \cdot \frac{1}{q^2} = \frac{1}{q}
$$

を得ます。各試行のコストは生成 $g$ と検証 $v$ の和 $g + v$ で一定なので、総コストは $(g+v)N$ です。期待値の線形性から

$$
E(g,v,q) = (g+v)\,\mathbb{E}[N] = \frac{g+v}{q}
$$

となります。
</Proof>

<Corollary id="cor-verify-floor" title="検証コストの床">
<Ref to="prop-retry" /> の設定で、生成コスト $g$ をどれだけ小さくしても

$$
E(g, v, q) \ge \frac{v}{q} \ge v
$$

であり、$g \to 0$ のとき $E(g,v,q) \to v/q$ です。すなわち、期待総コストの下限は生成側ではなく検証側と受理確率だけで決まります。
</Corollary>

<Proof of="cor-verify-floor">
$g \ge 0$ と $q > 0$ より $(g+v)/q \ge v/q$ です。また $q \le 1$ より $1/q \ge 1$ なので $v/q \ge v$ です。$g \to 0$ の極限は $(0+v)/q = v/q$ で、これは $g$ に関する連続性から従います。
</Proof>

この系が、この記事の主張の中核です。生成が事実上ただ同然になった世界では、**コストを決めるのは $v$（検証の重さ）と $q$（一発で通る確率）だけ**になります。そして $q$ を上げる手段は、モデルを賢くすることのほかに 2 つあります。仕様を精密にすること（第 6 節）と、検証しやすい構造を設計すること（第 7 節）です。どちらも人間側の仕事です。なお $v$ が十分に大きい領域では、床そのものが自力でやる場合のコストを上回り、委譲が原理的に割に合わなくなります（<Ref to="computer-science/ai-era/llm-and-programming#cor-no-delegation" />）。

<Definition id="def-passk" title="pass@k">
1 つの課題に対してモデルから $k$ 個の解を独立に標本抽出し、そのうち少なくとも 1 つが検証に合格する確率を **pass@k** と呼びます [5]。1 回あたりの合格確率を $q$、各標本が独立であるとすると

$$
\mathrm{pass}@k = 1 - (1-q)^k
$$

です（$\mathrm{pass}@1 = q$）。
</Definition>

<Example id="ex-passk" title="pass@k は上がるが、検証回数も上がる">
Chen らの評価では、Codex-12B は HumanEval の課題を 1 標本で 28.8%、100 標本で 70.2% 解きました [5]。<Ref to="def-passk" /> の独立モデルに $q = 0.288$ を入れると

$$
1 - (1 - 0.288)^{100} = 1 - 0.712^{100}
$$

となり、$\ln(0.712^{100}) = 100 \ln 0.712 = 100 \times (-0.3398) = -33.98$ なので $0.712^{100} \approx e^{-33.98} \approx 1.7 \times 10^{-15}$、すなわち pass@100 はほぼ $1$ になるはずです。実測の 70.2% はこれよりずっと低い。独立の仮定が成り立っていない（モデルは同じ誤解を何度も繰り返す）ことの現れです。

さらに重要なのは、この 70.2% という数字が「100 個の候補のうち正解が含まれる確率」でしかないことです。どれが正解かを知るには、100 個すべてを検証しなければなりません。<Ref to="prop-retry" /> の言葉でいえば、$g$ を下げて試行回数を増やす戦略は、$v$ の総額をそのぶん増やします。再生成を何回重ねても損益の構造が変わらないことは、<Ref to="computer-science/ai-era/llm-and-programming#prop-retry" text="再生成しても損益分岐点は変わらない" /> でも別の形で示されています。**検証が自動化されていない領域では、この戦略は成立しません。**
</Example>

## 5. 検証は完全には自動化できない（ライスの定理）

「では検証も AI にやらせればよい」という反論が当然出ます。部分的にはそのとおりで、テスト生成も静的解析も改善し続けています。しかし「プログラムが仕様を満たすかを一般に判定する機械」は、AI であるかどうかに関係なく存在しません。これは 1953 年の Rice の定理 [4] が述べていることです。

<Theorem id="thm-rice" title="ライスの定理">
$\varphi_0, \varphi_1, \ldots$ を部分計算可能関数の（受理可能な）番号付けとします。部分計算可能関数の集合 $\mathcal{P}$ が**非自明**、すなわち $\mathcal{P} \ne \varnothing$ かつ $\mathcal{P}$ がすべての部分計算可能関数の集合と一致しないとします。このとき、添字集合

$$
I_{\mathcal{P}} = \{\, e \in \mathbb{N} : \varphi_e \in \mathcal{P} \,\}
$$

は決定不能です。
</Theorem>

<Proof of="thm-rice">
どこでも未定義な部分関数を $\xi$ と書きます。

まず $\xi \notin \mathcal{P}$ の場合を示します。$\mathcal{P} \ne \varnothing$ なので、ある部分計算可能関数 $\psi \in \mathcal{P}$ を取れます。$\psi = \varphi_b$ となる添字 $b$ を固定します。

停止性問題の任意の入力 $(e, x)$ に対し、次の 2 引数部分計算可能関数を考えます。

$$
h(\langle e, x\rangle, y) = \begin{cases} \varphi_b(y) & \varphi_e(x) \text{ が停止するとき} \\ \text{未定義} & \text{それ以外} \end{cases}
$$

$h$ は計算可能です。実際、入力 $(\langle e,x\rangle, y)$ に対して「まず $\varphi_e(x)$ を（停止するまで）実行し、停止したら $\varphi_b(y)$ を実行して出力する」という手続きがそのまま $h$ を計算します。

s-m-n 定理より、全域計算可能な関数 $f$ が存在して、すべての $e, x, y$ に対し $\varphi_{f(e,x)}(y) = h(\langle e,x\rangle, y)$ が成り立ちます。すると

- $\varphi_e(x)$ が停止するなら、すべての $y$ で $\varphi_{f(e,x)}(y) = \varphi_b(y)$ なので $\varphi_{f(e,x)} = \psi \in \mathcal{P}$、つまり $f(e,x) \in I_{\mathcal{P}}$。
- $\varphi_e(x)$ が停止しないなら、すべての $y$ で未定義なので $\varphi_{f(e,x)} = \xi \notin \mathcal{P}$、つまり $f(e,x) \notin I_{\mathcal{P}}$。

したがって $I_{\mathcal{P}}$ が決定可能なら、$f$ が全域計算可能であることと合わせて「$\varphi_e(x)$ が停止するか」が決定可能になります。これは停止性問題の決定不能性に反します。よって $I_{\mathcal{P}}$ は決定不能です。

次に $\xi \in \mathcal{P}$ の場合は、補集合 $\mathcal{P}^{c}$（すべての部分計算可能関数のうち $\mathcal{P}$ に属さないもの全体）を考えます。$\mathcal{P}$ が非自明なので $\mathcal{P}^{c}$ も非自明であり、$\xi \notin \mathcal{P}^{c}$ です。前段より $I_{\mathcal{P}^{c}}$ は決定不能です。ところが $I_{\mathcal{P}^{c}} = \mathbb{N} \setminus I_{\mathcal{P}}$ であり、決定可能な集合の補集合は決定可能ですから、$I_{\mathcal{P}}$ が決定可能だとすると矛盾します。よってこの場合も $I_{\mathcal{P}}$ は決定不能です。
</Proof>

<Remark id="rem-rice-scope">
ライスの定理が扱うのは**外延的**な性質、つまり「そのプログラムが計算する関数」だけで決まる性質です。「ソースコードの行数が 100 行未満か」のような構文的性質は当然決定可能ですし、これは定理と矛盾しません。一方で「この関数はすべての入力で停止するか」「この関数はソート済みの列を返すか」「この関数は仕様 $S$ を満たすか」はいずれも外延的な非自明性質なので、決定不能です。

また、この定理は「個々のプログラムについて何も分からない」とは言っていません。型システム・契約・テスト・モデル検査はいずれも、判定を保守的にする（分からないときは「不明」または「不合格」と答える）か、対象を制限することで、実用的な精度を得ています。定理が禁じているのは、**完全かつ健全かつ常に停止する万能判定器**の存在だけです。
</Remark>

実務への含意はこうです。検証は永久に「人間が設計した近似」の集合体であり続けます。どんなテストを書くか、どんな不変量を型で表すか、どんな性質をアサートするか。これらを決める仕事は、原理的に生成側へは移せません。**AI が書いたコードが増えるほど、検証の設計者としてのエンジニアの単価は上がります。**

## 6. 課題設定の情報量：仕様は圧縮できない

ここまでで「検証が効く」ことは分かりました。次は $q$（一発で通る確率）を上げる話、すなわち仕様の精度の話です。素朴な直観として「曖昧な指示では望みのものは出てこない」というのは誰でも知っていますが、これは数え上げで正確に言えます。

<Definition id="def-spec-code" title="仕様符号">
実現しうる振る舞いの有限集合を $B$、$|B| = N$ とします。指示（プロンプト）を有限のビット列とみなし、指示から振る舞いへの決定的な写像 $D : \{0,1\}^{*} \to B$ を固定します（$D$ はモデルと復号手続きを合わせたものです）。$D$ が**全射**であるとき、すなわち $B$ のどの振る舞いもある指示によって得られるとき、$D$ を $B$ の**仕様符号**と呼びます。指示 $w$ の長さを $|w|$ と書きます。
</Definition>

<Proposition id="prop-spec-bound" title="仕様の記述長の下界">
$D$ を <Ref to="def-spec-code" /> の意味での $B$（$|B| = N$）の仕様符号とします。各 $b \in B$ に対して $b$ を実現する最短の指示の長さを $\ell(b) = \min\{|w| : D(w) = b\}$ とおきます。このとき

$$
\max_{b \in B} \ell(b) \ \ge \ \log_2 N - 1 \qquad \text{より正確には} \quad \max_{b \in B} \ell(b) \ \ge \ \lceil \log_2 (N+1) \rceil - 1
$$

が成り立ちます。とくに、長さ $\ell$ 以下の指示だけを使うなら、到達できる振る舞いは高々 $2^{\ell + 1} - 1$ 個です。
</Proposition>

<Proof of="prop-spec-bound">
長さ $\ell$ 以下のビット列の総数を数えます。長さ $j$ のビット列はちょうど $2^j$ 個あるので、長さ $0$ から $\ell$ までの合計は

$$
\sum_{j=0}^{\ell} 2^{j} = 2^{\ell+1} - 1
$$

です（等比数列の和）。$D$ は写像なので、長さ $\ell$ 以下の指示から得られる振る舞いは高々この個数しかありません。

いま $L = \max_{b \in B} \ell(b)$ とおくと、$B$ のすべての元は長さ $L$ 以下の指示で得られます。$D$ が全射であることから $N \le 2^{L+1} - 1$、すなわち $2^{L+1} \ge N + 1$ です。両辺の $\log_2$ を取ると $L + 1 \ge \log_2 (N+1)$。$L$ は整数なので $L \ge \lceil \log_2(N+1)\rceil - 1$ を得ます。さらに $\log_2(N+1) > \log_2 N$ なので $L > \log_2 N - 1$、すなわち $L \ge \log_2 N - 1$ が従います。
</Proof>

この命題は当たり前のことを言っているように見えますが、含意は鋭いものです。**指示の長さは、区別したい振る舞いの個数の対数以上でなければならない。** モデルがどれだけ賢くなっても、この下界は変わりません。賢さが変えられるのは「どの振る舞いに短い符号語を割り当てるか」、つまり**既定値の質**だけです。良いモデルとは、多くの人が望む振る舞いに短い符号を割り当てているモデルのことです。

<Example id="ex-api-spec" title="API 1 本の設計判断をビットで数える">
ページング付きの一覧取得エンドポイントを 1 本作る場合を考えます。実装前に決まっていなければならない二択の判断を挙げてみます。

1. ページングはオフセット方式かカーソル方式か。
2. 上限件数を超えた要求はエラーにするか、上限に丸めるか。
3. 削除済みレコードを含めるか除くか。
4. 並び順のキーが同値のとき、安定な副次キーを付けるか付けないか。
5. 総件数を返すか返さないか。
6. 認可はレコード単位か、コレクション単位か。
7. 空結果は 200 で空配列か、404 か。
8. 未知のクエリパラメータは無視するか、400 にするか。
9. タイムスタンプは UTC 固定か、要求されたタイムゾーンか。
10. レート制限の単位はユーザか、API キーか。
11. 応答をキャッシュ可能とするか、しないか。
12. 部分的な障害時に、取得できた分を返すか、全体を失敗させるか。

これで $2^{12} = 4096$ 通りです。<Ref to="prop-spec-bound" /> より、これらを区別するには最低 12 ビットの指示が要ります。「ユーザ一覧を返す API を作って」という日本語は、確かに 12 ビットよりずっと多くの情報量を持っていますが、**この 12 個の軸に関する情報はほぼゼロ**です。したがってモデルは既定値で埋めるほかなく、そのうち何個かは仕様と食い違います。<Ref to="prop-retry" /> の $q$ が下がるのは、まさにこの食い違いによってです。

逆に言えば、この 12 項目を列挙して答えを決める作業こそが、価値の源泉です。それは「プロンプトを書く」作業と外形上は同じですが、中身は課題設定と仕様化そのものです。
</Example>

<Remark id="rem-prompt-engineering">
プロンプトエンジニアリングを「モデルを騙す小技」と捉えると、モデルの世代交代で無価値になります。<Ref to="prop-spec-bound" /> の見方に立てば、その本体は**振る舞い空間の符号化**、すなわち「どの軸で何通りに分かれるかを列挙し、それを最小の記述で伝えること」です。この技能はモデルが変わっても腐りません。腐るのは、特定モデルの癖に依存した部分だけです。

より詳しい議論は [LLMとプログラミング](/computer-science/ai-era/llm-and-programming) を参照してください。とくに、指示を精密にする手間まで含めて委譲が得になる条件は <Ref to="computer-science/ai-era/llm-and-programming#prop-threshold" /> にまとめられています。
</Remark>

## 7. アーキテクチャ：分割が何を節約するのか

最後に $v$（検証コスト）を下げる話をします。検証が重くなる最大の要因は、部品どうしの相互作用です。Parnas が 1972 年に述べたモジュール分割の基準 [3] は、まさにこの相互作用を減らすためのものでした。これも数えれば定量化できます。

<Proposition id="prop-modularity" title="モジュール分割による検査対象の削減">
システムが $n \ge 2$ 個の要素からなるとします。分割しない場合、任意の 2 要素が直接相互作用しうるとして、検査すべき対の個数は $\binom{n}{2}$ です。

いま $m$ が $n$ を割り切るとして、要素を大きさ $n/m$ の $m$ 個のモジュールに分け、（i）同じモジュール内では任意の 2 要素が相互作用しうる、（ii）異なるモジュール間の相互作用はモジュール対ごとのインタフェース 1 個に集約される、と仮定します。このとき検査すべき対象の個数は

$$
I(m) = m \binom{n/m}{2} + \binom{m}{2} = \frac{n^2}{2m} - \frac{n}{2} + \frac{m^2}{2} - \frac{m}{2}
$$

です。$I$ を $m > 0$ の実変数関数とみなすと $I$ は狭義凸であり、最小点 $m^{*}$ は $m^3 - m^2/2 = n^2/2$ の唯一の正の解で、$n$ が大きいとき $m^{*} \sim (n^2/2)^{1/3}$ です。このとき

$$
I(m^{*}) = \Theta\!\left(n^{4/3}\right)
$$

となり、分割しない場合の $\Theta(n^2)$ より真に小さくなります。
</Proposition>

<Proof of="prop-modularity">
まず $I(m)$ の式を確かめます。各モジュールは $n/m$ 個の要素を持つので、モジュール内の対は $\binom{n/m}{2} = \frac{(n/m)(n/m - 1)}{2}$ 個、これが $m$ 個あるので

$$
m \cdot \frac{(n/m)(n/m-1)}{2} = \frac{n(n/m - 1)}{2} = \frac{n^2}{2m} - \frac{n}{2}
$$

です。モジュール対は $\binom{m}{2} = \frac{m(m-1)}{2} = \frac{m^2}{2} - \frac{m}{2}$ 個なので、和が主張の式になります。

次に凸性です。$m > 0$ で

$$
I'(m) = -\frac{n^2}{2m^2} + m - \frac{1}{2}, \qquad I''(m) = \frac{n^2}{m^3} + 1 > 0
$$

なので $I$ は狭義凸です。したがって $I'(m) = 0$ の解は高々 1 つで、それが最小点です。$I'(m) = 0$ を整理すると

$$
m - \frac{1}{2} = \frac{n^2}{2m^2} \iff m^3 - \frac{m^2}{2} = \frac{n^2}{2}
$$

を得ます。$m \mapsto m^3 - m^2/2$ は $m \ge 1/2$ で連続かつ狭義単調増加で、$m \to \infty$ で $\infty$ に発散するので、正の解 $m^{*}$ がただ 1 つ存在します。$n$ が大きいとき $m^{*}$ も大きく、$m^2/2$ の項は $m^3$ に対して無視できるので $m^{*} \sim (n^2/2)^{1/3}$ です。

このとき $m^{*} = c\, n^{2/3}$（$c = 2^{-1/3}$）とおくと

$$
\frac{n^2}{2m^{*}} = \frac{n^2}{2c\,n^{2/3}} = \frac{1}{2c}\,n^{4/3}, \qquad \frac{(m^{*})^2}{2} = \frac{c^2}{2}\,n^{4/3}
$$

で、残りの $-n/2 - m^{*}/2$ は $O(n)$ すなわち $o(n^{4/3})$ です。よって $I(m^{*}) = \left(\frac{1}{2c} + \frac{c^2}{2}\right) n^{4/3} + o(n^{4/3}) = \Theta(n^{4/3})$ となります。$n^{4/3} = o(n^2)$ なので、分割しない場合より真に小さくなります。
</Proof>

<Example id="ex-modularity-100" title="n = 100 での具体的な削減量">
$n = 100$ とします。分割しない場合は

$$
\binom{100}{2} = \frac{100 \times 99}{2} = 4950
$$

対を検査することになります。

$m = 10$（各モジュール 10 要素）とすると

$$
I(10) = 10\binom{10}{2} + \binom{10}{2} = 10 \times 45 + 45 = 495
$$

で、ちょうど 10 分の 1 です。

最適値は $m^3 - m^2/2 = 100^2/2 = 5000$ の解で、$m = 17$ のとき $17^3 - 17^2/2 = 4913 - 144.5 = 4768.5$、$m = 18$ のとき $5832 - 162 = 5670$ なので、$m^{*}$ は 17 と 18 のあいだにあります。整数で評価すると

$$
I(17) = \frac{10000}{34} - 50 + \frac{289}{2} - \frac{17}{2} = 294.1 - 50 + 144.5 - 8.5 = 380.1
$$

（$17$ は $100$ を割り切らないので近似値です）。おおよそ 380 対、分割しない場合の **13 分の 1** です。

この計算が示しているのは、**分割は「多ければ多いほどよい」ものではない**ということです。$m$ を大きくしすぎるとモジュール間インタフェースの数 $\binom{m}{2}$ が効いてきます。$m = 50$ なら $I(50) = 100 - 50 + 1250 - 25 = 1275$ で、$m = 17$ より悪くなります。マイクロサービスを細かく割りすぎた組織が経験する痛みの、いちばん単純なモデルです。
</Example>

ここで LLM との接続を述べます。モデルが一度に見られる文脈の長さは有限です。$I(m)$ が小さい設計、すなわち「1 つのモジュールを理解するのに、そのモジュールと少数のインタフェースだけ読めばよい」設計は、そのまま**モデルが正しいコードを出しやすい設計**でもあります。<Ref to="prop-retry" /> の言葉でいえば、良い分割は $v$ を下げると同時に $q$ を上げます。アーキテクチャ設計は、AI 時代に価値が下がるどころか、AI の性能を決めるパラメータになりました。

## 8. 何を学ぶか

以上の 4 つの結果を、学習の優先順位に翻訳します。

| 技能 | 根拠 | 生成で代替される度合い |
|---|---|---|
| 課題設定（何を解くか） | <Ref to="cor-amdahl-limit" />：自動化されない工程が上限を決める | 低い。目的は外から与えられない |
| 仕様化（曖昧さを消す） | <Ref to="prop-spec-bound" />：記述長の下界はモデルに依らない | 低い。既定値の質だけが改善する |
| アーキテクチャ設計 | <Ref to="prop-modularity" />：$v$ を下げ $q$ を上げる | 低い。制約の把握が必要 |
| 検証の設計（テスト・型・不変量） | <Ref to="cor-verify-floor" />、<Ref to="thm-rice" /> | 低い。原理的に完全自動化は不可能 |
| 定型的な実装（CRUD、変換、定型 API） | <Ref to="ex-copilot-amdahl" /> | 高い。ここが速くなった |
| 構文・ライブラリ API の暗記 | — | 非常に高い |
| 低レイヤーの理解（メモリ、並行性、性能） | 検証の前提。生成物の妥当性判断に必要 | 低い |
| 数学的基礎（離散数学、確率、計算量、線形代数） | 不変量と下界を言語化する道具 | 低い |

表の下 2 行について補足します。低レイヤーと数学が重要である理由は、「AI にできないから」ではありません。**検証の言語だから**です。

生成物が正しいかどうかを判断するには、正しさを述べる語彙が要ります。「このループは $O(n^2)$ だから $n = 10^6$ では回らない」「この共有カウンタはデータ競合を起こす」「この浮動小数点の総和は桁落ちで精度を失う」「この確率的アルゴリズムの誤り率は $2^{-k}$ で抑えられる」。こうした判断はどれも、コードを読むだけでは出てきません。計算量・メモリモデル・数値解析・確率という**モデル**を持っている人だけが下せます。<Ref to="cor-verify-floor" /> が言うとおり、コストの床を決めるのは検証です。検証の語彙を持たない人は、床の高さを下げられません。

数学の役割については [数学を学び直す意義](/computer-science/ai-era/relearning-mathematics) でさらに詳しく扱います。たとえば「検知率 99%、誤検知率 1% の警告をどう読むか」という判断は <Ref to="computer-science/ai-era/relearning-mathematics#ex-bayes-ppv" /> で、勾配降下法の学習率が発散しない条件は <Ref to="computer-science/ai-era/relearning-mathematics#thm-gd-convex" /> で扱われています。

<Aside type="caution">
この記事の分析はすべて、明示した仮定のもとでのモデル計算です。$p = 0.3$ も、生成の独立性も、検証の完全性も、実測ではありません。数字そのものより、**「どの量が上限を決めているか」という構造**を持ち帰ってください。仮定を変えても、<Ref to="cor-amdahl-limit" /> と <Ref to="cor-verify-floor" /> の形は変わりません。
</Aside>

## 9. 演習

<Exercise id="exr-amdahl-apply" difficulty="易">
ある開発チームでは、実装工程が全作業時間の 25% を占めています。

（1）コード生成ツールの導入で実装工程が 5 倍速くなったとき、全体の速度向上比を求めてください。

（2）実装工程の時間が完全にゼロになったとしても、全体の速度向上比が超えられない値を求めてください。

（3）全体を 2 倍速くしたいとき、実装以外の工程をどれだけ削減する必要があるか論じてください。

<Solution>
（1）<Ref to="prop-amdahl" /> に $p = 0.25$、$s = 5$ を代入します。

$$
S(0.25, 5) = \frac{1}{0.75 + \dfrac{0.25}{5}} = \frac{1}{0.75 + 0.05} = \frac{1}{0.80} = 1.25
$$

速度向上比は 1.25 倍（所要時間にして 20% の短縮）です。

（2）<Ref to="cor-amdahl-limit" /> より

$$
\lim_{s \to \infty} S(0.25, s) = \frac{1}{1 - 0.25} = \frac{1}{0.75} \approx 1.333
$$

すなわち約 1.33 倍が上限です。（1）の 1.25 倍は、この上限のすでに 94% を達成しており、これ以上コード生成を速くしても伸びしろは 7% 程度しか残っていません。

（3）全体を 2 倍にするには $T' = T/2$ が必要です。実装をゼロにしても残る時間は $0.75\,T$ で、これは $T/2$ より大きい。したがって**実装の高速化だけでは 2 倍は原理的に不可能**です。実装をゼロにしたうえで、残りの $0.75\,T$ をさらに $0.5\,T$ まで、つまり 3 分の 1 削る必要があります。実装以外の工程（課題設定・仕様化・設計・検証・運用）に手を入れない限り、2 倍という目標は立ちません。
</Solution>
</Exercise>

<Exercise id="exr-retry-cost" difficulty="標準">
<Ref to="def-generate-verify" /> のモデルで、生成コスト $g = 1$、検証コスト $v = 4$、1 回あたりの成功確率 $q = 0.4$ とします。

（1）受理までの期待総コストを求めてください。

（2）生成コストが $g = 0$ になったとき、期待総コストは何 % 減りますか。

（3）代わりに仕様を精密にして $q = 0.4$ から $q = 0.8$ に上げたとき（$g = 1$ のまま）、期待総コストは何 % 減りますか。（2）と比べて論じてください。

（4）$k = 5$ 個の候補を独立に生成してすべて検証する戦略を取ったとき、少なくとも 1 つが合格する確率と、検証コストの合計を求めてください。

<Solution>
（1）<Ref to="prop-retry" /> より

$$
E(1, 4, 0.4) = \frac{1 + 4}{0.4} = \frac{5}{0.4} = 12.5
$$

（2）$g = 0$ とすると $E(0,4,0.4) = 4/0.4 = 10$ です。減少率は

$$
\frac{12.5 - 10}{12.5} = \frac{2.5}{12.5} = 0.20
$$

で 20% です。<Ref to="cor-verify-floor" /> のとおり、$10$ が床であり、生成をどれだけ安くしてもこれ以下にはなりません。

（3）$E(1, 4, 0.8) = 5/0.8 = 6.25$ です。減少率は

$$
\frac{12.5 - 6.25}{12.5} = 0.50
$$

で 50%。生成コストをゼロにする（20% 減）よりも、成功率を 2 倍にする（50% 減）ほうが効果が大きい。<Ref to="prop-retry" /> の分母に $q$ があり、分子に $g$ があることの帰結です。成功率は仕様の精度と設計の良さで上がる量なので（<Ref to="ex-api-spec" />、<Ref to="prop-modularity" />）、投資先としては人間側の工程が有利になります。

（4）<Ref to="def-passk" /> より

$$
\mathrm{pass}@5 = 1 - (1 - 0.4)^5 = 1 - 0.6^5 = 1 - 0.07776 = 0.92224
$$

約 92.2% です。ただし、どれが合格かを知るには 5 個すべてを検証する必要があるので、検証コストは $5 \times 4 = 20$ かかります。これは（1）の期待総コスト 12.5 より大きい。**「たくさん生成して選ぶ」戦略は、検証が安い（自動テストが完備している）場合にだけ有利になります。**
</Solution>
</Exercise>

<Exercise id="exr-spec-bits" difficulty="標準">
あるシステムの振る舞いが、独立な二択の設計判断 20 個で決まるとします。すなわち $N = 2^{20}$ 通りです。

（1）<Ref to="prop-spec-bound" /> により、すべての振る舞いを指示で表現するために必要な最短指示長の最大値の下界を求めてください。

（2）長さ 15 ビット以下の指示しか使わないと決めた場合、到達できない振る舞いが少なくとも何通りあるか求めてください。

（3）（2）の結果を、実務における「短いプロンプトで済ませること」の意味に翻訳してください。

<Solution>
（1）$N = 2^{20} = 1048576$ です。<Ref to="prop-spec-bound" /> より

$$
L \ge \lceil \log_2(N + 1) \rceil - 1 = \lceil \log_2 1048577 \rceil - 1 = 21 - 1 = 20
$$

（$2^{20} = 1048576 < 1048577 \le 2^{21}$ なので天井は 21 です）。少なくとも 20 ビットの指示を要する振る舞いが存在します。

（2）長さ 15 以下のビット列は $2^{16} - 1 = 65535$ 個です（<Ref to="prop-spec-bound" /> の証明中の等比和で $\ell = 15$）。写像 $D$ の像はこれ以下の大きさなので、到達できる振る舞いは高々 65535 通り。したがって到達できない振る舞いは少なくとも

$$
1048576 - 65535 = 983041
$$

通り、全体の約 93.8% です。

（3）短いプロンプトで得られるのは、モデルの既定値が埋めた振る舞いだけです。20 個の判断のうち明示的に指定していないものは、すべてモデルの事前分布に委ねられます。既定値が偶然こちらの意図と一致すればよいのですが、一致しない判断が 1 つでもあれば生成物は不合格になり、<Ref to="prop-retry" /> の $q$ が下がります。

したがって「短く指示して速く回す」戦略が有効なのは、（i）既定値が意図とよく一致する定型的な領域か、（ii）検証が安く、不一致をすぐ検出できる領域に限られます。そうでない領域では、判断の軸を列挙して明示することが、結局いちばん速い道になります。
</Solution>
</Exercise>

## 参考文献

1. G. M. Amdahl, "Validity of the single processor approach to achieving large scale computing capabilities", *AFIPS Spring Joint Computer Conference* (1967), 483–485.
2. F. P. Brooks, Jr., "No Silver Bullet: Essence and Accidents of Software Engineering", *IEEE Computer* 20(4) (1987), 10–19.
3. D. L. Parnas, "On the Criteria To Be Used in Decomposing Systems into Modules", *Communications of the ACM* 15(12) (1972), 1053–1058.
4. H. G. Rice, "Classes of recursively enumerable sets and their decision problems", *Transactions of the American Mathematical Society* 74 (1953), 358–366.
5. M. Chen et al., "Evaluating Large Language Models Trained on Code", arXiv:2107.03374 (2021). [arXiv](https://arxiv.org/abs/2107.03374)
6. S. Peng, E. Kalliamvakou, P. Cihon, M. Demirer, "The Impact of AI on Developer Productivity: Evidence from GitHub Copilot", arXiv:2302.06590 (2023). [arXiv](https://arxiv.org/abs/2302.06590)

## Appendix: 検証が完全でない場合

**本文では検証が誤らないと仮定しましたが、現実の検証（テスト、レビュー）は見逃します。** ここではその影響を見積もります。

<Ref to="def-generate-verify" /> の仮定のうち「検証は正しく判定する」を外し、次のように置き換えます。不合格の生成物を誤って合格と判定する確率（見逃し率）を $\alpha \in [0,1)$、合格の生成物を誤って不合格と判定する確率は $0$ とします。この $\alpha$ は、<Ref to="computer-science/ai-era/llm-and-programming#def-verifier" text="検証器の偽陽性率" /> と同じ量です。1 回の生成が真に仕様を満たす確率は $q$ のままです。

このとき、1 回の試行が「合格」と判定される確率は、真に正しくて合格する確率 $q$ と、誤っているのに見逃される確率 $(1-q)\alpha$ の和で $q + (1-q)\alpha$ です。<Ref to="prop-retry" /> と同じ計算により、判定が出るまでの期待コストは

$$
\frac{g + v}{q + (1-q)\alpha}
$$

となり、見た目のコストは $\alpha$ が大きいほど下がります。ところが、判定が出た時点でその生成物が**実際に正しい**条件付き確率はベイズの定理により

$$
\Pr[\text{正しい} \mid \text{合格判定}] = \frac{q}{q + (1-q)\alpha}
$$

です。これは <Ref to="computer-science/ai-era/llm-and-programming#thm-acceptance-error" /> を、この記事の記号で書き直したものにほかなりません。たとえば $q = 0.5$、$\alpha = 0.3$ なら $0.5 / (0.5 + 0.15) \approx 0.769$ で、合格したものの約 23% が実は誤っています。

**この誤りは消えるのではなく、運用工程に移動します。** <Ref to="def-pipeline" /> の $T_{\text{運用}}$ が増えるということです。そして障害対応のコストは、開発時の修正コストより一般にはるかに高い。したがって $\alpha$ を下げること（テストの網羅性、レビューの質、型による静的保証）は、見かけの速度を落としてでも行う価値があります。<Ref to="cor-verify-floor" /> の「床」は、$\alpha$ を無視して測ると実際より低く見えます。生成が速くなった時代に品質指標を測り直すべき理由は、ここにあります。
