Lookerとセマンティックレイヤーで作る会話分析の運用と評価

はじめに

こんにちは。ファインディでデータエンジニアをしている開(@hiracky16)です。

今回はLookerに搭載されている会話分析機能を使って、ユーザーがより自律的にデータ抽出や分析ができるようにした話をします。セマンティックレイヤー(Explore)を会話分析に使用する設計や開発・テスト・評価までの運用のノウハウを紹介します。

会話分析(Conversational Analytics)は、Lookerに搭載されている機能で、自然言語で質問を投げるとエージェントが裏でクエリを組み立てて実行し、表やグラフを添えて答えてくれます。たとえば「1月から5月までのXXX数をUUベースで集計できますか?」と聞くと、次のように月別の集計結果とグラフが返ってきます。

Lookerの会話分析に自然言語で質問し、月別の集計結果とグラフが返ってきている画面

会話分析にセマンティックレイヤーを使う理由

以前公開したブログでは、Lookerを導入した背景や、登録ユーザーの7割がダッシュボードの作成・閲覧のために日々訪れていることを紹介しました。

tech.findy.co.jp

tech.findy.co.jp

これらの記事で触れたとおり、重要な指標や共通のディメンションは、セマンティックレイヤーへの集約を進めてきました。

集約した指標やディメンションを使ってダッシュボードを作り社内に展開していますが、「別の切り口で見たい」「集計ロジックを変えたい」といった要望に都度対応する運用となり、その工数が無視できなくなってきました。

一方で、会話ベースでデータ抽出・集計・分析ができるエージェントが世の中に広がってきました。私たちもGoogle製のADKを使って自作してみましたが、こちらもメンテナンスコストがかかります。さらに、全テーブルを対象にするため、SSoT(Single Source of Truth)が整っていない領域や、コンテキストが薄い領域では精度が出ず実用には届きませんでした。

ここで着目したのが、すでに集約を進めてきたセマンティックレイヤー(LookerのExplore)です。これを会話分析エージェントの入口に据えることで、ダッシュボード運用の工数と、自作エージェントのコンテキスト不足という両方の課題を解決できると考えました。全体像は次のとおりです。

flowchart LR
    subgraph DWH["データウェアハウス"]
        direction TB
        NOTE1[/"会社マスタが複数あり<br/>テーブル単位ではSSoTが曖昧"/]
        T1[fct_scouts<br/>送信済みスカウト]
        T2[fct_scout_replies<br/>スカウトへの返信]
        T3[fct_matches<br/>成立したマッチング]
        T4[dim_users<br/>ユーザー情報]
        C1[dim_companies_raw<br/>会社情報(raw)]
        C2[dim_company_master<br/>会社情報(master)]
    end

    subgraph SL["セマンティックレイヤー"]
        direction TB
        NOTE2[/"業務単位で集約し<br/>使う会社マスタもExploreで判断"/]
        E1["スカウトExplore<br/>送信数・返信率を定義"]
        E2["マッチングExplore<br/>成立件数・成立率を定義"]
    end

    T1 & T2 & T4 --> E1
    T3 & T4 --> E2
    C1 -->|スカウトはrawを使う| E1
    C2 -->|マッチングはmasterを使う| E2

    E1 --> A1[スカウト分析エージェント]
    E2 --> A2[マッチング分析エージェント]
    A1 & A2 --> U([利用者の質問])

左側のデータウェアハウスには大量のテーブルが並び、どれを使えばよいか、指標をどう計算すればよいかはテーブル単位では自明ではありません。たとえば会社マスタがdim_companies_rawdim_company_masterのように複数存在し、用途によってどちらを使うべきかが曖昧、といったこともあります。これをそのままエージェントに渡しても、うまく答えられないのは当然です。

そこで、Explore側で「スカウト」「マッチング」といった業務の単位にテーブルを集約し、送信数や成立率といった指標の定義まで含めて整えておきます。会社マスタのように複数の候補があるものも、Explore側でどちらを参照すべきかを判断します。たとえばスカウトExploreではdim_companies_rawを、マッチングExploreではdim_company_masterを使う、といった使い分けをExploreの定義として固定しておくわけです。エージェントは生のテーブルではなくこのExploreだけを見るので、何をどう集計すべきかが定まった状態で質問に答えられます。

セマンティックレイヤーを会話分析の入口にするメリット

セマンティックレイヤーを入口に据えると、大きく2つのメリットがあります。

参照先のデータを意味ある単位に限定できる

1つのエージェントは1〜5個のExploreに紐づきます。エージェントが触れられるデータがExploreの範囲に限定されるためエージェントの役割がはっきりします。

また、SSoTが担保できていないテーブルであっても、Lookerから参照する形にすればExplore上でSSoTを表現できます。生のテーブルをそのまま渡すのではなく、Lookerというフィルタを通すことで、エージェントが扱うデータの意味を整えられます。ダッシュボードと会話分析で使用するExploreは同じなので、両者で数値が食い違うという事態も避けられます。

コンテキストの肥大化を避けられる

全テーブルをエージェントに渡そうとすると、コンテキストが膨れ上がり、かえって精度が落ちます。Exploreを入口にすれば、LookMLのdescriptionや指標定義がそのまま「正しい意味」のソースになり、必要な範囲のコンテキストだけを渡せます。

LookerはKnowledge Catalog(旧Dataplex)との連携も進んでおり、メタデータをカタログ側で一元管理できるようになってきています。

docs.cloud.google.com

今はLookMLのdescriptionにコンテキストを持たせていますが、今後はKnowledge Catalog側にも用語や定義を蓄積し、それをエージェントのコンテキストとして活かせるようになることを期待しています。

デメリット

一方で、当然ながら弱点もあります。複数のExploreをまたぐ横断分析やファネル分析は苦手です。たとえば、ファインディが主催するイベントへの参加やメディア閲覧といったイベントを起点に、実際に企業とのマッチングに至るまでの流れを追う、といった分析は守備範囲の外でした。それぞれのイベントが別々のExploreに分かれていると、エージェントはどちらか一方しか見られず、つながりを追えません。

そこで、ユーザーという意味のある単位でフェーズをまたいだデータを1行にまとめた累積ファクトを作り、Exploreとして用意することで対処しました。次のように、1行=1ユーザーで各フェーズの日付を横並びに持たせておきます。

user_key イベント参加日 メディア閲覧日 マッチング日
u_001 2026-04-01 2026-04-03 2026-04-20
u_002 2026-04-05 (なし) (なし)
u_003 2026-04-10 2026-04-12 (なし)

こうしておけば、1つのExploreの中で「イベント参加からマッチングまで到達した割合は?」といったファネルをたどれます。横断的な問いに答える必要があるものは、あらかじめこの形に整形しておき、エージェントからも参照できるようにしています。

運用

LookMLの開発方法は前述のブログで紹介しているので割愛し、今回はLooker会話分析に使うエージェントの開発・運用について説明します。

エージェント定義をGitで管理する

会話分析のエージェントは、Lookerの画面上からでも作成・編集できます。ただ、画面で直接編集すると、誰がいつ何を変えたのかが追えず、システムプロンプト(instructions)の変更履歴も残りません。

そこで、エージェントに必要な項目をYAMLファイルとして定義し、Gitで管理することにしました。1ファイルにエージェントのID、名前、参照するExplore(sources)、システムプロンプト(instructions)、そして後述する評価ケース(cases)をまとめています。

id: hogefugapiyo   # 既存エージェントの更新用。新規作成時は省略
name: マッチング分析エージェント
description: 企業と求職者のマッチング状況を分析するエージェントです。
code_interpreter: false

# このエージェントが参照するExplore
sources:
  - model: sample_recruiting
    explore: fct_matchings
  - model: sample_recruiting
    explore: fct_daily_snapshot_matchings

# システムプロンプト
instructions: |
  あなたはマッチング分析の専門アシスタントです。
  LookerのExploreを使って、マッチングの成立件数・成立率・
  セグメント別トレンドに関する質問に回答します。

  ## 用語集
  - マッチング: 企業と求職者の間で選考が始まった状態
  - 成立率: マッチング成立件数 ÷ いいね件数
  ...

# 評価ケース(評価の章で後述)
cases:
  - prompt: "今月のセグメント別のマッチング成立率は?"
    reference: "セグメントでグループ化し、成立件数をいいね件数で割った値を回答する"

ExploreやYAMLが編集されると、GitHub ActionsでLookerのエージェントに反映されます。

回答品質の評価

エージェントを運用に乗せると、「ちゃんと正しく答えられているか」が気になります。システムプロンプトやExploreを変更するたびに、回答の質が上がったのか下がったのかを判断したくなります。

そこで、エージェント定義に評価ケースを書いておき、CIで自動評価する仕組みを用意しました。

評価ケースを定義する

評価ケースは、前述したエージェント定義のYAMLに質問(prompt)と期待する答え方(reference)の組として書いておきます。たとえば次のように、想定される質問のバリエーションを並べておきます。

cases:
  - prompt: "今月のマッチング成立件数は?"
    reference: "当月のマッチング成立件数の合計を回答する"

  - prompt: "セグメント別のマッチング成立率を高い順に教えて"
    reference: "セグメントでグループ化し、成立件数をいいね件数で割った成立率を降順で回答する"

  - prompt: "先月と今月でマッチング成立件数はどう変わった?"
    reference: "先月と当月の成立件数を比較し、増減と差分を回答する"

referenceには「完全一致の正解テキスト」ではなく、答えの作り方の説明を書いています。会話分析の回答は表現に幅があるため、文章の一致ではなく方向性で評価したいからです。

この評価ケースを使って、次の2軸で品質を測ります。

軸1: 回答テキストの品質をLLMで採点する

1つ目は、エージェントが返した回答テキストそのものの質です。これは Agent Platform(Vertex AI)の評価サービス を使い、LLM-as-a-Judge、つまり別のLLMに採点役をやってもらう形で測っています。

採点は4つの観点で行われます。

  • 質問の指示どおりに答えているか(Instruction following)
  • 与えられたコンテキストに基づいているか(Groundedness)
  • 十分な情報量で答えているか(Completeness)
  • 読みやすく整理されているか(Fluent)

これらをまとめたスコアを段階評価で受け取り、品質が落ちていないかを確認します。

軸2: 発行されたクエリを決定論的に検証する

LLMによる採点は揺れるため、それだけに頼るのは不安が残ります。そこでエージェントが実際に発行したクエリの中身を機械的に検証しています。

会話分析エージェントは、質問を受けると裏でLookerのクエリを組み立てて実行します。このクエリが「どのExploreを使い、どのフィールドを選び、どんなフィルタをかけたか」を期待値と照合して判定します。回答テキストの良し悪しではなく、構造として正しいクエリを組めているかを、ぶれのない形で担保します。

期待値は、評価ケースにassertionsとして書き足しておきます。

cases:
  - prompt: "今月のセグメント別のマッチング成立率は?"
    reference: "セグメントでグループ化し、成立件数をいいね件数で割った成立率を回答する"
    assertions:
      explore: fct_matchings          # このExploreを使っているか
      fields_include:                 # これらのフィールドが含まれているか
        - segment
        - matching_rate
      filters_include:                # このフィルタがかかっているか
        - field: matched_month
          value: this month

これらの評価はGitHub Actionsで自動実行され、結果はPRにコメントされます。QA Qualityの平均スコアやケースごとの内訳、アサーションの判定がひと目で分かるので、変更が品質に与えた影響をレビュー時に確認できます。

GitHub ActionsがPRにコメントしたLooker CA回答品質の評価結果。QA Quality平均スコアとアサーションの判定、ケースごとの内訳が表示されている

フィードバックサイクルを回す

評価ケースは社内メンバーから寄せられるフィードバックをもとに育てています。「この聞き方だと答えてくれない」「この数字が期待とずれている」といった、実際に使う人がぶつかった声ほど価値のあるテストケースになります。

寄せられた声は評価ケースに加えるだけでなく、エージェントに足りないコンテキストの手がかりとして、内容に応じてシステムプロンプト・LookML・BigQueryテーブルのどこに手を入れるかを判断します。こうしてフィードバックを起点にしたサイクルが回っています。

flowchart LR
    FB([社内メンバーのFB]) --> C[評価ケースに追加]
    C --> FIX{どこを直す?}
    FIX -->|答え方| P[システムプロンプト調整]
    FIX -->|定義不足| D[LookML description補強]
    FIX -->|データ構造の問題| M[dbtデータモデリング見直し]
    P & D & M --> EVAL[評価を実行]
    EVAL --> SCORE[スコアを比較]
    SCORE -.改善を確認.-> FB

成果

前回のブログで、Lookerの登録ユーザーの7割が週次でログインしていることをお伝えしました。いまではそのうちの半分が、会話分析を通じて自律的にデータ抽出や分析を行っています。

データチームへの影響も大きく、月に十数件あったデータ抽出の依頼はほぼなくなりました。代わりに届くのは、会話分析だけでは解けないより高度な分析を要する相談が中心です。単純な抽出作業から解放されて本来注力すべき仕事に時間を割けるようになりました。

利用者からのフィードバックも増えてきました。「便利になった」という好印象の声もあれば、期待した結果が得られなかったという指摘もあります。後者はチームにとって学びの多い情報で、どこにコンテキストが足りていないのかが明確になります。

このフィードバックサイクルが回り始めたことで、エージェントのinstructionsやLookMLの修正、データモデリングの見直しがこれまで以上に進むようになりました。前述したSSoTについても、Exploreで吸収するだけでなく、テーブルレベルで担保できるよう改善を進めています。

まとめ

本記事では、セマンティックレイヤー(LookerのExplore)を会話分析の入口に据える設計と、Git管理・2軸評価・改善サイクルまでの運用を紹介しました。この設計が効くのは、すでにLookMLで指標やディメンションの定義が進んでいる組織です。土台があるからこそすぐに始められ、成果につなげることができたと思います。

運用で意識しているのは、クイックウィンの積み重ねです。Lookerや生成AIは安価ではないため、小さな成功体験を積み上げ、利用者に定着してもらうことが重要です。そのためにはフィードバックを集める仕組みが重要で、社内勉強会で使い方を共有したり、要望を気軽に投げてもらえるチャンネルを用意したりしています。会話分析を実現させるためには技術的な仕組みと、声が集まる仕組みの両輪が必要だと感じています。

ファインディでは、データ基盤やデータ活用の仕組みづくりに一緒に取り組んでくれる仲間を募集しています。少しでも興味を持っていただけたら、まずはカジュアルにお話ししましょう。

herp.careers

コミットからPR作成までのリードタイムを26%短縮 AI活用の「定着 × 成果」の測り方

こんにちは。ファインディ株式会社でアプリケーションエンジニアをしている西村です。

ファインディの開発組織ではここ1年ほど、Claude Codeを使った開発プロセスのSkill化を進めてきました。Issue生成やセルフレビュー、タスク分解といった作業をSkillにして、社内のClaude Code Pluginに追加するのが日常になっています。

ただ、便利なSkillを揃えて配っただけでは、それが開発フローの中でどれだけ使われ、成果につながっているかまではわかりません。

そこで今回は、開発組織内で配布したPluginやSkillの利用データをどう集め、どう見て、どう改善に回しているか、その考え方と運用を紹介します。

作成したSkillが使われ続けるかを見る

開発プロセスのSkill化については、これまでのテックブログでも何度か紹介してきました。

繰り返す作業があればSkillとして作成し、社内のClaude Code Pluginへ追加する流れは今も続いています。

一方で、配ったSkillが日々の開発に定着するかどうかは別の話です。配布した当初は新しい取り組みに前向きなメンバーが使ってくれますが、その後も使い続けるとは限りません。Skillの数と種類は充実しているのに、実際に利用されているのは一部だけ、という状況が生まれます。

「作った」で満足せず、配ったあとも「使われているか」を継続的に見ていく必要があります。

定着と成果を組み合わせて評価する

ファインディでは、Claude Codeの活用度や作成したSkillの利用状況を計測して可視化しています。集めたデータから、開発工程のどこでどのSkillが効いているかを見て、改善しつづけています。

データを見てみると、よく使われていても成果につながっていないSkillもあれば、利用回数は少なくても効きどころで使われて大きな効果を出しているSkillもあります。つまり、Skillの利用回数だけを成果とみなすと、この違いを見落とすことがわかりました。

そこで私たちは、Skillが定着しているかを測る指標と、成果への貢献を測る指標を組み合わせて評価することにしました。

定着を測る指標は、一定期間内にSkillがどれくらいの頻度で使われているか、つまり利用頻度です。

成果への貢献を測る指標は、Skillを使い始めた後にPRの作成からマージまでの時間といったリードタイム等が短縮したかです。

利用データから使われ方そのものを変える

ファインディでは2026年3月に、セルフレビューSkillを導入しました(導入の経緯はこちらの記事で紹介しています)。ただ作成するだけでなく、ドッグフーディングして、AgentTeamに対応させたり、テストのカバレッジが十分かを見る観点を加えたりと、Skillの中身を改善してきました。

セルフレビューSkillは、変更したコードのバグを見つけたりテストの網羅性を高めたりする効果があり、組織全体での定着率を上げていきたいSkillです。

ただ、利用データを見て気づいたのは、改善すべきは中身だけではないということでした。導入直後の3月時点で月298回と一定の利用はあったものの、セルフレビューSkillを呼ぶかどうかは各メンバーの判断に委ねていました。そのため、エンジニア全員の開発フローに浸透するほどには使われていませんでした。

2026年3月時点のSkillの利用回数(左:アウトプット上位5名、右:組織全体96名)です。セルフレビューSkillの呼び出しは、アウトプット上位で117回、組織全体でも298回でした。

アウトプット上位 組織全体
2026年3月のアウトプット上位5名によるSkill呼び出し回数の一覧 2026年3月の組織全体によるSkill呼び出し回数の一覧

そこで、Skillの使われ方そのものを変えました。SlackのPluginリリース告知チャンネルで使い方を周知し、あわせてPR作成Skillの中からセルフレビューSkillを呼び出すようにして、PRを作るときには必ずセルフレビューSkillを使用する環境に変えました。

その結果、セルフレビューSkillの呼び出しは、アウトプット上位で185回、組織全体で479回まで増えました。セルフレビューSkillが組織全体の標準的な動作として定着したことがわかります。

アウトプット上位 組織全体
2026年5月のアウトプット上位5名によるSkill呼び出し回数の一覧 2026年5月の組織全体によるSkill呼び出し回数の一覧

さらに、導入前後でリードタイムを比べると、コミットからオープンまでの平均時間は22.2時間から16.3時間に縮みました。AIが書いたコードを人間がセルフレビューしていた手間を減らせたことが、短縮の一因だと考えています。

コミットからオープンまでの平均時間を比較したFindy Team+の画面

定着と成果のデータをFindy AI+で可視化する

これまで紹介してきたSkillの呼び出し回数データは、Findy AI+の分析機能を使っています。Findy AI+ではClaude CodeのMonitoring機能を活用し、Skillやコマンドの実行ログを収集して、「どのSkillがいつ実行されたか」を一元的に追えるようにしています。

集めたログからは、Skillごとの呼び出し回数や、それが一部のメンバーに偏っているか組織全体に広がっているかといった定着の度合いが見えてきます。ここにTeam+のPR作成数やレビューのリードタイムといった成果指標を重ねると、定着と成果のデータが揃い、「どのSkillで、どの指標に効いているのか」を分析できます。

まとめ

PluginやSkillは、配って終わりにすると少しずつ使われなくなっていきます。配ったあとも「使われているか」さらに「アウトプットの増加に貢献できているか」まで見て初めて、改善の打ち手が定まります。

もしすでにPluginやSkillを配っている方がいれば、まずは「どのSkillが、誰に、どれだけ使われているか」を1つでも数字で見えるようにするところから始めてみてください。配ったあとのデータがそろうと、次にどこへ手を入れるべきかが自然と見えてきます。

Findy AI+の分析機能については、Findy AI+の紹介ページもあわせてご覧ください。

ファインディでは一緒に会社を盛り上げてくれるメンバーを募集中です。興味を持っていただいた方はこちらのページからご応募お願いします。

herp.careers

Lambda PDF生成を27倍高速化した話 — Puppeteerから@react-pdf/rendererへの移行レポート

こんにちは。Findy Freelance開発チームの久木田です。 今回は、社内で運用している支払明細書PDFの生成基盤を、Lambda + Puppeteerから@react-pdf/rendererへ全面的に移行した話を書きます。最終的に処理時間はP50(中央値)で約27倍速くなり、メモリ消費も実測で約1/4まで落とせました。

これまでのPDF生成基盤と課題

現在、システムから発行しているPDFはいくつかありますが、本記事では一例として支払明細書PDFに絞って紹介します。ファインディからフリーランスエンジニアへの支払明細として月次で一括発行しているPDFです。

支払明細書PDFは1件あたり1ページで、支払先、支払者、明細表、特記事項、ファインディの社印を配置した構成です。情報量としては小規模ですが、月次でフリーランスエンジニア全員分を一括発行する必要があり、1回のバッチで数百件規模のPDFを並列生成していました。Rails側からParallelライブラリで並列にLambdaを呼び出す構成です。

このPDFは、AWS Lambda上でPuppeteerを起動し、EJSテンプレートから組み上げたHTMLをヘッドレスのChromiumでレンダリングしてPDF化する構成で動いていました。フロントエンド向けのHTML/CSSをそのまま流用できるため初期実装は速かったのですが、運用が進むにつれ次の課題が顕在化しました。

  • コールドスタートが重く、Chromiumバイナリの起動だけで5秒前後を消費していた
  • 大量生成で /tmp が枯渇し、一括処理で複数のレンダリングを並走させると、エフェメラルストレージが先に尽きてエラー終了する
  • タイムアウトやエラーが恒常化し、数百件規模の一括出力は途中で停止して再実行が必要になる

ボトルネックはChromiumの不安定さでした。Chromium起動のたびにプロファイル・キャッシュ・ソケットファイルが /tmp 配下に生成され、browser.close() 後もディスク上に残存します。さらに、レンダリング中にChromiumがハングした場合は、一時ファイルを保持したままプロセスが停止します。その間にも新規リクエストでLambdaが起動するため、ウォームスタートで使い回されるインスタンスでは /tmp の残存ファイルが蓄積し、一定量を超えた時点で全体が停止してしまう挙動になっていました。

この蓄積に備えて、メモリと /tmp は常にバッファを含めて多めに割り当てる必要があり、1Lambdaあたり2-3GB相当の確保を継続していました。それでもハングと並列度のピークが重なる局面では詰まるため、設定値を引き上げては別の閾値で詰まるという状態が続いていました。

一括出力は今後数倍に増加する見込みであり、現状の構成のまま継続することは現実的ではありませんでした。

対症療法でしのいだ期間

課題を踏まえ、徐々に対策を施すことにしました。Lambdaのメモリ割り当て増、タイムアウト延長、エフェメラルストレージの拡張、並列度の調整など、設定値で吸収できそうな対策は一通りしました。短期的な改善にはなったものの、根本にあるのはChromiumをサーバーレス環境で動かすこと自体のコストと描画コストが入力規模に比例して伸びる構造です。設定の積み増しではレイテンシーもエラー率も思うように改善しませんでした。

根本対応を決めた3つの背景

根本対応に踏み切る判断は、次の3点が同時に揃った時点で下しました。

  1. 現状の課題: レイテンシーが悪化傾向、エラーも日次で観測される
  2. 将来の悪化見込み: 一括処理の件数は今後さらに増える計画があり、対症療法の余地がもう残っていない
  3. 対症療法の限界: 設定値の調整ではレイテンシーやエラー率の改善が頭打ちで、いずれの打ち手も効果が薄れてきた

課題が大きくなったため、対策を施しました。逆に言えば、どれか1つでも欠けていたら、もう少し対症療法を続けていたと思います。

技術選定: 戻れる順に試す

根本対応の方針として、大きく2案を比較しました。

  • A案: Lambdaを維持し、@react-pdf/rendererでProgrammaticにPDFを組み立てる
  • B案: LambdaからECSなど別のランタイムへ移行する

A案は、いまのLambda構成を保ったままPDF生成方式だけを差し替える案です。@react-pdf/rendererはJSXを書く感覚でPDFを組み立てるライブラリで、Chromiumのヘッドレスブラウザは利用しません。

react-pdf.org

そもそも@react-pdf/rendererが候補に挙がったのは、ヘッドレスブラウザ以外でPDFを生成する方法を調べていたからです。継続的な利用料金が発生する外部サービスを使う選択肢は外し、OSSのプログラマティック生成ライブラリの中で、Reactと同じJSXで書ける@react-pdf/rendererを選びました。Reactはファインディの他プロダクトでも広く使われており、馴染みがあった点も決め手になりました。

B案はメモリやストレージの制約には強くなりますが、コスト構造が変わり、検証の立ち上げにも工数がかかります。Lambdaのタイムアウトに収まらない処理や、Chromiumの表現力(複雑なCSS・JavaScript描画など)をどうしても残したいケースではB案が候補ですが、今回のPDF生成はそのどちらにも当てはまりませんでした。 そのため、最終的には次の3つの理由からA案を選びました。

  • 戻れる: Lambda構成のままPDF生成方式だけを差し替えるので、問題が出てもPuppeteer版に戻すことが容易
  • テスト可能: PDF生成ロジックがJSXで書けるため、単体テストや出力差分テストを書きやすい
  • AIで移行コストが現実的になった: EJSテンプレートからJSXへの変換は、生成AIに任せられるレベルまで来ていた

B案はA案が失敗した場合の代替案として残し、まずは戻れるA案を試すことにしました。

移行で押さえておきたい実装ポイント

EJSからJSXへの書き換え自体は生成AIで一気に進められましたが、@react-pdf/rendererの実装スタイルに合わせるために事前に押さえておきたい点がいくつかありました。

  • @react-pdf/renderer v4はESM-onlyで、tscのCommonJS出力からは読み込めないため、esbuildを入れてESMをCJSにバンドルした
  • 日本語フォントはFont.register()で明示的に登録しないと文字化けする
  • Puppeteerのscale: 0.8相当が無いため、フォントサイズや余白を手で再計算した
  • HTMLの<a>自動展開は再現されず、URL部分だけ<Link>化する小さなコンポーネントを自作した
  • HTMLエスケープは自動化されていて、旧実装のエスケープ処理が不要になった(副産物)

特に大変だったのがJSXの空文字列の扱いです。{stringValue && (...)}と書くと、空文字列がchildとしてそのまま流れ込み、WARN Invalid '' string child outside <Text> componentが大量に出ます。Reactの文法としては正しいのですが、@react-pdf/renderer<View> / <Page>配下では{!!stringValue && (...)}と明示的にboolean化する書き方に揃える必要があります。さまざまなデータでPDFを作成していく過程で警告ログが出ていることに気付き、該当箇所をまとめて修正しました。

設計と実装のキモ

ここからは、@react-pdf/rendererをLambdaに載せていくときに考えた設計面のポイントを2つに分けてまとめます。

テンプレート構造

PDFそのものを1つのReactコンポーネントとして組み立てる構成にしました。リンクの自動展開のように共通で必要な要素は、専用の小さなコンポーネントとして切り出して再利用しています。

EJS時代は部分テンプレートをincludeで組み合わせる作りでしたが、JSXに移ってからはコンポーネントの組み合わせとして自然に再構成できました。

esbuildで橋渡し

@react-pdf/renderer v4はESM-onlyですが、Lambda側はCommonJSで動かしています。tscの出力では直接読み込めなかったため、esbuildでESM→CJSのバンドルを作ってLambdaにデプロイする構成にしました。

// esbuild.config.js(抜粋)
{
  entryPoints: ['src/index.ts'],
  bundle: true,
  platform: 'node',
  format: 'cjs',
}

設定としてはシンプルですが、ここを通さないと依存関係の解決でつまずくことになるため注意が必要です。

どう変わったか

支払明細書PDFの一括作成処理について、移行前後をCloudWatchメトリクスで比較した実測値が次の通りです。表のP50 / P95 / P99は、実行時間を昇順に並べたときの中央値 / 95パーセンタイル / 99パーセンタイルを表します。

指標 Before After 倍率
実行時間 P50 約3,963 ms 約145 ms 約27倍高速
実行時間 P95 約4,707 ms 約212 ms 約22倍高速
実行時間 P99 約5,249 ms 約458 ms 約11倍高速
平均メモリ 約912 MB 約222 MB 約1/4
最大メモリ 約1,589 MB 約239 MB 約1/7

特に改善幅が大きいのはP50です。旧構成では実行時間そのものの遅さに加え、Puppeteer/Chromium由来のエラー(ブラウザの接続切れやハングなど)が起きると、一括処理の中で個別のPDF生成がLambdaのタイムアウトに到達し、リトライしても最後まで完成せずエラーとして残るケースがありました。表のP99の大きさにそれが現れています。移行後はこれらのエラー、エフェメラルストレージの逼迫、コールドスタートによる遅延がいずれも解消され、Lambda上での実行を意識する必要のない構成になっています。

学び

今回の移行で特に有効だったのは、判断軸として置いた「戻れる順に試す」です。Lambda構成を維持したままPDF生成方式だけを差し替えるA案は、もし行き詰まっても旧版のLambdaに切り戻す選択肢を残せました。ランタイムごと載せ替えるB案を最初に選んでいたら、検証のために抱えるリスクははるかに大きくなっていたはずです。

もうひとつは、生成AIの活用で技術選定の前提条件が変わったことです。A案はEJSテンプレートのJSXへの全件書き換えを伴います。AIなしで工数を見積もると、規模だけでA案は採用候補から外れていました。書き換えだけでなく、旧PDFと新PDFのレイアウト差分の特定と修正案の生成までAIに任せられたため、A案の工数は当初の想定より低く収まりました。

最後に、@react-pdf/rendererを使った所感をまとめます。

メリットとして大きかったのは、Lambdaの割り当てメモリを大幅に減らせたことと、ヘッドレスブラウザを使っていたころよりテストが格段に書きやすくなったことです。PDFをバッファのまま受け取って中身を検証できるので、ブラウザを起動しない軽量な統合テストをCI上でも組めるようになりました。

一方で、HTML/CSSではなく<View> <Text>といったPDF専用のプリミティブをFlexboxで組み立てる、React Native寄りのコンポーネントモデルです。HTML/CSSしか経験が無い場合は、最初は書き方に戸惑う場面もあると思います。


ファインディでは一緒に会社を盛り上げてくれるメンバーを募集中です。興味を持っていただいた方はこちらのページからご応募お願いします。

herp.careers

Findy EventsのUIライブラリ選定 ― TamaguiからHeroUI Nativeへ乗り換えた理由と導入プロセス

こんにちは。Findyでモバイルアプリ開発を担当している加藤と主計です。

Findy初のモバイルアプリ「Findy Events」については、先日React Native選定の経緯と立ち上げの全体像を公開しました。

前記事ではUIライブラリ周りには深く踏み込めなかったので、今回はその続編です。UIライブラリの選定と実装パターンに絞ってお届けします。

具体的には、当初採用していたTamaguiからHeroUI Native + react-native-unistylesに乗り換えるまでの判断と、Wrapper Componentを軸にしたUI構築の進め方が中心です。

なお、本記事で題材にしているFindy Eventsは、App StoreGoogle Playで公開しています。

Tamagui採用の背景

Findyのモバイルアプリ開発で、当初UIライブラリとして採用していたのはTamagui

選定時の候補はgluestack-uiとTamaguiの2つで、最終的にTamaguiを選びました。

決め手となった理由は大きく3つあります。

1つ目は、ライブラリとしての信頼性と情報量です。

Tamaguiはv0.1.0が2021年3月にリリースされ、5年以上にわたって開発が続いているOSSです。

GitHubのスター数や提供されるComponent数の充実度から、本番プロダクトに採用しても大きく外さないと判断しました。歴史がある分、ネット上の記事も比較的多く、開発時の調査コストを抑えやすい点も後押し材料です。

2つ目は、iOSエンジニアとしての開発経験との親和性です。

iOS開発ではOSSとして提供されたUIをそのまま利用する、あるいは許容範囲で改変するアプローチが一般的で、Tamaguiはこのメンタルモデルに合致していました。

SwiftUIと同じ宣言的UIの思想を持ち、縦方向に並べるSwiftUIのVStackがTamaguiのYStackに対応するように、子要素を軸方向に積み上げてレイアウトを組み立てる考え方が共通している点も馴染みやすいポイントです。

一方のgluestack-uiは、shadcn/uiと同様にCLIでComponentのコードを自分のリポジトリに生成し、必要に応じて編集して使う方式を採っています。Tailwindライクなクラス指定と合わせて、モバイルエンジニア出身の自分にはやや取っ付きにくく感じました。

3つ目は、Propsベースで直感的にスタイリングできることです。

<Stack m="$2" p="$2" backgroundColor="$background">のように、Componentに対してProps経由でデザイントークンを直接当てられ、感覚としては昔ながらのCSSやSwiftUIのmodifierに近い書き味で扱えます。

デザイントークンによるテーマ管理でライト/ダークテーマの切り替えが容易な点も決め手になりました。

Tamaguiを使ってみて見えてきた課題

Tamaguiを採用してアプリを開発する中で、幾つか見えてきた課題もありました。HeroUI Nativeへの乗り換えを判断した背景でもあるため、ここで率直に共有します。

Android実機固有の挙動

iOSシミュレータやAndroidエミュレータでは問題ないものの、Android実機でのみ起きる挙動に幾つかぶつかりました。ここでは印象に残った2つのケースを、実コードと合わせて紹介します。

Sheet表示直後のタップが効かない

メニューや確認ダイアログとしてSheetを表示した直後に、内部のボタンをタップしても反応しないことがありました。

最初はダブルタップが必要なのかと感じましたが、少し時間をおくと反応するため、UIアニメーションの完了までタップが受け付けられていないと推測しました。デフォルトのアニメーションは完了までに時間がかかり、その間タップを取りこぼしていたようです。

そこでSheetanimation="200ms"のように短い時間のアニメーションを明示的に指定したところ、表示完了までの時間が短くなり、表示直後のタップにもすぐ反応するようになって解消しました。

<Sheet
  modal
  open={open}
  onOpenChange={setOpen}
  snapPoints={[50]}
  animation="200ms" // 明示しないとAndroid実機で表示直後のタップが効かない
>
  <Sheet.Overlay />
  <Sheet.Frame padding="$4">
    <Button onPress={handleConfirm}>確定する</Button>
  </Sheet.Frame>
</Sheet>

Sheet内のScrollViewでスクロールするとSheetが閉じる

選択肢が多い項目を選ばせるUIとして、選択肢一覧をScrollViewで表示したSheetを出していたのですが、Pixel 10で動作確認していたところ、Sheet内をスクロールしようとするとそのままSheetごと閉じてしまう事象が発生しました。

該当箇所はおおよそ次のような構成です。

import { ScrollView } from "react-native";
import { Sheet } from "tamagui";

<Sheet modal open={open} onOpenChange={setOpen} snapPoints={[80]}>
  <Sheet.Overlay />
  <Sheet.Frame>
    <ScrollView>
      {options.map((option) => (
        <Option key={option.id} option={option} />
      ))}
    </ScrollView>
  </Sheet.Frame>
</Sheet>

Sheet内のScrollViewでのスクロール操作が、Sheetを閉じるためのドラッグジェスチャと衝突しているのが原因と推測しています。

暫定対応としてSheet側にdisableDragを指定したところ、スクロール時にSheetが閉じる事象は解消されました。

<Sheet
  modal
  open={open}
  onOpenChange={setOpen}
  snapPoints={[80]}
  disableDrag // ドラッグでSheetを閉じる挙動を無効化し、ScrollViewのスクロールと衝突しないようにする
>
  <Sheet.Overlay />
  <Sheet.Frame>
    <ScrollView>
      {options.map((option) => (
        <Option key={option.id} option={option} />
      ))}
    </ScrollView>
  </Sheet.Frame>
</Sheet>

ただしdisableDragを指定すると、本来ドラッグで閉じられるはずのSheetが閉じられなくなり、UX上の妥協が発生してしまいます。

Sheet周りはAndroid実機での挙動が安定しないケースが続いたこともあり、根本的にはTamaguiのSheet自体の利用を見直す必要があると感じはじめ、後にHeroUI Nativeへの乗り換えを検討する一因にもなりました。

Expo SDK 54対応のタイミング

ExpoはReact Nativeでクロスプラットフォームのアプリを開発するためのフレームワークで、モバイルアプリの証明書周りを簡単に扱える仕組みや、様々な便利機能をSDKとして提供しています。そのExpo SDK 54は2025年9月11日にリリースされ、TamaguiのGitHub上でも対応に向けたWIP Pull Requestや議論が動いていました。

一方で、Tamagui側の対応版がリリースされたのは2025年11月15日で、対応版リリース日のアナウンスは事前にはなかったと記憶しています。

5年以上の歴史を持つライブラリで、内部に多くの実装を抱えている分、最新Expo SDKへの追従は簡単ではないのだろうと推測しています。OSSライブラリを採用する以上、こうした追従コストとはトレードオフだと改めて感じる出来事でした。

HeroUI Native + react-native-unistylesに変更を決めた理由

ここまで見てきた課題を踏まえ、次のUI基盤としてHeroUI Nativeとreact-native-unistylesを組み合わせる構成を選びました。

HeroUI Nativeを選んだ理由は、Componentの完成度が高く、画面実装のスピードを上げられると考えたためです。デフォルトの見た目が洗練されており、アニメーションやインタラクションも作り込まれているため、細かい調整を加えなくてもそのまま画面を組み立てられます。Findy Eventsはまだ機能を増やしていく段階にあり、必要な画面を素早く形にできることは、UIライブラリに強く求めていた点でした。

スタイリングにreact-native-unistylesを採用したのは、スタイル定義を標準のAPIに寄せたかったためです。HeroUI Native自体は内部のスタイリングにUniwind(Tailwind v4ベースの仕組み)を使っていますが、プロダクト側のスタイルは、React Native標準のStyleSheet APIに近い書き味で扱いたいと考えました。react-native-unistylesならStyleSheet.createを起点に、色やfontSize、spacing、radiusといったトークンを定義でき、モバイルエンジニアにとっても馴染みやすい書き味になります。

β版採用のリスクとその対策方針

一方で、HeroUI Nativeの採用にはリスクもありました。

検討していた当時のHeroUI Nativeはまだβ版段階のライブラリでした。正式版のv1.0.0は2026年3月19日にリリースされ、Findy Eventsのアプリリリース時点ではv1.0系に到達しています。ただ、採用を判断した時点ではまだ正式版前のライブラリです。β版段階のライブラリを本番プロダクトの土台に据える以上、特に破壊的変更には注意が必要でした。APIがまだ固まりきっておらず、バージョンアップのたびに利用側のコード修正を迫られる可能性があるためです。

そこで、HeroUI NativeのComponentは画面から直接使わず、必ずWrapper Componentを挟んで利用する方針にしました。Wrapper Componentという境界を1枚挟むことで、HeroUI Native側のAPIが変わっても、修正をPrimitive層に閉じ込められます。

この方針は、破壊的変更への備えであると同時に、将来的にHeroUI NativeのComponentを自分たちの独自実装へ切り替えたくなった場合にも効きます。利用側はWrapper Component経由でしかComponentに触れていないため、内側の実装をHeroUI Nativeから独自実装へ差し替えても、画面側のコードに影響を出さずに移行できます。

具体的なコード例

ここでは、HeroUI NativeのComponentをWrapper Componentとして定義し、react-native-unistylesと組み合わせて画面を構築するまでの流れを紹介します。

HeroUI NativeのWrapper Component

まず、最もシンプルな例として、SkeletonのWrapper Componentのコードを見ていきましょう。

// src/components/primitives/skeleton/skeleton.component.tsx
import type { SkeletonProps } from "heroui-native";
import type { PropsWithChildren } from "react";

import { Skeleton as HeroUISkeleton } from "heroui-native";

type Props = PropsWithChildren<
  Pick<SkeletonProps, "isLoading" | "style">
>;

export const Skeleton = ({ isLoading = true, style, children }: Props) => {
  return (
    <HeroUISkeleton isLoading={isLoading} variant="shimmer" style={style}>
      {children}
    </HeroUISkeleton>
  );
};

HeroUI NativeのComponentは多くのPropsを持っていますが、アプリ内で実際に使うPropsは限られます。

Wrapper Componentを定義する時に、Pick<>で必要なPropsだけを公開することで、利用側のインターフェースをシンプルに保つことができます。

また、variantのようにアプリ全体で固定したい設定値はWrapper Component内で埋め込むことで、利用側が意識する必要がなくなります。

次に、もう少し複雑な例として、ButtonのWrapper Componentのコードを見てみましょう。

HeroUI NativeにはButton.LabelCard.Bodyのように、サブComponentを持つCompound Componentがあります。

ラップした後もButton.Labelのような呼び出し方を維持したい場合は、Object.assign()を使って親ComponentにサブComponentを紐付けます。

// src/components/primitives/button/button.component.tsx
import type { PropsWithChildren } from "react";

import { Button as HeroUIButton } from "heroui-native";
import type { ButtonLabelProps, ButtonRootProps } from "heroui-native";
import { StyleSheet } from "react-native-unistyles";

export type ButtonComponentProps = PropsWithChildren<
  Pick<
    ButtonRootProps,
    "style" | "size" | "onPress" | "isDisabled" | "animation"
  >
>;

export type ButtonLabelComponentProps = PropsWithChildren<
  Pick<ButtonLabelProps, "style">
>;

const ButtonRoot = ({
  children,
  style,
  size,
  onPress,
  isDisabled,
  animation,
}: ButtonComponentProps) => {
  return (
    <HeroUIButton
      style={style}
      size={size}
      onPress={onPress}
      isDisabled={isDisabled}
      animation={animation}
    >
      {children}
    </HeroUIButton>
  );
};

const ButtonLabel = ({ children, style }: ButtonLabelComponentProps) => {
  return (
    <HeroUIButton.Label style={style}>
      {children}
    </HeroUIButton.Label>
  );
};

export const Button = Object.assign(ButtonRoot, {
  Label: ButtonLabel,
});

このようにButton.Labelの形でアクセスできるため、利用側のコードはHeroUI Nativeの元のAPIと同じ使い心地を維持できます。

react-native-unistylesとWrapper Componentを組み合わせたUI Componentの実装

続いて、定義したWrapper Componentをreact-native-unistylesと組み合わせて、実際の画面で利用するUI Componentを作る例を紹介します。

まず、カラートークンを定義したファイルを用意します。

// src/styles/generated/primitive-colors.ts
export const primitiveColors = {
  black: "#000000",
  white: "#ffffff",
  ...
  blue400: "#377ecd",
  blue500: "#055ec1",
  blue600: "#044b9a",
  ...
} as const;

export type PrimitiveColors = typeof primitiveColors;

次に、react-native-unistylesで、テーマに沿ったスタイリングを効率的に行えるように、StyleSheet.configureでアプリ全体のテーマトークンを定義します。

本アプリでは、色やフォントサイズ、spacing、角丸などをトークンとして管理しており、Componentごとの配色もここに集約しています。

// src/styles/index.ts
import { StyleSheet } from "react-native-unistyles";

import { primitiveColors } from "./generated/primitive-colors";

const tokens = {
  colors: primitiveColors,
  fontSize: { xs: 11, sm: 12, md: 14, lg: 16, xl: 20, "2xl": 22 },
  radius: { xs: 2, sm: 4, md: 6, lg: 8, xl: 12 },
  space: { "2xs": 4, xs: 8, sm: 12, md: 16, lg: 24 },
} as const;

const lightTheme = {
  // グローバル定義
  background: primitiveColors.white,
  backgroundPress: primitiveColors.grey50,
  ...

  // Componentごとの定義
  button: {
    danger: {
      background: primitiveColors.red500,
      color: primitiveColors.white,
      backgroundPress: primitiveColors.red600,
    },
  },

  fontSize: tokens.fontSize,
  radius: tokens.radius,
  space: tokens.space,
} as const;

const darkTheme = {
  ...
}

StyleSheet.configure({
  themes: { light: lightTheme, dark: darkTheme },
});

StyleSheet.createのコールバック関数からこのテーマトークンにアクセスでき、さらにスタイル定義自体を関数にすることで、Propsの値に応じた動的なスタイル切り替えも実現できます。

これを踏まえて、先程のButton Wrapper Componentをカスタマイズした「DangerButton」UI Componentの例を見てみましょう。

// src/components/buttons/danger-button/danger-button.component.tsx
import { StyleSheet, useUnistyles } from "react-native-unistyles";

import { Button } from "@/components/primitives/button";
import type {
  ButtonComponentProps,
  ButtonLabelComponentProps,
} from "@/components/primitives/button";

const DangerButtonRoot = ({
  children,
  style,
  ...rest
}: ButtonComponentProps) => {
  const { theme } = useUnistyles();

  return (
    <Button
      style={[styles.root, style]}
      animation={{
        highlight: {
          backgroundColor: { value: theme.button.danger.backgroundPress },
        },
      }}
      {...rest}
    >
      {children}
    </Button>
  );
};

const DangerButtonLabel = ({ children, style }: ButtonLabelComponentProps) => {
  return <Button.Label style={[styles.label, style]}>{children}</Button.Label>;
};

export const DangerButton = Object.assign(DangerButtonRoot, {
  Label: DangerButtonLabel,
});

const styles = StyleSheet.create((theme) => ({
  root: {
    backgroundColor: theme.button.danger.background,
  },
  label: {
    color: theme.button.danger.color,
  },
}));

このように、HeroUI NativeのclassNameなどを隠蔽し、プロダクト固有のスタイル定義をreact-native-unistylesで与えており、HeroUI Nativeとreact-native-unistylesで責務を分割しています。

まとめ

本記事では、Findy Eventsの開発で当初採用していたTamaguiからHeroUI Native + react-native-unistylesへ乗り換えるまでの判断と、Wrapper Componentを軸にしたUI実装パターンを紹介しました。

振り返って改めて感じたのは、UIライブラリの選定は「一度決めて終わり」ではなく、運用しながら継続的に見直す前提で向き合うべきだということです。

Tamaguiは選定時の評価としては妥当な候補で、採用後も多くの場面で十分に機能していました。一方で、Android実機特有の挙動やExpo SDK追従までのタイムラグなど、プロダクトとして長く育てる上で無視できない課題も見えてきます。β版というタイミングを活かしてHeroUI Native + react-native-unistylesに踏み込んだ判断は、開発体験の改善という形で素直に効いています。

実装面では、HeroUI NativeをそのままUI Componentとして使うのではなく、Wrapper Component経由で型・Propsを絞り、スタイル定義はreact-native-unistylesに寄せる責務分離が機能しました。プロダクト固有のテーマや配色をWrapper Component側で受け止めることで、UIライブラリ更新の影響範囲を最小化しつつ、画面側のコードもシンプルに保てています。

Findy初のモバイルアプリということで、技術選定もアーキテクチャも手探りで進めた部分が多くありますが、その都度の判断と振り返りそのものが大きな資産になっていると感じます。本記事の経験談が、同じような選定や設計に向き合っている方の判断材料になれば嬉しいです。

ファインディでは一緒に働くメンバーを募集しています。少しでも興味を持っていただけた方は、ぜひこちらをご覧ください!

herp.careers

【エンジニアの日常】エンジニア達の人生を変えた一冊 Part7

こんにちは、ファインディでFindy Toolsの開発をしている本田です。

この記事は「エンジニア達の人生を変えた一冊」として、ファインディのエンジニアが人生を変えた本を紹介していくシリーズです。

Part7では、本田・加藤・山田の3名でお届けします。アジャイル開発との出会いになった本、iOSアーキテクチャ設計の答え合わせになった本、AI時代に再読して背筋が伸びた本。3冊それぞれ切り口は違いますが、いずれも自分の中の「開発の判断軸」を作り直してくれた一冊です。

それでは、さっそく紹介していきましょう。

■ 本田 / フルスタックエンジニア ■

ファインディでFindy Toolsの開発をしている本田です。

RailsによるアジャイルWebアプリケーション開発 第4版

本書は、原著「Agile Web Development with Rails」をもとに、Rails 3.1に対応する形で翻訳された一冊です。著者には、アジャイルソフトウェア開発宣言の起草者の一人であるDave Thomasと、Railsの作者であるDavid Heinemeier Hanssonが名を連ねています。アジャイルの源流とRailsの源流が、ひとつの本のなかに揃っているというのは、今振り返ってみると改めてすごい組み合わせだと感じます。

ちなみに、そのDave Thomasが、2026年6月17日・18日にファインディが主催するオンラインカンファレンス「エンジニアの役割の変化に向き合うConference 2026」に登壇予定です。本書の著者本人の話を聞ける機会でもあるので、興味のある方はぜひチェックしてみてください。

この本を読んだきっかけ

業界4年目の頃、私はC#でウォーターフォールの開発をしていました。仕様を固めて設計書を書き、それに沿って実装し、最後にまとめてテストとリリース。それが「開発」というものだと思っていました。

そんなときに、Railsを使った新しいプロジェクトが立ち上がりました。チームメンバーは全員、Railsも本格的なアジャイル開発も未経験で、何から始めればいいのか分からない状態でした。そこで、みんなでこの本を教科書にして、毎日お昼に少しずつ読書会をしながら進めることにしました。各自一章ずつ読んできて集まり、分からないところを話し合い、サンプルを動かしてみる。それを繰り返して、RubyやRailsを少しずつ自分たちのものにしていきました。

本を中心にしたチームの学びの時間は、いまでも当時を思い出すたびに頭に浮かぶ景色になっています。

本の内容

構成のおもしろさは、Depot(デポ)というオンラインショップの架空のプロジェクトを、章を追って少しずつ作り上げていく形式になっているところです。最初は商品一覧の表示だけ、次にカートをつけて、注文できるようにして、メール送信を加えて……というふうに、本一冊を通じて一つのアプリケーションがインクリメンタルに育っていきます。

機能ごとの章タイトルが「タスクA」「タスクB」と続く構成で、まるで開発の現場でユーザーストーリーをこなしていくかのような体験になっています。Railsの機能を学べると同時に、「動くものを少しずつ広げていく」開発スタイルそのものを擬似体験できるように設計されていて、書名どおりまさに「Railsによるアジャイル」を体現した本だと感じました。

この本から影響を受けた点/学んだ点

この本を読んで一番大きかったのは、開発の進め方そのものに対する自分の考え方が変わったことです。

それまでの私にとって、「仕様を固める」「設計を作る」「実装する」「テストする」は、それぞれが大きな塊として順番に並んでいるものでした。ところがこの本でDepotを作っていく体験は、まったく違いました。小さく動くものをまず作って、そこに機能を足し、動かしてみて、足りなければ調整して、また次の機能を足していく。短いサイクルで「動くもの」を中心に開発が進んでいく感覚は、当時の自分には完全に新しいものでした。

同じくらい衝撃的だったのは、Rubyという言語とRailsというフレームワークの書き心地そのものです。それまで書いていたコードと比べると、コードの量が圧倒的に少なくて、それでいて表現したいことがすっと書ける。フレームワークが規約として用意してくれている部分にうまく乗ると、書くべきものに集中できる。これも「開発って、こんなに違うものになるのか」という気づきでした。

「Webアプリケーションをアジャイルに作る」というのは、ツールやフレームワークだけの話ではなく、考え方と書き心地と進め方が一体になって初めて成立するものなのだと、サンプルアプリケーションを作りながら肌で理解できた気がします。

特に印象に残った部分

特に印象に残っているのは、Depotを章ごとに育てていく構成そのものです。

本の最初のほうで作ったDepotは、商品一覧を表示するだけのとてもシンプルなものです。それが章を進めるごとに少しずつ機能が増えていき、最後にはちゃんと注文ができてメールが届く、ひとつのアプリケーションになっている。途中で「これで動くようになった」「ここはこういう改善ができるのか」と、小さな達成感が積み重なっていく感覚は、技術書を読みながら得られる体験としてとても新鮮でした。

そして、それをチームで読書会として体験したことも大きかったです。お昼に集まって、一章ずつ進めて、分からないところは議論して、サンプルを動かしてみる。今思えば、これ自体が「短いサイクルで動くものを増やしていく」という本書で学ぼうとしていたスタイルを、学び方そのもののなかで実践していたのだと思います。本を読むという行為が、ただ知識を得る時間ではなく、チームで開発のスタイルそのものを共有する時間になっていました。

このような方におすすめ

日本語版は更新が止まっているため、今からRailsを学び始める人に第一の選択肢として薦める本ではないかもしれません。ただ、原著は今もアップデートが続いていて、最新版ではRailsの新しいバージョンに対応した内容で読むことができます。

pragprog.com

私個人としては、Railsとアジャイル開発の出会いをひとつの本でまるごと体験した原体験の本として、今でも特別な位置に置いています。AIで開発の速度や進め方が変わっていく今だからこそ、「小さく動くものを作って、フィードバックを得ながら育てていく」という当時学んだスタイルが、自分のなかでの開発の考え方の土台になっていることをあらためて感じます。

続いては、Tools開発でファインディ初のモバイルアプリ「Findy Events」を担当している加藤さんです。Clean Architectureを採用したプロダクトをリリースした直後に出会った、設計選定の「答え合わせ」になった一冊について語ってもらいました。

■ 加藤 / モバイルエンジニア ■

ファインディ株式会社でモバイルエンジニアをやっている加藤です。ファインディ初のモバイルアプリ「Findy Events」を開発しています。

iOSアプリ設計パターン入門

peaks.cc

※現在はPEAKSの公式サイトで電子版のみ購入可能です。

私が紹介する「iOSアプリ設計パターン入門」は、iOSアプリのアーキテクチャを網羅的にまとめた書籍です。

この本を読んだきっかけ

当時の私はMVCのみの開発経験しかない中で、業務でClean Architectureに挑むことになりました。

手探りで設計を進め、なんとかリリースまで漕ぎ着けたものの、本当にこれで良かったのか、正しい形は何だったのかという確信は最後まで持てませんでした。

チーム内でClean Architectureに対する共通認識を取り切ることができず、QCDのDを優先して走り切った経緯もありました。そもそもClean Architectureを採用すべきだったのか、別のアーキテクチャの方が適していたのではないかという迷いも残っていました。

そんなタイミングで本書が発売され、自分の中の答え合わせをしたいという気持ちで手に取った一冊です。

本の内容

本書は冒頭で、一般的なアーキテクチャの歴史と構造を整理するところから始まります。

GUIアーキテクチャとシステムアーキテクチャの両面から解説されており、それぞれの位置づけがクリアになる構成です。

その上で、各アーキテクチャをiOSアプリにどう適用するかが具体例とともに示されています。

アーキテクチャの選定に関する考え方にも一定のページが割かれており、サンプルコードがGitHubで配布されているため、手を動かしながら理解を深められるのもありがたいポイントでした。

この本から影響を受けた点/学んだ点

一番大きかったのは、GUIアーキテクチャとシステムアーキテクチャは組み合わせて使うものだという視点を得られたことです。

それ以前の私は、この二つを同じレイヤーのものとして捉えており、MVCとClean Architectureを並べて比較するような考え方をしていました。

本書を読んでから、例えば、「MVPでGUIを構築しつつClean Architectureでシステム全体を整理する」といった組み合わせの発想ができるようになりました。

実際にその後の業務で「MVP + Clean Architecture」という形に取り組み、本書で得た視点を現場に持ち込めたのは大きな収穫でした。

特に印象に残った部分

第2部のまとめ「アーキテクチャの選定基準」に書かれていた次のような問いかけが、強く印象に残っています。

なんとしてでもClean Architectureを採用すべきでしょうか。 どんなアーキテクチャパターンを採用するとしても、そのパターンの経験があったり、パターンに精通していることが望ましいです。 アーキテクチャパターンが目的になっていないか

これらは、当時の私の悩みに正面から答えてくれる内容でした。

Clean Architectureを採用したプロダクトをリリースした直後に本書を読んだこともあり、チームでパターンへの精通が揃わないまま走り切ってしまったことを、ページをめくるごとに痛感させられました。

本来であれば設計を選ぶ前に向き合うべきだった問いを、後追いで突きつけられている感覚です。

iOSに限らず、アーキテクチャ選定の場面では常に心に置いておくべき視点だと感じています。

長期的な開発・保守・運用まで見据えて選ぶことの大切さを、自身の反省と重ねて受け取った章でした。

このような方におすすめ

これから、あるいは今まさにiOSアプリのアーキテクチャ設計に取り組むエンジニアにおすすめの一冊です。

iOSアプリのアーキテクチャを体系的にまとめた書籍は今でもそう多くないため、最初に手に取る一冊として機能すると思います。

また、iOSに限らずアーキテクチャの歴史や系譜に興味がある方にも価値のある内容です。

発売から7年近くが経つためTCAは扱われていませんが、TCAにつながるFluxの解説があり、現在のiOS開発の文脈でも参考になる部分は多く残っていると感じています。

最後は、事業推進でカンファレンスサービスを開発している山田さんです。山田さんが選んだのは、世界トップクラスのエンジニアの思考プロセスを言語化した一冊。AIコーディングエージェントが日常になった今だからこそ、再読の意味が増した本について語ってもらいました。

■ 山田 / フルスタックエンジニア ■

ファインディ株式会社でフルスタックエンジニアをやっている山田です。ファインディのカンファレンスサービスを開発しています。

世界一流エンジニアの思考法

私が紹介する「世界一流エンジニアの思考法」は、Microsoft Azure Functions開発チームに身を置く著者が、世界トップクラスのエンジニアたちの思考プロセスと働き方を観察して言語化した一冊です。生産性の本質を「基礎理解の深さ」と「試行錯誤の前に思考する姿勢」に求めており、AI時代に再読する価値がむしろ増した本だと感じています。

この本を読んだきっかけ

最初に手に取ったのは、自身やチームの生産性向上を目的に、社内で技術的に優れたアウトプットを圧倒的な量で出し続ける同僚を意識した時期でした。実装スピードも設計の練度もPRの打数も桁が違う。その差分を言語化したくて、世界一流の現場で同種の人々に囲まれて働く著者の本を読みました。

最近になってAIコーディングエージェントが日常の道具となった時期に、自分の中の「思考順序」がAIを挟んで再び崩れ始めている自覚が芽生え、改めて初心に返り本書を再読することになりました。

本の内容

本書は7章構成で、第1章「世界一流エンジニアは何が違うのだろう?」から始まり、マインドセット、情報整理・記憶術、コミュニケーション、チームビルディング、生活習慣を経て、第7章「AI時代をどう生き残るか?」で締められます。

主軸は「生産性は時間ではなく思考の質で決まる」という立場で、具体的には次のような原則が繰り返し強調されます。

  • Be Lazy: 作業量を減らし、インパクトのある対象に集中する
  • 理解の三要素: 説明可能/いつでも使える/応用可能、を満たして初めて「理解した」と言える
  • 仮説駆動: 試行錯誤を悪とし、事実→仮説→検証の順序を守る
  • シングルタスク/タイムボックス: マルチタスクは生産性が最低なのでやらない
  • AI時代の生存戦略: 自分が学んだものとAIをどう掛け算するか

この本から影響を受けた点/学んだ点

初読で強く影響を受けたのは次の二点でした。

  1. 基礎理解には時間をかけること。シニアが平気で数日を「読むだけ」に費やす描写から、表面的に動かす速度よりその後の実装と判断の速度を取りに行く姿勢を学びました。
  2. 試行錯誤の前に思考すること。手を動かす前に「事実を掴む → 仮説を立てる → 検証する」の順序を必ず通す習慣です。

再読で痛烈に蘇ったのは、AI時代において自分が崩していた順序の自覚でした。具体的には次の3つの兆候です。

  1. AIのアウトプットの質を上げるために、プロンプトを試行錯誤で叩く回数が、自分の思考時間より長くなっていた
  2. AIを並列で走らせる並列業務に流され、シングルタスク原則が崩れていた
  3. AIが最初に出した "good code" を正にしてしまい、「bestなコードは何か/そこに至る道筋」を自分の頭で検討する工程が薄くなっていた

ここから得た結論は、AIの登場は基礎の重要性を消したのではなく、見えにくくしただけということ。基礎力の向上は今も昔も時間がかかり、長い時間、同じ対象に向き合って初めて芽が出ます。AIを係数として効かせるための「自分側の係数」を、地道に上げ続ける意志が問われていると改めて認識しました。

特に印象に残った部分

第3章で示される「理解の三要素:説明可能/いつでも使える/応用可能」は、初読時から自分の判断基準として一番手元に残った概念です。再読時には、この三要素をそのままAI出力のレビュー基準として転用し、「生成されたコードを自分で説明できるか/別文脈で使えるか/応用が効くか」を改めて意識するようになりました。

もう一箇所は、第1章の「マルチタスクは生産性が最低なのでやらない」という言葉です。AIが並列処理を肩代わりしてくれる時代だからこそ、人間側がマルチタスクで思考の解像度を落とすのは本末転倒で、今手をつけている仕事を1つに限定することがAIを使いこなす前提条件だという文脈は再読で初めて腹に落ちたところがありました。

このような方におすすめ

  • 周囲に桁違いのアウトプットを出すエンジニアがいて、その差分を言語化したい方
  • AIコーディングエージェントを使い始めてから、プロンプトの試行錯誤に時間を溶かしている自覚がある方
  • AIが出してきた "動くコード" をつい best として採用してしまっていることに違和感を持ち始めた方
  • 並列タスクで日々が埋まり、1つの対象に深く向き合う時間が減っている方
  • 「AI時代に技術力は不要になるのでは」という風潮に、戒めとしての軸を持ち直したい方

おわりに

アジャイルを支える開発スタイル、iOSアーキテクチャを選ぶ視点、そしてAI時代に通用する思考法。今回紹介した3冊は扱うテーマこそバラバラですが、いずれも「自分の中の判断軸」を作り直す体験を与えてくれた本でした。

私はRailsとの出会いを通じて「小さく動くものを育てる」スタイルを、加藤さんはアーキテクチャ選定の問いを、山田さんは基礎理解と思考順序の重要性を、それぞれ本から受け取っています。年次を重ねるほど一冊の影響は薄まるのかと思いきや、振り返ってみるとむしろ「あのとき出会えた」一冊が、いまの判断や設計、思考のクセを支え続けている。エンジニアという仕事ならではの面白さだなと感じます。

皆さんにも、そんな一冊との出会いが一つでも増えるきっかけになれば嬉しいです。

ファインディでは一緒に働くメンバーを募集しています。少しでも興味を持っていただけた方は、ぜひこちらをご覧ください。

AI-DLCをClaude Skill化して「エンジニアの役割越境」を実現した話

はじめに

こんにちは!ファインディのTeam+開発部でエンジニアをしている澁谷(TENTEN11055)です。
普段はチームで Findy Context というプロダクトの開発に取り組んでいます。

prtimes.jp

2025年11月、AWS主催のAI-DLC Unicorn Gymに参加し、AI駆動開発の手法であるAI-DLCを実践しました。
学びはとても大きかった一方で、自分たちのチームにそのまま持ち込むには壁もあり、現場の実態に合わせて作り変える必要がありました。

Unicorn Gym参加時の様子はこちら。

tech.findy.co.jp

AI-DLCはAIが作業を実行し、人間が監視と判断に集中する開発手法です。
要件・ストーリー・作業単位を整理するInception、設計・実装・テストを進めるConstruction、IaCやデプロイを担うOperationの3フェーズで構成され、AIが提案する成果物をチーム全員で検証しながら進めます。
チームではそのうち実装に関わるInceptionとConstructionの2フェーズを採用しました。
詳細は次の記事を参照ください。

aws.amazon.com

この記事ではAI-DLCをClaude Skill化し、Epic制開発フローの中で要件整理からテストまでを一人のメンバーが一貫して担えるようにした取り組みを紹介します。

チームが抱える課題

これまではPMやデザイナー、QAエンジニアといった専任者と分業する形で開発してきましたが、チーム構成の変化により、エンジニア自身が担当領域を越境していく必要が出てきました。

担当領域越境の必要性

チームのフロントエンド専任者が一人のため、フロントの設計・実装を全て頼るわけにもいきません。
また4月末でQAエンジニアが別プロダクトへ異動となり、実装者のみでのテスト設計、ケース実装を担うことになりました。
さらに専任のPMも他プロダクトを兼務しており割ける時間が限られるため、要件整理など本来PMの領域だった部分にも開発者が踏み込んでいく必要があります。
これらを加味し、エンジニア一人一人が担当領域を広げる必要が出てきました。

AI-DLCをそのまま取り入れる難しさ

そこで越境を支援する手段として、これまで個々人で自由に行なっていたAI-DLCをチームの開発フローとして取り入れることを検討しました。
しかしInceptionフェーズは関係者が集まって仕様を決めていくスタイルであり、その実施には一定の同期コストが発生します。
作りたい機能の具体が曖昧であればあるほど全員参加は効果的になりますが、Epicイシューが作成された段階で既に一定の解像度がある場合は、PMも含め全員で擦り合わせをすると、得られる解像度向上に対して投入する時間(チーム全体の同期コスト)の方が目立ってしまいます。

Epic制開発フローの導入

これらの課題を解決する一環としてチームではEpic制を導入しました。
こちらはVPoEのhamさんがFindy Team+開発で実施した個人アサインへのシフトに影響を受けています。

tech.findy.co.jp

各Epicにメンバーをアサインすることで要件整理から開発まで一貫して責任を持つようになり、ボトルネックも発生しづらくなります。

実際の運用としては、大まかに次の流れで開発を進めています。

  1. Epic担当者はAI-DLCスキルを用いてInceptionドキュメントを作成する
  2. ワイヤーフレーム作成スキルに1のドキュメントを読み込ませ、HTMLでワイヤーを作成する
  3. 1と2を元にデザイナーとPMに確認をとる
  4. AI-DLCスキルでConstructionドキュメント、テスト設計、テストケースを実装する
  5. デザイン、実装、テスト
  6. 受け入れ確認、リリース

チーム内ではこれらを用いたフローをAI-E-DLC (AI - Epic - Driven Development Life Cycle)と名付けており、今後も運用と改善を重ねていこうとしています。

ここからはこのフローの1と4で利用するAI-DLCスキルについて掘り下げていきます。

AI-DLCの2フェーズと付随する作業をClaude Skill化する

同じプロンプトを用いてドキュメントを作成しても、AIの成果物にはばらつきが生じる可能性があります。
そのためAI-DLCにおけるInceptionとConstructionのドキュメント生成をそれぞれスキル化し、潜在的な課題をいくつか改善することができました。

1. フォーマットのばらつきが改善

同じAI-DLCのプロンプトを使っても、書き手によって構成・粒度にばらつきが生じたり、必要な情報が抜けたりすることで、実装・テスト設計・レビューなどの工程でAIが読み取りにくいドキュメントが生まれていました。
この課題に対し、スキルで手順と出力フォーマットを固定し、誰が実行しても同じ骨格のドキュメントを出力できるようにしています。
ちなみにこのフォーマットの固定化が多くの改善の起点になっており、これなしでは後述の内容も実現しえませんでした。

2. 横展開を容易にする

AI-DLCの手法を各メンバーが再現するには、フェーズ理解・プロンプト設計・観点整理の習熟が必要であり属人化しやすいものですが、スキル化により手順がガイドされるため、未経験者でも気軽に実践できる「型」として提供できます。

またFindy Contextが複数リポジトリで構成されるため、リポジトリ非依存で動くスキルにしています。
今まで各リポジトリに合わせたプロンプトを用意する必要がありましたが、どれにおいても同じスキルを呼べばOKな状態にしています。

汎用性の高いスキルにすることでどこでも誰でもAI-DLCに則ったドキュメントを作成できるようになりました。

3. ツールを利用してよりシームレスな設計ができる

AIからの質問事項にはClaude CodeのAskUserQuestionツールを必ず利用させています。 InceptionではAIから人間への質問フェーズが存在し、ドキュメントに記載された質問に対し人間が回答を書き込むのが基本でした。
これに対し選択 or 自由回答という形式をとり回答候補を提示してもらうことで一から考えることがなくなるため、回答作業だけでなく脳の負担も軽減されました。
他にも一部の難易度の高い作業をAgentに任せるなど、場面に適した設定を共有できるのもスキル化の利点の一つです。

AskUserQuestionを利用した際のサンプル

4. 付随するスキルの精度が上がり、人間の負担を下げる

フォーマットのばらつきが改善されたことにより、専用のテスト設計・ケース実装スキルとドキュメントレビュースキルも作成することができました。

テストをAIで用意する際、何を材料とするかが重要です。
もしコードを材料とした場合、そのコードが仕様に沿っていなければ誤ったテストとなってしまいます。
このテスト設計・作成スキルはInception・Constructionドキュメントを材料とし、エンジニア・PM・デザイナー間で認識が揃った仕様を前提とするため高い精度を出すことができます。

またドキュメントレビューに関してはAI-DLCの中で最も人間の負荷が高い作業です。
AI-DLCスキルで生成されるドキュメントは10ファイルを超え、全部均等にレビューしようとすると破綻します。
そこでレビュースキル側で、生成されたドキュメントを「核となるドキュメント(仕様の中核を担うもの)」と「補助となるドキュメント(核を支える補足)」に分類し、Epicイシューとも照らし合わせて齟齬や抜けがないかをチェックさせています。
これにより人間が熟読すべきドキュメントを明確にし、レビュー負荷を下げることができました。

スキル運用後の効果とまとめ

運用を始めて数日が経ちましたが、想像以上に強力なスキルとフローであると感じています。
チームの課題でもあった「役割の越境」に対して有効なアプローチとなり、これまで専任者(PM・デザイナー・QAエンジニアなど)に頼っていた作業の下地を実装者が担い、専任者はレビュー・判断に集中できるようになりました。

習熟度の壁を下げられたことも大きな成果で、スキル作成後に説明する場を設けなくても気づいた時には他メンバーが使っており、数日後にはスキルの改善PRも上げてくれていました。
レビューにおいてもAI-DLCを始めた頃によくあった「ドキュメントレビューしんどい...」という声は幸いまだ聞こえてきていません。

今回の取り組みはAI-DLCをそのまま導入するのではなく、チームの開発体制や課題に合わせて適応させる試みでした。
まだ運用を始めて間もないため改善の余地はありますが、その部分に誰もが参加できる間口を用意できるのもスキル化の利点かもしれません。


ファインディでは一緒に働くメンバーを募集中です!
よかったら覗いてみてください。

herp.careers

Dev-Bizの壁を、可視化で越える — DevOpsDays Tokyo 2026 参加・登壇レポート

こんにちは、ファインディのCTO室でスタッフエンジニアを担当している及川(@rojoudotcom)です。

4月14日(火)〜16日(木)にDevOpsDays Tokyo 2026が開催されました。本記事は、スポンサー登壇者として参加してきたレポートです。

DevOpsDaysは、世界各地で開催されるエンジニア向けの国際カンファレンスです。 開発(Dev)と運用(Ops)の連携、自動化、組織文化、最新の事例やプラクティスを発表しています。日本ではDevOpsDays Tokyoとして、年に1回開かれています。

本記事では、開発組織の成果を経営層にどう伝えたらいいのかを悩まれている方や、AIを導入したのに成果が見えないと感じる方に向けて書きました。3日間のイベント参加を通じて改めて確信した「可視化は組織が動き出すための前提条件である」というメッセージを、登壇・基調講演・現地での会話の3つの角度から振り返ります。

開発組織は、経営から「見えない」

DevOpsという言葉についてのおさらいですが、もともと「DevとOpsの壁を壊す」ことから始まりました。コンテナ、CI/CD、SREといった方法論が広まり、Dev-Ops間の景色が変わったと感じる方は多いと思います。

一方で、Devと経営(Biz)の間の壁は依然として高いままではないでしょうか

開発部門が作るソフトウェアは形がなく、損益計算書には給与や販管費などの「費用」としてしか記録されません。経営層の関心は売上向上や費用削減にあるのに対して、開発の関心は製品開発におけるリリース頻度の向上やリードタイム削減にあります。お互いが重要だと思う指標が異なることが、すれ違いを生み続けていると考えています。

成果を見やすい形で可視化すれば、見える投資に変わる

今回の登壇では、ファインディ以前に在籍していた現場の事例を話しました。経営層から状況が見えないと言われていた開発組織を意図を持って再設計し、活動の良し悪しを計測したことで投資の判断ができる状態とした体験談です。

テーマは「Dev-Bizの壁をどう越えるか」。20分のスポンサーセッションで持ち時間は短かったものの、自分の問題意識を言語化する貴重な機会になりました。

 

経営から「見えない」と言われた開発組織を、仕組み・文化・習慣の3層で診断

私が過去に在籍していたある企業の開発部門の話です。配属された開発部門は発足から20年が経つ自社サービスを担当しており、内製化を進めていたところでした。歴代の担当者は退職済みで、ドキュメントもなく、現場の判断は開発担当者の属人的なナレッジに委ねられていました。

エンジニアリングマネージャとして組織の状況を把握するために現場関係者にヒアリングを始めると、様々なことが見えてきました。

まず、組織のサイロ化が進む仕組みが見えてきました。開発に関わるメンバーはプロジェクトの終了と共に解散するため組織ナレッジが溜まりにくいこと、そして新機能開発チーム(Dev)と運用改善チーム(Ops)は分断されており、特にOpsチームに届けられる改善要求は、その背景が不透明なまま改修が行われていました。

次に、受け身の文化が根付いていました。歴史的に要求は事業部が作成し、開発側は要求を待つ立場だったためです。さらに、上位下達の関係性から、開発側からの提案や意見を出しづらい状況でした。

最後に、経営層からすると、PLやBSの数字以上に開発の状況が見えない状態が続いていて、それ以上踏み込めない状況が習慣化されていました。リソースをどこに集中すべきか、開発予算は適正なのかの判断ができない状況でした。

組織設計に意図を持つことで組織の活動が見える

見えない状態は、組織の設計に「なぜそうしたか」の意図がないことが原因です。最初の取り組みとしてTeam Topologiesで組織体制を再設計しました。すると、顧客の要望がどこからきてどこに流れていくのか(バリューストリーム)を見えるようになり、それぞれの開発チームの責任範囲やチーム間の関係性も明確になりました。

組織の枠組みが整ったところで次はチームの中身(マインドセットや運用ルール)です。アジャイル開発のスクラムフレームワークを導入し、自己管理化を進めていきました。チーム自身が開発案件の優先度や実現方法を話し合うようになりました。さらに、システム的観点から事業部の要望に対する逆提案が生まれるようになりました。

そして、新体制が動き出してきたところでFindy Team+を導入し、開発チームのリードタイムやボトルネックを可視化し、定量的なデータを取り始めました。半年後にはリードタイムが従来比8割短縮、デプロイ頻度も3割増加し、経営層に対しても改善を数字で示せるようになりました。

成果を見る側に立って翻訳する

Four Keysによる開発活動の成果や、新機能が完成したときに得られるであろう期待される売上やコスト削減効果は重要な数字ではありますが、経営層が理解するにはもう一歩踏み込む必要があります。

例えば開発生産性が向上したことで稼働可能な人月が増えたと同等の効果と捉えれば、管理会計の項目のどこに増減の影響が生まれるのか。あるいは、リリース回数が増えたことで事業計画がどの程度短縮できるのか。こうした情報は決算書にも書けるような話題で、経営層にとって必要な情報です。

このように、投資判断を行う経営層の立場に立ち、開発の活動を翻訳して伝えることで、ようやく開発部門に対する投資判断の議論ができるようになりました。

仕組みを作り、文化を育て、習慣を変えれば、見えない現場は見える投資に変わる

以上、目の前の問題の直接解決ではなく、問題を生み出す構造(仕組み・文化・習慣)に目を向けて根本解決を促すストーリーでした。 さらに詳しい内容はスライドをご覧いただけるとうれしいです。

可視化がない改善は、AIに増幅されない

「可視化が大事」とは言われ続けてきました。それでも今このテーマで登壇しようと思ったのは、AI時代に入って、その重要性が以前にも増して大きくなっていると感じていたからです。そして、その直感はカンファレンスの基調講演で裏付けられました。

Google CloudでDORA(DevOps Research and Assessment)プログラムを率いるNathen Harvey氏によれば、2025年の調査でAI採用率は90%に達したそうです(前年比+14%)。

一方で、コード品質や個人の生産性は上がったものの、ソフトウェアデリバリーの安定性は悪化していました。Harvey氏はこの現象を踏まえて、AIの役割を「増幅器(amplifier)」と表現していました。良いシステムには良い結果を、悪いシステムには悪い結果を増幅するものという意味合いです。

この比喩がしっくり来たのは、増幅されるためにはまず「何が起きているか」が見えていなければならない、という当たり前の前提が浮かび上がるからです。

可視化されていないデータをAIに与えてもノイズを増幅するだけです。可視化されていない開発組織は、AIを入れても何が良くなったのか悪くなったのかすら判断できません。

AI採用は、可視化された土台の上ではじめて成果に結びつく 基調講演から受け取ったメッセージはそこに集約されていました。

そして、もう一つの基調講演でDevOpsムーブメントの礎を築いた一人であるAndrew Clay Shafer氏が残した「行動を変えるまでは、何も学んでいない」という一言も、同じことを別の角度から言っていると感じました。見えなければ学ぶことはできないですし、闇雲に行動することになります。それは学んでいないのと同じことです。

ここで触れた2つの基調講演のセッション情報は次のとおりです。

confengine.com

confengine.com

「可視化されてはじめて、人は動く」

3日間で一番心に残ったのは、技術的な議論ではなく、夜の懇親会で交わした一つの会話でした。

懇親会で日本酒を参加者に勧めていたとき、たまたまDay1の基調講演スピーカーのNathen Harvey氏が近くにいました。興奮して握手を交わし自己紹介をすると、彼は「ファインディのプロダクトを見せて欲しい」とリクエストしてくれました。

その場でブース担当者を呼んで、開発生産性可視化のデモをしました。彼はそれを見終えた後、こう言いました。

可視化することで、ようやく人は動き出すんだよね。

自分の登壇で伝えたかったことも、基調講演で語られていたDORAの研究も、3日間で出会った人たちが共有していた問題意識も、この一文で言い表されていると感じました。

Four Keysのような生産性指標で内側の現在地を、DORAのような調査レポートで外側の方向性を捉え、両者のギャップを埋めていきます。

可視化はゴールではなく、組織が動き出すための出発点です。

Dev-Bizの壁を越えるにも、AIを増幅器として活かすにも、共通してまず見えていることが要ります。これが3日間を貫いていたメッセージでした。

翌日のスピーカーインタビューのアイスブレイクで「日本に来てどうですか?」と聞かれたHarvey氏が「ファインディに会えたことが思い出になった」と答えてくれたのは、嬉しいおまけでした。

おわりに

開発組織を率いるマネジメント層にとって、可視化は「あれば便利な機能」ではなく「動き出すための前提条件」だと、今回の3日間で改めて確信しました。当社は指標を計測するサービスを提供する側だからこそ、組織を歪ませない設計に責任があると、身が引き締まる思いです。

DevOpsDays Tokyoに参加することで、スピーカー同士、運営の方々、そして海外登壇者との繋がりをつくることができ、DevOps文化に貢献したい気持ちも高まりました。来年の開催も楽しみにしています。

ファインディでは一緒に会社を盛り上げてくれるメンバーを募集中です。興味を持っていただいた方はこちらのページからご応募お願いします。

herp.careers