不要なレビューをAIにまかせてAIコーディングの環境改善を加速した

こんにちは。こんばんは。 Findy Team+ 開発のフロントエンドリードをしている @shoota です。 今回はフロントエンドからは少し離れ、AIによるプルリクエストのレビューシステムを作成した話を書きます。

Findy Team+フロントエンドの現状と課題

過去のブログでも触れていますが、Findy Team+のフロントエンドは、非常に大きなモノレポで構成されています。

tech.findy.co.jp

このため、CIの最適化・チューニングはもちろんのこと、コードベース全体の秩序の品質を維持するためにさまざまな工夫や制約をもって運用してきました。 AIコーディングが主流になってからは更にこれらを強化し、制約やルールの策定、ESLintやユニットテストなどのガードレール整備や強化をおよそ1年をかけて進めてきました。 (この内容については別で発表したいと思います)

これらの投資とAIモデルの進化により、これまでバックエンドを中心に活動しているメンバーのフロントエンドの参入障壁を大きく下げることに成功しています。 現在では、AIが生成するコードの質は1年前から大きく向上していると体感しています。

AIコーディング時代の新たな課題

しかしここで新たな課題が生まれました。それは、「AIが真似すべきでないコードの修正や、それらを防ぐルールの導入のためのレビューコストが異常に高い」ということです。

  • AIの活用のためにコードを直し続ける活動は巨大なコードベースではかなりコストがある
  • 技術的な障壁よりも単純な物量がつらく、対抗する手段がない
    • ESLintのルールを1つ変更するだけでプルリクエストが大量に必要になる
      • 特定のルールのちょっとした厳格化で25件のプルリクエストを作成
      • あたらしいルールを追加したときは150件のプルリクエストが必要になった
    • 軽微な変更でも意味のある粒度で修正しなくてはレビューできないため、ひたすらに数を重ねる

このような事象が定常的に発生しており、次のFindy Team+でのグラフでも確認できます。

大量のプルリクエストが発生する様子

Vitestの一部のassertionがdeprecatedになったことに対して、私が一人でClaude Codeを回し、1日に72件、2日で100件以上のプルリクエストを作成しました。 レビュー待ちが多く発生する割に、内容は関数の置き換えなのでレビュー価値が低いプルリクエストが大量に必要になったのです。

このような 「AIを利用して品質を維持するための整備が人間を苦しめている」 という状況は、近頃話題になっていたHustle Pornに近いのではないか?と考えました。

プルリクエストをたくさん書くことは素晴らしいですが、そこに意味のある粒度と価値が伴わなければなりません。

AIによる自動レビュー(Approve)を作ろう

このような背景からレビュー負荷を可能な限り低減させ、ただしい怠惰を取り戻すために、AIレビューの仕組みを作成することにしました。 まずはやりたいことから全体の構想とコンセプトを決めていき、次のような内容になりました。

解決したい課題

  • プルリクエストを高速に作り続けることはむしろ歓迎すべき
  • 課題の本質は「レビュープロセスが対ひとでしか機能していない」こと
    • 人の時間をうばう
    • 人のアクションを待つ
    • 両者にメリットが少ない

レビューは大きく3種類ある

  1. レビューする価値がないことを確認するレビュー。レビューというワークフローを実施しているだけであり、本質的なレビューはしていない。レビューワークフローに相乗りしている儀式のひとつ。
  2. 品質、プログラム観点としてのコードレビュー。いわゆる本質的なコードレビュー。
  3. レビュー結果が反映されていることのレビュー。論点が絞られており、コードの内容がレビュー指摘に即して修正されているかを「確認する」だけの作業的な儀式。

今回は1. の「レビューする価値がないことを他者が保証する儀式」をAIにやらせることをゴールとします。

誰が使えるのか

初手はフロントエンドのレビュワーチームのみに絞る

  • 最大リスクは、誤判定や手順のハックによるレビューのザル化
  • フロントレビュワーチームはセルフレビューで一定の品質担保ができている前提で考える
  • 条件的に一般解放は想定するが、判定の成功率データがたまるまではリスクのほうが大きい

何を判定するのか

  • 要はプルリクエストの分類器をつくる
  • Claude CodeのReview (~$25) は必要がない
  • 変更内容から人間がレビューする必要があるかどうか?を判定する
    • 振る舞いが変わらない構造的なリファクタリング
    • code generatorなどの既存の機械的なワークフローの結果も不要と判断する

判定のキモはTidy First?の分類

レビューの必要性を測る判定処理に、構造的なリファクタリングという概念を持ち込みました。 これは昨年の弊社のイベントでキーノートを務めていただいたKent Beck氏の書籍Tidy First?から引用した言葉です。 この書ではリファクタリングの分類が紹介されており、リファクタリングを効率的に進めるための作業を指します。 和訳では「整頓」と表現されています。

  • 振る舞いの変化を伴わない、「コードの移動・分割」、「名前の変更」、「配置の変更」
  • プログラム(群)の凝集度に対するリファクタリングアプローチ
  • これらの修正は人間が目で追うのは意外と面倒で、レビューコストが割に合わない

例)ファイル移動や統合、変数・関数のリネームor削除、関数統合・分離

www.oreilly.co.jp

Tidy First?に関する考察は非常に多く議論されているので、Claude Codeに調査させながら「人間のレビューが不要」の定義を確定し、プロンプトに起こしていきました。 次に示すのがそのパターンの概要です。詳しい内容はぜひ書籍を確認していただけるとよいかと思います。

  • 対象パターン(T1〜T14)

    • T1: ファイル移動・リネーム
    • T2: import整理・barrel export
    • T3: 型定義の移動・分離
    • T4: コンポーネント・関数の分割・統合・抽出
    • T5: 変数・関数・propsのリネーム(テスト/モックへの機械的波及含む)
    • T6: ESLint/Prettier修正
    • T7: 不要コード・ファイル削除
    • T8: 非推奨APIの機械的移行(1:1対応)
    • T9: GraphQL codegen実行結果
    • T10: Nx generator実行結果
    • T11: ガード節への変換
    • T12: 説明変数・定数の導入
    • T13: Feature Flag名の追加
    • T14: コメントの追加・修正・削除
  • NGパターン(NG1〜NG7)

    • 新条件分岐、新ビジネスロジック、API変更、状態管理変更、テスト追加、UI変更、設定値変更

実装とポイント

全体の要件が決まったのでここからは実装です。

GitHub Actions

GitHub Actionsをランナーとし、プロンプトファイルを読み込んだうえでClaude Code Actionsに渡して実行、結果をパースしてレポートする非常にシンプルなフローです。 またFindy Team+はCI結果を送ってCIの実行分析ができるので、これも組み込んであります。

GitHub Actionsのフロー

Claude Code Actions

Claude Code Actionsの呼び出しのポイントは--max-turnsです。 これはClaude Codeが処理を何手まで行うかを制限するもので、変更ファイル数やそれらのファイルの関連性によって前後します。

      - name: Classify PR changes
        id: claude
        uses: anthropics/claude-code-action@5d5c10a4f389689f992ea10bb14dcb6fcc83146d # v1.0.102
        with:
          anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }}
          claude_args: '--max-turns 20'
          prompt: ${{ steps.prompt.outputs.content }}

何パターンかの構造的なリファクタリングのプルリクエストで試したところ〜15 turnsにおさまるようでしたが、余裕を見て20を設定しています。 これを設定しておくことでプロンプトの誤認や想定しない大きな差分で実行された場合でも「判定不能」で安全に終了するようにしています。

ワークフローの変化とプロンプトの改善

何度か試していくうちに、ひとつの気づきがありました。

これまでプルリクエストは 「人間が認知できる変更量」にサイジングするようにすべき でしたが、AIは機械的にこれを分析できるので「同じ作業なら差分が大きくても1つのプルリクエストにまとめる」ことができる、ということです。 そのため大きな差分でもApproveができるような負荷耐性をもたせる必要があります。

そこで変更ファイルが多い場合は変更そのものを見るのではなく、いくつかをサンプリングし、「diffのパターンが同一であるか?」をClaudeが確認するようにしています。

<!-- 追加したプロンプトの抜粋 -->
## 大規模 PR のフォールバック

- **変更ファイル数が 30 ファイルを超える場合**: 同一パターンの変更が繰り返されるファイル群はサンプリング(代表的な数ファイルの確認)で判定してよい。サンプリングした旨と確認したファイルを注意事項に記載する
- **サンプリング時は副作用の取り逃しに注意する**: ある T パターンを特定したら、**その波及先ファイル群(スナップショット、spec、モック、`index.ts` の re-export、import 更新 等)も必ず1件以上サンプリングに含め、元の T パターンとの対応を確認する**。波及先のファイルだけを単独で見ると NG に誤分類しやすい(例: T5 リネームのインラインスナップショット更新を NG5 と誤認)。波及元・波及先を**セットで**サンプリングすること
(以下略)

実行コスト

Claude CodeのReviewは 最大$25の費用が必要ですが、今回はプルリクエストの分類器なのでそこまでのコストがかかりません。 CIから実行したログからみると、1 runあたり、$0.20〜0.50で分類ができていました。

{
  "type": "result",
  "subtype": "success",
  "is_error": false,
  "duration_ms": 62472,
  "num_turns": 10, // Claude Codeの手数の実績値。最大20で絞ってある。
  "total_cost_usd": 0.24731475000000003, // 約$0.25を消費
  "permission_denials_count": 0
}

おわりに

プルリクエストの分類器をGitHub Actions + Claude Code Actionsで作成することで、人がレビューしなくて済むプルリクエストは作成者が単独でマージできるようになりました。

この仕組みの導入は想定以上に開発者体験がよく、また機能開発の合間でリファクタリングするモチベーションも高めてくれます。 まさにTidy First?のなかでの「先に整頓、あとに整頓」を仕組みで支えることができたと感じています。

Claude Code Actionsはチャット形式に似たJSONレスポンスを返すため、AIチャットの実装経験もCIのデバッグに役立ちました。

今後はさらに対象を拡大させつつ、組織的に利用するAIワークフローを充実させていきたいと思います。


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

herp.careers

イベント「事業成長に効かせるファインディ流データエンジニアリングの実践」開催レポート

こんにちは。ファインディ株式会社でデータエンジニアをしているです。

2026年4月28日(火)に、データソリューションチーム主催の採用イベント「事業成長に効かせるファインディ流データエンジニアリングの実践」を開催しました。

findy-inc.connpass.com

この記事では、イベントを企画した背景と当日の3本のセッションを参加できなかった方にもイメージが伝わるようにまとめます。

イベント開催の背景

ファインディでは、既存4プロダクトに加えて、新たに4つのプロダクトをリリースし、エンジニアの皆さまへサービスを多角的に展開しています。会社規模の拡大とともに、扱うデータの量と種類は急速に広がってきました。

prtimes.jp

変化の激しい事業環境のなかで客観的な意思決定を支えるには、社内の情報流通をより活性化させる仕組みが欠かせません。私たちは、その土台を担うのがデータエンジニアリングだと考えています。

データソリューションチームは少数精鋭で推進してきましたが、事業成長のスピードに合わせてデータ基盤をさらにスケールさせるには、共に挑戦してくれる仲間の存在が不可欠です。今回のイベントは、ファインディがどのような課題に向き合い、どのような技術と組織で解いているかを直接お話しする機会としました。

セッションは3本立てで、データ基盤・データモデリング・BIの3つの観点からファインディのデータエンジニアリングをお話ししました。

セッション1: ファインディの事業拡大を支える拡張可能なデータ基盤へのリアーキテクチャ

登壇者: 開

speakerdeck.com

事業拡大に合わせてデータ基盤をどうリアーキテクチャしているかを紹介しました。直近1年でデータソースは10倍、Google Cloudプロジェクトは6倍に増える一方、データエンジニアは3名のままで、認知コストと運用負荷が膨らんでいました。

これまでは事業=ドメインとしたデータメッシュ的な構成で、技術選定も各チームに委ねていました。アジリティは出る一方で、ドメイン間の連携不足や技術のばらつき、作業重複が課題になっていました。

そこで、データメッシュの利点は残しつつ実装を見直し、Google Cloudプロジェクトの統合、IAMのデータセット単位での管理、dbt Platformへのオーケストレーション集約やマネージドサービスの活用を進めています。これによりマネージドサービスのAPIやMCPを用いてAIエージェントに運用を一部移譲することができています。作成したスキルやサブエージェントは以前テックブログで紹介したプラグインとしてチーム全体で使えるようにしています。

tech.findy.co.jp

DataOpsの省力化が進む一方、コスト透明性の低下といった新しい課題も見え、FinOps体制の構築や、浮いた時間をデータ活用者との会話やイネーブリングに使っていくことを次のテーマにしています。

セッション2: データモデリングを通して管理会計のオペレーションを再設計

登壇者: 田頭さん

speakerdeck.com

経営判断に直結する管理会計という業務領域に対して、データモデリングの観点からオペレーションを再設計した取り組みを紹介しました。

ファインディの管理会計は、長らくスプレッドシートを中心に回っていました。月次のたびに関数とピボットを手作業で組み直し、IMPORTRANGEやVLOOKUPで絡み合ったスプレッドシートのリネージは50件を超え、どこか1枚崩れると全体が連鎖して壊れる脆さを抱えていました。同じKPIが部署ごとに別ロジックで計算されて数字が合わない、月次締めに2〜3日かかって意思決定が後追いになる、といった状態も常態化していました。

再設計の起点に置いたのは、技術選定ではなく業務担当者へのヒアリングです。「計上組織」「補助科目」「配賦」「予算番号」といった専門用語が飛び交うなか、勘定元帳やマクロを眺めるだけでは掴めない集計粒度や分析軸を、経営管理部の担当者と何度もMTGを重ねて引き出していきました。書籍『アジャイルデータモデリング 組織にデータ分析を広めるためのテーブル設計ガイド』のBEAM✲を参考に、誰が・何を・いつ・どこで集計したいのかを対話から輪郭化し、総勘定元帳を起点に売上・費用・原価を月次粒度のファクトとして整理しています。

実装は、会計データソースをGoogle DriveにアップロードしてTROCCOで取り込み、dbtで集計してLookerやスプレッドシートから参照する構成に落としています。これにより、ワンボタンで月次の実績値が揃い、想定外の科目も自動で検出できるようになりました。「どの数字が正しいか」を議論する場面はなくなり、月次締めの所要時間と数字の信頼性が同時に改善しています。

今後は、実績ファクトと同じ粒度で予算・見通しを取り込んだ予実分析の自動化や、整備済みのファクトを起点にAIエージェントが自然言語で会計分析を行える基盤への展開を進めています。

セッション3: 社内で使われるLooker整備の進め方

登壇者: 出相さん

speakerdeck.com

社内で実際に使われるBIにするためにLookerをどのように整備してきたかを紹介しました。「ダッシュボードを作った瞬間がピークになって使われなくなる」「事業部からはデータ活用の入口が見えない」「スプレッドシート運用が属人化して限界が見えている」といった、よくある課題を出発点にしています。

ファインディでは、Lookerを意思決定にひも付くダッシュボードを定常的に見る場としてだけでなく、Exploreや会話分析でデータそのものを探索する場にすることを目指しています。ただし、最初はLookerを見に行く習慣もExploreの操作にも慣れていないため、進め方の工夫が欠かせません。

そこで、ヒアリングで課題を引き出す → 最低限の機能に絞って最初のダッシュボードを素早く提供する → 共有MTGで一緒に触りながら改善ループを回す → 利用が定着してからディメンショナルモデルやメタデータを整える、という4ステップで価値を積み上げてきました。完璧な設計よりも早い体験提供を優先し、苦労していたことから先に解消していくことを大切にしています。詳しい進め方は以前のテックブログでも紹介しています。

tech.findy.co.jp

その結果、MAUは2026年1月から4月途中で約1.5倍、WAUは1月中旬から4月中旬で約2.6倍に成長しました。経営管理部からも「BigQueryやLookerを駆使したモニタリングが事業拡大に不可欠」というコメントが届くなど、Lookerが信頼できるデータソースとして社内に定着してきています。

まとめ

今回のイベントを通じて、ファインディがデータエンジニアリングをどのように事業に効かせようとしているかを、3つの異なる切り口でお伝えできたと思います。データ基盤・データモデリング・BIのいずれも、技術そのものよりも「事業や業務にどう接続するか」を軸に進めてきた取り組みです。

参加してくださった皆さん、ありがとうございました!

ファインディでは、データエンジニアリングの力で事業成長を支える仲間を募集しています。今回のイベント内容に少しでも興味を持っていただけた方は、ぜひお気軽にカジュアル面談などでお話しできるとうれしいです。

herp.careers

コーディングをAIに任せても、エンジニアの仕事は減らなかった ― ほぼ一人で1か月、AI機能をリリースしてみて

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

このたび、Findy Toolsの新機能として「アーキテクチャAI」をリリースしました。要件を入力するとAWSのアーキテクチャ図と設計の提案が生成される機能です。

findy.co.jp

今回の開発では、PM・仕様策定・スコープ定義・インフラ・FE/BE開発・テストまで、ほぼ一人で1か月で担当しました。そして、コーディングはほとんどClaude Codeに任せ、私自身はほぼコードを書いていません。

この記事では、そんな開発を進めるなかで分かったこと、難しかったこと、そして改めて実感したエンジニアの仕事について紹介します。

アーキテクチャAIについて

本題に入る前に、リリースした機能を軽く紹介させてください。

アーキテクチャAIは、Findy Toolsの中で提供している機能の1つで、「作りたいシステムの要件」を入力するとAWSを使った構成案とアーキテクチャ図、そして設計の考え方の解説がまとまった形で得られるものです。

ai-architecture.findy-tools.io

アーキテクチャの設計はサービスの土台を決める工程で、経験や周辺知識の広さが問われる領域です。そうした知見を持った人に相談しづらい場面でも、構成の検討を前に進める助けになることを目指しています。

ここから先は、この機能を一人で1か月で作ったなかで気づいたことを中心に書いていきます。

一人開発の全体像

今回は、PMから開発、テスト、リリースまでを一人で担当しました。具体的には次のような範囲です。

  • プロダクトの方向性・優先順位の決定
  • 仕様策定、スコープ定義
  • インフラの構築
  • フロントエンド・バックエンドの実装
  • テスト、リリース作業

開発のワークフローはシンプルで、だいたいこのサイクルを回していました。

  1. GitHub Issueに「なぜ作るのか」「何を作るのか」を、PRDやユーザーストーリーの形で書く
  2. Claude CodeにIssueを渡して実装してもらう
  3. 自分は差分をレビューし、必要に応じて指示を出す
  4. 別チームのメンバーにレビューしてもらい、PRをマージする

実装の途中でも、作りたい背景やユーザーに届けたい価値に立ち戻って方針を調整することはよくありました。

仕様策定やUIのモック作成も、AIと壁打ちしながら進めていました。結果として、自分でコードを書いた量はごくわずかで、ほとんどの実装はClaude Codeが書いています。

一方で、「自分はほぼコードを書いていない」からといって仕事が少ないわけではありませんでした。むしろコーディング以外にやることが山ほどあるというのが実感です。

  • 何を作るか、何を削るかの判断
  • 仕様の細部に関する意思決定
  • 技術選定
  • コードレビューと品質の担保
  • スケジュール管理とリリース計画

ここからは、この進め方で1か月やってみて感じたことを書いていきます。

エンジニアが価値とコストを自分で判断する

最も強く感じたのは、エンジニア自身がユーザー価値と実装コストのバランスを判断しながら開発に携わることの強みです。

今回は一人で担当していたので、事業やプロダクトの意図、技術的な実現可能性、実装コストが、すべて自分の頭の中に揃っていました。「ユーザー価値として外せない部分はどこか」「コストをかける価値があるのはどこか」を、自分の中でつなげて考えながら開発を進めることができます。

具体的には、開発の途中でアーキテクチャ図の描画ライブラリを切り替える判断をしたり、リリースの2週間前になってからSNSで共有されたときに見栄えの良い画像を生成する機能を追加する判断をしたりと、方針転換を素早く進められました。技術的に成立するかをClaude Codeで素早く検証しつつ、プロダクトとして本当に必要かを自分で判断できたことで、価値とコストの両取りを狙える選択肢を見つけやすくなっていました。

体制を一人にしたことが本質ではないと思っています。大事なのはやはり、作る側のエンジニアがユーザー価値や事業価値を理解したうえで、コストと価値のバランスを判断しながら開発に携われているかだと、今回の開発を通じて改めて感じました。

対話で判断の視野を広げる

意思決定が速いのは良いことですが、速ければ良いわけではありません。一つの視点だけで速く判断を続けていると、気づかないうちに筋の悪い方に流れてしまいます。

ここで効いてきたのが、一人で担当していても孤立はしていないという体制づくりでした。

開発中は、事業部長・PO・デザイナと定期的に相談・共有できる場を持ち、それ以外の場面でもいつでもコミュニケーションを取れるようにしていました。

  • プロダクトとして何を大事にしているか
  • 事業としてこの機能にどういう期待があるか
  • デザインとして譲れない部分はどこか

こうした観点を継続的にすり合わせることで、自分一人では見えていなかったより良い選択肢を見つけやすくしていました。

自分の頭の中だけで判断すると、どうしても視野が狭くなります。そこに他の立場の視点が入ると、「他にもっと良い選び方はないか」という問い直しができます。スピード重視の体制であっても、というよりスピード重視だからこそ、チームとの対話は欠かせないと感じました。

動くもので共通認識を作る

もう1つ強く感じたのが、速く見せられるもの・動くものを使って共通認識を作ることの効果です。これ自体は昔からよく言われる話ですが、AIの力でぐっとやりやすくなりました。

今回は、実プロダクトの開発を始める前に、別リポジトリでPoCアプリを作り、ステークホルダーやインタビューにご協力いただいた方に、実際に見たり触ったりしてもらう時間を取りました。

PoCアプリの画面
PoCアプリの画面。デザインは作り込まず、ユーザーに価値を体験してもらうことを優先した。

文章やスライドだけで議論すると、どうしても抽象的になり、認識のズレに気づきにくくなります。動くものがあると、「これは欲しい」「ここは期待と違う」というフィードバックが具体的に返ってくるだけでなく、「どういう価値が得られそうか」という部分でも共通認識を作りやすくなります。

PoCを見たり触ったりしてもらうなかで見えてきたのは次のようなことです。

  • ユーザーが本当に価値を感じるのはどの部分か
  • 最初はあると便利そうに見えたが、実際はなくても困らない機能
  • 技術的に見落としていた制約や、逆に想定より軽く実現できそうな部分

PoCで得られたこれらの情報が土台になって、本開発のスコープと方針が定まっていきました。AIで「仮に動くもの」を高速に作れるようになった強みを活かして、動くものを中心に仮説検証のサイクルを回せたことが、1か月という限られた期間のなかで特に効いたポイントだと思います。

自分の仕事は減らず、判断と意思決定の時間が増えた

ここまで書いてきたことを踏まえて、改めて感じたのは少なくとも今回の自分の経験では、コーディングをAIに任せても、エンジニアとしてやることは減らなかったということです。むしろ、本質的な判断に集中する必要が出てきた感覚でした。

今回の開発で自分が時間を使っていたのは、次のような部分です。

  • この機能を作る意味は何か、誰のどんな課題を解くのか
  • スコープをどこまで広げ、どこで切るか
  • どの技術を選び、どの技術を見送るか
  • 出てきたコードが本当に要件を満たしているか、将来の運用に耐えるか

どれも、意思決定と判断の仕事です。コーディングそのものをAIに任せられるからこそ、こうした判断に集中できる時間が増えました。

AIと協業するなかで変わる部分と変わらない部分があり、「コードを書く時間」は確実に減りますが、「エンジニアリングを行う時間」は減らない、むしろ増えている感覚でした。

まとめ

ほぼ一人で1か月かけてアーキテクチャAIをリリースしてみて、次のことが学びとして残りました。

  • エンジニア自身がユーザー価値と実装コストのバランスを判断しながら開発に携われると、価値とコストの両取りを狙える選択肢を見つけやすい
  • 速さだけを追うのではなく、複数の視点を対話で取り込んで選択肢を広げていく
  • AIで仮の動くものを高速に作れるようになった強みを活かせば、短期間でも動くものを軸に仮説検証を回しやすい
  • 少なくとも今回の自分の経験では、コーディングをAIに任せても仕事は減らず、判断と意思決定に集中する時間が増えた

AIと一緒に開発する時代になっても、「何を作るか」「どう作るか」「なぜそれを選ぶか」を考え抜くことの大切さは変わらないなと、今回の開発を通じて改めて感じました。

アーキテクチャAIは次のページから触れます(Findy Toolsの会員登録が必要です)。ご興味のある方はぜひ試してみてください。

findy-tools.io

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

herp.careers

RubyKaigi 2026で発表されたSpinelを触ってわかった「コンパイルが通っても動いているとは限らない」話

こんにちは。プロダクト開発部の森 @jiskanulo です。

2026年4月22日から24日までRubyKaigi 2026 Hakodateが開催されました。

rubykaigi.org

函館アリーナを3日間に渡って貸し切る大規模イベントの運営をしていただきましたスタッフの皆様に感謝を申し上げます。

ファインディ株式会社もPlatinumスポンサーとして協賛しました。 私もブースに立って出展やファインディ各サービスのご案内をさせていただきました。

お話しをしていただいた皆様にも重ねて感謝申し上げます。

ブースに立つ合間にセッションを聞いて自分でやれそうなことに思いを馳せたり、他社様のブースを回ってプロダクトのお話をしたり、昔の同僚や知人と再会したりと個人的にもいい刺激の多い3日間でした。

さて、4月24日、最後のセッションのMatzさんのキーノートにてRubyファイルをネイティブバイナリにコンパイルするSpinelが発表されました。

早速Spinelを使ってみた感想とつかいどころを記します。

Spinelとは

SpinelはRubyのAOT (Ahead-Of-Time) コンパイラです。

RubyソースコードをPrismでパースし、全プログラム型推論を行いCのコードに変換してネイティブバイナリを生成します。

パイプラインは次のとおりです。

Ruby → Prism → AST → 全プログラム型推論 → Cコード → ネイティブ実行ファイル

リポジトリのREADMEによると、miniruby比で約11.6倍、Conway's Game of Lifeでは86.7倍という性能が示されています。

またバイナリサイズもmrubyを含めずに数十KBに収まるので容量が厳しい環境にも適用できるとのことです。

実体としての./spinelはPOSIX shell wrapperで、内部でspinel_parse → spinel_codegen → ccを直列に呼び出している、という作りです。

検証環境とセットアップ

検証は手元のmac環境で行いました。

4月24日公開直後にmacで動かそうとしたタイミングではmalloc.hが存在しないなどでエラーになったのでDockerでUbuntuのコンテナを起動して操作しましたが、現在は対応されています。

土日で函館観光しているうちにコントリビュートが進んでいますね。コントリビュートのチャンスですね。

mac build対応

https://github.com/matz/spinel/commit/4593581eb87cea45b59fb28b9dcf2cd75a9bcbab

windows対応

https://github.com/matz/spinel/commit/1fe3136aa9bf834b5f37176faca7346503fb1446

環境は次のとおりです。

  • macOS (arm64)
  • Apple Clang
  • Spinel masterブランチ(2026-04-28時点)

ビルドと実行の基本フローはシンプルです。

spinelをmakeし、spinelにrubyファイルをわたせば実行形式バイナリが出力されます。

make deps          # 初回のみ: libprism 取得
make               # spinel_parse / spinel_codegen / ランタイムをビルド
./spinel app.rb    # Ruby → 実行形式(./app)
./app

動かしてみた

Matzのキーノートとも被りますがサンプルコードを動かしてみました。

またキーノートで話があったとおりevalrequireはサポートしていないとのことです。

eval, requireを使うと具体的にどうなるのかも試してみました。

Hello, World!

最小の動作確認はそのまま動きます。

# hello_world.rb
puts "Hello, World!"
./spinel hello_world.rb
./hello_world
# => Hello, World!

eval / requireの挙動:エラーにならず無音で間違う

2026年4月28日の現時点ではコンパイル時にエラーにならず、warningも出ず、しかし実行すると無音で間違った出力を返すという挙動です。

具体的に2つのケースを見ていきます。

eval は no-op に置き換わる

次のRubyコードをSpinelでコンパイルして実行します。

# test_eval.rb
code = "1 + 2"
result = eval(code)
puts result
$ ./spinel ./tmp/jiska/samples/test_eval.rb
./tmp/jiska/samples/test_eval.rb -> test_eval

実行すると結果は0です。

$ ./test_eval
0

生成されたCのコードを見るとeval()の呼び出しがno-opに置き換わり、戻り値は型推論で決まったゼロ値(mrb_intのため0)になります。

const char *lv_code = "1 + 2";
mrb_int lv_result = 0;        // mrb_int = int64_t typedef
lv_code = "1 + 2";
lv_result = 0;                // ← eval() 呼び出し自体が消滅、0 を代入するだけ
printf("%lld\n", (long long)lv_result);

mrb_int はmrubyのような」命名に見えますが lib/sp_runtime.hint64_t のtypedefとして定義されたSpinelランタイムの整数型です。

https://github.com/matz/spinel/blob/64105ec86d08c9edc92c3d17bf059126ceaa15d3/lib/sp_runtime.h#L53

require は文ごと消える

次はrequire "json"JSON.generateを使うケースです。

# test_require.rb
require "json"
puts JSON.generate({ name: "spinel", year: 2026 })

こちらも実行すると0になります!

$ ./spinel ./tmp/jiska/samples/test_require.rb
./tmp/jiska/samples/test_require.rb -> test_require
$./test_require
0

requireの行は生成されたCのコード上で文ごと消えます。

続くJSON.generateもSpinelから見れば未定義メソッドであり、evalと同じくゼロ値を返す呼び出しに化けます。

最終的にputsには0が渡って表示される、という流れです。

現時点での注意点

現時点では「コンパイルが通った」「実行ファイルができた」「実行できた」の3点だけを見ていると間違った結果を吐いていることに気づけません。

Spinel向けにRubyを書くときはREADMEを都度確認することが重要です。

Spinelを使ってみる

次に実際のユースケースを想定してみます。

実例として、GitHubのPR一覧JSONを入力に取り、各PRのnumberauthorを表示するスクリプトを書いてみました。

入力はARGV、stdinの優先順で受け取ります。

requireなしでJSONを扱う

require "json"が使えないので標準のRegexpString操作だけで処理を組む必要があります。

なお、ここで示すコードは正しいJSONパーサではありません。require "json"が使えないSpinelの制約下で、入れ子オブジェクトや},を含む文字列値が現れない、フラットな配列形式の固定データからnumberauthorをアドホックに取り出す例として読んでください。本格的なJSON処理が必要な場合は、入れ子・エスケープなどで容易に壊れるため、この実装は使えません。

実際のRubyファイルは次のとおりです。

多少トリッキーなところは感じますが、こうした限定的な用途であれば、RegexpとStringの標準機能だけで実装できることがわかりました。

if ARGV.length > 0
  input = ARGV[0]
else
  input = readlines.join
end

records = input.split(/},/)

results = []
records.each do |chunk|
  if chunk =~ /"number"\s*:\s*(\d+)/
    num = $1
    if chunk =~ /"author"\s*:\s*"([^"]+)"/
      auth = $1
      results << "##{num} by #{auth}"
    end
  end
end

if results.length == 0
  $stderr.puts "Error: failed to parse JSON (no matching number/author pairs found)"
  exit 1
end

results.each do |line|
  puts line
end

実行サンプルは次のとおりです。

引数渡し、パイプ、リダイレクトそれぞれに対応しています。

JSONを展開できない場合は標準エラーへエラーメッセージを出してexit 1します。

SAMPLE='[{"number":1,"author":"Findy"},{"number":2,"author":"jiskanulo"}]'

# 1) 引数渡し
./pr_extract "$SAMPLE"

# 2) パイプ
echo "$SAMPLE" | ./pr_extract

# 3) リダイレクト
./pr_extract < sample.json

# それぞれの出力:
# #1 by Findy
# #2 by jiskanulo
$ ./pr_extract "not json"
Error: failed to parse JSON (no matching number/author pairs found)
$ echo $?
1

なお、引数もパイプもなしで./pr_extractを起動するとreadlinesがEOF待ちでブロックします。

最初は$stdin.tty?で判定してUsageを表示しようとしましたが、実機で試すとTTYでもパイプでも両方0が返ってきました。これもeval / requireと同じく、Spinel未対応のメソッドが silent failしている例です。

# test_tty.rb
puts $stdin.tty?
$ ./test_tty           # TTY実行
0
$ echo "" | ./test_tty # パイプ実行
0

Spinelで標準入力を扱うときは$stdin.tty?に頼った分岐ができないため、引数で渡すかパイプ・リダイレクトで明示的に流し込む運用に倒すのが安全です。

ハマりどころは個別に踏みに行く価値がある

実装してみるとString#<<でmutable化するとRegexp#scanに渡せない、Regexp#scan(/(...)/) { |m| ... }のキャプチャは効かない...などとハマりどころがいくつかありました。

ただ、現時点での詳細を解説しても開発が活発に進んでいるのですぐに風化してしまうので詳しくは記しません。

Spinelを試すときの判断基準

ここまで触ってみた感触から、Spinelを触るときの判断基準を次にまとめます。

成功体験を得やすいのは次のようなコードです。

  • 入出力がputs / printfで完結する
  • 標準ライブラリに依存しない
  • 数値計算・文字列処理・正規表現(=~ + $1)で書ける範囲

逆に、現時点で踏むと詰まる、もしくはsilent failになるのは次のような領域です。

  • requireを前提とするコード(標準ライブラリの利用)
  • evalを含むメタプログラミング
  • 未対応メソッドに依存する処理(呼び出しがゼロ値返却に化ける)

「コンパイルが通ったから動いている」とは限らない、というのが現時点での最も大事な感触です。

CRubyとSpinelの両方で実行して出力をdiffする運用を組むのが安全です。

個人的所感

ローカル環境のlintやチェッカーなど今までワンライナーでやっていたことを置き換えて使ってみたいですね。

コンパイル済みのバイナリを配布できるのであれば環境構築の手間も減るかもしれません。

まだ少し触っただけですが、Spinelに可能性を感じています。また、こういうワクワクするものを提供してくれるMatzさんの凄さをあらためて実感します。

これから機能が拡充されていくのが楽しみです。

自分でもコントリビュートしていきたいですね。

おまけ: Spinelの由来

Rubyと同じく宝石つながりでSpinelなのかなあとはじめ思っていましたが、漫画アニメ『カードキャプターさくら』のスピネル・サン(スッピー)から名前をとっているそうです。

相棒はルビー・ムーンなのでここでもRubyに繋がってきますね。

由来聞いた時はそっちか〜〜!!となりました。かわいい。

おわり

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

herp.careers

PluginをCIから呼び出す:Claude Code Pluginの一歩先の使い方

こんにちは。

ファインディ株式会社でテックリードマネージャーをやらせてもらっている戸田です。

Claude CodeのPluginを使うと、社内で育てたSkillやAgentを、組織のメンバーにまとめて配布できるようになります。ファインディでも、この仕組みでセルフレビュー用のSkillを開発組織全体に配り、各自がPull request作成前に呼び出す形で活用してきました。

この記事では、そこからさらに一歩踏み込んだ使い方として、PluginをGitHub Actionsから呼び出してCIで動かす取り組みを紹介します。具体的には、Pluginに含まれるセルフレビューSkillをCIから定期実行し、指摘内容を反映したPull requestを自動で生成する仕組みです。

Pluginの使い方というと「Skillを社内で共有して、各自がローカルで叩く」という一面が語られがちですが、同じPluginをCI基盤から起動するという選択肢をとると、Pluginで育てたSkillが個人の開発体験だけでなく、チーム全体のプロセスにも効いてきます。

PluginとセルフレビューのSkillについては次の記事を参照してください。

tech.findy.co.jp

tech.findy.co.jp

Pluginで配って各自に使ってもらう運用

まず、ファインディでのセルフレビューの流れを整理します。

社内の開発用Pluginには、セルフレビューSkillが含まれていて、Pull request作成前にローカルでPull request作成用のコマンドから呼び出されます。Pluginとして配布しているので、新しくジョインしたメンバーも、追加の手順なしに同じSkillを使える状態になっています。

このあたりの「Pluginで開発ナレッジを横展開する」話は、冒頭に貼った記事で詳しく紹介しているので、そちらをぜひご覧ください。

この運用は、個人の開発体験を底上げするうえで十分機能しています。この記事が公開された頃には実に1500個ものPull requestで実行されており、「最後の一押し」として働いてくれています。

PluginをCIから呼び出す

この運用をベースに、さらにもう一歩踏み込んで、同じPluginをCIからも呼び出してみるというのが本記事のテーマです。

PluginはSkillやSub Agentなどの集まりなので、Pluginを参照できる実行環境さえ作れば、人間の代わりにCIから呼び出すこともできます。普段ローカルで走っているものと同じSkillを、anthropics/claude-code-action経由でGitHub Actionsから起動すれば、Skillを再実装することなくCI化できるわけです。

github.com

本記事では、この考え方の一例として、セルフレビューSkillを定期実行し、指摘事項ごとに別々のPull requestを自動生成するWorkflowを取り上げます。対象にするのは、既存コードのうち長期間触られていないファイルです。人間のレビューでは目が届きにくい箇所に対して、機械がレビューすることで技術的負債の蓄積を抑えるというねらいがあります。

具体的に組み上げたWorkflowは、2つのjobで動きます。

  1. 指摘抽出 job:Pluginのセルフレビュー用Skillを呼び出し、対象コードに対する指摘を抽出する。
  2. 修正Pull request作成 job(matrix):指摘の件数を matrix strategy に展開し、指摘内容ごとに独立したjobを並列起動。各jobは新しい runner 上で指摘内容を1件だけ修正したのち、PluginのPull request作成SkillでPull requestを作成

結果として、朝出社すると改善Pull requestの候補が並んでいる状態になりました。ローカルで個人が手動実行するSkillと、CIから定期実行されるSkillが、同じPluginの中身を共有している——これが、Pluginの使い方をもう一段広げてくれるポイントでした。

この仕組み自体はセルフレビュー専用のものというより、「PluginをCIから起動する」というパターンの1つの応用です。同じ構造で、様々なSkillをCIに組み込めます。

実際のWorkflowを読み解く

まずは、今回組み上げたWorkflowの全体像です。PluginのSkillをCIから呼び出して指摘、Pull requestを並列生成するという流れを一通り確認できます。

name: Scheduled Self Review

on:
  schedule:
    - cron: '0 19 * * 0-4'  # 平日 JST 04:00(UTC 19:00 Sun-Thu)
  workflow_dispatch:

env:
  SELF_REVIEW_MAX_FINDINGS: "3"

jobs:
  # ---------------------------------------------------------------------------
  # Job 1: findings 抽出
  #   セルフレビューSkillで stale files をレビューし、
  #   指摘を findings.json として artifact 化。matrix 展開用の index も出力。
  # ---------------------------------------------------------------------------
  extract-findings:
    runs-on: ubuntu-latest
    timeout-minutes: 30
    outputs:
      finding_indices: ${{ steps.compute-matrix.outputs.finding_indices }}
      findings_count: ${{ steps.compute-matrix.outputs.findings_count }}
    steps:
      - id: app-token
        uses: actions/create-github-app-token@fee1f7d63c2ff003460e3d139729b119787bc349 # v2.2.2
        with:
          app-id: ${{ secrets.SELF_REVIEWER_APP_ID }}
          private-key: ${{ secrets.SELF_REVIEWER_APP_PRIVATE_KEY }}

      - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
        with:
          token: ${{ steps.app-token.outputs.token }}
          fetch-depth: 0

      - name: Compute stale file list
        run: |
          git log --since="1 month ago" --name-only --pretty=format: origin/main \
            | grep -v '^$' | sort -u > .stale-recent.txt
          git ls-files | sort > .stale-all.txt
          comm -23 .stale-all.txt .stale-recent.txt > .stale-files.txt

      - name: Clone plugin repository
        run: |
          git clone https://x-access-token:${{ secrets.PLUGIN_REPO_READ_TOKEN }}@github.com/your-org/plugin-repo.git /tmp/plugin-repo

      - name: Extract findings via self-reviewer
        uses: anthropics/claude-code-action@4e5d8b13ca281a6d163cdb287d8917b216e00d6f # v1.0.103
        with:
          anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }}
          github_token: ${{ steps.app-token.outputs.token }}
          plugin_marketplaces: |
            /tmp/plugin-repo
          plugins: |
            example-dev-plugin@plugin-repo
          claude_args: >-
            --allowedTools Read
            --allowedTools Write
            --allowedTools "Skill(example-dev-plugin:<review-skill>)"
            --allowedTools "Skill(<review-skill>)"
            --allowedTools "Bash(cat:*)"
            --allowedTools "Bash(jq:*)"
            --allowedTools "Bash(gh pr list:*)"
          prompt: |
            .stale-files.txt に列挙された stale files を対象に、
            セルフレビューSkillを呼び出して指摘を抽出し、
            各指摘を 1 finding としてスキーマに従い findings.json に書き出せ。
            コード修正・Pull request作成は行わない(後続 matrix job が担当)。

      - id: compute-matrix
        run: |
          COUNT=$(jq length .self-review-findings.json)
          INDICES=$(jq -c 'to_entries | map(.key)' .self-review-findings.json)
          echo "findings_count=$COUNT" >> "$GITHUB_OUTPUT"
          echo "finding_indices=$INDICES" >> "$GITHUB_OUTPUT"

      - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
        if: steps.compute-matrix.outputs.findings_count != '0'
        with:
          name: self-review-findings
          path: .self-review-findings.json
          include-hidden-files: true

  # ---------------------------------------------------------------------------
  # Job 2: finding ごとに修正 + Pull request 作成(matrix で並列)
  #   各 matrix job は独立した runner で新規 checkout するため、
  #   worktree を使わずに済み、job 同士の編集衝突もそもそも発生しない。
  # ---------------------------------------------------------------------------
  fix-per-finding:
    needs: extract-findings
    if: needs.extract-findings.outputs.findings_count != '0'
    runs-on: ubuntu-latest
    timeout-minutes: 40
    strategy:
      fail-fast: false
      matrix:
        finding_index: ${{ fromJSON(needs.extract-findings.outputs.finding_indices) }}
    steps:
      - id: app-token
        uses: actions/create-github-app-token@fee1f7d63c2ff003460e3d139729b119787bc349 # v2.2.2
        with:
          app-id: ${{ secrets.SELF_REVIEWER_APP_ID }}
          private-key: ${{ secrets.SELF_REVIEWER_APP_PRIVATE_KEY }}

      - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
        with:
          token: ${{ steps.app-token.outputs.token }}

      - uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0
        with:
          name: self-review-findings

      - name: Extract single finding
        id: finding
        run: |
          FINDING=$(jq -c ".[${{ matrix.finding_index }}]" .self-review-findings.json)
          {
            echo "finding<<EOF"
            echo "$FINDING"
            echo "EOF"
          } >> "$GITHUB_OUTPUT"
          echo "branch=fix/self-review-$(date -u +%Y%m%d-%H%M)-${{ matrix.finding_index }}" >> "$GITHUB_OUTPUT"

      - name: Clone plugin repository
        run: |
          git clone https://x-access-token:${{ secrets.PLUGIN_REPO_READ_TOKEN }}@github.com/your-org/plugin-repo.git /tmp/plugin-repo

      - name: Fix finding and create Pull request
        uses: anthropics/claude-code-action@4e5d8b13ca281a6d163cdb287d8917b216e00d6f # v1.0.103
        env:
          FINDING_JSON: ${{ steps.finding.outputs.finding }}
          FINDING_BRANCH: ${{ steps.finding.outputs.branch }}
        with:
          anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }}
          github_token: ${{ steps.app-token.outputs.token }}
          plugin_marketplaces: |
            /tmp/plugin-repo
          plugins: |
            example-dev-plugin@plugin-repo
          claude_args: >-
            --allowedTools Read
            --allowedTools Edit
            --allowedTools "Skill(example-dev-plugin:<pr-skill>)"
            --allowedTools "Skill(example-dev-plugin:<review-skill>)"
            --allowedTools "Skill(<pr-skill>)"
            --allowedTools "Skill(<review-skill>)"
            --allowedTools "Bash(git switch:*)"
            --allowedTools "Bash(git commit:*)"
            --allowedTools "Bash(git push:*)"
            --allowedTools "Bash(gh pr create:*)"
          prompt: |
            $FINDING_JSON が担当 finding、$FINDING_BRANCH が担当ブランチ。
            origin/main から $FINDING_BRANCH を切り、
            affected_locations のみを Edit → PR作成Skill で Pull request を作成。

ここからは、このWorkflowを設計するうえでポイントになった箇所を取り上げて解説していきます。

トリガーとスコープ

Pull requestではなくスケジュールをトリガーにしているのは、普段のPull requestで触る機会が少ないコードにも、同じSkillの観点を届けたいからです。

さらに対象ファイルは「直近1ヶ月以上変更されていないファイル(stale files)」に絞っています。最近の変更は通常のPull requestレビューで既にカバーされている前提で、それ以外の領域にフォーカスする設計です。

絞り込み自体は、git log --sinceで直近の変更ファイル一覧を作り、git ls-filescommで差分を取るだけのシンプルな処理です。AIにリポジトリを丸ごと渡すと、コンテキストも費用も膨らむので、「今効かせる価値がある範囲」に絞り込むための前処理を1段挟むのが運用上のコツでした。

PluginをCIに引き込む

Workflow内では、社内Plugin配布リポジトリを都度cloneし、Claude Code Actionのplugin_marketplacesに渡しています。

ここで効いているのが、Skillの実体をWorkflow側にコピーせず、Pluginからそのまま引き込むという構造です。Skillの改善はPlugin配布リポジトリにマージするだけで、自動的にすべてのリポジトリのCIに反映されます。ローカルで配っているSkillと同じものが、何のコピーもなしにCIで動く。これが、「Pluginで配るとローカルで使える」から「Pluginで配ったものがCIでも走る」への、ちょっとした発想の飛躍でした。

自動生成Pull requestにもCIを走らせるための GitHub App

地味に重要だったのが、github_tokenにGitHub Appのinstallation tokenを渡している部分です。

なぜGITHUB_TOKENではなくAppを使うのか。理由は、GITHUB_TOKENで作成されたPull requestはWorkflowを自動で発火させないというGitHub Actionsの仕様にあります。

自動生成されたPull requestに対しても、通常のPull requestと同じようにCIやセルフレビューのSkillを走らせたい。しかしGITHUB_TOKENで作るとこれらが自動で発火しません。App token経由のPull requestはこの制約の対象外なので、「AIが生成したPull requestにも、人間のPull requestと同じCIが自動で実行される」状態を作れます。

「PluginのSkillをCIから走らせる」を本気で運用に乗せるなら、token戦略はワンセットで考える必要がありました。

1指摘1Pull requestに切り出す

このWorkflowで考慮した設計判断の1つが、「見つかった複数の指摘を、1つのPull requestにまとめるか、1指摘1Pull requestに分けるか」です。

最終的に選んだのは、指摘事項ごとに別々のPull requestに切り出す方針でした。理由は次の通りです。

1つ目はレビュー観点の分離です。1つのPull requestに複数の無関係な改善が混ざると、レビュアーが見るべき観点がまばらになってしまいます。「このリファクタは妥当だが、あちらは慎重に見たい」といった判断を、Pull request単位で切り分けられる状態にしたいというのが背景でした。

2つ目はrevert単位の粒度です。生成AIによる特定の変更が本番で不具合を起こした場合を想定すると、他の変更を巻き込まずに戻せるような粒度に保つのが安全であると考えました。1指摘1Pull requestであれば、revertひとつで該当の変更だけ元に戻せます。

3つ目は依存関係なしにマージできることです。複数のPull requestを1本にまとめると、どれか1つがマージできないだけで全体がブロックされます。依存関係が少なくなるようにPull requestを分けておけば、マージできるものから順に処理できます。

代償として、レビュアーは「Pull requestの件数が増える」ことになります。ただし各Pull requestの変更量は小さく、観点も絞られているので、1本あたりのレビュー時間はむしろ短めになります。

GitHub Actionsのmatrixを使って並列でPull requestを作る

指摘ごとにPull requestを分ける前提に立つと、次は「どうやって並列で安全にPull requestを作るか」が論点になります。ここで採用したのが、GitHub Actionsの matrix strategy による job 並列化です。

具体的には、job を2つに分けています。

  1. 最初の job(extract-findings)でセルフレビューSkillを走らせ、指摘内容をまとめる
  2. 後続 job(fix-per-finding)は、その指摘内容ごとに matrix に展開する。matrix の要素数ぶん、独立した runner 上で並列に job が起動する

各matrix job は、指摘内容を1件だけ取り出して修正して、PluginのPull request作成SkillでPull requestを作ります。

並列化でありがちな事故は、編集の衝突です。複数のエージェントが同じファイルを同時に触ると、どちらかの変更が失われます。今回はこれを、matrix で分散した各 job が別 runner 上で独立に checkout するという構造で、そもそも発生しない形にしました。

strategy:
  fail-fast: false
  matrix:
    finding_index: ${{ fromJSON(needs.extract-findings.outputs.finding_indices) }}

fail-fast: false にしているのは、1つのmatrix jobの修正が失敗しても他の matrix job を止めないため。AIが生成する修正は当たり外れがあるので、「一部が失敗しても残りは流す」という姿勢で運用するほうが現実的でした。

この構造で効いているのが、Pluginとして配布されているSkillの存在です。extract-findings ではセルフレビューSkill、fix-per-finding ではPull request作成Skillをそのまま呼び出すだけで、「レビューする→修正する→Pull requestを作る」という流れが組み上がります。Pluginで配ったSkillが、CIの job 分割の単位とそのまま噛み合う。これが、「Pluginで配ってローカルで使う」の一歩先にある使い方です。

まとめ

Pluginで配ったSkillをGitHub Actionsにも載せたい場合に使えるポイントをまとめます。

  1. Plugin配布用リポジトリをWorkflow内でcloneし、plugin_marketplacesに渡す。Skillの改善はPluginリポジトリへのマージだけで、ローカルにもCIにも同じ内容が反映される
  2. 自動生成Pull requestにもCIを回したいなら、GITHUB_TOKENではなくGitHub App tokenを使う。発火チェーン抑制の対象外になる
  3. 並列で動かすなら、GitHub Actionsの matrix strategy で job を分離する。各 job は独立した runner で新規 checkout するので、編集衝突はそもそも発生しない

Pluginで配ったSkillは、個人の開発体験を底上げするだけでなく、CIに載せることでチーム全体のプロセスにも効いてくる使い方ができます。「ローカルで配って使う」の次の一歩として、参考になればうれしいです。


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

herp.careers

RubyKaigi 2026 Day1 - 『Exploring RuboCop with MCP』を現地で聞いてきた

はじめに

こんにちは。プロダクト開発部 転職開発チームでエンジニアリングマネージャーをしている松村(@shakemurasan)です。

2026-04-22(水)から2026-04-24(金)までの3日間開催されているRubyKaigi 2026に現地参加しています。

rubykaigi.org

この記事は、Day1 13:00-13:30のkoicさんのセッション『Exploring RuboCop with MCP』について、事前準備編と当日編(セッション当日に残したメモと感想)を1本にまとめたものです。 事前準備編にはAIによる予想が含まれるため、実際のセッション内容と一致するとは限らない点をあらかじめ断っておきます。

事前準備編

koicさんの人となり

koicさんはRuboCopのトップコミッターで、2025年にはMCPの公式Ruby SDK (modelcontextprotocol/ruby-sdk) のメンテナーにも加わっています。

github.com

そこまでの道のりはFindy Engineer Labの取材記事『365日欠かさずコミットを積む。なぜRuboCopコミッター伊藤浩一はOSSと向き合い続けるのか』と、直近のお話はご本人のブログ『MCPの公式Ruby SDKのメンテナーに加わった』で詳しく綴られています。

findy-code.io

koic.hatenablog.com

個人的な話をすれば、私がRubyを書き始めて数年の頃、地域のRubyコミュニティで何度かkoicさんをお見かけし、ジュニア時代の自分に刺激をくれた方の一人でもあります。

AIに予想させてみたら、本人告知で焦点が絞れた

セッションタイトルだけでは論点が見えにくかったので、実は事前に予習の材料として、AIにkoicさんの直近のPRやIssueをもとに内容を予想させていました。

返ってきたのは大きく3通りの案でした。 1つ目は、stdio実装とMCP primitives(tools / resources / prompts)の写像を中心に据える構成。 2つ目は、MCPのtool annotationとRuboCopのCop属性の対応を核に据える構成。 3つ目は、Streamable HTTPを軸に据え、組織運用論にまで広げていく構成。 いずれもkoicさんの公開PRやIssueから拾えそうな筋ではあり、最初は「なるほどこのどれかだろうな」と眺めていました。

その後、koicさんご本人が『RubyKaigi 2026に登壇します』で背景と予習の案内をすでに公開されていることに気づきました。

koic.hatenablog.com

本人告知で軸に置かれていたのは「RubyツールチェインへのAI時代の課題提起」で、Streamable HTTPは副題として明示されていました。 一方で、tool / resource / promptといった「よく溢れている情報」は深掘りしない、とも明言されています。

AIの予想と本人告知を観点ごとにまとめると、次の通りです。

観点 AIの予想 本人告知
中心軸 RuboCop実装詳細 / MCP primitivesの写像 RubyツールチェインへのAI時代の課題提起
Streamable HTTP 1案の中心、他2案では末尾に触れる程度 副題として明示、講演の大きなトピック
tools / resources / prompts 写像の中心概念として扱う 「よく溢れている情報」として深掘りしない旨を明言

当たっていたのは「Streamable HTTPが主要トピックの1つ」と見立てていた点で、大きく外れたのは「tool / resource / promptを写像の中心概念として扱う」という方向性でした。 AIの予想は世に広く出回っている話題の重心に引っ張られやすく、本人が「あえて深掘りしない」と宣言していた領域こそがむしろ中心に置かれていた、というコントラストになります。 そのおかげで、当日聞きに行くべき焦点が一気に絞れました。

本人おすすめの教材とPRを少し覗いた

本人告知で紹介されていた教材のうち、RubyWorld Conference 2025でのkoicさんの登壇アーカイブに目を通しました。 「なぜstdioだけでは足りず、双方向のHTTP通信が必要なのか」という道筋が登壇の流れで追えるため、セッション前のウォームアップに助かりました。

当日編

本編は事前告知どおりの2部構成で、前半がMCP Ruby SDKの話、後半がRuboCop x MCPの話でした。 なお、当日のセッション資料は『Exploring RuboCop with MCP』としてすでに公開されています。

speakerdeck.com

以降は、現地で受け取った温度感の記録として読んでもらえればと思います。

前半: MCP Ruby SDKとStreamable HTTP

Ruby SDKはもともとShopify社内で少人数のエンジニアが始めたもので、koicさん自身もRuboCopを念頭に早い段階から参画し、コミッターに招かれたのち、mcp gemとして公開された、という経緯が紹介されました。 MCPにはTier1〜3のランクが定義されていて、現在のRuby SDKはTier3。 Tier1を目指していく、と明確に宣言していました。

本題のtransportsの話では、規格としてstdioとStreamable HTTPの2つが並びます。 stdioの単純さはさらりと触れ、時間が割かれたのはStreamable HTTPのほうでした。 HTTPの規格のままで、WebSocketのような独自プロトコルに寄らずに双方向通信を成立させる。 実装はRackアプリケーションとして組んでおり、HTTPで叩かれたタイミングでMCPセッションIDを払い出してセッションを確立し、Queueを介してやり取りを何往復も成立させる。 1リクエスト1レスポンスではなく、1リクエストを起点にサーバー・クライアント間で通信が走る、という絵が丁寧に描かれていきます。

手を動かさないと実感が湧きにくい領域ですが、Rackの上に素直に乗っているという話を聞いた瞬間、普段触っているWeb技術の延長線上で捉えていい話なのだ、と急に距離が近く感じられました。

そして前半の締めでkoicさんが置いた次のようなニュアンスの一言が、個人的に一番刺さっています。

「AIになってガラッと変わったと思われるが、Linuxであるとか低レイヤの世界は何も変わっていなくて、我々はその上で開発をしているに過ぎない」

弊社でもたびたび話題に上がる「AIの台頭によりエンジニア自身にとってはむしろ基礎が大事になっていく」という考え方と一致しており、強く共感しました。

後半: この1年間の試行錯誤

後半はトーンが少し変わりました。 「1年間いろいろ試行錯誤してきたが、うまくいかなかったことが多かった。その話をします」とkoicさんが率直に切り出します。 成功譚ではなく、試行の経過と現在地の共有だ、というモードに切り替わりました。

最初のアイデアはシンプルで、RuboCopの結果をJSON構造でMCP化すればAIフレンドリーになって良いのではないか、というものでした。 しかし実際に手を動かしてみると、RuboCopはそもそも著名なgemなので、AI側もすでに相当学習していたように感じられた、と振り返ります。 わざわざMCPの皮を被せなくても、ある程度のことは既にできてしまう相手だった、ということです。

アイデアを広く募集して議論もしたものの、寄せられたものをそのまま実装して全員が喜ぶ機能になる自信は持てなかった、とも続きます。

結びへ: 決定性と非決定性、そしてElicitation

そこから結びに向けて「RuboCopはこれまで、インプットに対して結果が一意に定まるものだった」と改めて位置づけ直されます。 「決定性こそがLinterとしての価値だった」という前提を言葉にしたうえで、そこに非決定性を持つLLMを組み合わせたら何ができるのか、と続きました。

具体例として並べられたのは、LLM側に委ねるSamplingと、ユーザーに聞き返すElicitationでした。 どちらもサーバーからクライアントに問い合わせを返せる仕組みですが、自分の耳に特に残ったのはElicitationのほうです。 ユーザーに対して定まったフォーマットの質問と回答フォームを返せる仕組みで、「この修正、どう進めたい?」と聞き返す余地をMCPの上で作れる、という話として置かれていました。 決定性のあるRuboCopの流れのなかに人間に問いを返すポイントを差し込み、そこを挟んだ非決定性を許容する。 決定性を捨てる話ではなく、どこで問いを立て、どこで非決定性を受け入れるかの設計の話として受け取りました。

この時私が思い出したのは、自チームでRuboCopのルール改廃を議論する時の話です。 一元的に適用するか悩ましいものが出てきたとき、その悩みには既存コードの暗黙的な規約、各エンジニアの思想や経験、copとして定義されているベストプラクティス、と様々な要素が絡んでいます。 これらは機械的に一意には定められないものです。 この統治過程にLLMが自然な形で介入し、対話を経てLinterに落とし込んでいくのはありそうな役割だと感じました。 (それをRuboCopの機能として提供されてユーザーが嬉しいかはさておき)

ちなみにMCPの規格としてElicitationが発表された当時、弊社でも管理者向けのLocal MCP Serverにこれを組み込む事例があり、『生成AI時代のサービス運営管理 - MCP Server for Administratorの実践 -』(2025年9月16日公開)としてまとめられています。

tech.findy.co.jp

一方通行になりがちなMCPのやりとりに confirm / approveのステップを挟むという考え方は、koicさんが当日RuboCopの文脈で示したElicitationの位置づけとそのまま重なるものでした。

聴き終えて腹落ちしたこと

少なくともセッションの内容は「RuboCop x MCPのベストプラクティスがバシッと決まっている」わけではないということです。 むしろ、当事者として踏み抜いた人しか持ち得ない肌感を、苦労ごと公開してもらった30分だった、というのが近いと思います。 文字にするとシンプルですが、現場で聴講した皆さんにはkoicさんの試行錯誤と苦労が生々しく伝わっていたのではないかと思います。 現地参加する醍醐味をこういうところにも改めて感じました。

MCP化が思ったほど刺さらなかったこと、アイデアを集めても一意の答えに収束しないこと、Human in the Loopをどう設計するかが実装より先にあること。 どれも、ドキュメントや事例紹介を眺めていても掴めない、当事者として踏み抜かないと分からない種類の手触りです。

前半と後半を通して一本の線で繋がっていたのは、RubyツールチェインをAI時代にどう位置づけ直すかという問いだったように思います。 Streamable HTTPという具体的な技術基盤の話と、RuboCop x MCPという応用の試行錯誤、そして「低レイヤの世界は何も変わっていない」という立ち位置。 3つが重なって、Rubyのツール群とAIの関係をReframingする、という輪郭が浮かび上がる構成になっていたように私は感じました。

先頭に立ってこれだけの領域を踏み抜いてきたkoicさんへは、感謝と尊敬の気持ちでいっぱいです。 Ruby SDKへの貢献はRuboCop単体の話に留まらず、Rubyツール群全体が立つ足場を整える仕事なのだろう、と自分には受け取れました。 本セッションはAI時代にもRubyが最前線で使われる言語であり続けるための重要な歩みの1シーンを見せてくれたのだと改めて感じました。

おわりに

AI時代だからといって銀の弾丸はなく、Rubyツールチェインも、HTTPやRackやQueueといった土台の上で地道に積まれていきます。 前半の締めの「低レイヤの世界は何も変わっていなくて、我々はその上で開発をしているに過ぎない」という言葉は、AIに振り回されずに使う側に回るのに必要なのは近道ではなく基礎だ、ということを改めて思い出させてくれました。

ファインディでは、一緒にRubyやRailsの開発をしてくれる仲間を募集しています。 興味のある方は、ぜひこちらからチェックしてみてください! herp.careers

Findy初のモバイルアプリ開発におけるReact Nativeのリアル 〜技術選定の裏側と実践的OSS活用〜

こんにちは。ファインディ株式会社でモバイルエンジニアをしている加藤です。

先日、「React Native Lunch Talk ~いま選ばれる理由とアプリの現在地~」にて、「新規サービス開発におけるReact Nativeのリアル〜技術選定の裏側と実践的OSS活用〜」というテーマで登壇しました。

本記事は、その発表内容を改めてテックブログとして書き起こしたものです。

発表では時間の都合で駆け足になった部分や、質疑応答で答えきれなかった論点もあったため、本記事ではそのあたりも含めて踏み込んで書いています。

背景:Findy Events β版の開発

前回の記事 でも少し触れましたが、昨年、Findy初のモバイルアプリ「Findy Events」をα版としてAndroidアプリのみリリースしました。

現在はα版から得た学びをもとにUI・UXをフルリニューアルし、技術カンファレンス向けのiOS/Androidアプリとしてβ版のリリースを目指して開発を進めています。

Findy Eventsが長期的に目指しているのは、「カンファレンスでの学びとつながりを、その場限りで終わらせず継続的な成長の資産に変える」ことです。

β版では、その第一歩として、カンファレンス当日の体験をスムーズにする次のような機能を提供予定です。

  • QRコードを提示してのチェックイン
  • 予約済みセッションやタイムテーブルなど、カンファレンス情報の閲覧
  • カンファレンス前日やセッション開始前に届くリマインダーPush通知

Findy Eventsの主な機能

本記事では、そんな新規モバイルアプリの立ち上げにあたって、なぜReact Nativeを選ぶに至ったのか、そしてOSS選定や実装で直面したリアルな所感について紹介します。

前回の記事 では技術選定の結論だけを紹介しましたが、本記事ではその判断に至るまでの比較過程やOSS選定の裏側までを踏み込んで書いています。

React Nativeの技術選定の背景

まず触れておきたいのは、「なぜ数ある選択肢の中からReact Nativeを選んだのか」という話です。

Findyにとって初のモバイルアプリ開発。しかも、技術選定を一から設計できる、エンジニア冥利に尽きる環境です。

ただ、自由度が高いということは、言い換えれば判断の重みも大きいということ。「モバイルアプリを開発する」と一言で言っても、その道筋はひとつではありません。

出発点として置いたのが、次の3つのアプローチの比較です。

アプリ開発のアプローチ表

最適な開発手法は、置かれた環境と考え方で変わる。これが私の基本スタンスです。

そして今回の前提は、「最小リソースで最速リリース」。この一点に照らして、まずCross Platformを選びました。

次に、Cross Platformの中から、国内外で実績が豊富なFlutterとReact Nativeの2つに絞って比較しました。

次の表は、iOSエンジニア出身である私の主観も踏まえて、2025年8月頃に整理したものです。

Cross Platform Frameworkの比較

一見、表だけを見ればFlutterが優位に映ります。

正直なところ、私自身も最初はFlutterに馴染みを感じていました。

それでも最終的に選んだのは、React Nativeです。

決め手は、「組織のアセット」と「モバイルエンジニアとしての自身のナレッジ」の掛け算。この2軸をかけ合わせることこそ、「最小リソースで最速リリース」という目標に最短で近づく道だと確信しました。

仮にFlutterを選べば、私だけでなく将来参画するメンバー全員に一定の立ち上げコストがのしかかります。特にモバイルエンジニア出身でないメンバーほど、負担は大きい。

対してReact Nativeなら、ReactとTypeScriptの素養を持つメンバーが多い今の組織にそのまま馴染みます。

自分さえ立ち上がってしまえば、その後の開発速度は中長期的に最も出せる。これが最終的な決め手です。

具体的に、「組織のアセット」と「モバイルエンジニアとしての自身のナレッジ」についてですが、「組織のアセット」としては、

  • 社内の優秀なWebフロントエンドエンジニアからReactやTypeScriptの知識提供、レビュー協力を得られる
  • React製の既存プロダクトの設計思想やソースコードを参考にできる

という期待感がありました。

また、「モバイルエンジニアとしての自身のナレッジ」として、

  • iOS/Androidのプラットフォーム特性への理解
  • プッシュ通知などのモバイル固有の課題への対応力

を活かすことができると考えていました。

「社内のWeb資産をどこまで活かすことができたのか?」という部分が気になる方もいらっしゃるかもしれません。結果としては、次の技術スタックのとおり、組織のアセットをフル活用することができました。

Findy Eventsの技術スタック

実は、アーキテクチャに関しても、既存のWebプロダクトをほぼ流用する形で開発しています。

つまり、設計思想や実装パターンまで踏み込んで参考にできるほど、React NativeはReactと親和性が高いということです。

OSSモジュールの選定と活用事例

続いて、OSSモジュールの選定についてです。

iOSエンジニア出身の私がReact Nativeに触れて最初に感じたのは、「とにかくOSSが豊富」ということ。

ただ、裏を返せば「多過ぎて、どれを選べば良いか迷う」という別の課題が立ち上がってきます。

そこで採った方針は、Expo公式ドキュメントを「羅針盤」として活用すること。

Expo公式ドキュメントは非常に充実していて、Expo公式モジュールはもちろん、推奨される3rd Party OSSも明記されています。第一候補をここに置くだけで、選定コストは大きく圧縮できるというわけです。

もちろん、これで100%をカバーできるわけではありません。その場合は、プロダクトのコンテキストに合わせて独自に選びます。

ここからは、その独自選定した2つのOSSを紹介します。

独自選定の事例1:Sign in with Apple

モバイルアプリの認証では、GitHub・Google・Appleの3つのOAuth認証を新規に導入する方針を採りました。中でもSign in with Appleは、iOSの審査要件として必須の機能です。

そこで選定軸に据えたのは次の2点です。

  • 審査で認証ボタンのデザインも厳格に確認されるため、iOS SDK標準のASAuthorizationAppleIDButtonを内部で利用していること
  • iOSだけでなくAndroidでもSign in with Apple機能を提供できること

Expo公式には expo-apple-authentication があるものの、Androidが対象外。今回は見送りました。

最終的に選んだのは、両OSに対応した react-native-apple-authentication です。

独自選定の事例2:UIライブラリ

冒頭で述べた通り、自社初のモバイルアプリということもあり、社内にモバイル用のデザイン資産はありません。「最小リソースで最速リリース」を実現するには、UIライブラリの活用が欠かせない要素になります。

選定軸に据えたのは次の2点です。

  • 豊富なUIコンポーネントが提供されていること
  • iOSエンジニア出身の自分にとって、学習コストが高すぎないこと

実はα版では Tamagui を採用していました。ただ、β版でUI・UXをフルリニューアルすることが決まり、より要件に合致するライブラリを改めて探すことに。

たどり着いたのが、選定軸2点をしっかり満たす HeroUI Native です。

ここで一つ頭を悩ませたのが、採用を決めた当時(2026年1〜2月頃)のHeroUI Nativeがまだβ版だったこと。利用によって課題が顕在化するリスクを抱えての判断になります(※現在はstable版が提供されています)。

そこで採った工夫が、「HeroUI NativeのWrapper Componentを実装し、画面側からは直接HeroUI Nativeに触れない構成」にすること。

UIライブラリ活用時の工夫

影響範囲をWrapper層に閉じ込めておけば、将来の差し替えや仕様変更への耐性が確保できる。β版OSSを採用する際のリスクヘッジとして、有効な型の一つだと感じています。

React Nativeに対するリアルな所感

立ち上がりは、AI時代でも想像以上に苦労した

React Nativeを選んだ結果として率直に感じたのは、「AI時代と言えど立ち上がりにはそれなりに苦労した」ということ。

実は10年ほど前に少しだけReactに触れたことがあったのですが、React HooksやLifecycleに相当する考え方はほぼ初学者の状態。概念の再学習が必要でした。

また、TypeScriptも「Swiftと似て非なるもの」であることを痛感します。

型による安全性という思想は近いものの、asによるキャストやenumがベストプラクティスとしては非推奨とされている点など、Swift感覚で書くと足をすくわれる場面が少なくありません。

一方で、React Native自体に対するハードルはそれほど感じません。理由は次の2つです。

  • 宣言的UIによるUI構築は、SwiftUIで経験していた
  • プッシュ通知など、モバイル固有機能の仕組みそのものを理解していた

LT会では「どうやってReact Nativeを勉強したか?」という質問もいただきました。取ったアプローチは、過去にSwiftで開発していた個人アプリをReact Nativeで書き直してみるというもの。

題材を一から考えずに済む上に、Swift版という答え合わせの対象がある状態で進められるため、SwiftUIとの共通点や差分を体感しながら学べます。

こうして実感できたのは、React Nativeを選んでも、モバイルエンジニアとしての経験や強みは十分に活かせるということです。

OSS活用で感じたこと

OSS活用の面でも、幾つか印象的な気付きがあります。

1つは、OSSの更新速度の速さです。

React / React Native界隈は更新サイクルが早く、iOS Nativeとの文化の違いを肌で感じます。

加えて、脆弱性検知などのエコシステムが整っており、開発者体験として足かせになっていない点も心強いところです。

もう1つは、Expoの利便性への驚きです。

ExpoはReact Native界隈ではデファクトスタンダードと言える立場を確立しており、信頼性も高く、証明書やリリース周りといった「モバイルアプリ開発ならでは」の知識を手厚くカバーしてくれます。

これは、Webフロントエンドエンジニアがモバイルアプリ開発に参入するハードルを相当下げていると言って良いでしょう。

実際、Webフロントエンドエンジニアの方から「React Nativeって実際どうなの?」と聞かれた際は、Expoのおかげで「モバイルアプリ開発は想像以上に始めやすい」と答えることができています。

AI時代に、今からReact Nativeをやる意味

最後に登壇の中では時間の都合で踏み込めなかった論点について書いておきます。

それは、「AI時代に、今からReact Nativeをやることに意味はあるのか?」という問いです。

個人的には、少なくとも次の3点で意味があると考えています。

1つ目は、iOSとAndroidを同時に立ち上げる中で、OS間の差分に向き合う経験が積めることです。

Cross Platformと言えど、プッシュ通知や認証、OSの作法といった領域では差分が必ず顔を出します。

AIに任せれば書ける時代だからこそ、差分の勘所を自分の中に持っているかどうかが効いてくると考えています。

2つ目は、アーキテクチャ選定やOSS選定といった、プロダクトの土台を作る判断を経験できることです。

ゼロからモバイルアプリを立ち上げる機会はそう多くなく、「何を選んで、何を選ばなかったか」を自分の言葉で語れるようになる経験は、AI時代でも色褪せにくい資産だと感じています。

3つ目は、Webフロントエンドへの足掛かりになることです。

React NativeでReactやTypeScriptを書いている時間は、そのままWebフロントエンドの学習コストを前払いしていると言えるかもしれません。

そのため、モバイルエンジニアとしてWebフロントエンドへ領域を広げたい人にとって、React Nativeは自然な入り口になると思います。 (もちろん、Webフロントエンドエンジニアとして、モバイルアプリへ領域を広げたい人にとっても同様です。)

まとめ

Findy初のモバイルアプリ開発を通じて、React NativeやOSSの選定と実装で多くの知見を得ることができました。

初めは慣れないReact Nativeに四苦八苦することもありましたが、一度慣れてしまえば、モバイルエンジニアとしての強みを存分に活かすことができると実感しました。

AI時代においては、人がガードレールとしてソフトウェアの責任を背負う場面がますます増えていきます。

その観点で見ても、React Nativeは「AI時代に、人が責任を取れる技術」として十分に選定に耐えうると考えています。

モバイルアプリの技術選定に迷っている方や、iOS/Androidの経験を活かしてReact Nativeに踏み出そうとしている方は、ぜひ一度触ってみてください。

本記事がその一歩を踏み出すきっかけになれば幸いです。

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

herp.careers