Dev Study
← 何を作るかを言葉にする
レッスン 1 / 10·約 9 分·第21期 何を作るかを言葉にする

1つの依頼文から、正しいプログラムが2つできる

リクエストがアプリケーションに届くまで
  1. ブラウザURL入力
  2. DNS名前→IP
  3. ルータ経路選択
  4. ロードバランサ振り分け
  5. Nginx受け口
  6. アプリ処理今ここ
  7. DB保存

このコースの初回です。第16期『開発の実務』は、比べる基準である期待する出力の出どころを問いました。今回はその一段手前、基準の元になる依頼文そのものを見ます。

まず結論

「利用者名を名前順に並べ、重複を除いて返す」という1文には、どちらもこの文に反しない2つの実装が書けます。同じ入力に対する2つの結果がどれだけ違うかを、手元の実演で確かめます。

依頼文に書かなかったこと(何を同じ名前とみなすか、何の順か、どれを残すか)は消えずに残り、実装した誰か(人でも AI でも)が黙って決めます。このコースは、その選択を書く側に取り戻す方法を扱います。

? 読む前に予測

「利用者名を名前順に並べ、重複を除いて返す」を満たすつもりで書かれた2つの実装に、同じ8件の名前を渡しました。結果はどうなったでしょうか。

当てることが目的ではありません。外した予測ほど記憶に残るので、直感で選んでください。

抽象原理

書かれていない部分は、読んだ側が埋める

このコースは、序章『AIエージェント時代に、人が持つ土台』の「生成されたコードの読み方 — 「動く」と「正しい」の差」と、第16期『開発の実務』の初回「テストの「期待する出力」は、どこから来たのか」と最終回「期待する出力が書けないとき、何と比べるか」を読んだ前提で進みます。第16期が問うたのは比べる基準の出どころでした。このコースはさらに上流、何を作ってほしいかを伝える文を扱います。「仕様」という語は次回で定義するので、それまでは依頼文と呼びます。

コース全体の芯を先に置きます。

依頼文に書かなかったことは、消えずに残り、実装した誰か(人でも AI でも)が黙って決める。

次の1文を考えます。

利用者名を名前順に並べ、重複を除いて返す

読んだ瞬間に意味が1つに決まるように見えます。けれども、この文は少なくとも3つを決めていません。何を同じ名前とみなすか(大文字と小文字、全角と半角)、「名前順」とは何の順か、重複のうちどれを残すかです。

Berry らの Handbook は、読み手がこの種の空白を埋める過程をこう書きます。

Unrecognized or unconscious disambiguation is that process by which a reader, totally oblivious to other meanings of some text that he has read, understands the first meaning that comes to mind and takes it as the only meaning of the text. That unconsciously assumed meaning may be entirely wrong.

ほかの意味に気づかないまま、最初に浮かんだ意味を唯一の意味として受け取る。選んだ本人に、選んだという自覚がありません。

Handbook が対象にしているのは要求仕様書と契約書で、AI には触れていません。読み手が AI でも同じことが起きる、というのは本教材の主張です。

1つの依頼文が、2つの実装に分かれるまで
依頼文名前順に並べ、重複を除く
書かれていない3つ
何を同じとみなすか
何の順か
どれを残すか
実装した誰かが黙って埋める
実装A8件を返す
実装B5件を返す
  • ・どちらの実装も依頼文には反していない
  • ・空白を埋めた選択は、コードの中に入ったまま結果に出る
具体実物

同じ8件から、8件と5件

本教材で、この1文を満たす実装を2つ書いて実行しました。

本教材の再現用コード(l1-two-programs.mjs の抜粋。コメントは本教材で書き直した)設定ファイル
const input = ["tanaka", "Suzuki", "suzuki", "Ōta", "abe", "Abe", "sato", "sato"];
 
// 実装A: 完全に同じ文字列だけを重複とみなし、比較関数なしで並べる
const A = xs => [...new Set(xs)].sort();
 
// 実装B: NFKC 正規化と小文字化で同一視し、先に出たほうを残し、ja の照合で並べる
const B = xs => { const m = new Map();
for (const x of xs) { const k = x.normalize("NFKC").toLowerCase(); if (!m.has(k)) m.set(k, x); }
return [...m.values()].sort((a, b) => a.localeCompare(b, "ja")); };
node v24.21.0(macOS 26.5.2 arm64、ICU 78.3、2026-09-24)。env 行と、後半で使う税込・空配列の行を省略コマンド出力
A 8 ["Abe","Suzuki","abe","sato","suzuki","tanaka","Ōta","sato"]
B 5 ["abe","Ōta","sato","Suzuki","tanaka"]

実装Aは8件をすべて残しました。並びは「アルファベット順」ではありません。ECMA-262 では、比較関数なしの sort は文字列を UTF-16 コード単位の数値で比べます。だから大文字の "Abe" が小文字の "abe" より前に来て、"Ō"(U+014C)や全角の "s"(U+FF53)は英字の後ろに回ります。

実装Bは、大文字・小文字と全角・半角の違いを同じ名前とみなして5件にし、日本語ロケールの照合順で並べました。残ったのは "abe"・"Suzuki"・全角の "sato" で、どれも入力で先に出てきたほうです。どれを残すかも、実装が黙って決めたもう1つの選択です。

localeCompare の順は、ECMA-262 が ECMA-402(国際化 API)に委ね、それを持たない実装では implementation-defined としています。確かめた Node 20(ICU 78.2)・22(ICU 78.3、この2つは Linux コンテナ)・24 では同じ結果でしたが、ほかの環境で同じになるかは確かめていません。

✓ ここまでの確認

実装Bは "abe" と "Abe" を同じ名前とみなし、"abe" を残しました。どちらを残すかは、何によって決まっていましたか。

抽象原理

書かなかった選択は、どこへ行くのか

AとBのどちらが正しいかは、依頼文からは決まりません。依頼文が決めなかった選択は消えず、実装の中に移っただけです。 移った先で、選択はコードの1行になり、結果の件数と順になって現れます。

第15期『運用の土台』の「同じ事実から、複数の正しい報告が作れる」では、1つの SLI 仕様に3つの測り方が並ぶのを、報告を読む側として見ました。今回は同じ構図を依頼文を書く側から見ています。

ここで区別しておきたいのは、「どれでもよい」と書くことと、何も書かないことの違いです。前者は書き手が決めたことで、後者は決めていないことです。結果が同じに見えても、選んだのが誰かが違います。

具体実物

「任意」と書かれた端数処理

次の依頼文を考えます。

税込価格(10%)を円単位で返す

10% は 2026年9月時点の標準税率です。この文は切捨て・四捨五入・切上げのどれかを決めていません。本教材の実演で、3つを整数演算で計算しました(Node v24.21.0)。

税込価格(円)。p×110÷100 を整数演算で丸めた値表
p 切捨て 四捨五入 切上げ
99 108 109 109
105 115 116 116
1005 1105 1106 1106
1 1 1 2
15 16 17 17
25 27 28 28

99円は切捨てだけが108円、1円は切上げだけが2円です。3つとも「円単位で返す」には反していません。

では現実の制度はどうか。国税庁タックスアンサー No.6371 は、適格請求書に記載する消費税額等の端数について、こう書きます。

適格請求書の記載事項である消費税額等に1円未満の端数が生じる場合は、一の適格請求書につき、税率ごとに1回の端数処理を行う必要があります。 なお、切上げ、切捨て、四捨五入などの端数処理の方法については、任意の方法とすることができます。

方法は任意です。けれども任意だと書いてあります。どこで丸めるか(1枚の請求書につき、税率ごとに1回)は決めたうえで、方法を選ぶ自由を明示しています。続く注は、個々の商品ごとに消費税額等を計算して端数処理し、その合計額を記載することを「認められません」としています。同じページで、課税標準額・消費税額・納付税額などの端数は切り捨てと定まっています。

一方、このページは1品ごとの税込価格の丸め方について何も書いていません。上の依頼文の答えを、このページから引くことはできません。

✓ ここまでの確認

国税庁タックスアンサー No.6371 で「任意の方法とすることができます」とされている端数処理はどれですか。

⇄ 2つを見比べる
具体例 A

名前順に並べ、重複を除く

同じとみなす基準、並べる順、残す1件の3つが書かれていません。実演では8件と5件に分かれ、残った表記も違いました。

実装が埋めた選択表
書かれていないもの 実装A 実装B
同じとみなす基準 完全一致 NFKC+小文字
並べる順 UTF-16の数値 ja の照合
残す1件 (区別なし) 先に出たほう
具体例 B

税込価格を円単位で返す

丸め方が書かれていません。実演の6件すべてで、3つの丸め方のどれかが別の金額を返しました。国税庁のページは適格請求書の税額について方法を任意と明示していますが、1品の税込価格には触れていません。

同じ1文から出た金額表
p 切捨て 四捨五入 切上げ
99 108 109 109
1 1 1 2

並べ替えと税込計算。題材は違いますが、2つに共通する構造は何でしょうか。

実装が分かれた箇所が、依頼文のどこに書かれていたかを探してみてください。

この章のまとめ

  • 「名前順に並べ、重複を除く」の1文から、依頼文に反しない2つの実装ができ、同じ8件から8件と5件を返した。
  • 書かれていなかったのは、同じとみなす基準・並べる順・残す1件。どれも実装が黙って決めた。
  • Handbook は、読み手がほかの意味に気づかず最初の意味を唯一とする過程を書く。AI への当てはめは本教材の主張。
  • 1.1倍の計算で結果まで狂ったのは切上げだけ(1〜100,000 円で 5,524 件)。
  • 国税庁 No.6371 は適格請求書の税額の端数処理を任意と書いている。書かれた任意と、書かれなかった選択は別物。

自分の現場で確かめる

読んだだけでは身につきません。手元の環境で実際に見てみてください。

理解度チェック

読んだ直後は「分かった」と感じやすいものです。実際に解いて確かめてください。

問題 1 / 3正解: 0

実装Aの `sort()`(比較関数なし)で "Abe" が "abe" より前、"Ōta" が末尾近くに来たのはなぜですか。

用語集(2語)
依頼文
作ってほしいものを言葉で伝える文。本コースでは「仕様」を次回で定義するまで、この呼び方を使う。
無自覚な曖昧さ解消 (unconscious disambiguation)
Berry らの Handbook の語。読み手がほかの意味に気づかず、最初に浮かんだ意味を唯一の意味として受け取ること。