目次を表示する

コードの考古学 ── 設計思想に宿る哲学の源流

第10話 コペルニクスの転回 ── カントの逆転がテストを先行させた

場面

1781年、プロイセン王国のケーニヒスベルク。バルト海に近い港湾都市に、一人の哲学者が10年間の沈黙を破って本を出版した。57歳のイマヌエル・カントは、哲学の世界に地殻変動をもたらそうとしていた。

本のタイトルは『純粋理性批判』。しかし内容を説明するのに、カントは哲学用語を使わずに天文学の比喩を使った。「私がやろうとしていることは、コペルニクスが天文学でやったことと同じだ」。

コペルニクスが転倒させたのは何か。それまでの天文学は地球を中心に置いていた。太陽が地球の周りを回る(天動説)。しかし惑星の動きを計算すると奇妙な「逆行運動」が現れ、モデルは複雑になる一方だった。コペルニクスの逆転:太陽が中心で、地球が動く(地動説)。中心と周縁を入れ替えただけで、惑星の動きが見事に説明できた。

カントが認識論でやろうとしたのはこれと同じ転倒だった。

従来の認識論の前提:心(主体)が世界(客体)を受動的に受け取る。世界がどうあるかを心が写す。ちょうど蝋に印が押されるように、外の世界の形が心の中に刻まれる。

カントの逆転:心が世界を受け取るのではなく、心の構造が経験を成形する。空間・時間・因果性——私たちが「世界の性質」だと思っているものは、実は私たちの認識装置が経験に課している形式だ。心は受け皿ではなく、鋳型だ。

どちらが中心に来るかで、何が何に従うかが変わる。地動説では地球が太陽に従う。カントの転倒では、経験が心の形式に従う。「何が何に合わせて形を変えるか」という方向が、理解全体を決定する。

カントが生きた時代から200年以上後。同じ転倒が、まったく違う文脈で繰り返された。

答え

「何が何に従うか」という従属の方向が、設計の全てを決める。コードがテストに従うのか、テストがコードに従うのか。ビジネスロジックがデータベースに従うのか、データベースがビジネスロジックに従うのか。この方向を逆転させることで、関係全体が組み直される。カントが認識論でやったことを、ソフトウェア設計でも繰り返せる。

CSへの翻訳

TDDにおけるコペルニクス的転倒

テスト駆動開発(Test-Driven Development)は、ケント・ベックが2002年の著書『Test-Driven Development By Example』で体系化した(実践はそれ以前からあった)。

従来の開発の方向:実装を書く → テストを書いて検証する。テストはコードに従う。テストは、コードが「すでにやっていること」を記述するドキュメントだ。

TDDの転倒:テストを先に書く → テストを通るように実装する。コードがテストに従う。テストは、コードが「やるべきこと」を定義する仕様だ。

この転倒は小さく見えて根本的だ。「実装を先に書いてからテストを書く」スタイルのテストは、知らず知らずのうちに実装のバイアスを引き受ける。実装が間違えていれば、テストも同じ間違いを学習してしまう。しかし「テストを先に書く」スタイルでは、テストは実装に汚染されていない。意図が純粋に記述される。

依存性逆転原則(DIP)における転倒

1996年、ロバート・C・マーティンが依存性逆転原則(Dependency Inversion Principle)を定式化した。

従来の依存の方向(転倒前):高レベルのモジュール(ビジネスロジック)が低レベルのモジュール(データベース、フレームワーク)に依存する。ビジネスロジックがインフラに従う。

DIPの転倒(転倒後):高レベルのモジュールがインターフェースを定義する。低レベルのモジュールがそのインターフェースを実装する。インフラがビジネスロジックに従う。

ヘキサゴナルアーキテクチャにおける転倒

2005年、アリスター・コバーンが「ポート&アダプター(ヘキサゴナルアーキテクチャ)」を発表した。

従来のアーキテクチャでは、アプリケーションコアが外部システム(DB・メッセージキュー・HTTP API)に合わせて設計された。外側が中心を決める。

ヘキサゴナルアーキテクチャの転倒:アプリケーションコアが「ポート(インターフェース)」を定義する。外部システムが「アダプター(インターフェースの実装)」を提供する。外側がコアに合わせる。コア(ドメイン)が太陽になり、外部システム(DB・UI・API)が地球になる。

設計への示唆

カントの転倒をソフトウェア設計に当てるとき、問うべき問いはひとつだ。「何が何に従うか?」


❌ 転倒前(インフラが中心)

// データベースが先にある。ビジネスロジックはDBに従う。
class UserService {
  private pg: PostgresClient;  // 具体的なDBクライアントに依存

  async updateEmail(userId: string, newEmail: string): Promise<void> {
    // ビジネスロジックがSQL構文と結合している
    await this.pg.query(
      'UPDATE users SET email = $1 WHERE id = $2',
      [newEmail, userId]
    );
  }
}

// テストにはPostgresが必要。テストDBのセットアップが必要。
// テストが遅い。テストが脆い(DB接続が切れると落ちる)。
// コードはDBに従っている。

✅ 転倒後(ドメインが中心)

// ビジネスロジックが先に要件を定義する。インフラはそれに従う。
interface UserRepository {
  findById(id: string): Promise<User | null>;
  save(user: User): Promise<void>;
}

class UpdateEmailUseCase {
  constructor(private repo: UserRepository) {}  // インターフェースに依存

  async execute(userId: string, newEmail: string): Promise<void> {
    const user = await this.repo.findById(userId);
    if (!user) throw new UserNotFoundError(userId);

    if (!isValidEmail(newEmail)) throw new InvalidEmailError(newEmail);

    const updated = user.withEmail(newEmail);
    await this.repo.save(updated);
  }
}

// テスト用:インメモリ実装(DBなし)
class InMemoryUserRepository implements UserRepository {
  private store = new Map<string, User>();
  async findById(id: string) { return this.store.get(id) ?? null; }
  async save(user: User) { this.store.set(user.id, user); }
}

// テストはインメモリを使う。DBなし。高速。
describe('UpdateEmailUseCase', () => {
  it('有効なメールアドレスへの更新を許可する', async () => {
    const repo = new InMemoryUserRepository();
    const useCase = new UpdateEmailUseCase(repo);
    // ... テストの本体。DBなしで実行できる。
  });
});

// 本番用:PostgreSQL実装
class PostgresUserRepository implements UserRepository { /* ... */ }

転倒後のコードでは、UpdateEmailUseCaseの単体テストにデータベースは不要だ。ビジネスロジックがインフラに依存していないから、インフラなしで動かせる。テスト速度が桁違いになる。


カントが「コペルニクス的転回」と名づけたのは、中心と周縁を入れ替える転倒を示すためだった。天文学では太陽と地球が入れ替わった。ソフトウェアでは、ドメインとインフラが入れ替わる。

プラトンとカントの分業

第7話のプラトンも「方向の転倒」を語っていた——個物(影)はイデア(Form)に従う。これはあくまで論理的な順序の主張だ:「抽象が具体に先行する」。

カントの転倒はそこから一歩深い。何が何を構成するかの方向を入れ替える。プラトンが「個物はイデアの不完全な模倣だ(実在の階層)」と言ったのに対し、カントは「経験そのものが心の形式に従って構成される(構成の方向)」と言った。

ソフトウェアにあてはめると:

  • **プラトン的:**インターフェース(イデア)が先で、実装(影)が後(DIP)
  • **カント的:**ユースケースの要請(心の形式)が、インフラの形(経験対象)を構成する(TDD・ヘキサゴナル)

二つは矛盾せず、入れ子になっている。プラトンが「抽象が具体より根本だ」と言い、カントが「では何が抽象を決めるのか——それは認識する主体だ」と問いを更新した。設計でも、まずインターフェースを先に書き(プラトン)、次にそのインターフェースの形を決めるのはユースケース側の必要だと自覚する(カント)——この二段の転倒で、依存関係は完全に逆向きになる。

graph TB
    subgraph "❌ 転倒前:インフラが中心"
        BL1[ビジネスロジック] -->|依存| DB1[PostgreSQL]
        BL1 -->|依存| HTTP1[HTTPクライアント]
        BL1 -->|依存| MQ1[メッセージキュー]
        note1[インフラを変えるとビジネスロジックが壊れる\nテストにインフラが必要]
    end

    subgraph "✅ 転倒後:ドメインが中心(ヘキサゴナル)"
        PORT1[UserRepository\nport/interface]
        PORT2[EmailService\nport/interface]
        DOMAIN[ビジネスロジック\nドメイン] --> PORT1
        DOMAIN --> PORT2
        DB2[PostgresAdapter] -.->|implements| PORT1
        MOCK[InMemoryAdapter] -.->|implements| PORT1
        SMTP[SmtpAdapter] -.->|implements| PORT2
        note2[ドメインはポートだけを知る\nインフラはアダプターとして差し替え可能]
    end

    style DOMAIN fill:#fffacd,stroke:#d4a017
    style PORT1 fill:#e8f5e9
    style PORT2 fill:#e8f5e9
    style note2 fill:#e8f5e9
    style note1 fill:#ffebee

従属の方向が設計を決める。インフラ(具体的なDB・フレームワーク・外部API)を中心に置けば、ビジネスロジックはインフラに引きずられる。変更に弱く、テストが重い。ドメイン(ビジネスロジック・ユースケース)を中心に置けば、インフラは交換可能なアダプターになる。変更に強く、テストが軽い。

コペルニクスが太陽を中心に置いたとき、惑星の動きが単純になった。ドメインを中心に置いたとき、テストが単純になる。どちらも同じ構造の転倒だ。

カントは「この転倒なしには形而上学は進歩できない」と主張した。現代のソフトウェアにおいても、「この転倒なしには、ビジネスロジックはインフラの奴隷のままだ」と言える。テストを先に書くこと。インターフェースを先に定義すること。ドメインにインフラを従わせること——これらはすべて、同じコペルニクス的転倒の異なる表現だ。


問い:あなたのビジネスロジックは何に従っているか。DBのスキーマに引きずられていないか、フレームワークの制約に形を決められていないか。コペルニクス的転倒がまだ起きていないなら、地球はまだ宇宙の中心だ。