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

AIエンジニアリングの進化—目標達成とペアリング開発が織りなす新次元

Posted at

前回までのおさらい:コミュニティ機能の完成から

前回の記事では、Threads風SNS機能の実装でコーヒー焙煎師たちのコミュニティが誕生しました。しかし、開発はここで終わりません。

28.mdの開発計画書に記載された3つの短期改善項目が残されていました:

  1. 目標達成グラフの可視化機能
  2. 目標設定のプリセット機能
  3. チャレンジ進捗の詳細表示機能

これらの実装を通じて、今回はAI協働開発そのものの進化を体験することになります。


第一幕:目標達成グラフが描く成長の軌跡 📈

データ可視化の新次元

class TargetAchievementData {
  int totalAttempts;          // 総挑戦回数
  int targetAchievements;     // 目標達成回数
  double averageError;        // 平均誤差
  final DateTime month;       // 月

  /// 達成率(0.0〜1.0)
  double get achievementRate {
    if (totalAttempts == 0) return 0.0;
    return targetAchievements / totalAttempts;
  }

  /// パーセント表示
  String get achievementRateFormatted {
    return '${(achievementRate * 100).toStringAsFixed(1)}%';
  }
}

これまでの焙煎アプリでは「今回の焙煎がどうだったか」しか分からませんでした。
しかし、月別の目標達成率を可視化することで、ユーザーの成長の軌跡が見えるようになったのです。

fl_chartライブラリとの出会い

LineChart(
  LineChartData(
    gridData: FlGridData(show: true),
    titlesData: FlTitlesData(
      bottomTitles: AxisTitles(
        sideTitles: SideTitles(
          showTitles: true,
          getTitlesWidget: (value, meta) {
            return Text('${value.toInt()}月');
          },
        ),
      ),
    ),
    lineBarsData: [
      LineChartBarData(
        spots: achievementSpots,
        isCurved: true,
        gradient: LinearGradient(
          colors: [AppTheme.success, AppTheme.aiAssistant],
        ),
        barWidth: 3,
        dotData: FlDotData(show: true),
      ),
    ],
  ),
)

fl_chartライブラリの威力は圧倒的でした。
わずか数行のコードで、プロフェッショナルレベルのグラフが実現できるのです。

成功判定ロジックの革命

従来の味覚評価ベースから、目標ベースの判定に変更:

// 従来:主観的な味覚評価
final avgTaste = (acidity + sweetness + bitterness) / 3;
if (avgTaste >= 3.0) successfulRoasts++;

// 新方式:客観的な目標達成判定(±0.5許容誤差)
final acidityDiff = (acidity - targetAcidity).abs();
final sweetnessDiff = (sweetness - targetSweetness).abs();
final bitternessDiff = (bitterness - targetBitterness).abs();
final overallError = (acidityDiff + sweetnessDiff + bitternessDiff) / 3;

if (overallError <= 0.5) successfulRoasts++;

これにより、より科学的で公平な成功判定が可能になりました。


第二幕:プリセット機能がもたらすUX革命 🎨

「理想の味」をワンクリックで

初心者焙煎師にとって最大の障壁は**「目標をどう設定すればいいか分からない」**ことでした。

そこで誕生したのが、4つの内蔵プリセット

static const List<TargetPreset> builtInPresets = [
  TargetPreset(
    name: 'バランス型',
    description: '酸味・甘味・苦味のバランスが取れたオールラウンダー',
    acidity: 3.5,
    sweetness: 3.5,
    bitterness: 3.0,
  ),
  TargetPreset(
    name: '明るい酸味',
    description: 'フルーティーで爽やかな酸味が特徴',
    acidity: 4.5,
    sweetness: 3.0,
    bitterness: 2.5,
  ),
  TargetPreset(
    name: '甘み重視',
    description: 'チョコレートのような深い甘みを重視',
    acidity: 3.0,
    sweetness: 4.5,
    bitterness: 2.5,
  ),
  TargetPreset(
    name: 'ダークビター',
    description: '力強い苦味とコクが楽しめる本格派',
    acidity: 2.5,
    sweetness: 3.0,
    bitterness: 4.5,
  ),
];

Firestoreでのカスタムプリセット管理

class TargetPresetService {
  final FirebaseFirestore _firestore = FirebaseFirestore.instance;

  /// カスタムプリセットを保存
  Future<void> saveCustomPreset(TargetPreset preset) async {
    final userId = FirebaseAuth.instance.currentUser?.uid;
    if (userId == null) throw Exception('ユーザーが認証されていません');
    
    await _firestore
        .collection('users')
        .doc(userId)
        .collection('targetPresets')
        .add(preset.toMap());
  }

  /// プリセット一覧を取得(内蔵 + カスタム)
  Future<List<TargetPreset>> getAllPresets() async {
    final customPresets = await getCustomPresets();
    return [...builtInPresets, ...customPresets];
  }
}

この設計により、スケーラブルな味覚プロファイル管理が実現しました。

UIの進化:フレーバーチップ

Widget _buildFlavorChips(TargetPreset preset) {
  return Wrap(
    spacing: 8,
    children: [
      Chip(
        label: Text('酸味 ${preset.acidity}'),
        backgroundColor: _getFlavorColor(preset.acidity),
      ),
      Chip(
        label: Text('甘味 ${preset.sweetness}'),
        backgroundColor: _getFlavorColor(preset.sweetness),
      ),
      Chip(
        label: Text('苦味 ${preset.bitterness}'),
        backgroundColor: _getFlavorColor(preset.bitterness),
      ),
    ],
  );
}

視覚的に味覚プロファイルが理解できる、直感的なUIの完成です。


第三幕:レースコンディション解決 - 開発者魂の勝利 🔧

謎の現象に立ち向かう

冒頭で紹介した**「3個vs8個チャレンジ」問題**。
この解決過程こそが、今回最大の技術的ハイライトでした。

問題の根本原因

調査の結果、3つの複合的な問題が判明:

  1. デュアルデータソース問題

    • 画面側:8個のチャレンジ(全レベル混合)
    • サービス側:3個のチャレンジ(スキルレベル別)
  2. レースコンディション

    // 危険な並行処理
    WidgetsBinding.instance.addPostFrameCallback((_) {
      _loadDefaultChallengesImmediately(); // 8個表示
      _loadChallenges(); // 3個の可能性 → 競合状態
    });
    
  3. アニメーションタイミング問題

    Transform.scale(
      scale: _cardAnimation.value, // これが0.0で見えない状態
      child: ChallengeCard(),
    )
    

解決策:統一データソース + シーケンシャルローディング

1. データソースの統一

// サービス層で統一管理
class RoastChallengeService {
  /// 全レベル含む統一チャレンジ生成
  List<RoastChallenge> getDefaultChallenges() {
    return [
      // 初心者向け(3個)
      RoastChallenge.beginner1(),
      RoastChallenge.beginner2(),
      RoastChallenge.beginner3(),
      // 中級者向け(3個)
      RoastChallenge.intermediate1(),
      RoastChallenge.intermediate2(),
      RoastChallenge.intermediate3(),
      // 上級者向け(2個)
      RoastChallenge.advanced1(),
      RoastChallenge.advanced2(),
    ]; // 合計8個で統一
  }
}

2. 明確な状態管理

enum ChallengeDisplayMode {
  loading,           // ローディング中
  defaultChallenges, // デフォルトチャレンジ表示
  aiChallenges,      // AI生成チャレンジ表示
  error,             // エラー状態
}

3. シーケンシャルローディング

Future<void> _loadChallengesSequentially() async {
  debugPrint('🎯 シーケンシャルローディング開始');
  
  try {
    // 1. 即座にデフォルトチャレンジを表示
    setState(() {
      _displayMode = ChallengeDisplayMode.defaultChallenges;
      _challenges = _challengeService.getDefaultChallenges();
      _isLoading = false;
      _lastError = null;
    });
    
    debugPrint('🎯 デフォルトチャレンジ表示完了: ${_challenges.length}個');
    _cardAnimationController.forward();
    
    // 2. バックグラウンドで統計データを取得
    await _loadUserData();
    
    // 3. AI生成は手動実行のみ(自動実行停止)
    debugPrint('🎯 シーケンシャルローディング完了');
    
  } catch (e) {
    setState(() {
      _displayMode = ChallengeDisplayMode.error;
      _lastError = e.toString();
    });
  }
}

この解決により、レースコンディションが完全に解消され、予測可能で安定したチャレンジ表示が実現しました。


第四幕:AI協働開発の新次元 🤖

Claudeとの「ペアプロ」体験

今回の開発で最も印象的だったのは、Claude Codeとの協働レベルの向上でした。

自動的な問題分析

Claudeの分析:
「レースコンディションが発生しています。2つの非同期処理が
競合状態を起こし、データ不整合が生じています。」

「解決策として、シーケンシャルローディングパターンを
提案します。統一データソースと明確な状態管理も必要です。」

段階的なリファクタリング提案

Claude Codeは複雑な問題を小さなステップに分解して提示:

  1. ✅ デフォルトチャレンジ生成を一箇所に集約
  2. ✅ AI/デフォルトの状態管理を明確に分離
  3. ✅ シーケンシャルローディングでレースコンディション解消
  4. ✅ 重複コードの除去とエラーハンドリング統一

TODOの自動管理

// Claude Codeが自動で管理したTODO
[
  {"content": "デフォルトチャレンジ生成を一箇所に集約", "status": "completed"},
  {"content": "AI/デフォルトの状態管理を明確に分離", "status": "completed"},
  {"content": "シーケンシャルローディングでレースコンディション解消", "status": "completed"},
  {"content": "重複コードの除去とエラーハンドリング統一", "status": "completed"}
]

品質向上の実感

今回のリファクタリングにより、以下の劇的な改善を実現:

  • バグ耐性: ★★★★★(レースコンディション解消)
  • 保守性: ★★★★★(統一データソース)
  • 予測可能性: ★★★★★(明確な状態管理)
  • コード品質: ★★★★★(重複コード削除)

エピローグ:Git 7コミットの物語 📝

今回の開発成果は、段階的な7つのコミットとして記録されました:

04990dd docs: 設計書・仕様書を今日の成果で全面更新
504904e docs: プロジェクト設定と開発ドキュメントを更新  
fb628af feat: プロフィール機能と関連画面の統合改善
942d608 chore: 不要なファイルとAPI監視機能を削除
c3ac815 refactor: チャレンジシステムの根本的リファクタリング
463f77f feat: 目標設定のプリセット機能を実装
09b84fc feat: 目標達成グラフの可視化機能を実装

各コミットには詳細な説明実装の背景が記載され、将来の開発者(自分自身を含む)への完璧な道しるべとなっています。


学びと気づき:次世代開発への示唆 💡

AI協働開発の進化パターン

今回の体験で確信したのは、AI協働開発には明確な進化段階があることです:

Level 1: AI = ツール

  • Stack Overflowの代替
  • コード補完程度の活用

Level 2: AI = アシスタント

  • バグ修正の支援
  • 実装アドバイスの提供

Level 3: AI = パートナー(今回到達)

  • 問題の根本原因分析
  • 設計レベルでの改善提案
  • 段階的なリファクタリング戦略

Level 4: AI = チームメンバー(未来)

  • プロジェクト全体の戦略立案
  • アーキテクチャ設計の主導
  • 品質保証の完全自動化

技術的学習の加速

fl_chartライブラリの習得も、Claude Codeとの協働により劇的に短縮されました:

// 30分でマスターしたグラフ実装
LineChartBarData(
  spots: monthlyData.entries.map((entry) {
    final month = entry.key.month.toDouble();
    final rate = entry.value.achievementRate;
    return FlSpot(month, rate);
  }).toList(),
  isCurved: true,
  gradient: LinearGradient(
    colors: [AppTheme.success.withOpacity(0.8), AppTheme.aiAssistant],
  ),
)

従来なら1-2日かかる学習が、30分で完了したのです。

UX設計思考の深化

プリセット機能の実装を通じて、**「技術とUXの融合」**の重要性を学びました:

  • 技術的実装: Firestoreでのデータ管理
  • UX設計: 初心者でも直感的に使える操作性
  • 視覚的表現: フレーバーチップによる味覚可視化

この3つが完璧に調和したとき、真に価値のある機能が生まれるのです。


未来への展望:次回予告 🚀

プロジェクト統計の進化

  • コード行数: 15,000行(+2,000行)
  • ファイル数: 42ファイル(+2ファイル)
  • アーキテクチャ成熟度: Level 6 (Advanced Production)
  • 完成度: 95%

次回実装予定

Phase 8では、以下の高度な機能に挑戦します:

  1. FCM通知システム: リアルタイム通知でユーザーエンゲージメント向上
  2. 検索機能強化: ユーザー・投稿・タグの高速検索
  3. データエクスポート: 焙煎データのCSV出力機能
  4. オフライン対応: ネットワーク不安定時の堅牢性確保

AI協働開発の未来

今回の体験で、AIとの協働開発が単なる効率化ツールを超えて、開発体験そのものを変革する力を持つことを確信しました。

近い将来、**「AIとのコミュニケーション能力」**がエンジニアの必須スキルになる日が来るでしょう。

そして、その先には**「AIチーム開発」**という新しい開発パラダイムが待っているのかもしれません。


おわりに:技術と感情の融合点で

コーヒー焙煎アプリの開発は、単なる技術的な挑戦を超えて、人間とAIの協創体験となりました。

目標達成グラフは単なるデータ可視化ではなく、ユーザーの成長の物語を描く機能です。
プリセット機能は単なる設定機能ではなく、初心者の学習の扉を開く機能です。
レースコンディション解決は単なるバグ修正ではなく、品質への執念の表れです。

コーヒーという、最も人間的で感情的な飲み物を題材にしながら、
最先端のAI技術と融合させることで、
新しい開発体験の可能性を探求し続けています。

次回の記事も、きっと想像を超える発見と学びが待っているはずです。


🔗 関連リンク

📝 技術スタック

  • Flutter: 3.19.0
  • Firebase: Auth + Firestore
  • AI: Google Gemini 1.5 Flash
  • Charts: fl_chart 0.68.0
  • 開発支援: Claude Code

最後まで読んでいただき、ありがとうございました!

AI協働開発の世界は、想像以上に深く、魅力的です。
いいね・ストック・コメントで応援いただけると、次の開発がますます楽しくなります 🚀

続編にもご期待ください!

#Flutter #Firebase #AI #Claude #Gemini #データ可視化 #UX設計 #個人開発 #協働開発

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