logoChibiham
cover
📐

「仕様」の正体:Requirements・Contract・Structureによるソフトウェアの理解

導入:「仕様」という言葉の曖昧さ

ソフトウェア開発において「仕様」「機能」という言葉は頻繁に使われるが、その意味は驚くほど曖昧だ。

「この機能の仕様は?」と聞かれたとき、何を答えるべきだろうか。ユーザーが何を期待しているかの話なのか、APIのリクエスト・レスポンスの形式の話なのか、内部のデータモデルとロジックの話なのか——同じ「仕様」という言葉が、まったく異なる概念を指して使われている。

この曖昧さは単なる言葉の問題ではない。異なるレイヤーの概念を無区別に扱うことが、ソフトウェアについての思考そのものを妨げている

本稿では、「仕様」「機能」と呼ばれるものを Requirements・Contract・Structure/Behavior という枠組みで分解し、それぞれの関係を明確にすることを試みる。

3つのレイヤー

Requirements(要求)

Requirementsとは、ステークホルダーが世界に対して望む状態・結果だ。

  • 「商品を検索できる」
  • 「注文後にメールが届く」
  • 「月末に請求書が発行される」

これらはソフトウェアの外側にある。ユーザーや事業者がどのような結果を得たいかという期待であり、ソフトウェアの構造や実装とは独立して存在する。

要求工学における Zave-Jacksonの枠組み では、Requirementsは環境(世界)に対する望ましい性質として定義される。ソフトウェアはその性質を実現する手段にすぎない。

Structure と Behavior(構造と振る舞い)

ソフトウェアの実体は、構造と振る舞いという二つの側面から構成される。

Structure(構造) とは、概念間の結合・依存関係・境界から生まれる制約の総体だ。モジュール間の依存関係、概念間の不変条件(「出荷済み注文には支払い日時が必ず存在する」)、技術的制約がこれに含まれる。

Behavior(振る舞い) とは、特定の入力に対する出力、あるいは操作による状態変化だ。

そして決定的に重要なのが、Structure が Behavior を制約する という関係だ。構造が状態空間を限定すれば、その中で起こりうる振る舞いのパターンも限定される。良い構造は振る舞いの実装を単純にし、検証すべきケースを減らす。

Contract(契約)

Contract とは、モジュールの境界(Interface)における Structure と Behavior の両面の約束だ。

Contractには二つの側面がある:

  • Structure的側面:インターフェースの形の定義——引数の型、戻り値の型、許容される状態。型システムが静的に保証する
  • Behavior的側面:特定の入力に対する具体的な出力、状態遷移の正しさ。テストが動的に検証する

Contractはモジュールの境界に存在し、内部のImplementationを隠蔽しつつ、外部に対して何を約束するかを宣言する。

3層の依存関係

因果の方向

Implementation(Structure + Behavior) ↓ 決定する Contract(境界での約束) ↓ 満たすか? Requirements(世界に対する期待)
  • Structure が Behavior を制約する
  • Structure と Behavior が Contract を実現する
  • Contract が(Domain Knowledgeと合わさって)Requirements を達成する

これは Zave-Jacksonの枠組み の定式化 S ∧ D ⊨ R に対応する。S(Specification/Contract)と D(Domain Knowledge)が R(Requirements)を論理的に含意する。

プロセスの方向は逆

開発プロセスでは Requirements → Contract → Implementation の順で考えることが多い。しかしこれは思考の順序であって因果の方向ではない。

実際の設計は構造と振る舞いの往復運動であり、コードに落とす段階では「構造を先に、振る舞いを後に」が有効だ。

Contract の推論可能性

ここで、Contractの質について重要な基準がある。良いContractとは、I/Fから内部のStructureが推察可能であることだ。

推論可能性がなぜ重要か

ソフトウェアの利用者(他のモジュールの開発者、APIの呼び出し側)は、Contractを通じて内部のStructureを推察し、「この状態ならこう相互作用できるはず」と推論する。その推論に基づいて利用する。

  • 推察通りに動く → 利用者は「期待通りだ」と感じる
  • 期待通りの動作がRequirementsと合致する → 「正しい」と判断される

つまり、「正しいソフトウェア」に到達する経路は:

Contract が Structure を推察可能にする → 推察に基づいて利用する → 推察通りに動く(= 期待通り) → それが Requirements と合致する(= 正しい)

Contractの推論可能性が、この経路全体の起点になっている。

推論可能性と型の表現力

型システムはContractの推論可能性を高める強力な手段だ。例えば判別共用体で状態を表現すれば、I/Fの型シグネチャを見ただけで状態遷移の構造が読み取れる。

typescript
// 推論しやすい — 型がStructureを伝えている
type Order =
  | { status: "draft" }
  | { status: "paid"; paidAt: Date }
  | { status: "shipped"; paidAt: Date; shippedAt: Date };

// 推論しにくい — Structureが隠れている
type Order = {
  status: string;
  paidAt: Date | null;
  shippedAt: Date | null;
};

前者は型を見ただけで「出荷済みには支払い日時が必ずある」というStructureの制約が推察できる。後者ではドキュメントやソースコードを読まなければ分からない。

ドメインの認知構造がコードに直接反映されている状態では、ドメイン知識を持つ観測者がI/Fを見たとき、自分のドメイン理解からStructureを正しく推察できる。だからFBループが速い。

「仕様と違う」の切り分け

この枠組みを使えば、「仕様と違う」「バグだ」という曖昧な報告を構造的に切り分けられる。

症状実際に壊れている箇所対処
推察通りに動かないContract が Structure を正しく伝えていないI/Fの設計を改善し、型の表現力を上げる
推察通りだが期待と違うRequirements と Contract が乖離している要件定義とI/F設計を見直す
そもそも推察できないContract の推論可能性が低いI/Fの再設計、型による状態表現の導入
推察通りかつ期待通りだが正しくないRequirements 自体が誤っているステークホルダーとの再確認

「仕様」という一語では、これらの区別がつかない。Requirements・Contract・Structure/Behavior という語彙を持つことで、問題の所在を正確に特定できるようになる。

「機能」の多義性も同様

「機能」という言葉も同じ構造の曖昧さを持っている。

レイヤーより正確な語意味
RequirementsCapabilityユーザーが得たい能力
ContractProtocolその能力を提供するI/Fの約束
ImplementationMechanismその約束を実現する内部構造と振る舞い

「この機能を追加してください」が Capability の追加なのか、Protocol の変更なのか、Mechanism の実装なのかで、やるべきことはまったく異なる。

結論

「仕様」「機能」という日常語は、Requirements・Contract・Structure/Behavior という異なるレイヤーの概念を無区別に指している。

  • Requirements はソフトウェアの外にある世界への期待
  • Contract は境界における Structure と Behavior の約束であり、型とテストがそれぞれの側面を保証する
  • StructureBehavior を制約し、両者が Contract を実現する

そして、良いContractの条件は推論可能性だ。I/Fから内部のStructureが推察でき、推察通りに動き、それがRequirementsと合致するとき、人はソフトウェアが「正しく動いている」と感じる。

この語彙を持つことで、「仕様と違う」という曖昧な問題報告を、どのレイヤーの何が壊れているのかという構造的な分析に変換できる。ソフトウェアについて正確に思考し、正確に伝えるための道具として、この枠組みが役立つことを願っている。