every Tech Blog

株式会社エブリーのTech Blogです。

AI 駆動開発ライフサイクル(AI-DLC)を試してみた

Image

はじめに

こんにちは、リテールハブ開発部の杉森です。

近年、AIを活用した開発ツールが急速に普及しています。私たちのチームでも積極的にAIツールを導入し、要件定義でのユーザーストーリー作成、設計ドキュメントの生成、コードの自動補完、テストコードの生成など、各開発フェーズの作業効率化を図ってきました。

しかし、個々の作業は確かに早くなっているのに、プロダクト開発フロー全体を見ると期待したほどの生産性向上を実感できないという課題に直面しました。

本記事では、この課題に対するアプローチとして導入を検討しているAI-DLC(AI-Driven Development Lifecycle)について紹介します。

AI-DLCとは

AI-DLCは、AWSが提唱するAIネイティブな開発方法論です。方法論のホワイトペーパーで理論的な枠組みが定義されており、これを実装するためのワークフローがaidlc-workflowsとしてGitHub上で公開されています。

AWSの公式ブログでは、現在のAI活用における2つのアンチパターンが指摘されています。

  • AI-Assisted: 人間が設計を主導し、AIはコード補完など狭い範囲の支援にとどまる。生産性向上は限定的で、AIの能力を十分に引き出せない
  • AI-Managed: 複雑な問題をAIに丸投げし、自律的にすべてを解決することを期待する。出発点が曖昧なためAIが多くの仮定を立て、プロトタイプ以外ではほぼ機能しない

AI-DLCは、これらのアンチパターンに対するアプローチとして設計されています。AIが作業計画の作成やタスク分解を主導し、人間がその内容を検証・承認し、AIが承認された計画に基づいて実行するというサイクルで、開発ライフサイクル全体を進めます。

従来の開発手法との違い

AI-DLCは、既存の開発手法にAIを後付けするのではなく、AIを前提とした開発プロセスをゼロから設計しています。ホワイトペーパーでは、以下の設計思想が示されています。

  • AIが会話を主導する: 従来は人間がAIに指示を出していたが、AI-DLCではAIがタスク分解や提案を行い、人間は承認・判断に集中する
  • Intent / Unit / Bolt: ビジネス目標(Intent)を作業単位(Unit)に分解し、数時間〜数日の短いサイクル(Bolt)で実装を回す。ScrumのSprintに近いが、サイクルが短い
  • 各ステップで人間がチェックする: AIの出力を段階ごとに検証し、誤りを早期に検出する。ホワイトペーパーでは「損失関数のように機能する」と表現されている
  • 設計技法を方法論に組み込む: ScrumやKanbanがチームに委ねていたDDD等の設計技法を、方法論の一部として標準化する

aidlc-workflowsの設計原則

aidlc-workflowsは、上記の設計思想を実装するにあたり、以下の5つの設計原則に基づいています。

原則 説明
No Duplication 設定やルールを一箇所で管理し、重複を排除する
Methodology First 特定のツールに縛られず、方法論そのものを軸にする
Reproducible ルールを明文化し、使うAIモデルが変わっても結果がぶれないようにする
Agnostic IDE・エージェント・モデルを問わず動作する
Human in the Loop 重要な判断には必ず人間の承認を挟む

3フェーズ構成

AI-DLCは、以下の3つのフェーズで構成されています。

  1. INCEPTION PHASE: WHATとWHYの決定
  2. CONSTRUCTION PHASE: HOWの実装
  3. OPERATIONS PHASE: デプロイと監視(aidlc-workflows上は未実装)

INCEPTION PHASE

「何を作るか(WHAT)」「なぜ作るか(WHY)」を決定するフェーズです。方法論のホワイトペーパーでは「Mob Elaboration」というプラクティスとして定義されており、共有画面を使ってチーム全体でAIの質問と提案を検証します。AIがビジネス意図(Intent)を明確化する質問を投げかけ、ユーザーストーリー、非機能要件、リスク記述を生成し、凝集度の高い作業単位(Unit)へ分割します。

aidlc-workflowsでは、このフェーズが以下のステージに細分化されています。

ステージ 説明
Workspace Detection プロジェクトの状態を分析(新規/既存の判定)
Reverse Engineering 既存コードベースの理解(Brownfieldの場合)
Requirements Analysis 要件の収集と整理
User Stories ユーザーストーリーの作成
Workflow Planning 実行計画の策定
Application Design アプリケーション設計
Units Generation 作業単位への分割

CONSTRUCTION PHASE

「どう作るか(HOW)」を決定し、実際にコードを生成するフェーズです。方法論のホワイトペーパーでは、Domain Design(ビジネスロジックのドメインモデリング)→ Logical Design(非機能要件を含むアーキテクチャ設計)→ Code & Unit Tests(コードとテストの生成)→ Deployment Units(デプロイ可能な成果物の構築)という流れで進みます。「Mob Construction」でチームがリアルタイムで技術的決定とアーキテクチャの選択を行います。

aidlc-workflowsでは、このフェーズが以下のステージに細分化されています。

ステージ 説明
Functional Design 機能設計(ユニットごと)
NFR Requirements/Design 非機能要件の設計
Infrastructure Design インフラ設計
Code Generation コード生成
Build and Test ビルドとテスト

OPERATIONS PHASE

デプロイと監視を担当するフェーズです。方法論としては定義されていますが、aidlc-workflowsには含まれておらず、将来的にワークフローが追加される予定です。

対応プラットフォーム

公式では以下のプラットフォームがサポートされています。

  • Kiro CLI
  • Amazon Q Developer IDE plugin
  • Kiro IDE(Coming Soon)

試してみる

導入の背景

私たちのチームの課題は、まさにAI-Assistedパターンに該当します。AIを個々の作業の効率化には活用できているものの、生産性向上は限定的にとどまっていました。

私たちのチームでは、Kiro CLIをすぐに使える環境ではなかったため、Claude Code向けにカスタマイズして使用しました。AI-DLCはツールに依存しない設計を謳っているため、ルールファイルを調整すれば他のAIツールでも問題なく適用できると考えています。

Claude Code向けのカスタマイズ

以下のようにClaude Code向けにカスタマイズしました。

1. カスタムコマンド(スキル)の作成

.claude/commands/aidlc.md にワークフロー定義を配置し、/aidlc コマンドで起動できるようにしました。

.claude/
├── commands/
│   ├── aidlc.md          # メインワークフロー
│   ├── aidlc-pr.md       # PR作成用
│   └── aidlc-archive.md  # アーカイブ用
└── aidlc-rule-details/
    ├── common/           # 共通ルール
    ├── inception/        # INCEPTIONフェーズ
    ├── construction/     # CONSTRUCTIONフェーズ
    └── operations/       # OPERATIONSフェーズ

2. ルールファイルの分割

各ステージの詳細指示を .claude/aidlc-rule-details/ 以下に分割配置しています。これにより、AIが必要なタイミングで必要なルールのみを読み込み、コンテキストを効率的に使用できます。

クーポン機能を題材にした検証

AI-DLCの有効性を検証するため、小売向けアプリのクーポン機能開発を題材に検証を実施しました。

検証概要

  • 対象システム: Flutter + Laravel + Vue.js + Goで構成されたマルチプラットフォームアプリ
  • 題材: ポイント後付けクーポンと即時値引きクーポンの2種類
  • 検証範囲: モバイルアプリ、管理画面、バックエンドAPI、バッチ処理
  • チーム構成: PdM1人+エンジニア2人

検証の進め方

「クーポン機能を追加したい」というビジネス意図(Intent)を起点に、AI-DLCのフェーズに沿って進めました。

  1. INCEPTION PHASE(3人で実施): AIが要件を深掘りする質問を投げかけ、ユーザーストーリーや非機能要件を生成。PdMとエンジニアがその内容を検証・修正し、作業単位(Unit)に分割
  2. CONSTRUCTION PHASE(1人で実施): Unitごとにドメイン設計、コード生成、テスト生成を実施。各ステージでAIの出力を確認し、承認・修正を繰り返した

今回は検証目的だったこともあり、各フェーズ半日ずつの計1日で実際に動くものまで作成できました。Inceptionフェーズの要件・設計をより作り込み、Constructionフェーズではガードレールの整備やAIが自律的に改善できる体制を組むことで、さらに短縮できる余地があると感じています。

実際の様子

AIからの深掘り質問(Inceptionフェーズ)

Requirements Analysisでは、AIが要件の曖昧な部分を選択肢付きで質問してきます。以下はその一例です。

AI: クーポン利用状態の管理について確認させてください。ユーザーがクーポンを「利用」した後の状態管理はどうなりますか?

  • A) 1回利用したら即座に使用済みになる(再利用不可)
  • B) 有効期限内であれば何度でも利用可能
  • C) クーポンごとに利用回数を設定可能(1回、3回、無制限など)

AI: クーポンの種類と適用範囲について確認させてください。

  • A) 全ての種類が両方のクーポンタイプで使用可能
  • B) クーポンタイプごとに使用可能な種類が決まっている

このように、AIが仕様の選択肢を提示し、人間が判断するというサイクルでRequirements Analysisが進みます。初回の質問10問、追加の深掘り質問6問を経て、要件定義ドキュメントが生成されました。

人間による設計修正(Inceptionフェーズ)

Application Designでは、AIが設計の選択肢を提示し、人間が判断するケースがありました。

人間: アクティブユーザーではないユーザーにもレコードが作成されてしまいませんか?

AI: 2つの選択肢があります。

  • A) クーポン公開時に全ユーザー分のuser_couponsレコードを作成
  • B) クーポン利用開始時にのみuser_couponsレコードを作成

人間: B

生成されたユーザーストーリー(Inceptionフェーズ)

User Storiesでは、管理者向け6件、会員ユーザー向け6件、システム向け1件の計13件が生成されました。以下はその一部です。

US-01: クーポン新規作成

As a 管理者 I want to 管理画面から新しいクーポンを作成したい So that 会員ユーザーに対してキャンペーンを提供できる

Acceptance Criteria:

  • クーポンタイプ(ポイント後付け/即時値引き)を選択できる
  • クーポン名と説明文を入力できる
  • 有効期限(開始日・終了日)を設定できる
  • 対象店舗を選択できる
生成されたコード(Constructionフェーズ)

Constructionフェーズでは、Unitごとにドメイン設計 → コード生成 → テスト生成が進みます。最終的に以下の規模のコードが生成されました。

Unit 対象 生成ファイル数 主な成果物
Backend Laravel 51ファイル Enum, Model, Migration, Service, Controller, Test
Dashboard Vue.js 16ファイル Composable, Component, Schema, Page
Mobile Flutter 38ファイル Entity, Repository, Provider, Widget, Page

わかったこと / 今後の展望

良いと感じた点

実際にAI-DLCを触ってみて、以下の点が良いと感じました。

  • Human in the Loopの実現: AIが実行し、人間が監視するという関係性が明確。各ステージで人間の承認が必要なため、重要な意思決定は人間がコントロールできる
  • コンテキストの保存と再開: aidlc-state.mdでプロジェクトの状態を追跡しているため、セッションが途切れても前回の続きから再開できる
  • ドキュメント化による追跡可能性: audit.mdにすべてのやり取りが記録されるため、なぜその決定をしたのかを後から追跡できる
  • 適応的なワークフロー: プロジェクトの複雑さに応じて、実行するステージが自動的に調整される

試した上で見つかった課題

Inception前の準備の必要性

今回「クーポン機能を追加したい」というリクエストからInceptionを開始しましたが、背景知識や「なぜこの機能が必要なのか」がアウトプットに反映されにくいことがわかりました。また、要件の解像度が低い状態でInceptionを始めると、議論が発散しやすくなります

AI-DLCのInceptionに入る前に、ビジネス背景や目的を整理するステップが必要だと感じました。

仕様とAI実装のギャップ

Inceptionフェーズで仕様を決め切った上でも、以下の2つの問題が発生しました。

  1. 仕様の記載漏れ: Inceptionフェーズで決めた仕様に漏れがあり、Constructionフェーズで初めて気づくケース。例えば、APIレスポンスのラッパー形式やお気に入り店舗のパラメータなど、実装段階で判明した考慮漏れがありました
  2. 仕様通りに実装されない: 仕様として記載されているにもかかわらず、AIが異なる実装をするケース。例えば、既存の認証方式と異なるパターンで実装したり、既存のアーキテクチャパターンに従わない実装が生成されることがありました

前者は要件定義やアプリケーション設計の精度を上げていく必要があります。今回検証だったので細部まで確認できていないところがありました。そのため、実業務に導入した場合はよりこの部分に時間を使うべきだと思いました。 後者はモデルの進化を待ちつつ、コンテキストの渡し方の工夫や、実装が仕様に準拠しているかを監査するサブエージェントの整備など、ガードレールを張っていくことが必要だと感じました。

コンテキスト管理の課題

AIツール固有の課題として、コンテキスト管理の難しさがあります。実装フェーズではコードの読み書きが多く発生するため、auto-compact(コンテキストの自動圧縮)が頻発しました。その結果、audit.mdへの書き込みが不安定になったり、要件定義ファイルへの指摘を繰り返してもアウトプットに反映されないことがありました。

対策として、コンテキストの使用量を抑えるためにルールファイルを分割して必要なタイミングでのみ読み込む方式にしたり、サブエージェントを活用して処理を分散させるなどの工夫が必要です。

レビュー負荷への対応

AIのアウトプット量が増えることで、人間のレビュー負荷が増大するという課題があります。この課題に対しては、以下のアプローチを検討しています。

  • レビューを軽減するプロセスの構築: 自動テストやLintの活用
  • AIの出力品質を上げる工夫: プロンプトの改善、ルールの整備
  • 段階的なレビュー: 各ステージでの承認による分散

これらの最適解は、チームやプロダクトによって異なるため、継続的に改善していく必要があります。

最後に

AI-DLCは、AIを活用した開発における「ボトルネックを特定し、解消していく」ためのフレームワークとして有望だと感じています。

今回見えてきた課題はAI-DLCのフレームワーク自体の問題ではなく、AIと人間が協働する上で必然的に発生する問題です。今後も継続的に活用しながら、チームに最適な形にカスタマイズしていきたいと考えています。

iOSアプリにアニメーションツールのRiveを導入しようとして止めた話

Image

はじめに

こんにちは。開発部でiOSエンジニアをしている野口です。

ヘルシカiOSアプリの開発を担当しており、アプリ内にはすでに「ヘルシカ」をはじめとしたキャラクターが実装済みです。

これらのキャラクターを生かしてよりユーザーに愛着を持っていただけるようにするため、アニメーションを導入したいと考えています。

Riveとは

Riveとは、Webやアプリ、ゲーム向けの「インタラクティブなアニメーション」を作成・実装するためのデザインツールです。

Riveについて調べたところ、アニメーションの作成コストが低く、パフォーマンスがいいという記事を何記事か拝見し、導入を検討しました。

Riveの導入を断念した理由

Zombie Objectの発生

Riveの検証の際に、Zombieが発生していることがわかりました。(画像はDebug Memory Graphを使っています)

Zombieとは、通常であれば削除されるはずの不要なオブジェクトが、何らかの理由でメモリに残り続けてしまう状態のことです。

Image

つまり、プログラムのどこからも参照されていないにもかかわらず、メモリを占有し続けている状態です。この状態でアプリを使うと、思わぬクラッシュが発生するリスクがあります。

なお、XcodeのデバッグビルドでZombie Objectsをオンにしていたところ、アプリがクラッシュしてしまい、Zombieが発生していることが判明しました。

Zombie Objectsを有効にするには、XcodeでプロジェクトのScheme設定から[Product] > [Scheme] > [Edit Scheme]を選択し、[Run] > [Diagnostics]タブ内の"Enable Zombie Objects"にチェックを入れます。

Image

Duolingoをはじめ他の企業が使用しているため、実際はこのZombieはそこまで問題ないのかもしれませんが、どうしてもRiveでないとできないことがある場合でない限りは使わないほうが良いと判断し、今回は導入をやめました。

Riveのissueを見るとクラッシュの報告はいくつか上がっており、まだ不安定なパッケージなのかもしれません。

代替アニメーション形式の検討

Riveの導入を断念したため、代替となるアニメーション形式を検討しました。

Riveでのアニメーションは断念しましたが、Riveでのアニメーション作成はAfter Effectsを使う場合と比べてデザイナーの学習コストが低いため、アニメーションの作成自体はRiveで行います。その上で、iOSアプリへの組み込み方法として以下の形式を検討しました。

GIF

  • CPU: 低
  • メモリ: 多
  • 懸念点: 24fpsで実験したところカクつきを感じるが、フレーム数を増やすとパフォーマンスに影響が出る可能性あり

Image

APNG

  • CPU: ほぼ0
  • メモリ: 多
  • 懸念点: 透過する場合はパフォーマンスが悪くなる可能性があるらしい(要調査)、Riveから直接出力できず、変換用のアプリケーション(After Effects)を使用する必要がある

Image

MP4

  • CPU: 再生時に上昇
  • メモリ: 少
  • 懸念点: Riveから直接出力できず、変換用のアプリケーション(After Effects)を使用する必要がある。また、デザイナーがAfter Effectsに慣れていないため、作成時の学習コストや工数がAPNGよりもかかるらしい

Image

以下の画像の通り、MP4は動画再生時に一瞬CPUが上がり、再生中はほぼ0になります。

Image

Rive(参考)

  • CPU: 高
  • メモリ: 中
  • 懸念点: 開発側でZombie問題あり

Image

検証結果まとめ

検証結果を以下の表にまとめました。

先ほどの項目にデザイン工数を追加しています。 Riveでアニメーションを作成した際のデザイン工数を1として、各形式のデザイン工数を比較しています。工数はデザイナーの主観です。

  • GIF: Riveから直接出力できるため、デザイン工数は1
  • APNG・MP4: Riveから直接出力できず、変換用のアプリケーションを使用する必要がある。
項目 GIF APNG MP4 Rive
CPU ほぼ0 再生時に上昇
メモリ
懸念点 24fpsでカクつきあり 透過時の性能低下(要調査) After Effects学習コスト Zombie問題
デザイン工数 1 1.2 1.3 1

結論:APNGを採用

エンジニアとデザイナーが協議した結果、APNGを採用することにしました。

APNGを選んだ理由

  • CPU負荷がほぼ0で、パフォーマンスへの影響が最小限
  • GIFと比較して滑らかなアニメーションが可能
  • Riveで作成したアニメーションを変換する必要はあるがMP4よりもわかりやすいため、デザイナーの工数は低いらしい
  • 透過が必要になった場合にも対応可能(要調査)

他形式を採用しなかった理由

GIF - 検証時点のフレームレート(24fps)では若干カクつきを感じる - 他形式と同様のパフォーマンスを出すには、CPUやメモリ使用量が増える

MP4 - デザイナーがAfter Effectsに慣れていないため、作成時の学習コストや工数がAPNGよりもかかるらしい - 透過ができない

Rive - アニメーションの動きは一番綺麗だが、他形式との差は誤差の範囲 - Zombieが発生しており、Xcodeビルドではクラッシュする(TestFlight配布では問題なし) - 「Riveでないとできない」という理由がなければ使わない方がいいと判断

終わりに

今回はRiveの導入を見送り、APNGを採用することにしました。

Riveはアニメーションの動きが綺麗で魅力的なツールです。しかし、iOS版ではZombie Objectの発生によるクラッシュリスクがあり、現時点では安定性に不安が残ります。今後のアップデートでこの問題が解消されれば、再度導入を検討したいと思います。

アニメーション形式の選定は、パフォーマンス・開発工数・デザイン工数など、さまざまな観点から総合的に判断する必要があります。この記事が同じような課題を抱えている方の参考になれば幸いです。

データ品質担保のために取り組んだこと

Image

はじめに

こんにちは!
開発1部デリッシュキッチンの蜜澤です。
現在はデリッシュリサーチという、食トレンド分析ツールの開発を行っています。
本記事では、デリッシュリサーチで提供するデータの品質担保をするために行なったことを紹介させていただきます。

データ品質担保の必要性

デリッシュリサーチは食トレンドを分析するために、ダッシュボードで様々なデータを提供しています。
データが間違っていると、誤った意思決定につながるおそれがあるため、データの正確性に細心の注意を払う必要があります。

また、提供するデータが多岐にわたるため、テーブルの依存関係が複雑になっていきつつあり、放置しておくと集計のロジックを間違えてしまう可能性や、似たようなデータで整合性が保たれない可能性があります。
実際のER図を一部抜粋すると以下のようになっており、結構複雑です。
Image

複雑な集計の中でも安心してサービスをご利用していただくために、データの品質の担保には特に注力しています。

実際に取り組んだこと

以下の2つの取り組みを行いました。

  • テーブル作成処理の単体テストの作成
  • 元となる検索ログデータの増減に対するアラート作成

1つずつ詳細を紹介させていただきます。

テーブル作成処理の単体テストの作成

前述のER図を見てわかるようにテーブル数が多く、ETLが複雑になってしまうだけではなく、各テーブル作成の処理でも複雑なことをしているため、各テーブル作成の処理ごとに単体テストを行うようにして、テーブルごとに品質を担保できるようにしました。
入力データと期待される出力データを作成し、入力データを使用して実際のテーブル作成処理を行い、出力されたデータと期待される出力データを比較し、完全に一致しているかどうかを確認します。
具体例を用いて、どのようにテストを行なったのか紹介します。
ETLの作成はdatabricksを使用しています。

処理内容

検索ログデータから指定した期間(2024-01-01~2025-12-31)のデータを抽出し、検索ワードカラムの前後のスペースを削除する。
※今回は簡単な例にしていますが、実際にはもっと複雑な処理を行なっています。

コードの実装例

実際に作成したテストのコードを簡略化したものを紹介します。

期待される出力データを作成して、データフレームに格納します。
最終的にデータフレームが一致しているかのテストを行うため、全てのカラムでソートを行なっています。

columns = ['event_date', 'user_id', 'search_word']

expected_data = [
    ('2024-01-01', 2, 'キャベツ'),
    ('2024-01-01', 5, 'キャベツ'),
    ('2024-01-01', 6, 'キャベツ'),
    ('2024-01-01', 7, 'キャベツ'),
    ('2024-01-01', 8, 'キャベツ'),
    ('2024-01-01', 9, 'キャベツ'),
    ('2024-01-01', 10, 'キャベツ'),
    ('2024-01-01', 11, 'キャベツ 豚肉'),
    ('2024-01-01', 12, 'キャベツ 豚肉'),
    ('2025-12-31', 3, 'キャベツ')
]

expected_df = pd.DataFrame(expected_data, columns=columns).sort_values(['event_date', 'user_id', 'search_word'], ascending=[True, True, True]).reset_index(drop=True)

入力データ(検索ログデータ)を作成します。
以下の項目を確認できるように作成します。

  • 2024-01-01~2025-12-31のデータのみが抽出されるか
  • 前後のスペースが削除されるか
  • 前後ではない場所にスペースが入っている場合に削除されないか
columns = ['event_date', 'user_id', 'search_word']

input_data = [
    # 期待通りの期間のデータが入るかの確認
    ('2023-12-31', 1, 'キャベツ'),
    ('2024-01-01', 2, 'キャベツ'),
    ('2025-12-31', 3, 'キャベツ'),
    ('2026-01-01', 4, 'キャベツ'),
    # スペース削除の確認
    ('2024-01-01', 5, ' キャベツ'),
    ('2024-01-01', 6, 'キャベツ '),
    ('2024-01-01', 7, ' キャベツ'),
    ('2024-01-01', 8, 'キャベツ '),
    ('2024-01-01', 9, ' キャベツ '),
    ('2024-01-01', 10, ' キャベツ '),
    ('2024-01-01', 11, 'キャベツ 豚肉'),
    ('2024-01-01', 12, ' キャベツ 豚肉 ')
]

input_df = pd.DataFrame(input_data, columns=columns)
spark_input_df = spark.createDataFrame(input_df).createOrReplaceTempView("spark_input_df")

作成した入力データを開発環境のDeltaテーブルへ書き込みます。

input_data = spark.sql((f"""
    SELECT
        event_date,
        user_id,
        search_word
    FROM
        spark_input_df
"""))

input_data\
    .write\
    .format("delta")\
    .mode("overwrite")\
    .partitionBy("event_date")\
    .save(delta_table_path/search_log)

実際のETLの処理を開発環境で実行します。
処理内容の詳細は後述します。

args = {
    "env": "dev",
    "date": "2025-12-31"
}
dbutils.notebook.run("../01_SearchLog", 0, args)

上記の処理で書き込まれたデータを読み込み、データフレームに格納します。
データフレームの完全一致比較を行うため、全てのカラムでソートしています。

output = spark.sql((f"""
    SELECT
        event_date,
        user_id,
        search_word
    FROM
        delta_table_path/search_data
    ORDER BY
        event_date,
        user_id,
        search_word
"""))

output_df = output.toPandas()

出力データと期待出力データを比較します。

def assert_output_equals_expected(output_df: pd.DataFrame, expected_df: pd.DataFrame):
    output_df = output_df.reset_index(drop=True) 
    try:
        assert_frame_equal(output_df, expected_df)
        print("データフレームが完全に一致しています。")

    except AssertionError as e:
        dbutils.notebook.exit(f"データフレームが一致しませんでした:\n{e}")

assert_output_equals_expected(output_df, expected_df)

実際に実行した01_Searchの処理の内容はこちらになります。

dbutils.widgets.text("env", "dev")
dbutils.widgets.text("date", "yyyy-MM-dd")

end_date = dbutils.widgets.get("date")
env = dbutils.widgets.get("env")

search_data = spark.sql(f""" 
    select 
        event_date,
        time,
        user_id,
        regexp_replace(search_word, '^[\\u0020\\u3000]+|[\\u0020\\u3000]+$', '') as search_word --前後の半角スペースと全角スペースを削除
    from 
        delta_table_path/search_log
    where
        event_date >= '2024-01-01'
        and event_date <= '{end_date}'
""")

search_data\
    .write\
    .format("delta")\
    .mode("overwrite")\
    .partitionBy("event_date")\
    .save(delta_table_path/search_data)

課題・改善点

1つの処理を変更したことで、他の処理にも影響が及ぶ可能性があるため、基本的には全ての処理に対してテストを実行するのですが、量が多いため、現状だと全て完了するまでに1時間半ほどかかってしまいます。
依存関係を整理して、必要最低限のもののみテストを実施するようにすれば改善できるとは思います。

元となるログデータの増減に対するアラート作成

テーブル作成処理の単体テストの作成によって、デリッシュリサーチのために作成するテーブルのデータの品質は担保されるようになりますが、元の検索ログ自体に不具合が起きた場合には対処することができません。
例えば、検索ログデータのETLが遅延して、件数が正常ではなかった場合は、今回紹介した単体テストのみでは、対処することができません。
そこで、元の検索ログデータを使用して、検索ログ数の前週比を毎日集計して、閾値を上回る増加率・減少率だった場合にアラートを出す仕組みを作成しました。
アラートはPdMの人でも触れるようにするために、redashで作成しました。
以下のようなクエリを作成して、alert_flag=1となるレコードがあった場合にアラートを出すようにしました。

アラート用のSQL

実際にはユーザー属性ごとのログ数も確認しますが、今回は全体のログ数のみを確認するクエリを紹介します。

WITH search_log AS (
  SELECT
    event_date,
    user_id,
    search_word
  FROM 
    search_log
  WHERE
    event_date >= DATE_SUB(FROM_UTC_TIMESTAMP(CURRENT_DATE(), 'Asia/Tokyo'), 8)
    AND event_date < FROM_UTC_TIMESTAMP(CURRENT_DATE(), 'Asia/Tokyo')
),
daily_count AS (
  SELECT
    event_date,
    COUNT(*) AS count
  FROM 
    search_log
  GROUP BY 
    event_date
)
growth_rate AS (
  SELECT
    event_date,
    count AS current_count,
    LAG(count, 7) OVER (ORDER BY event_date) AS prev_week_count,
    ROUND((count - LAG(count, 7) OVER (ORDER BY event_date)) * 100.0 / LAG(count, 7) OVER (ORDER BY event_date), 2) AS growth_rate
  FROM
    daily_count
)
SELECT
    event_date,
    current_count,
    prev_week_count,
    CASE
        WHEN growth_rate < -20 OR growth_rate > 20 THEN 1
        ELSE 0
    END AS alert_flag
FROM
    growth_rate

課題・改善点

現状は過去のデータを元に1年のうち年n回程度発生する増加率・減少率をalert_flagの閾値に設定していますが、このnが決め打ちになってしまうので、適切な閾値を設定するのが難しいです。
閾値を厳しくし過ぎると本当に問題が起きている時に気付けず、閾値を緩くしすぎてしまうと頻繁にアラートが鳴り信憑性がなくなってしまうので、運用していく中でちょうど良い閾値を見つけていきたいです。

まとめ

データの品質担保をするために取り組んだことについて紹介させていただきました。
課題や、レビューとテスト作成にかなりの時間がかかるといった辛い点はありますが、データの品質担保ができているため、実施してよかったと思っています!
同じような課題を持っている方の参考になれたら幸いです。
最後まで読んでいただきありがとうございました。

AI勉強会の半年を振り返って

Image

はじめに

開発本部でデリッシュキッチンアプリ課金ユーザー向けの開発を担当しているhondです!
2025年6月から社内勉強会の一つとして開催している「AIツールを活用した開発効率化勉強会」が開催から半年かつ現状の参加メンバーで一周したので、そもそもどのような勉強会だったのかやアンケートの結果からどのような成果が得られたのかについて振り返ろうと思います。

AIツールを活用した開発効率化勉強会

現在エブリーの開発部では、入社時に振り分けられる勉強会グループで開催する定期的な勉強会と勉強会のテーマに興味があるメンバーが集まって行う任意の勉強会があります。今回紹介する「AIツールを活用した開発効率化勉強会」は後者にあたり、AIに興味があるメンバー18人ほどが集まり開催しているものになります。

勉強会の目的

勉強会の目的は以下の3点として運営しました。

  • AIに関するインプット意欲を向上
  • AIツールをとりあえず試すマインドの向上
  • 実務の中で活用していく意欲向上

2025年4月に開発部とPdMにCursorが導入されました。当時はCursor以外にもClaude CodeやClineなど多様な選択肢があり、AIツールは進化が早く個人でのキャッチアップには限界がありました。
そこで勉強会では、参加メンバーの多角的な視点でいろいろなツールや活用方法を吸収できる場にしたいと考えました。
具体的には、ハンズオン形式で実際に使ってみることや、他のメンバーから便利なユースケースを共有してもらうことで、活用の促進とユースケースの増加を狙いました。
また、新しいツールの導入には一定のハードルがあるため、「とりあえず試す」ことでそのハードルを緩和することも意識しました。

実施形式

実施形式は以下のように設計しました。

  • 隔週1回で開催
  • 発表者は参加者で順番に行う
  • 事例紹介形式&ハンズオン形式
  • 導入してみたけどうまくいかなかった例、うまく導入できていない例紹介タイムを設ける
  • Cursorに限らずClaude CodeやRoo Code、ClineなどもOK

目的にある「とりあえず試す」を実現するため、勉強会ではなるべくハンズオン形式を採用するよう設計しました。
基礎的な内容よりも「〇〇をしたら△△になる」といった具体的なユースケースにフォーカスすることで、実務への応用をイメージしやすくしています。
また、うまくいった事例だけでなく、うまくいかなかった事例も積極的に共有してもらうようにしました。変化が激しく簡単に正解に辿りつけるフェーズではないと感じていたので失敗例を参加メンバーで考察することで、より深い理解に繋げることを狙っています。
Coding Agentにはそれぞれ良し悪しがあるため、ツールを絞らずそれぞれの長所や短所を学び、自分が使っているツールにどう活かせるか考えられる場にしました。

発表内容

半年間で行われた発表テーマは以下の通りです。

発表日 テーマ
6/16 MCP使い倒してコンテキストスイッチ最小限に
6/30 Cursor × iOS開発私はこうやってます
6/30 A/Bテストの実験設定をcursorに任せてみる
7/14 Claude Codeは言語化ムズイがいい感じという話
7/14 MCPサーバーを自作してみる
7/28 KiroでSpec駆動開発
7/28 Claude Codeのサブエージェント
8/25 CodeRabbitについて調べてみた
8/25 LLMで爆速論文検索
9/8 spec-kitを使ってみよう
9/8 コンテキストエンジニアリング
10/20 Amazon Bedrock AgentCoreでエージェント開発を加速させよう
11/17 いろいろあるよ!AWS MCP Servers
11/17 コンテキストエンジニアリングについて真面目に考える
12/1 Google Workspace Flows アルファ版
12/1 AIカンパニーをつくって遊んでみる
12/15 AWS Transform Custom

Kiroやspec-kitをはじめとする開発体験を向上するツールを試す発表や、普段業務で使っているClaude CodeやCodeRabbitについての深掘りなど、幅広いジャンルの発表が行われました。
Amazon Bedrock AgentCoreを実際に参加者全員で作ってみるハンズオン形式の回もとても好評でした。
発表テーマを振り返ると、改めてAIが開発に与えるインパクトの大きさを感じます。同時に、いろいろなツールが出過ぎて手探りな期間だったなとも思います。
これだけのテーマを個人でキャッチアップするのは難しいので、勉強会という形で吸収できてよかったです。

アンケート結果

勉強会の振り返りとして、参加メンバーにアンケートを実施しました。14人から回答があり、以下の3項目については5段階評価で回答してもらいました。また、いくつか自由記述を設けて今後の改善に向けた意見を募りました。
5段階評価については下記の結果が得られました。

項目 1 2 3 4 5
総合満足度 0人 0人 3人 7人 4人
AIツール活用の参考になったか 0人 0人 3人 5人 6人
開発効率化の助けになったか 0人 1人 2人 9人 2人

全体的に高評価でしたが、特に「AIツール活用の参考になったか」に関してはNPS 21.5%という高い数字を出すことができました。
自由記述でのポジティブな意見としては、「自分ではキャッチアップしきれない部分について情報をインプットすることができた」や「色々なAIツールがある中でどのようなものがあるのかを知るとっかかりになったのが良かった」といった声が上がりました。どちらも勉強会を開催するにあたって目的にしていた部分が反映できたことが確認できる意見でした。
一方で、改善点として「回を重ねるごとに難易度が上がっていき、発表ネタを作るのが大変になっていた」という意見が複数見られました。確かに、勉強会を開始した当初と比較すると新しいツールや大きな開発体験の変化を伴うことは減ってきたのでテーマ的に一歩深掘りしたものが必要になっていました。あくまで、任意の勉強会なので負担にならないようある程度コントロールできるとよかったのではないかと思っています。

まとめ

他の勉強会と比較してデファクトスタンダードが出ていなかったりとても変化が激しい期間の勉強会の運営だったので、参加メンバーが満足いくものに設計できるか不安でしたが満足度もそれぞれ目的としていた指標に対しても高い評価が得られた結果となりよかったです。
ハンズオン形式に関しては発表者教材を準備しないといけないので負荷になっていたとは思いますが、聞いた内容を確実にアウトプットする機会や勉強会中にメンバー同士で議論できる場の提供になりプラスに働いたのではないかと思っています。
今後はより各チームのメンバーのユースケースの共有を活性化することで、よりお互いに刺激し合える勉強会を設計していきたいと思います。
社内勉強会の設計や運営の参考になったら幸いです!

CursorでXCUITestの仕組みを使ったワークフローの構築でUI実装から修正までを自動化する

Image

はじめに

デリッシュキッチンのiOSアプリを開発している成田です。 デリッシュキッチンではデザイン管理にFigmaを利用し、実装時にはDev Mode MCPサーバーを活用して精度を高めています。しかし、実際にビルドして確認してみると、レイアウト崩れが生じたりで期待するUIになっておらず、手動での「スクショ撮影→Cursorへ添付→指示→確認」という反復作業が発生していました。 この課題を少しでも解決するため、XCUITestによるスクリーンショット自動取得とCursorを組み合わせたUI自己改善ワークフローを構築しました。

概要

今回自動化したのは主に以下の2つです:

  1. スクリーンショットの取得: UI実装後に、自動的にアプリをビルド、画面をナビゲーションしてスクリーンショットを取得
  2. 画像の読み込み: スクリーンショット取得後、画像を読み込んで分析し、レイアウト崩れなどを検出して実装を修正する

実現したワークフロー:

UI実装依頼
  ↓
AIがUIコードを生成
  ↓
xcodebuildでビルド・UITestの実行
  ↓
UITestが画面をナビゲーションしてスクリーンショット取得
  ↓
スクリーンショットパスを一時ファイルに保存
  ↓
画像を読み込んで分析
  ↓
必要に応じてレイアウト崩れなどを自動修正

手順

このワークフローを実現するために必要な設定や実装手順は以下の通りです。

1. XCUITestの実装

XCUITestの標準API(XCUIApplicationscreenshot()メソッド)を使用してスクリーンショットを取得します。

実装すべき内容は以下の通りです:

  1. スクリーンショット取得テスト: 画面をナビゲーションしてスクリーンショットを取得するテストメソッドを実装します。このテストメソッド内で以下の2つのヘルパーを使用します

  2. スクリーンショットヘルパー: XCUIApplicationscreenshot()メソッドを使用してスクリーンショットをファイルに保存するヘルパークラスを実装します

  3. UI要素待機ヘルパー: UI要素が表示されるまで待機するユーティリティを実装します。XCUITestには自動待機機能がありますが、ネットワーク通信や非同期処理による画面更新の遅延がある場合などには、明示的な待機処理が必要です。これにより、テストの安定性を確保できます

これらのファイルをUIテストのターゲットに追加します。

2. スクリーンショット取得スクリプトの作成

スクリーンショット取得は、xcodebuildコマンドを使ってコマンドラインからビルド・テストを実行することで実現します。

スクリプト(scripts/cursor-screenshot.sh)で実装する処理は以下の通りです:

  1. 画面名の受け取りと環境変数の設定: スクリプトは引数として画面名を受け取り、環境変数として設定します。この環境変数はUITestに渡され、どの画面にナビゲーションするかを決定します

  2. アプリのビルド: xcodebuild buildでアプリをビルドします

  3. UIテストの実行: xcodebuild testDynamicScreenshotTests.testTakeScreenshotForScreenを実行します。UITestは環境変数を参照して、画面名に応じて適切な画面にナビゲーションしてからスクリーンショットを取得します

  4. スクリーンショットファイルの検索とパスの保存: テスト実行後、最新のスクリーンショットファイルを検索し、パスを/tmp/cursor_screenshot_info.txtに保存します。このファイルを参照してスクリーンショットを読み込むようにします

3. Cursorのルール設定 - .cursor/rules/my-custom-rule.mdcにUI実装後の自動ワークフローのルールを追加

  - UI実装後のワークフロー
    - UIを新規実装または大幅に変更した後は、必ず以下を実行すること:
      - 大幅な変更の定義: - 大幅な変更の定義: レイアウト、UIコンポーネント、View構造、スタイルなど、レイアウトや見た目に影響を与える変更
      1. 当該の画面に対して`scripts/cursor-screenshot.sh`を実行
      2. 取得した画像をもとに元のデザインと比較してレイアウト崩れなどがあれば修正する
      3. 修正後、再ビルドして確認
      4. 必要に応じて、上記のステップ(1-3)を繰り返す(最大n回まで)

これによって期待するワークフローが実現できました。

おわりに

UIを実装した後は、手動でビルドして確認し、レイアウト崩れなどを発見したら修正依頼を投げるという作業を繰り返す必要がありました。 XCUITestの仕組みを使ったワークフローによってUI実装から自己改善までを自動化することができるようになったので、プロンプトで修正依頼を投げる量を最小限に留めることができるようになりました。 とはいえ、UITestは壊れやすくワークフローの途中でこけることも度々起こりうるので、その辺はデメリットかなと思います。

Image