LLMを活用した状態管理システムの動的設計: デプロイロールバック機能の実装を例に

## はじめに

「設計段階でちゃんとテストできたらいいのに...」

こんな風に思ったことはありませんか？特に状態管理のような複雑な機能を設計するとき、実装前に動作を確認できないのってすごく不安ですよね。今回は、そんなモヤモヤを解決する方法として、LLMを使った設計アプローチを紹介します。具体的には、Webアプリのデプロイ機能にあるロールバック機能を例に、どんな風にLLMを活用できるのか、実践的に見ていきましょう。

## LLMと設計を進める

設計プロセスにおいて、LLMとの対話は大きく3つのフェーズで行いました：

### Phase 1: 初期設計の探索

最初の対話では、基本的なデータ構造の設計を探りました：

**Input to LLM:**
```
rollbackを実装したいので、状態をjsonで管理したい。
1 -> 2 -> rollback -> 1
1 -> 2 -> 3 -> rollback -> 2 -> 4 -> rollback -> 2
のような操作を実現するデータ構造を提案して。
```

### Phase 2: 設計パターンの検討

初期設計を得た後、既存のベストプラクティスを探るため、以下のような対話を行いました：

**Input to LLM:**
```
状態管理とロールバックの実装に関して、一般的なデザインパターンはある？
それぞれの利点と欠点を教えて。
```

### Phase 3: 具体的な実装設計

最後に、選択したパターンに基づいて具体的な実装を検討しました：

**Input to LLM:**
```
Mementoパターンを使って、具体的なJSONスキーマを設計したい。
状態履歴とメタデータを含める必要がある。
```

## LLMと一緒に設計を進めてみた

### Step 1: まずは基本構造を考えよう

最初は、とにかく「これって実現できるの？」というところから始めました。LLMに投げかけた最初の質問は、こんな感じです：

```
ねぇ、ロールバック機能を作りたいんだけど、状態をJSONで管理するとしたらどんな感じになると思う？
例えば、こんな感じの操作ができるようにしたいんだけど：
1 -> 2 -> rollback -> 1
1 -> 2 -> 3 -> rollback -> 2 -> 4 -> rollback -> 2
```

これに対してLLMくん、なかなか良い提案をしてくれました：

```python
class StateManager:
    def __init__(self):
        # 状態の履歴を配列で管理
        self.state_history: List[Dict[str, Any]] = []
        # 現在の状態を指すインデックス
        self.current_index: int = -1
```

この提案のイイところ：
- 履歴を配列で管理するのは直感的でシンプル
- インデックスで現在位置を管理するのは分かりやすい
- 拡張性もありそう

### Step 2: もっと良いやり方を探ってみよう

「でもさ、こういうの、もっと良いやり方があるんじゃない？」

そう思って、LLMに「この手の実装って、何かベストプラクティスとかないの？」って聞いてみました。すると、興味深い提案が返ってきました。

#### Commandパターン
```typescript
interface Command {
  execute(): void;
  undo(): void;
}
```

このパターンの特徴：
- 各操作を独立したオブジェクトとして管理
- 操作の履歴を詳細に記録できる
- 複雑な操作もサポートできる
- ただし、実装が少し複雑になりがち

#### Mementoパターン
```typescript
class Memento {
  constructor(private state: any, private timestamp: Date) {}
}
```

こっちの特徴：
- シンプルで分かりやすい
- 状態のスナップショットを管理しやすい
- 実装が比較的楽
- ただし、細かい操作の制御は難しいかも

正直、どっちにするか迷いました。でも、今回のケースではシンプルさを重視して、Mementoパターンを採用することにしました。

### Step 3: 具体的な状態管理を設計してみよう

「じゃあ、具体的にJSONってどんな構造にする？」

この質問に対して、LLMは以下のような構造を提案してきました：

```json
{
  "mementos": [
    {
      "snapshot": {
        "value": 1,
        "data": "initial state",
        "deployInfo": {
          "version": "1.0.0",
          "timestamp": "2024-11-15T10:00:00",
          "deployer": "tanaka"
        }
      },
      "metadata": {
        "version": 1,
        "timestamp": "2024-11-15T10:00:00",
        "description": "初期デプロイ"
      }
    }
  ],
  "currentIndex": 0
}
```

これ、なかなか良いですよね：
- スナップショットとメタデータを分けて管理
- デプロイ情報もちゃんと含まれている
- タイムスタンプや説明文で追跡可能性も確保

### Step 4: 実際の動きを確認してみよう

設計ができたら、次は実際の動きを確認。以下のような操作シーケンスを考えてみました：

1. **Undo操作のケース**
   ```javascript
   // currentIndexを1減らして前の状態に戻る
   const undo = () => {
     if (currentIndex > 0) {
       currentIndex--;
       return mementos[currentIndex].snapshot;
     }
     throw new Error("これ以上戻れません！");
   };
   ```

2. **Redo操作のケース**
   ```javascript
   // currentIndexを1増やして次の状態に進む
   const redo = () => {
     if (currentIndex < mementos.length - 1) {
       currentIndex++;
       return mementos[currentIndex].snapshot;
     }
     throw new Error("これ以上進めません！");
   };
   ```

3. **新しい状態の追加**
   ```javascript
   // 新しい状態を追加する時は、current以降の履歴を消して追加
   const addState = (newState) => {
     mementos.splice(currentIndex + 1);
     mementos.push({
       snapshot: newState,
       metadata: {
         version: currentIndex + 2,
         timestamp: new Date().toISOString()
       }
     });
     currentIndex++;
   };
   ```

エッジケースの確認のため、以下のようなインタラクティブな対話をLLMと行いました：

**Input to LLM:**
```
以下の操作シーケンスでどうなる？

1 -> 2 -> 3 (add)
undo
undo
4を追加
redo
```

**LLMの回答:**
```
各ステップでの状態変化を確認します：

1. 初期状態: [1]
   currentIndex: 0

2. 2を追加: [1, 2]
   currentIndex: 1

3. 3を追加: [1, 2, 3]
   currentIndex: 2

4. undo実行: [1, 2, 3]
   currentIndex: 1
   現在の値: 2

5. もう一度undo: [1, 2, 3]
   currentIndex: 0
   現在の値: 1

6. 4を追加: [1, 4]
   // ここで重要: currentIndex以降の履歴(2,3)は削除される
   currentIndex: 1

7. redo実行: redoできる履歴がないためエラー
   // 4の追加時に2,3の履歴が削除されているため
```

この動的な確認により、以下の重要な点が明確になりました：

1. undo/redoの状態管理
2. 履歴の分岐処理
3. エラー発生条件

さらに、エッジケースについても確認を行いました：

**Input to LLM:**
```
以下のようなケースで、どのような問題が発生する可能性がある？

1. 最初の状態に戻ろうとした場合
2. 履歴の最後まで進んだ後の操作
3. ロールバック後に新しい状態を追加
```

**LLMの回答:**
各ケースについて以下のような問題点と対策が提示されました：

1. 初期状態へのロールバック
   ```typescript
   // 提案された対策
   if (currentIndex <= 0) {
     throw new StateManagementError('INITIAL_STATE_REACHED');
   }
   ```

2. 最終状態での操作
   ```typescript
   // 提案された対策
   if (currentIndex >= mementos.length - 1) {
     throw new StateManagementError('FINAL_STATE_REACHED');
   }
   ```

3. 分岐履歴の処理
   ```typescript
   // 提案された実装
   function addStateAfterRollback(newState) {
     // ロールバック後の履歴を適切に管理
     mementos.splice(currentIndex + 1);
     addNewState(newState);
   }
   ```

### Step 5: 仕様書にまとめよう

設計が固まってきたところで、実装のための仕様書を作成しました。

#### データ構造の定義

```typescript
// 基本的な状態を表す型
interface State {
  value: number;
  data: string;
  deployInfo: {
    version: string;
    timestamp: string;
    deployer: string;
  };
}

// メタデータの型
interface Metadata {
  version: number;
  timestamp: string;
  description: string;
  tags?: string[];
}

// スナップショット全体の型
interface Snapshot {
  state: State;
  metadata: Metadata;
}

// システム全体の型
interface StateManagement {
  snapshots: Snapshot[];
  currentIndex: number;
  maxHistory: number;
}
```

#### 主要な操作の仕様

1. **状態の追加**
   ```typescript
   interface AddStateOptions {
     description?: string;
     tags?: string[];
   }
   
   function addState(state: State, options?: AddStateOptions): void;
   ```

2. **ロールバック操作**
   ```typescript
   interface RollbackOptions {
     preserveHistory?: boolean;
   }
   
   function rollback(targetIndex: number, options?: RollbackOptions): void;
   ```

3. **状態の取得**
   ```typescript
   interface GetStateOptions {
     includeMetadata?: boolean;
   }
   
   function getCurrentState(options?: GetStateOptions): State;
   ```

#### エラーハンドリング

想定されるエラーケース：
1. 無効なインデックスへのロールバック試行
2. 状態データの不整合
3. メモリ制限の超過

対応方針：
- 各操作の前に必ず状態の整合性をチェック
- エラーは専用の型で定義して明示的に処理
- ロギングによる追跡可能性の確保

## この設計アプローチの良かったところ

### 1. 動的な設計検証ができた！
- LLMと対話しながら設計を進められた
- 実装前に問題点を発見できた
- 設計の改善サイクルが早かった

### 2. パターンの選択が適切にできた
- 既存のデザインパターンを活用
- プロジェクトに合った実装方法を選択
- 拡張性と保守性のバランスが取れた

### 3. 問題点の早期発見
- エッジケースを事前に検討できた
- 実装上の課題が明確になった
- 設計の妥当性を確認できた

## まとめと今後の展望

### 得られた成果
1. **設計プロセスの改善**
   - LLMとの対話による設計の検証
   - 早期のフィードバック獲得
   - 柔軟な設計の改善

2. **技術的な成果**
   - 堅牢な状態管理システムの設計
   - 明確な仕様の策定
   - エラーケースへの対応方針の確立

3. **プロジェクト管理面での効果**
   - 設計段階でのリスク低減
   - チーム内での共通理解の促進
   - 実装工数の正確な見積もり

### 今後の可能性

この経験から、以下のような展望が見えてきました：

1. **設計プロセスの進化**
   - LLMを活用した設計レビュー
   - 自動的な設計検証の可能性
   - より効率的な設計手法の確立

2. **機能の拡張**
   - より高度な状態管理機能の追加
   - パフォーマンス最適化
   - セキュリティ強化

3. **チーム開発への応用**
   - 設計ドキュメントの自動生成
   - コードレビューでの活用
   - 知識共有の促進

最後に、この方法は特に以下のようなケースで効果を発揮すると感じました：
- 複雑な状態管理が必要なプロジェクト
- チーム間での設計の共有が重要なケース
- 早期のフィードバックが求められる開発

みなさんも、ぜひプロジェクトでLLMを活用した設計アプローチを試してみてください！きっと新しい発見があるはずです。