5
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

WYSIWD入門:「見たままが動作する」ソフトウェア設計

5
Last updated at Posted at 2025-12-16

What You See Is What It Does

はじめに

2024年、MITのDaniel Jacksonらによって発表された論文 "What You See Is What It Does: A Structural Pattern for Legible Software" は、現代のソフトウェア設計が抱える根本的な課題に対し、新しい構造的パターンを提案しました。

その核となる思想は、タイトル通り 「コードに見えているものが、そのままシステムの振る舞いである(What You See Is What It Does)」という点にあります。

この記事を知るそうと思ったのは、他のアークテクトの方から「私のソフトウエアは WYSIWDから影響を受けたか?」という質問を受けたことです。知らなかったので、その時しらべたことをまとめます。興味深い内容でした。またなぜこれが大規模言語モデル(LLM)を用いた開発において重要な意味を持つのかも解説します。

従来のソフトウェアが抱える問題

隠れた複雑性

典型的なWebアプリケーションのユーザー登録処理を見てみましょう:

class UserService {
    public User register(String email, String password) {
        User user = new User(email, password);
        userRepository.save(user);
        emailService.sendWelcome(user);
        auditLog.record("user_registered", user.getId());
        return user;
    }
}

一見シンプルに見えますが、実際には多くの疑問が生じます:

  • userRepository.save() は何をトリガーする?キャッシュの更新は?イベントの発行は?
  • emailService.sendWelcome() は同期?非同期?失敗したらどうなる?
  • 他のサービスがこのイベントを監視していない?
  • 継承やAOPによる暗黙の処理はない?

LLMによる開発とコンテキストの爆発

LLMを用いたコード生成(いわゆる "Vibe Coding")が普及する中で、この「隠れた依存関係」は致命的なボトルネックになります。

既存のシステムに機能を追加しようとした際、LLMが正しくコードを生成するためには、そのクラスだけでなく、依存するクラス、さらにその先の依存関係までを含めた膨大なコンテキストを理解する必要がありるからです。

コンテキストの要求量がシステム規模の二乗で増加するため、コンテキストウィンドウの制限や、AIの推論能力の限界により、既存機能を破壊してしまうリスクが高まります。

コンテキスト要求 = O(n²)

1つのクラスを理解するには
  → 依存するクラスを理解する必要
    → その依存先も理解する必要
      → さらにその依存先も...

フレームワークはその魔法の分、トークンを消費しAIの集中力を削いでしまいます。

WYSIWDの解決策

2つの核心概念

WYSIWDは、ソフトウェアを2つの要素で構成します:ConceptsとSynchronizationsです。

要素 役割
Concepts 完全に独立したサービスモジュール
Synchronizations モジュール間を調整するルール

Concepts:完全独立モジュール

Concept は、他の何にも依存しない自己完結したモジュールです。

concept Password [U]
  purpose: 認証情報を安全に保存・検証する

  state:
    passwords: U -> one Hash      // ユーザーごとのパスワードハッシュ
    salts: U -> one Salt          // ユーザーごとのソルト

  actions:
    set [user: U, password: String] => []
      // パスワードをハッシュ化して保存

    check [user: U, password: String] => [valid: Boolean]
      // パスワードを検証

重要なポイント:

  1. 型パラメータ [U]: Password は「ユーザー」が何かを知りません。単なる型パラメータです
  2. 完全な状態定義: すべての状態が明示的に宣言されています
  3. 明確な入出力: 各アクションが何を受け取り、何を返すかが明確です
  4. 副作用なし: 他の Concept に影響を与えません

Synchronizations:宣言的で見える調整

独立した Concept 同士をどう連携させるのか?それが Synchronization です。「いつ(When)、何をするか(Then)」を宣言的に記述します。

sync UserRegistration
  when {
    Web/request: [method: "POST", path: "/register", body: ?b] => []
    User/create: [email: ?b.email] => [user: ?u]
  }
  then {
    Password/set: [user: ?u, password: ?b.password]
    Profile/create: [user: ?u, name: ?b.name]
    Email/sendWelcome: [to: ?b.email, user: ?u]
  }

これは「指揮者」のように機能します:

          Web リクエスト到着
                ↓
    ┌─────────────────────────┐
    │  Synchronization Engine │  ← 指揮者
    │   "UserRegistration"    │
    └─────────────────────────┘
         ↙    ↓    ↘
    [User]  [Password]  [Email]  ← 独立した演奏者
    create    set      sendWelcome

各 Concept は互いの存在を知りません。Synchronization だけが「誰が何をするか」を知っています。

Provenance Graph:完全な追跡可能性

WYSIWDの最大の強みの一つが、Provenance Graph(来歴グラフ)です。

すべてのアクションとその因果関係が記録されます:

実行ログ:
═══════════════════════════════════════════════════════════════
[001] 10:00:03.234  Web/request
                    method="POST" path="/register"
                    body={email: "user@example.com", ...}
      │
      ├──[sync: UserRegistration]
      │
      ├──→ [002] User/create => {user: "u_abc123"} ✓
      │
      ├──→ [003] Password/set => {} ✓
      │
      ├──→ [004] Profile/create => {} ✓
      │
      └──→ [005] Email/sendWelcome => {} ✓

[006] 10:00:03.456  Response status=201
═══════════════════════════════════════════════════════════════

従来の人が読むことを重要視したログではなく、構造的でいわばセマンティックなログです。

デバッグが劇的に簡単になる

問題: 「ユーザー登録したのにウェルカムメールが届かない」

従来のアプローチ:

1. UserService のコードを読む
2. EmailService のコードを読む
3. 非同期キューの設定を確認
4. ログを grep で検索
5. 複数のログファイルを突き合わせる
6. 30分〜数時間かかる

WYSIWDのアプローチ:

1. Provenance Graph を確認
2. Email/sendWelcome が存在しない → sync が発火していない
3. sync: UserRegistration の when 条件を確認
4. User/create が失敗していた → 原因特定
5. 5分で完了

コードからの推測でなく、実行の説明からAIが原因を知ることができます。

実践例:記事投稿システム

より具体的な例として、ブログの記事投稿システムを見てみましょう。

Concepts の定義

concept Article [U, A]
  purpose: ユーザーが記事を作成・管理する

  state:
    articles: set A
    author: A -> one U
    title: A -> one String
    body: A -> one String
    status: A -> one Status        // draft, published, archived
    publishedAt: A -> lone DateTime

  actions:
    create [author: U, title: String, body: String]
      => [article: A]

    publish [article: A]
      => [article: A]

    archive [article: A]
      => []
concept Comment [U, A, C]
  purpose: 記事にコメントを付ける

  state:
    comments: set C
    article: C -> one A
    author: C -> one U
    body: C -> one String

  actions:
    add [article: A, author: U, body: String]
      => [comment: C]

    delete [comment: C]
      => []
concept Notification [U, N]
  purpose: ユーザーに通知を送る

  state:
    notifications: set N
    recipient: N -> one U
    message: N -> one String
    read: N -> one Boolean

  actions:
    send [recipient: U, message: String]
      => [notification: N]

    markRead [notification: N]
      => []

Synchronizations の定義

sync ArticlePublished
  when {
    Article/publish: [article: ?a] => [article: ?a]
    Article/getAuthor: [article: ?a] => [author: ?u]
    Follow/getFollowers: [user: ?u] => [followers: ?fs]
  }
  then {
    for f in ?fs:
      Notification/send: [
        recipient: f,
        message: "新しい記事が公開されました"
      ]
    Analytics/track: [event: "article_published", article: ?a]
  }
sync CommentAdded
  when {
    Comment/add: [article: ?a, author: ?commenter] => [comment: ?c]
    Article/getAuthor: [article: ?a] => [author: ?articleAuthor]
  }
  then {
    Notification/send: [
      recipient: ?articleAuthor,
      message: "記事にコメントがつきました"
    ]
  }

実行の流れ

ユーザーが記事を公開:

POST /articles/123/publish
        │
        ▼
┌─────────────────────────────┐
│    Synchronization Engine   │
│     checks all sync rules   │
└─────────────────────────────┘
        │
        │ matches "ArticlePublished"
        ▼
┌─────────────────────────────────────────────────────┐
│ Provenance Graph                                    │
│                                                     │
│ [001] Article/publish {article: "a_123"} ✓         │
│   │                                                 │
│   ├→ [002] Article/getAuthor => {author: "u_456"}   │
│   │                                                 │
│   ├→ [003] Follow/getFollowers => {followers: [...]}│
│   │                                                 │
│   ├→ [004] Notification/send {recipient: "u_789"} ✓ │
│   ├→ [005] Notification/send {recipient: "u_012"} ✓ │
│   │                                                 │
│   └→ [006] Analytics/track ✓                        │
└─────────────────────────────────────────────────────┘

AI/LLM との親和性

WYSIWDが特に重要なのは、AI時代のソフトウェア開発においてです。

なぜ従来のコードはAIに難しいのか

LLMに「UserServiceを修正して」と依頼

LLMが必要とするコンテキスト:
├── UserService.java
├── UserRepository.java
│   └── DatabaseConfig.java
├── EmailService.java
│   ├── EmailTemplate.java
│   └── SmtpConfig.java
├── AuditLog.java
├── UserEventListener.java(暗黙の依存)
├── SecurityAspect.java(AOP による暗黙の処理)
└── ... さらに続く

延々と関連情報を紐解いていく時に コンテキストウィンドウを超過、または不完全な理解による誤りが問題となります。

WYSIWDなら

LLMに「Password Conceptを生成して」と依頼

LLMが必要とするコンテキスト:
└── Password Concept の仕様のみ

結果: 他のConceptの知識不要、単独で正しく生成可能

論文によると:

"concept specifications and code generation almost all completed successfully in a single shot"
(Concept の仕様とコード生成は、ほぼすべてが一発で成功した)

とのことです!(すごい!)

コンテキスト要求の比較

アプローチ コンテキスト要求 スケーラビリティ
従来のOOP O(n²) システムが大きくなると破綻
WYSIWD O(n) システムサイズに比例

WYSIWDの3つの原則

論文では、WYSIWDが実現する3つの重要な性質を挙げています:

1. Incrementality(漸進性)

変更を局所的に行える能力です。その時の大事なことだけが切り出されます。

image.png

新しい機能を追加したい: 「記事公開時にXに投稿」

従来:

  • UserService を修正
  • 依存関係を追加
  • テストを修正
  • 他の機能への影響を確認

WYSIWD:

  • 新しい sync を追加するだけ
sync ArticlePublishedToX
  when {
    Article/publish: [article: ?a] => []
  }
  then {
    X/post: [article: ?a]
  }

2. Integrity(整合性)

新しい変更が既存の機能を壊さないこと。

Concept は完全に独立
  → 新しい Concept を追加しても既存に影響なし
Synchronization は宣言的
  → 新しい sync を追加しても他の sync に影響なし

3. Transparency(透明性)

コードの変更と実行時の動作が明確に対応すること。

コードを見れば動作がわかる:

  • Concept の定義 = そのモジュールができること
  • Synchronization = いつ何が起こるか
  • Provenance Graph = 実際に何が起こったか

従来のアーキテクチャとの比較

観点 マイクロサービス イベント駆動 WYSIWD
独立性 サービス単位 イベント単位 Concept単位
調整方法 API呼び出し イベントバス Synchronization
可視性 分散トレーシング イベントログ Provenance Graph
依存関係 サービス間で発生 暗黙的 Syncで明示的
デバッグ 複雑 複雑 単純

イベント駆動アーキテクチャとの違い

一見似ていますが、重要な違いがあります:

イベント駆動:

// Publisher(発行者)
class ArticleService {
    void publish(Article article) {
        // ... 処理 ...
        eventBus.publish(new ArticlePublishedEvent(article));
        // 誰が購読しているかは知らない(暗黙的)
    }
}

// Subscriber(購読者)- 別の場所に散在
@EventListener
class NotificationListener {
    void onArticlePublished(ArticlePublishedEvent event) {
        // この処理の存在を知るのは難しい
    }
}

WYSIWD:

// すべての調整が一箇所で明示的
sync ArticlePublished
  when { Article/publish: [...] => [...] }
  then {
    Notification/send: [...]    // 明示的
    Analytics/track: [...]      // 明示的
  }

まとめ

WYSIWDは、ソフトウェアの「見える化」を実現するパターンです。ソフトウェアの「振る舞い」と「構造」を一致させます。 What You See Is What It Does(見たものが、そのまま動作する) が核心メッセージです。

主要な構成要素は3つ:

  1. Concepts: 完全に独立したモジュール
  2. Synchronizations: モジュール間の明示的な調整
  3. Provenance Graph: 完全な実行履歴

得られる効果

  • システムが何をしているのかの透明性
  • AIの使用トークンの減少=コード生成の精度向上
  • 変更による副作用の排除

私のソフトウエアと比べて

AIに対しての説明を重視するという点では私のいくつかのソフトウエアと完全に一致しています。大きな違いは WYSIWD が外からの制御に委ねているのにたいし、Beは内在的変化から達成することです。

同じ目線を持ったソフトウエア設計の存在は心強く、方向性の確かさに自信をもたらしてくれました。教えてくれたPMJonesさん、ありがとうございました。

またBEAR.Sundayの宣言性もこのアーキテクチャの親和性が高いと思います。

参考文献


この記事は WYSIWD 論文の解説を目的としています。詳細は原論文を参照してください。

5
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
5
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?