1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

ユニークビジョン株式会社Advent Calendar 2024

Day 25

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

Posted at

はじめに

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

こんな風に思ったことはありませんか?特に状態管理のような複雑な機能を設計するとき、実装前に動作を確認できないのってすごく不安ですよね。今回は、そんなモヤモヤを解決する方法として、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くん、なかなか良い提案をしてくれました:

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

この提案のイイところ:

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

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

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

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

Commandパターン

interface Command {
  execute(): void;
  undo(): void;
}

このパターンの特徴:

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

Mementoパターン

class Memento {
  constructor(private state: any, private timestamp: Date) {}
}

こっちの特徴:

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

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

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

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

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

{
  "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操作のケース

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

    // currentIndexを1増やして次の状態に進む
    const redo = () => {
      if (currentIndex < mementos.length - 1) {
        currentIndex++;
        return mementos[currentIndex].snapshot;
      }
      throw new Error("これ以上進めません!");
    };
    
  3. 新しい状態の追加

    // 新しい状態を追加する時は、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. 初期状態へのロールバック

    // 提案された対策
    if (currentIndex <= 0) {
      throw new StateManagementError('INITIAL_STATE_REACHED');
    }
    
  2. 最終状態での操作

    // 提案された対策
    if (currentIndex >= mementos.length - 1) {
      throw new StateManagementError('FINAL_STATE_REACHED');
    }
    
  3. 分岐履歴の処理

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

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

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

データ構造の定義

// 基本的な状態を表す型
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. 状態の追加

    interface AddStateOptions {
      description?: string;
      tags?: string[];
    }
    
    function addState(state: State, options?: AddStateOptions): void;
    
  2. ロールバック操作

    interface RollbackOptions {
      preserveHistory?: boolean;
    }
    
    function rollback(targetIndex: number, options?: RollbackOptions): void;
    
  3. 状態の取得

    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を活用した設計アプローチを試してみてください!きっと新しい発見があるはずです。

1
0
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
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?