目次を表示する

共通基盤の設計軸 2026 ─ 抽象・責務・非機能要件を設計する 15 章

抽象度の選択 ─ Pit of Success と Sharp Tools

抽象度の選択 ─ Pit of Success と Sharp Tools

責務の境界を決めたら、次に問われるのが どの抽象度で API を提供するか。低すぎれば利用者が苦労し、高すぎれば自由が奪われる。この章では設計哲学として Pit of Success を導入する。

“Pit of Success” の起源

Microsoft Brad Abrams のブログ “The Pit of Success” で広まった概念。Rico Mariani の発言が元:

We strive to design our APIs and frameworks so that you simply fall into the pit of success.

Jeff Atwood も「Falling Into The Pit of Success」 で論じた。要点:

正しい使い方が “自然” になる API を設計する。 利用者が考えなくても、デフォルトの選択肢で正しく動く。

逆を考えると分かりやすい。Pit of Despair(失望の穴) API は、

  • デフォルトが「間違った設定」になっている
  • 詳細を理解しないと正しく使えない
  • 一見動いているが、後で破綻する

Pit of Success の API は、何も考えずに使うとすでに正しく動いている

設計の 3 原則

原則 1:デフォルトが「正しい」

// ❌ Pit of Despair:デフォルトが retry 無し
const client = new HttpClient();
client.get('/api');  // 失敗したら例外、retry なし

// ✅ Pit of Success:デフォルトが retry あり
const client = new HttpClient();  // 内部でデフォルト retry 付き
client.get('/api');  // 一時的失敗は自動 retry
// 必要なら .withRetry({ maxAttempts: 0 }) で disable

デフォルトを 慎重に決める 必要がある。「何も指定しなければこうなる」が、ほとんどの利用者にとって正しいようにする。

原則 2:間違いが目立つ

// ❌ Pit of Despair:黙って動いてしまう
notify.send({ to: '[email protected]' });  // → API リクエストせず黙ってログだけ書いて成功扱い

// ✅ Pit of Success:間違いは即エラー
notify.send({ to: '[email protected]' });  // configuration が無いと例外で起動時に失敗

動いているように見えて実は動いていない のが最も悪い。起動時 / 設定時に失敗する 設計が望ましい。

原則 3:危険な操作は明示的

// ❌ Pit of Despair:destructive な操作が submit() に紛れる
api.submit({ deleteAfter: true });  // ← 引数 1 つで delete してしまう

// ✅ Pit of Success:destructive は明示的に
api.submit({ data });
api.submitAndDelete({ data });  // ← 名前から意図が明確

型 / メソッド名 / 引数名で意図を表す。読み書き両方を見て分かる API。

Sharp Tools の必要性

ただし、Pit of Success だけでは expert user が困る。

「私は分かっている。デフォルトじゃなくて自分で制御させて」

これに応える概念が Sharp Tools。鋭利だが、上級者が望むときだけ手に取れる:

// 普通の使い方(Pit of Success、デフォルト動作)
await db.transaction(async (tx) => {
  await tx.update(...);
});

// Expert 用(Sharp tool、明示的な isolation level)
await db.transaction({ isolationLevel: 'Serializable' }, async (tx) => {
  await tx.update(...);
});

両方が同じ API に共存する設計が望ましい:

  • デフォルトは Pit of Success(初級者が落ちる)
  • Sharp tool は明示的に取り出せる(上級者が必要なときだけ)

抽象度の階層

API の抽象度は階段状に提供できる:

graph TB
  L4[L4: Declarative<br/>「メールを送って」とだけ宣言]
  L3[L3: High-level SDK<br/>retry / 認証 / log を内蔵]
  L2[L2: Low-level Client<br/>HTTP 直接、retry 自分で]
  L1[L1: Raw API<br/>HTTP リクエストを手書き]

  L4 --> L3 --> L2 --> L1

  Note1[多くの利用者は L3-L4 を使う]
  Note2[Expert は L2 に降りる]

  style L4 fill:#e1ffe1
  style L3 fill:#e1ffe1
  style L2 fill:#fff4e1
  style L1 fill:#ffe1e1

例:通知基盤

// L4: Declarative(最も抽象的)
@OnUserSignUp()
sendWelcomeEmail(user: User);
// → デコレータで宣言、基盤が呼び出しを自動生成

// L3: High-level SDK
await notify.sendEmail({ to, subject, body });
// → SDK が retry / 認証 / log を全部内蔵

// L2: Low-level Client
const client = createNotifyClient({ retries: 3 });
await client.post('/notifications', { ... });
// → 利用者が retry を制御

// L1: Raw API
await fetch('https://notify.internal/api/v1', { method: 'POST', ... });
// → 認証 token、retry、log を全部手書き

全レベルを提供すると、利用者は自分のレベルを選べる

“Magic” の罠

Pit of Success の極端な形は「全部 magic にする」── 利用者が何も書かなくても動く。だがこれはになる:

問題
デバッグできないエラーが起きたとき、何が走っているか分からない
挙動が予測できない同じコードが環境で違う動きをする
学習曲線が高い「どう動いているか」を理解するのに時間がかかる
拡張できないカスタマイズしたいとき、抜け道がない

Boring API(前章)を思い出す。Magic は短期的に楽だが、長期的にコストが高い透明性のある抽象が望ましい:

  • 何が起きているかをログ / トレース で見られる
  • デフォルトを上書きできる “抜け道” がある
  • マニュアルで一読すれば全体像が掴める

段階的に深めていける設計

良い API は オニオン構造

利用者の理解度

表面: SDK の関数呼び出し(ほとんどの人がここ)

中層: SDK の内部構造、設定オプション

深層: HTTP API、認証プロトコル、エラーモデル

最深: 内部実装、運用ルール

最初は表面で動く、必要に応じて中層・深層を学べる。学習曲線が緩やか になる。

この章の要点

  • Pit of Success = 正しい使い方が自然になる API 設計
  • 3 原則:デフォルトが正しい / 間違いが目立つ / 危険な操作は明示的
  • Sharp Tools は上級者向けに明示的な引数で残す
  • 抽象度は L4 (Declarative) → L1 (Raw API) の階段で提供
  • Magic は短期的に楽だが長期的にコスト高。透明性のある抽象を選ぶ
  • API は オニオン構造で、利用者が必要なだけ深く潜れる設計

次章への問いかけ

ここまでは「API の表面の設計」。だが API が呼ばれる時、制御の所在 が問題になる。

利用者が API を呼ぶのか、基盤が利用者の callback を呼ぶのか。これは Library と Framework の本質的違い ── Inversion of Control の話。