なぜ仕様策定者が設計にまで踏み込んではいけないのか
あなたの「仕様書」は本当に仕様書ですか?
こんな「仕様書」を見たことはないでしょうか。
# 注文管理システム仕様書
## データベース設計
- ordersテーブルにorder_id (BIGINT AUTO_INCREMENT), customer_id (FK),
status (ENUM('pending','confirmed','shipped','cancelled')),
created_at (DATETIME) を持つ
- order_itemsテーブルでorder_idとproduct_idを中間テーブルとして紐づける
- インデックスはcustomer_idとstatusに貼る
## 注文作成API
- POST /api/v1/orders
- リクエストボディはJSON: { customer_id: number, items: [{product_id, qty}] }
- RedisでカートセッションをTTL=30分で保持し、注文確定時にDBへflushする
- トランザクション分離レベルはREAD COMMITTEDとする
- 在庫チェックはSELECT ... FOR UPDATEで排他ロックを取得する
## キャンセル処理
- RabbitMQのorder.cancelキューにメッセージをpublishする
- ワーカーが非同期でステータスをUPDATEし、在庫戻しはSagaパターンで実装する
- リトライは指数バックオフで最大5回
「しっかり書けている仕様書だ」と思いましたか?
これは仕様書ではありません。設計書です。
仕様と設計の区別がついていないエンジニアが「仕様」のつもりで作ったドキュメント——現場では驚くほど頻繁に目にします。そして、この混同は「ちょっとした書き方の問題」では済みません。プロジェクト全体の柔軟性と品質を静かに蝕む構造的な問題です。
なぜこの混同が起きるのか。そしてなぜ危険なのか。ソフトウェアの話に入る前に、日常のわかりやすい例から見てみましょう。
「壁を厚くしてほしい」——発言は要望ではない
あるお客さんが「壁を厚くしてほしい」と言ったとします。あなたが業者なら、どうしますか?
言われた通り壁を厚くすることもできます。でもプロはまず「なぜ壁を厚くしたいのですか?」と聞く。すると「実は隣の音がうるさくて眠れない」と出てくる。さらに「いつ、どんな音が気になりますか?」と掘り下げれば、問題の本質が特定される。
この対話のプロセスを整理すると、実は4つの段階があることがわかります。
| 段階 | 壁の例 |
|---|---|
| お客さんの発言 | 「壁を厚くしてほしい」 |
| 真の要望(対話で引き出す) | 隣の部屋の音が気にならない、静かな部屋にしてほしい |
| 課題(対話で見えてきた本当の問題) | 壁の向こうの騒音がひどくて、夜眠れない |
| 仕様(課題を解決する測定可能な条件) | 外部騒音を室内においてx dB以上低減すること |
| 設計(仕様を満たす技術的手段) | 壁にグラスウール100mmを追加し、窓を二重サッシにする |
注目してほしいのは、お客さんの発言がすでに「壁を厚くする」という設計に踏み込んでいるということです。お客さん自身が、自分の本当の要望(静かにしたい)にも課題(騒音源がどこか)にも気づかないまま、解決策(壁を厚く)を口にしている。もしこの発言をそのまま受け取って壁を厚くしたら、窓から入る音は何も変わりません。
お客さんの「発言」は、真の「要望」ではないことが多い。 お客さんは自分なりに問題を解決しようとして、すでに手段(設計)を発言に混ぜ込んでいます。だからプロフェッショナルの最初の仕事は、発言の裏にある真の要望を対話で引き出すことです。
この構造はあらゆる場面にある
壁の話は特殊な例ではありません。同じ4段階の構造は、日常のあらゆる「依頼」に潜んでいます。
サッカーコーチへの依頼
| 段階 | 内容 |
|---|---|
| 親の発言 | 「毎日3時間ドリブルを練習させてください」 |
| 真の要望(対話で引き出す) | 試合で活躍できる選手になってほしい |
| 課題 | 基礎体力の不足、サッカーの基礎技術(トラップ、パス、ポジショニング)の未習得 |
| 仕様 | ジュニアユースのセレクションに合格できるレベルの技術・体力・判断力を身につけること |
| 設計 | 週3回のボールコントロール、週2回のゲーム形式練習、週1回のフィジカルトレーニング |
「ドリブル3時間」は、真の要望も課題も仕様も飛ばしていきなり設計——しかもコーチから見れば不適切な設計——を指定しています。コーチがすべきは「お子さんにどうなってほしいですか?」と聞くこと。そこで「試合で活躍してほしい」と出てくれば、コーチは現在の課題を見極めた上で最適なメニューを組めます。
医者への依頼
| 段階 | 内容 |
|---|---|
| 患者の発言 | 「ロキソニン60mgを1日3回処方してください」 |
| 真の要望(問診で引き出す) | この頭痛をなんとかしてほしい |
| 課題 | 2週間続く頭痛で仕事に集中できない。原因がわかっていない |
| 仕様 | 頭痛を日常生活に支障がないレベルまで軽減すること。副作用を最小限にすること |
| 設計 | まずCT検査で器質的疾患を除外。緊張型頭痛であればカロナール + 肩周りのストレッチ指導 |
共通するパターン
他にも同じ構造は至るところにあります。
- 料理: 「170℃で25分揚げて」ではなく「外はカリッと中はジューシーに」——温度と時間はシェフが肉の厚みや油の状態を見て決めること
- 引越し: 「2トントラック2台、スタッフ4名」ではなく「日曜午前中に新居へ搬入完了」——トラックの台数は業者が荷物量から判断すること
- タクシー: 「環七から甲州街道で首都高4号線」ではなく「新宿まで30分以内で」——ルートは運転手がリアルタイムの渋滞を見て決めること
- 建築: 「グラスウール100mmにLow-Eペアガラス」ではなく「冬でもリビングが18℃を下回らない家」——断熱仕様は建築士が予算・気候・構造から決めること
- デザイン: 「見出しを赤に」ではなく「見出しを一番目立たせて」——色の選択はデザイナーが背景色やブランドカラーとの対比で決めること
どれも同じです。手段(How)を指定した瞬間、プロフェッショナルが持っている「もっと良い方法」が封じられる。 そして依頼者にはそれが見えない。
ソフトウェアに戻す
ソフトウェア開発でも全く同じ構造です。冒頭の「仕様書もどき」を4段階で分析してみましょう。
| 段階 | 注文管理システムの場合 |
|---|---|
| お客さんの発言 | 「注文履歴の検索にElasticsearchを入れてください」 |
| 真の要望(対話で引き出す) | 昔の注文もすぐに探せるようにしてほしい |
| 課題 | 3年前の注文を検索すると30秒以上かかり、カスタマーサポートの対応が遅延している |
| 仕様 | 全期間の注文履歴を2秒以内に検索できること |
| 設計 | 注文データを年次パーティションに分割し、検索用インデックスを追加。3年超のデータはウォームストレージに移行しつつ検索可能に保つ |
お客さんの発言「Elasticsearchを入れてください」は、壁の話やサッカーの話と同じく、真の要望も課題も仕様も飛ばしていきなり設計を指定しています。「なぜElasticsearchを入れたいのですか?」と聞けば「検索が遅くて」と出てくる。「どのくらい遅いですか?」と掘れば課題が特定される。そこまで来て初めて、Elasticsearchが本当に最適解なのかをエンジニアが判断できます。
4段階のどこで混同が起きるかを見極めることが重要です。
- 発言をそのまま要望にする → 手段を要望だと思い込む
- 発言をそのまま設計にする → 真の要望も課題も仕様も飛ばして手段だけが決まる
- 仕様に設計を混ぜる → 設計者の自由度が消える
最も危険なのは2番目のパターンです。そしてこれが現場では驚くほど多い。
現場で最もよくある発生源——「技術がわかる営業」
ソフトウェア開発の現場で、仕様と設計の混同が最も頻繁に起きるのはどこか。それは中途半端にエンジニアリングを学んだ営業やPMが、お客さんの要望を「翻訳」してエンジニアに伝える瞬間です。
お客さん(仕様策定者)はこう言っています。
「注文履歴の検索が遅い。もっと速くしてほしい。特に、3年前の注文も1〜2秒で出てほしい」
これは完璧な仕様です。What(何を満たすべきか)だけが述べられている。エンジニアがこのまま受け取れば、インデックス戦略、キャッシュ、データパーティショニング、検索エンジンの導入——あらゆる選択肢をテーブルに並べて最適解を設計できます。
しかし、中途半端に技術を知っている営業は「翻訳」してしまう。
「お客さんが検索が遅いって言ってるんで、Elasticsearchを入れてください。あと、過去データはS3に退避してLambdaで引っ張る構成で」
お客さんは「Elasticsearch」とも「S3」とも「Lambda」とも言っていません。営業が「気を利かせて」設計判断を混ぜ込んだのです。
何が起きるか。
お客さんの本当の要望が見えなくなる。 エンジニアに届くのは「Elasticsearchを入れろ」という指示であって、「3年前の注文も1〜2秒で」という制約ではありません。もしかしたらDBのインデックスを見直すだけで解決するかもしれない。でもエンジニアはElasticsearchの構築に着手します。それが「要件」だと思っているから。
過剰な設計が正当化される。 営業が「Elasticsearch + S3 + Lambda」と言った以上、見積もりも工数もその構成で積まれます。お客さんに提示される金額には、本来不要だったかもしれないインフラの構築費用が含まれている。でも誰もそれを疑えない。営業が「お客さんの要望です」と言っているから。
責任の所在が消える。 納品後にElasticsearchが性能要件を満たさなかったとき、誰の責任でしょうか。お客さんは「速くしてほしい」としか言っていない。営業は「技術的に良いと思った」。エンジニアは「指示通りに作った」。全員が正しいことを言っているのに、誰も責任を取れない構造が出来上がります。
この営業は悪意があるわけではありません。むしろ「エンジニアが楽になるように、具体的に伝えよう」と親切心で動いています。しかし**「具体的にすること」と「設計に踏み込むこと」は違います。** 仕様を具体的にするとは「3年前のデータも1〜2秒で」のように制約を明確にすることです。「Elasticsearchを入れろ」と言うのは、具体的にしているのではなく、設計を決定しています。
サッカーの例に戻れば、これは**「うちの子をサッカー選手にしてください」という親の要望を、マネージャーが「毎日3時間ドリブルさせてください」に翻訳してコーチに伝えている**のと同じ構造です。親の言葉をそのままコーチに届ければよかっただけなのに。
WhatとHow——仕様と設計を分ける一本の線
ここまでの4段階モデルを、ソフトウェアの文脈でより厳密にまとめます。
仕様は「何を(What)」に答える。 システムが満たすべき性質、制約、振る舞いの定義。
設計は「どうやって(How)」に答える。 その性質を実現するための技術的な選択と構造。
この基準で冒頭の「仕様書」を検証してみます。
| 書かれていること | What(仕様) | How(設計) |
|---|---|---|
BIGINT AUTO_INCREMENT |
注文を一意に識別できること | 採番方式が連番であること |
ENUM('pending',...) |
ステータスが4状態であること | DBカラム型がENUMであること |
Redis TTL=30分
|
カートを一時的に保持すること | 保持手段がRedisでTTLが30分であること |
SELECT ... FOR UPDATE |
在庫の二重引き当てが起きないこと | 排他制御が行ロックであること |
RabbitMQ Sagaパターン
|
キャンセルが確実に処理されること | 非同期基盤とトランザクション方式 |
POST /api/v1/orders |
注文を作成できること | エンドポイントのパスとHTTPメソッド |
指数バックオフで最大5回 |
失敗時にリトライされること | リトライ戦略のアルゴリズムと回数 |
左の列が全て「How」に分類されます。 「What」の情報は行間から推測するしかない。つまりこのドキュメントには、肝心の仕様がほぼ書かれていないのです。
簡単な判定法——技術名詞リトマス試験紙
自分の仕様書を今すぐチェックできる基準があります。
「実装技術を全て入れ替えても、この記述は変わらないか?」
「注文を一意に識別できること」——DBをPostgreSQLからDynamoDBに変えても、あるいはRDBを使わなくても変わりません。これは仕様です。
「BIGINT AUTO_INCREMENTで採番する」——DynamoDBに変えたら成り立ちません。これは設計です。
もっと簡単な目安として、仕様書に以下のような単語が出てきたら、設計が混入しているサインです。
Redis, RabbitMQ, Kafka, PostgreSQL, MySQL, DynamoDB, MongoDB, S3, REST, GraphQL, gRPC, JSON, Protocol Buffers, Docker, Kubernetes, ArrayList, HashMap, SELECT, INSERT, INDEX, Saga, CQRS, pub/sub, WebSocket, cron ...
これらは全て「How」の世界の言葉です。
なぜ踏み込んではいけないのか
「仕様書に設計が入ってても、詳しく書いてあるんだから良いのでは?」——この疑問にはっきり答えます。良くありません。 理由は5つあります。
1. 真の要望が見えなくなる
これが最も根本的な害です。
仕様書に「Elasticsearchで全文検索を実装する」と書かれていたら、エンジニアはElasticsearchの構築に着手します。しかしお客さんが本当に望んでいたのは「3年前の注文も2秒以内に見つかること」かもしれない。DBのインデックスを見直すだけで済んだかもしれない。
設計が仕様に混入すると、「なぜそれが必要なのか」という問いが立たなくなります。 「Elasticsearchを入れろ」と書いてあれば、エンジニアは「なぜ?」とは聞かない。書いてあるから入れる。その結果、本来不要だったインフラの構築費用が見積もりに載り、お客さんの本当の問題は解決されないまま、誰もそのことに気づかない。
前のセクションで見た「技術がわかる営業」の問題も、根本はここにあります。設計を混ぜ込んだ瞬間に、上流にあったはずの要望と課題が覆い隠され、下流の全てが歪む。設計の混入は、単に選択肢を減らすだけでなく、そもそも何を解決すべきかを見失わせる。 これが最も危険な理由です。
2. 設計の選択肢を潰す
仕様書に「RabbitMQでキューイングする」と書いた瞬間、設計者はKafka、AWS SQS、Google Cloud Pub/Sub、あるいは将来登場する新技術を検討する自由を失います。
仕様が言うべきは「キャンセル要求が確実に処理されること」だけです。その制約をRabbitMQで満たすかKafkaで満たすかは、スループット要件、運用チームのスキル、既存インフラとの整合性——設計者がシステム全体の文脈の中で判断すべきことです。
仕様策定者にその文脈は見えていません。にもかかわらず設計を固定してしまうと、局所最適が全体最適を阻害することが起きます。
3. ArrayかListかは設計者が決める
「商品をArrayListに格納する」と仕様書に書かれていたとします。
仕様が本当に言いたいことは何でしょうか。おそらくこうです。
- 商品の集合を保持する
- 同じ商品が複数回出現してよい(重複許容)
- 追加された順序を保つ
これは**データの性質(What)**の記述です。VDM-SLなら seq of Product と書けば過不足なく表現できます。
その seq を実装時に何で実現するかは、別の次元の問題です。
| 実装の選択 | どんなときに有利か |
|---|---|
ArrayList |
ランダムアクセスが頻繁、サイズがある程度予測可能 |
LinkedList |
先頭・中間への挿入削除が頻繁 |
ImmutableList |
並行処理で安全性を重視 |
Stream<Product> |
遅延評価でメモリ効率を重視 |
Vec<Product>(Rust) |
そもそも言語が違う |
この判断には、対象データの件数、アクセスパターン、並行処理の有無、言語の特性——実装時にしかわからない情報が必要です。仕様策定の段階でArrayListと書くのは、これらの情報がないまま設計判断を下しているということです。
「些細なことだ」と思うかもしれません。しかしこの「些細な」設計の混入が仕様書の至るところに蔓延すると、設計者に残された自由度はほとんどなくなります。設計者は仕様を実現するのではなく、仕様書に書かれた設計を「写経」するだけの存在になる。 それでは設計レビューも技術選定も形骸化します。
4. テスト可能性が下がる
仕様が「RabbitMQにpublishする」と言っていたら、テスト環境にもRabbitMQが必要です。CIのたびにRabbitMQコンテナを立ち上げ、接続を待ち、メッセージの到達を確認する。テストは遅くなり、不安定になり、環境依存のフレーキーテストが増えます。
仕様が「キャンセル要求が受け付けられ、最終的にステータスがキャンセルに変更されること」と言っていれば、キューイングの仕組みに依存しないテストが書けます。RabbitMQの結合テストは設計・実装レベルで別途やればいい。仕様レベルのテストが実装技術に汚染される必要はありません。
5. 責任の境界が曖昧になる
仕様策定者がRedisのTTLを30分と書いたとします。運用後に「カートの保持時間が短すぎる」とクレームが来たとき、誰の責任でしょうか。
- ビジネス要件として「30分」なのか? → 仕様策定者の判断
- 技術的な都合で「30分」なのか? → 設計者の判断
仕様書に両方が混在していると、意思決定の出処がわからなくなります。 変更するにも「これはビジネス要件を変えることになるのか、技術的な調整で済むのか」が判断できない。
仕様と設計が分離されていれば明確です。仕様に「カートは一定時間後に無効になる」とあり、設計で「TTL=30分(Redisで実装)」としていれば、30分という数値は設計者の判断であり、ビジネス要件を変えずに調整できることが一目瞭然です。
同じ要件を「仕様」として書くとこうなる
冒頭の「設計書もどき」と同じシステムを、仕様として正しく書くとこうなります。
# 注文管理システム仕様書
## データ
- 注文は一意に識別でき、顧客・注文品目・ステータスを持つ
- ステータスは保留中・確認済み・発送済み・キャンセルの4状態
- 注文品目は商品と数量(1以上)のペアの集合で、空の注文は不可
## 注文作成
- アクティブな顧客のみ注文を作成できる
- 注文作成時、各商品の在庫が注文数量以上であること
- 同一商品に対する複数の同時注文で、在庫を超える引き当てが発生しないこと
- 作成された注文は即座にシステムから参照可能であること
## 注文キャンセル
- 保留中または確認済みの注文のみキャンセル可能
- 発送済み・既キャンセルの注文はキャンセルできない
- キャンセルにより引き当て済みの在庫が復元されること
- キャンセル要求は確実に処理されること(部分的な処理状態で放置されない)
Redis、RabbitMQ、BIGINT、SELECTどれも出てきません。出てこなくていいのです。この仕様を読んだ設計者は、制約を満たすために最適な技術を自分で選べます。
PostgreSQL + Redis + RabbitMQでもいい。DynamoDB + SQS + Step Functionsでもいい。SQLiteの単一ファイルでも、仕様の制約を満たしていれば正しい実装です。仕様はゴールを定義し、設計はルートを選ぶ。 それが本来の関係です。
VDM-SLは設計の混入を「構造的に」防ぐ
ここまでの話は「気をつけましょう」という精神論に聞こえるかもしれません。でも、実際に人間が気をつけるだけでは限界があります。
VDM-SL(形式仕様記述言語)を使うと、この分離が構造的に強制されます。
VDM-SLで書けるのは、型、事前条件、事後条件、不変条件つまり 「何が真であるべきか」だけです。Redis、RabbitMQ、ArrayListこれらはVDM-SLに書く場所がそもそもありません。
-- ❌ VDM-SLでは書けない(書く場所がない)
-- "RedisでTTL=30分でカート保持"
-- "SELECT ... FOR UPDATEで排他ロック"
-- "商品をArrayListに格納する"
-- ✅ VDM-SLで書くこと
-- 注文品目は商品IDから数量(正の自然数)への写像。空は不可。
OrderItems = map ProductId to nat1
inv items == items <> {|->}
-- 在庫の二重引き当てが発生しない
inv mk_OrderSystem(orders, stock) ==
forall pid in set dunion {dom o.items | o in set rng orders} &
totalAllocated(orders, pid) <= stock(pid)
-- キャンセルは保留中または確認済みの注文のみ
CancelOrder(oid: OrderId)
pre oid in set dom orders
and orders(oid).status in set {<PENDING>, <CONFIRMED>}
post orders(oid).status = <CANCELLED>
map ProductId to nat1 と書いた時点で、「キーの一意性」「値が正の自然数であること」が数学的に保証されます。これを実装時にHashMapにするかTreeMapにするかは、VDM-SLの関知するところではありません。仕様の言語に設計の語彙がないから、混入しようがない。 これが「構造的に防ぐ」の意味です。
「でも、パフォーマンス要件は仕様では?」
よくある反論です。「レスポンス95パーセンタイルが200ms以下」は仕様か設計か?
これは仕様です。 システムが満たすべき性質だからです。ただし、VDM-SLでは表現できない種類の仕様(非機能要件)です。
VDM-SLが扱えるのは機能的な性質——型、制約、操作の前提と保証——です。パフォーマンス、可用性、セキュリティといった非機能要件は仕様書に記載すべきですが、形式化の対象外として明示します。
逆に言えば、「200ms以下を達成するためにRedisでキャッシュする」は設計です。「200ms以下」がWhat、「Redisでキャッシュ」がHow。仕様書に書くのは前者だけです。
仕様書セルフチェック
自分の仕様書(あるいは今から書く仕様書)を点検するためのチェックリストです。
1. 技術名詞の有無
Redis、Kafka、PostgreSQL、ArrayList……特定の製品名やデータ構造名が出てきたら設計が混入しています。
2. 技術を入れ替えたとき
「DBをPostgreSQLからDynamoDBに変えたら、この文は成り立たないか?」——成り立たないなら設計です。
3. 主語の確認
仕様の主語は「システム」「ユーザー」「注文」などドメインの概念です。「ワーカー」「キュー」「テーブル」が主語なら設計です。
4. 動詞の確認
仕様の動詞は「〜できる」「〜であること」「〜を保証する」です。「publishする」「flushする」「ロックを取得する」は設計の動詞です。
5. 「なぜ」が説明できるか
「なぜRabbitMQなのか」に答えるには設計上の理由が必要です。「なぜキャンセル要求は確実に処理されるべきか」にはビジネス上の理由で答えられます。ビジネス上の理由で説明できる記述だけが仕様です。
まとめ
お客さんの「発言」は要望ではありません。「壁を厚くして」と言うお客さんが本当に望んでいるのは静かな部屋であり、「Elasticsearchを入れて」と言うお客さんが本当に望んでいるのは速い検索です。発言の裏にある真の要望を対話で引き出し、課題を特定し、測定可能な仕様に落とし込む——この4段階を順番に踏むことが出発点です。
そして仕様策定者が設計に踏み込むと、5つの害が生じます。最も根本的なのは真の要望が見えなくなること設計が混入した瞬間に「なぜそれが必要なのか」という問いが立たなくなり、解くべき問題そのものを見失います。さらに設計の選択肢が潰れる。テスト可能性が下がる。責任の境界が曖昧になる。設計者が「写経係」になる。
ArrayかListかは設計者が決める。 仕様が決めるのは「順序付きの集合で、重複を許容し、要素数は1以上」ということだけです。その性質を満たすデータ構造の選択は、実装時の文脈を持っている設計者に委ねてください。
仕様はゴールを定義する。設計はルートを選ぶ。この分離を守ることが、柔軟で変更に強いソフトウェアへの第一歩です。