TECH PLAY

Findy/ファインディ

Findy/ファインディ の技術ブログ

167

こんにちは。Developer Productivity Engineer(DPE)兼 Findy Tech Blog編集長の高橋( @Taka-bow )です。 おかげさまで、Findy Tech Blogは2年目に突入しました。 この1年で、私たちのブログは多くのエンジニアやIT業界関係者の皆さんにご愛読いただき、記事の内容もますます多様化・充実してきたと感じています。 日々の開発現場で役立つノウハウや、最新技術のトレンド、エンジニアの日常など、さまざまなテーマを取り上げてきました。 「現場で本当に使える知見がほしい」「他社のエンジニアはどんな工夫をしているのか知りたい」「自分の成長のヒントを探したい」――そんな方々にとって、少しでもヒントや刺激となる記事をお届けできていれば幸いです。 今回は、2025年上半期に特に多くの反響をいただいた記事を、ランキング形式でご紹介します。 技術のトレンドから生産性向上テクニックまで、見逃していた記事が見つかるかもしれませんよ! それでは、2025年上半期のFindy Tech Blogを一緒にふりかえっていきましょう! 2025年上半期トピックス 「テックブログ運営 井戸端会議〜2025年を走り切るために〜」というLT大会を開催 【2025年上半期(1月〜6月)】PV数ランキングTop3 第3位 第2位 第1位 【2025年上半期(1月〜6月)】はてなブックマーク数Top3 第3位 第2位 第1位 おわりに 2025年上半期トピックス 「テックブログ運営 井戸端会議〜2025年を走り切るために〜」というLT大会を開催 年明け早々に「テックブログ運営」という、ありそうでないテーマでLT大会を開催しました。 はじまった〜📝 #techbrew_findy https://t.co/uFBws89kch pic.twitter.com/ERWbz5ZcT2 — ゆーだい@Findy Team+ (@dai___you) 2025年1月27日 LT大会では、株式会社ログラスの粟田さん、株式会社Goalsの今村さん、株式会社リブセンスの鈴木さん、フューチャーアーキテクト株式会社の伊藤さん、株式会社タイミーの吉野さん、ウェルスナビ株式会社の長さんという豪華なメンバーにご登壇いただきました。 テックブログと一口に言っても、その目的や運営の悩みは本当にさまざまであることを、あらためて実感しました。 当日の雰囲気や盛り上がりは、Xのまとめからも感じていただけると思います。 posfie.com ファインディからは、戸田さんが Findy Tech Blog の誕生秘話や立ち上げ時のターニングポイントについて、私(高橋)は運営における定量的な測定目標の考え方や、普段どんな点を意識しているかについてお話ししました。これらは今も変わっていません。資料もぜひご覧ください! speakerdeck.com speakerdeck.com 【2025年上半期(1月〜6月)】PV数ランキングTop3 2025年上半期、Findy Tech Blogには数多くの注目記事が登場しましたが、ここからは、上半期で一番アクセス(PV)のあった記事を紹介します! 第3位 \ 第3位 /13,710 PV ファインディのエンジニアたちが自身の成長に影響を与えた一冊を語る人気シリーズ「エンジニア達の人生を変えた一冊 Part4」がランキング。 記事では、『コンピュータの構成と設計』(通称「パタ&ヘネ」)や『体系的に学ぶ 安全なWebアプリケーションの作り方』、そして『現場で役立つシステム設計の原則 変更を楽で安全にするオブジェクト指向の実践技法』といった名著が取り上げられており、それぞれがどのようにエンジニアたちの視点やスキルに変化をもたらしたのかが語られています。 とても実直なラインナップでしたが、多くの方に読まれました。ぜひチェックしてみてください! 第2位 \ 第2位 /14,012 PV 2位は、こちらも人気シリーズの「エンジニア達の自慢の作業環境を大公開 Part6」でした! 第6弾となるこの記事では、ファインディエンジニアの安達さん、土屋 (しゅんそく)さん、秋田さんの3名それぞれが、生産性向上と快適性を追求した作業環境を詳しく紹介しています。単なるガジェット紹介にとどまらず、各エンジニアがどのような思想で作業環境を構築しているかが詳しく語られています。生産性向上、身体への配慮、空間の美しさやコストパフォーマンスなど、それぞれ異なる優先順位で環境を整えており、エンジニアの多様な働き方スタイルが見えてくる内容となっています。 第1位 \ 第1位 /14,743 PV 2025年上半期、最も多くの方に読まれた記事は「【エンジニアの日常】これが私の推しツール!〜日々の開発を豊かにするおすすめツール〜 Part2」でした。 エンジニアの日常シリーズの人気企画「推しツール」第2弾。Findyのエンジニアたちが日々の開発で愛用しているツールやOSSを紹介し、Neovimやlazygit、Raycastなど、業務効率化に役立つツールの活用法やおすすめポイントを詳しく解説しています。他のエンジニアの工夫やツール選びを知りたい方におすすめの記事です。 2025年上半期のトップ3は、いずれも【エンジニアの日常】シリーズからのランクインとなりました。 【2025年上半期(1月〜6月)】はてなブックマーク数Top3 続いては、はてなブックマーク(はてブ)の数が多かった記事のランキングをご紹介します。 PVランキングとはまた違った切り口で、読者の皆さんが「あとでじっくり読み返したい」「何度も参考にしたい」と思った記事が上位に並びました。 日々の業務や学びの中で役立つと感じていただけた記事が多くランクインしているのではないでしょうか。 それでは、はてブ数Top3を見ていきましょう! 第3位 \ 第3位 /131 users Findyの爆速開発シリーズの一環として、生成AI活用の実践編を紹介しています。MCPサーバーの作成を通じて、実際の開発現場でどのようにAIを活用しているか、具体的な実装方法や効果について詳しく解説しています。AI技術を活用した開発効率化に関心のあるエンジニアにおすすめの記事です。 第2位 \ 第2位 /136 users PVランキングでも3位に入った人気シリーズ「エンジニア達の人生を変えた一冊 Part4」が、はてブランキングでも2位にランクイン! 第1位 \ 第1位 /190 users 2025年上半期、最も多くのはてブが付いた記事は「Findyの爆速開発を支えるタスク分解」でした! ファインディの開発チームが実践している効率的なタスク分解手法について詳しく解説。複雑な機能開発を小さな単位に分割し、段階的に実装していくことで、開発速度と品質を両立させる方法を紹介しています。開発生産性向上に関心のあるエンジニアやマネージャーに特に人気の記事となっています。 おわりに 2025年上半期のトピックスや、公開した35本の記事の中から注目の内容をランキング形式でご紹介しました。ここでご紹介しきれなかった記事もたくさんありますので、ぜひ他の記事もあわせてご覧いただければと思います。 今後も新しい技術への挑戦や、ファインディならではの開発の取り組みについて、積極的に発信していきます。 また、読者の皆さんからのコメントやSNSでの反応は、私たち編集部にとって大きな励みです。 これからも「面白い!」「参考になった!」と思っていただける記事をお届けできるよう、ファインディエンジニア一同取り組んでまいります。 引き続きFindy Tech Blogをよろしくお願いいたします。 ファインディでは一緒に会社を盛り上げてくれるメンバーを募集中です。興味を持っていただいた方はこちらのページからご応募ください。 herp.careers
アバター
こんにちは。データエンジニアの田頭( tagasyksk )です。 本記事では、MCPとAIエージェントを活用して、複数CRMの顧客情報を横断的に検索できるようにした事例をご紹介します。 背景 システム構成図 技術選定 エージェント間連携の簡易さ Google Cloud統合 MCP Toolbox for Databasesによる簡単なBigQuery接続 工夫した点 オーケストレーションの設計 ツールを搭載したサブエージェントの呼び出しについて Slack統合 導入後の効果 今後の展望 終わりに 背景 ファインディでは、エンジニア組織をあらゆる場面で支援するため、複数のプロダクトを展開しています。 事業成長に伴う課題として、お客様の大切な情報がプロダクト毎にサイロ化してしまう状況が起きました。 そこで、この課題を解消し、一社でも多くのお客様にファインディの価値を届けるため、CRMに蓄積されていた顧客情報をBigQueryに集約し、部門横断で利用できる「共通企業マスタ」を構築しました。 これにより、全部門が常に同じ最新の顧客情報を参照できるようになり、スムーズな連携が可能な体制を整えました。 しかし、この共通企業マスタをどのようにセールスチームやCSチームに届けるかが新たな課題となりました。 全員にSQLを書かせるのは現実的でなく、新しい分析インターフェースを作るのも工数がかかります。そこで、MCPとAIエージェントを活用したSlack上での企業検索システムの構築に取り組みました。 システム構成図 Slackとソケットモードを利用して疎通するAIエージェントサーバーと、後述するBigQueryのMCPサーバーをCloud Runのマルチコンテナ構成を利用してホスティングしています。 技術選定 AIエージェントのフレームワークには、GoogleからリリースされているAgent Development Kit(ADK)を採用しました。 google.github.io ADKを選定した理由は次の通りです。 エージェント間連携の簡易さ Google Cloud統合 MCP Toolbox for Databasesによる簡単なBigQuery接続 エージェント間連携の簡易さ ADKでは、次のようにシンプルなコードでエージェント間でのオーケストレーションを実装できます。 今回のケースでは、企業の業務情報や従業員数をWEBから取得したいユースケースがあったので、Google検索ができるエージェントをサブエージェントとして実装しています。 # Google検索で企業の情報を取得するエージェント google_search_agent = LlmAgent( model= "gemini-2.5-flash" , name= "google_search_agent" , description= "A helpful AI assistant that can search company information from google." , instruction=GOOGLE_SEARCH_AGENT_INSTR, tools=[google_search], ) root_agent = LlmAgent( model= "gemini-2.5-pro" , name= "company_master_agent" , description= "A helpful AI assistant that can search company." , instruction=ROOT_AGENT_INSTR, sub_agents=[google_search_agent] #Google検索エージェントをサブエージェントとして追加 ) タスクごとに細かくサブエージェントを立てられるため、非常に見通しの良いエージェントシステムが構築できます。 Google Cloud統合 データ基盤をBigQueryで構築しており、Geminiも活用している弊社にとって、権限やホスティングに必要な知識を新しくキャッチアップする必要がないのはかなり大きかったです。 MCP Toolbox for Databasesによる簡単なBigQuery接続 MCP Toolbox for Databasesでは、次のようなyamlファイルを定義するだけで簡単にBigQueryへの接続情報をMCP化できます。 my-bigquery-source : kind : bigquery project : ${your_project} location : asia-northeast1 toolsets : search_company : - search-company-by-name tools : search-company-by-name : kind : bigquery-sql source : my-bigquery-source description : 企業情報を企業名から検索する。 parameters : - name : company_name type : string description : 企業名 statement : SELECT * FROM `${your_dataset}.companies` WHERE company_name LIKE CONCAT('%', @company_name,'%'); MCPサーバーは、次のようなコマンドで簡単に起動できます。 containers: - image: us-central1-docker.pkg.dev/database-toolbox/toolbox/toolbox:latest name: toolbox args: - "toolbox" - "--tools-file" - "/app/tools.yml" - "--address" - "0.0.0.0" - "--port" - "5001" エージェントからは次のように参照させると、yaml上で登録したtool(今回は search-company-by-name )を使えるようになります。 from toolbox_core import ToolboxSyncClient toolbox = ToolboxSyncClient( "http://localhost:5001" ) search_company_tools = toolbox.load_toolset( "search_company" ) agent = LlmAgent( model= "gemini-2.5-pro" , name= "company_search_agent" , description= "A helpful AI assistant that can search company in BigQuery" , tools=search_company_tools ) このように事前にBigQueryとの接続を定義しておくことで、意図したSQLを使ってデータにアクセスさせる事ができ、不要なデータベースの参照や誤ったコマンドを防ぐことができました。 工夫した点 オーケストレーションの設計 メインエージェントのInstructionはサブエージェントのユースケースのみを記載し、BigQueryやGoogle検索の具体的な利用方法はサブエージェントのInstructionに記載しています。 これにより、メインエージェントは全体のオーケストレーションに集中し、各サブエージェントは専門的なタスクに特化できるような設計と なっています。 ツールを搭載したサブエージェントの呼び出しについて 2025年7月現在、ビルトインツールやPythonの関数で定義したツールを搭載したエージェントを2つ以上同時にサブエージェントとして呼び出すことができません。 github.com エラー回避策として、エージェントをToolとして呼び出すAgentToolというラッパーを使っています。 # toolを持ったサブエージェントを定義 google_search_agent = LlmAgent( ~~~, tools=[google_search], ) bigquery_search_agent = LlmAgent( ~~~, tools=[bigquery_search], ) # AgentToolでラップ google_search_agent_tool = AgentTool(agent=google_search_agent) bigquery_search_agent_tool = AgentTool(agent=bigquery_search_agent) # メインエージェントからはツールとして参照 root_agent = LlmAgent( ~~~, tools = [google_search_agent_tool, bigquery_search_agent_tool], ) Slack統合 新しいツールを導入することで利用者の負担を増やすことを避けるため、既に社内で日常的に使用されているSlack上でやりとりできるシステムを構築しました。 ソケットモードで送信されてきたメッセージでエージェントを起動し、返答をSlackに返却するMCPクライアントを実装しています。 class SlackToADKClient : """Slack メッセージを ADK エージェントに橋渡しする MCP クライアント""" def __init__ (self): self.slack_app = AsyncApp(token=SLACK_BOT_TOKEN) self.session_service = InMemorySessionService() async def handle_slack_message (self, event, say): """Slackメッセージイベントを処理""" user_id = event.get( "user" ) message_text = event.get( "text" , "" ) # エージェントに問い合わせ response = await self.query_adk_agent(message_text, user_id) await say(text=response) async def query_adk_agent (self, message: str , user_id: str ): """エージェントにクエリを送信""" # セッション管理 session = await self.session_service.get_or_create_session(~~~) runner = Runner( agent=self.company_agent, session_service=self.session_service, ) # エージェントの実行 events = runner.run_async(~~~) # ~~~応答処理~~~ return response_text Slack側からは、Slack Botにメンションする形で簡単に企業情報を検索できるようにしました。 企業名で検索すると共通企業マスタの内容を回答してくれ、かつ事業内容などの企業マスタに含まれない情報はGoogle検索をベースにして回答してくれるようになっています。 導入後の効果 運用開始から1ヶ月で、インサイドセールスやカスタマーサクセスのメンバーの40%近くに利用されています。 ユーザーからは次のような声をいただきました。 各サービスごとの社内担当者がすぐわかるようになり、社内連携が迅速になった Slackで情報の確認ができるため、展示会やイベントの会場からスマホで企業の情報を得られるようになった 一方で、検索機能については次のフィードバックもいただいています。 全角アルファベットで検索したら、共通企業マスタ上では半角で登録されていて検索できなかった 同一会社名で重複しているレコードが表示され、どちらが正しいかわからない この課題については情報源である共通企業マスタの品質向上によって解決していけるものです。データ品質のテストをより厚くしたり、各CRMの担当者と協力してデータクレンジングを進めることで改善を進めています。 今後の展望 現在の検索機能をベースに、さらなる機能拡張を計画しています。 例えば、現在は企業名に基づいた検索のみ実装していますが、企業マスタに存在する様々なカラムから検索フィルタをエージェントが自動で作成し、営業リストとして提供する機能などを考えています。 終わりに いかがでしたでしょうか?今回は弊社でのMCPとAIエージェントの活用について書きました。 AIエージェントの活用は、単純な検索だけでなく、将来的にはより高度な顧客分析や提案機能へと発展させることができる可能性を秘めています。今回の事例が、同様の課題を抱える組織の参考になれば幸いです。 弊社ではデータ基盤を共に育てていくメンバーや、AIエージェントなどのデータ利活用を推進するメンバーを募集しています。少しでも興味が湧いた方はカジュアル面談お待ちしております! herp.careers herp.careers
アバター
こんにちは。こんばんは。 開発生産性の可視化・分析をサポートする Findy Team+ 開発のフロントエンドリードをしている @shoota です。 ファインディのフロントエンドでは多くのプロダクトで Nx を用いたモノレポを構築しています。 tech.findy.co.jp Findy Team+ のフロントエンドもNxを採用し、各パッケージ間の依存関係の管理やライブラリのマイグレーションなどの恩恵を受けています。 なかでも強力なキャッシュ機構をベースとしたCIの高速化はなくてはならない存在となっています。 以下は、このブログの執筆時点での Findy Team+ のフロントエンドリポジトリが実行するCIの統計情報です。 Team+ の CI実行 直近30日間でCIの実行回数は2500回、平均時間は7分です。 そしてNxの機能によって 削減された実行時間はなんと167日分(約4000時間) にもなります(キャッシュはネットワークで共有されるので開発者のローカルでも有効です)。 しかし我々はこのような強力かつ高速なCIの環境を常に維持し続けていたわけではありません! 過去には平均のCI実行時間が13分以上かかっており、開発速度に大きな影響を及ぼしていました。 そこで今回はNxでのキャッシュ率を高め、CI時間を短縮するためのNxをベースとしたリアーキテクチャの実例についてご紹介したいと思います。 CI高速化のためのリアーキテクチャ ページレイアウトモジュールの分離、ライブラリ化 共有モジュールの分割 翻訳アーキテクチャの刷新 UI コンポーネントのモジュール分割 コードベースとCI時間の比較 まとめ CI高速化のためのリアーキテクチャ 2024年のはじめ頃から、Team+のフロントエンドのCI時間は伸び続ける傾向が見え始めてきました。 プロダクトの肥大化と複雑化、そして開発メンバー数の急増によって、コードベースの増加速度も急成長をしていました。 「開発速度が上がるほどCI時間が伸びる」というアンチパターンを打開すべく、1年以上をかけてモノレポの内部構造の再設計をしました。 ページレイアウトモジュールの分離、ライブラリ化 共有モジュールの分割 翻訳アーキテクチャの刷新 UI コンポーネントのモジュール分割 今回はこの内容を紹介していきたいと思います。 ページレイアウトモジュールの分離、ライブラリ化 まず初めに目をつけたのがページレイアウトに関連するモジュールでした。Team+のサイドナビゲーションやヘッダーUIなどのページ全体のレイアウトに関わるコードです。 これらはモノレポの共有モジュールの中に配置されていましたが、その役割ゆえにほぼすべての画面で利用します。 レイアウトを変更することは稀ですが、共有モジュール内にレイアウトが所属することでモジュール全体に依存関係を持ちます。このときレイアウト以外のコードを変更をすると、レイアウトを利用しているものすべてが依存関係にあるため、全画面のテストが発生してしまいます。 そこでレイアウト関連のコードを別モジュールに分離して、依存関係が集中しないようにしました。 図1. レイアウトモジュール分離 共有モジュールの分割 次にレイアウトを分離した後の共有モジュールを更に分割し、変更の影響範囲を効率化しました。 共有モジュールの中には古くからあり、成熟しており、そしてテストの重いものがいくつかありました。このような古参モジュールは多くの機能で利用します。一方で、最近の開発ドメインに向けて作られた新米モジュールも共有モジュールに存在します。 両者が同居していると日々のコード変更頻度と重いテストが複合し、全体効率を下げる要因になっていたため、別居計画を進めました。 図2. 共有モジュールの別居 変更頻度の低い古参モジュールを分離することで、開発を進めているドメインに関連した比較的軽いテストのみがよく回るような構造にできました。 翻訳アーキテクチャの刷新 これまでは通常のモノレポ構造の再設計でしたが、CI時間に最も影響を及ぼしていたのはここで紹介する翻訳システムでした。 Findy Team+ は日本語・英語・韓国語の3言語に対応していますが、これらの翻訳は1つのモジュールで集約管理していました。 しかし機能開発の高速化・大規模化に伴って、翻訳対象の文言も急増し、さらに韓国語対応を追加したことで爆発的な依存関係の連鎖が生まれ、CI時間を圧迫してしまいました。 図3. 翻訳辞書の一元管理 画面が追加されるごとに翻訳辞書が増え、かつ翻訳モジュールの変更が発生するので、全画面のテストが実行されます。 次の画面が追加されるとその前に追加された画面もテスト対象になり、さらに実行時間が伸びます。この繰り返しによってCI時間が指数的に長くなっていきました。 そこで画面個別の辞書を画面モジュールに分散し、それぞれの画面の開発で発生するコード変更が画面モジュールのなかで閉じるようにしました。分散した辞書は画面表示前に動的に読み込んで表示できるような修正も行いました。 図4. 翻訳辞書の分散 画面がどんどん追加されても全画面のテストが起きなくなり、開発速度・並行数が上がってもCI時間が伸びない構造に生まれかわることができました。 共有モジュールの分割とは異なり、依存関係は変更せずにコード変更の発生箇所を移動することで、平均の実行時間を大きく縮めることができました。 UI コンポーネントのモジュール分割 上記の3点の改善によってCI時間は概ね安定して短縮できていましたが、更にTeam+を構成するUI コンポーネントの分離を進めました。 ボタンやフォームなどのプリミティブなUI(HTMLに近いもの)とTeam+特有のセマンティックなUIに分類し、依存の親子関係となるように分けました。 これも先述のようにボタンコンポーネントやフォームなどのプリミティブなUIは変更頻度が少なく、かつ依存度が高いことを見据えた分離です。 UI分離 変更頻度の高いモジュールとそれに依存したモジュールのテストが実行されるので、Nxのキャッシュ率を向上させることができました。 コードベースとCI時間の比較 CI高速化のためのリアーキテクチャの内容をご紹介してきましたが、タイトルの「60万行」にも触れたいと思います。 今回のリアーキテクチャを開始した時点では、Team+のコード量は次のようになっていました。 Language files blank comment code TypeScript 5422 42462 13430 353578 JSON 349 0 0 51694 以下略 SUM: 6212 46170 17230 419602 TypeScriptのコードとして約35万行、翻訳辞書を定義しているJSONを合わせると約41万行です。 これらのコードの 平均のCI実行時間が13分以上 かかっていました。 そして冒頭にも記載したとおり、現在では 平均のCI実行時間が7分程度 になっています。この改善の間にも多くの機能リリースをしてきました。 こちらが現在のTeam+のコード量です。 Language files blank comment code TypeScript 9171 68280 22279 552539 JSON 672 6 0 66888 以下略 SUM: 10453 69658 22424 635517 TypeScriptのコードとして約55万行、翻訳辞書を定義しているJSONを合わせると約62万行です。 つまりコードベースとして約1.5倍の成長をしながら、CI時間を53%程度まで削減できたのです!! まとめ ここまで拝読いただきありがとうございます。 今回は Findy Team+ の開発を支えるCIとその改善内容についてご紹介いたしました。内容を以下3点にまとめてます。 Nxベースのモノレポアーキテクチャを再設計することで変更に依存しない高速なCI環境をつくることができる。 モノレポ間の依存関係と、開発現場のコード変更の傾向をつかむことがCI速度改善のカギになる。 CI速度の改善は規模の大きなコード、組織にとって不可欠な改善のひとつと言える。 なお今回のリアーキテクチャでいちばん大変だったのは、変更対象のCI時間が基本的に長いことでした。 CI時間が長いモジュールを修正し続けているので当然なのですが、効果がでるまでの我慢期間が長かったことは胸に刻みたいと思います。 現在、ファインディでは一緒に働くメンバーを募集中です。 興味がある方はこちらから herp.careers
アバター
こんにちは。 ファインディ株式会社 で Tech Lead をやらせてもらってる戸田です。 現在のソフトウェア開発の世界は、生成AIの登場により大きな転換点を迎えています。 GitHub CopilotやCursorなど、生成AIを活用した開発支援ツールが次々と登場し、開発者の日常的なワークフローに組み込まれつつあります。 そこで今回は、弊社のGitHub Copilotの活用方法について紹介します。 それでは見ていきましょう! カスタムインストラクション MCP Agent mode Coding Agent まとめ カスタムインストラクション GitHub Copilotを始めとする生成AIツールを効果的に活用するためには、まず最初にカスタムインストラクションの設定が必要不可欠です。 docs.github.com カスタムインストラクションに関しては以前の記事で紹介しておりますので、併せてご覧ください。 tech.findy.co.jp tech.findy.co.jp カスタムインストラクションを設定することで、生成AIがプロジェクトのコンテキストを理解し、より適切な提案を行うことが可能になります。 GitHub Copilotの場合は .github/copilot-instructions.md というファイルをリポジトリに配置するだけで利用することが出来ます。 他にも、実装コード、テストコード、コミットメッセージなど、目的別にカスタムインストラクションのファイルを分けることができるようになっています。 code.visualstudio.com 弊社の場合、ファイルや変数の命名規約に留まらず、コミットメッセージやテストコードについてもカスタムインストラクションに記載しています。 コミットメッセージにSemanticVersionを採用しているケースがあり、リリース時にコミットメッセージを元にリリースノートを自動生成しています。 そのため、コミットメッセージが持つ意味合いが重要となっており、誤ったSemanticVersionになっていた場合はレビュー時に修正依頼を出すこともあります。 しかし、Copilotが作業を行いコミットまで自動で行うケースの場合、AIが生成したコミットメッセージが誤っているケースが出てきました。 そこでカスタムインストラクションに次のようなコミットメッセージのルールを記載することで、Copilotが作成するコミットメッセージに適切なSemanticVersionが設定されるようにしています。 ## コミットメッセージ規約 ### 1. 基本構造 < semantic version > - < type > : < message > ### 2. 例 - major-feat: remove user field from api params - minor-feat: add api for get user list - patch-docs: fix typo in README ### 3. 各要素の説明 #### semantic version https://semver.org/ - major - 互換性のないAPIの変更 - 既存のフィールドの削除 - 既存のフィールドの型の変更 - 必須パラメータの追加 - エンドポイントの削除や名前変更 - minor - 互換性のあるAPIの変更 - 新しいフィールドの追加 - 新しいエンドポイントの追加 - 任意パラメータの追加 - 既存の機能を拡張するが後方互換性を維持する変更 - patch - リファクタリング - ドキュメントの追加、変更 - CI/CDの追加、変更 - コードの意味に影響を与えない変更 #### type - test - テストの追加、修正 - feat - 新機能の追加 - fix - バグ修正 - chore - ビルドプロセスや補助ツールの変更 - docs - ドキュメントの変更 - refactor - リファクタリング - style - コードの意味に影響を与えない変更 - ci - CIに関連する変更 - perf - パフォーマンスを向上させるコードの変更 #### message - 変更内容を要約した72文字以内の英語のメッセージ - 技術用語は英語のままにする カスタムインストラクションには事実のみを簡潔に書くのがポイントです。 複雑で長いルールを適用させたい場合は、具体例を示すと良いでしょう。今回紹介したコミットメッセージのカスタムインストラクションにも具体例を記載しています。 また、カスタムインストラクションは最初に1度作成して完成するようなものではなく、徐々に加筆修正して精度を上げていきましょう。 このように、ドメイン知識やプロジェクトのルールをカスタムインストラクションに記載することで、AIがより適切で自分たちの開発ルールに沿った提案を行ってくれるようになります。 MCP GitHub Copilotや各種AIツールを効果的に活用するためには、MCPの活用も非常に重要になってきます。 tech.findy.co.jp カスタムインストラクションに加え、MCPを活用することで、Copilotが扱う情報の範囲を広げ、より適切な提案やタスクの実行を行うことが可能になります。 例えばGitHubのMCPを利用する場合、次のような記述で設定します。 { " servers ": { " github ": { " command ": " docker ", " args ": [ " run ", " -i ", " --rm ", " -e ", " GITHUB_PERSONAL_ACCESS_TOKEN ", " ghcr.io/github/github-mcp-server " ] , " env ": { " GITHUB_PERSONAL_ACCESS_TOKEN ": " <YOUR_GITHUB_PERSONAL_ACCESS_TOKEN>> " } } } } GitHubのMCPの設定を行い、対象のIssueの情報を取得して、その内容をそのままCopilotに渡すことが出来ます。 Issueに要件やタスクの内容を記載しておくことで、Copilotがその内容を理解し、適切なコードの提案やタスクの実行を行うことが可能になります。 SentryのMCPも同様に活用することが出来ます。次のような記述で設定します。 { " servers ": { " sentry ": { " command ": " uvx ", " args ": [ " mcp-server-sentry ", " --auth-token ", " <YOUR_SENTRY_AUTH_TOKEN> " ] } } } Sentryで障害を検知した際、MCP経由でSentryのIssueの情報を取得して、その内容をそのままCopilotに渡すことが出来ます。その情報を元に、Copilotに原因を特定してもらい、Copilotにコードを修正してもらうことが可能です。 このように、MCPを活用することで、Copilotがプロジェクトやリポジトリ、さらにそれらに関連する情報を取得して、より適切な提案やタスクの実行を行うことが可能になります。 Agent mode GitHub CopilotのAgent modeを利用することで、Copilotが自律的にタスクを実行し、コードの修正や新機能の追加などを行うことが可能になります。 docs.github.com その際にカスタムインストラクションやMCPの設定が非常に重要な役割を果たします。 また、Agent modeを実行する際に、コンテキストを絞ることも非常に有効です。 特定のファイルや、コンテキストを指定することで、Copilotが参照する範囲を絞り込むことに繋がり、結果的に生成AIの提案の精度を向上させることができます。 例えば次のような実装コードとテストコードがあるとします。単純な描画を行うコンポーネントと、それに対するテストコードです。 export const TestComponent = () => { return ( <> < h1 > Test </ h1 > < div > < div > < span > Test Text </ span > </ div > </ div > </> ); } ; import { render } from '@testing-library/react' ; import { TestComponent } from './test.component' ; describe ( 'TestComponent' , () => { const setup = () => { const utils = render( < TestComponent /> ); return { ...utils, } as const ; } ; describe ( 'render' , () => { it ( 'render snapshot' , () => { const { asFragment } = setup(); expect (asFragment()).toMatchSnapshot(); } ); } ); } ); 一見問題ないように見えますが、弊社で開発しているFindy Team+は複数言語での表示に対応しています。そのため、表示されるテキストに翻訳処理を追加する必要があります。 翻訳処理を追加して、それに伴ってテストコードも修正するのは手間がかかりますが、ルールさえ覚えれば単純作業になります。こういったケースはAIエージェントの得意分野です。 翻訳処理を一気に追加したい場合、まずカスタムインストラクションに次のような記述を追加します。 # 翻訳処理 - 辞書データを定義する - json 形式のファイルで定義する ```json:libs/feature-hoge/src/lib/locales/ja.json { "component": { "hogeLabel": "ほげ", "hogeButtonText": "ほげボタンテキスト" } } ``` - Component 内 で ` useTranslation ` を利用する - ` keyPrefix ` に ` component ` を指定する ```tsx import { Trans, useTranslation } from 'react-i18next'; export const HogeComponent = () => { const { t } = useTranslation('hoge', { keyPrefix: 'component', }); return ( <> <span>{t('hogeLabel')}</span> <button type="button">{t('hogeButtonText')}</button> </> ); }; ``` - Component のテストコードで ` I18nextTestProvider ` と ` translateResources ` を利用する - ` translateResources ` は ` libs/feature-hoge/src/lib/locales ` を相対パスで import する ```tsx import { render } from '@testing-library/react'; import { I18nextTestProvider } from '@provider/i18n'; import { translateResources } from '../../locales'; import { HogeComponent } from './hoge.component'; const setup = () => { const utils = render( <I18nextTestProvider resources={{ ja: { hoge: translateResources.ja }, }} > <HogeComponent /> </I18nextTestProvider> ); return { ...utils, }; }; describe('HogeComponent', () => { it('should render', () => { const { asFragment } = setup({}); expect(asFragment()).toMatchSnapshot(); }); }); ``` そして次のようなプロンプトをAgent modeで実行します。 あなたは優秀なフロントエンドエンジニアです。 次の指示に従って作業を行ってください。 ## 対象ディレクトリ - ` libs/feature-test ` ## 辞書データ - ` libs/feature-test/src/lib/locales/ja.json ` ## 1. 辞書データのファイルに書き込みを行う ### 作業内容 - 対象ファイルに合致する既存ファイルの実装コードから文字列を抽出する - 抽出した文字列の中で辞書データのファイルに定義されていない文字列のみを、辞書データのファイルに追加する ### 対象ファイル - Component - **/*.component.tsx ## 2. 翻訳処理を追加する ### 作業内容 - 対象ファイルに合致する既存ファイル全てに翻訳処理を追加する - 辞書データに記述されているテキストを翻訳処理に利用する ### 対象ファイル - Component - **/*.component.tsx ## 3. テストコードを修正する ### 作業内容 - 対象ファイルに合致する既存のファイル全てに翻訳処理を追加する ### 対象ファイル - Component - **/*.component.spec.tsx ## 4. snapshotを更新する ### 作業内容 - 対象ファイルに合致する既存のファイル全てのsnapshotを更新する - MCPのWallabyを使用して、変更したテストコードの実行とsnapshotの更新を行う カスタムインストラクションとMCPをAgent modeで活用することで、次のようなコードへの変更が自動で実行されます。 実装コードに useTranslation での翻訳処理が追加され、テストコードには I18nextTestProvider と translateResources を利用した翻訳処理が追加されていることがわかります。 import { useTranslation } from 'react-i18next' ; export const TestComponent = () => { const { t } = useTranslation( 'test' , { keyPrefix : 'component' , } ); return ( <> < h1 > { t( 'testTitle' ) } </ h1 > < div > < div > < span > { t( 'testText' ) } </ span > </ div > </ div > </> ); } ; import { render } from '@testing-library/react' ; import { I18nextTestProvider } from '@provider/i18n' ; import { translateResources } from '../../../locales' ; import { TestComponent } from './test.component' ; describe ( 'TestComponent' , () => { const setup = () => { const utils = render( < I18nextTestProvider resources = { { ja: { test : translateResources.ja } , }} > < TestComponent /> </ I18nextTestProvider > ); return { ...utils, } as const ; } ; describe ( 'render' , () => { it ( 'render snapshot' , () => { const { asFragment } = setup(); expect (asFragment()).toMatchSnapshot(); } ); } ); } ); このように、カスタムインストラクションを使ってリポジトリのルールやドメイン知識をCopilotに提供することで、Agent modeの精度を向上させることができます。 またMCPを使うことで、Copilotが参照するリソースを増やしたり、実行できるタスクの幅を広げることができます。 Coding Agent Coding Agentを利用することで、Copilotをバックグラウンドで独立して動作させて、人間の開発者と同じようにタスクを実行できます。 docs.github.com 使い方はとても簡単です。タスクに対するIssueを作成して、Issueの担当者にCopilotを指定するだけです。その後、Copilotが自動でPull requestを作成して、GitHub Actions上でタスクが実行されます。タスクが完了した段階で、依頼者がレビュワーに設定されます。 また、Copilotが作成した変更に対してレビューコメントを書くことが出来ます。そのレビューコメントに対する修正もCopilotが自動で行ってくれます。 Coding Agentで作成されたPull requestに対してレビューコメントを書く場合、 Add single comment で一個ずつコメントを追加するのではなく、 Start a review から複数のコメントを追加した後にまとめて送信することをオススメします。全てのコメントを一括で送信すると、Copilotは個々のコメントを個別に処理するのではなく、レビュー全体を対象に処理を開始します。 docs.github.com また、ここでもカスタムインストラクションは効果を発揮します。 Copilotが作成するPull requestの内容やコミットメッセージ、コードのスタイルなどをカスタムインストラクションに記載しておくことで、より自分たちの開発ルールに沿った内容でPull requestを作成してくれます。 Coding AgentはMCPの利用も簡単に設定することが出来ます。リポジトリの設定でMCPの設定ファイルを記述することで簡単に設定可能です。 docs.github.com また、Coding AgentがGitHub Actions上でタスクを実行する前に事前に実行しておきたい処理を設定することも可能です。 .github/workflows/copilot-setup-steps.yml というファイルを作成して、GitHub Actionsのworkflowを定義することで、事前に必要な処理を実行しておくことができます。 次の例は、Pythonのプロジェクトで必要なライブラリのインストールと、テスト用のデータベースを事前に起動しておくための copilot-setup-steps.yml です。この設定をしておくことで、Coding Agentがファイルを修正してテストを実行する時に、必要なライブラリがインストールされており、テスト用のデータベースも起動している状態でタスクを実行できます。 name : "Copilot Setup Steps" on : workflow_dispatch jobs : copilot-setup-steps : runs-on : ubuntu-latest permissions : contents : read steps : - uses : actions/checkout@v3 - name : Set up Python uses : actions/setup-python@v4 with : python-version : '3.13' - name : Start database with Docker Compose run : docker compose up -d db --wait - name : Install dependencies run : | pip install ".[test]" - name : Run migrations run : | python -m alembic upgrade head Coding Agentが作成するPull requestの内容の精度は、次の要素に大きく左右されます。 カスタムインストラクションの内容 Issueに記載されているタスクの内容 既存実装の内容 特にIssueに記載されているタスクの内容は非常に重要です。タスクは具体的に且つ簡潔に記載することが重要です。そこで重要になってくるスキルがタスク分解です。 tech.findy.co.jp Issueのタスクを細分化して箇条書きにすると良いでしょう。大きなタスクをIssueに起票する際は、まず親Issueとして大きなIssueを作成することをオススメします。この親Issueに対してはCoding Agentを利用しません。 次に、親Issueに対して子Issueとして複数のSub Issueを作成します。このSub Issueに対してCoding Agentを利用します。Sub Issueの内容は具体的に且つ簡潔に箇条書きで記載しましょう。 このように簡潔で単純なタスクの集合体であるSub Issueを幾つも作成し、それに対してCopilotを担当者として指定することで、Copilotが自律的にタスクを実行し、Pull requestを作成してくれます。 Issueを作ってCopilotを担当者に設定するだけでPull requestが作成されるので、並行して複数のPull requestを作成してもらえることも強みの1つです。エンジニアはIssueを作って、担当者にCopilotを指定して、作成されたPull requestをレビューするだけです。 Coding AgentがPull requestを作成している間に、自分はローカル環境で別の作業をやっているのは、今では当たり前の光景になってきています。 まとめ いかがでしたでしょうか? 今回紹介した機能はGitHub Copilotの機能のほんの一部であり、今回は特に重要な機能に絞って紹介しました。 カスタムインストラクションを整備しつつ、MCPを活用することが、GitHub Copilotを始めとする生成AIを効率的に活用するための最初の一歩です。 その上で、Agent modeやCoding Agentを活用することで、より自律的にタスクを実行してもらい、開発の効率化を図ることが可能になり、エンジニア自身は他の作業に集中することが出来るようになります。 生成AIを活用した開発フローに変化していく上で、生成AIが出力したコードが正しいかどうかを判断するスキルが非常に重要になってきました。 この判断するスキルは基礎と経験によって培われるものであり、今まで以上に基礎力が重要になってきていると感じています。弊社でもエンジニアとしての基礎力を重要視しており、これを強化するための取り組みも幾つか行っていますが、それはまた別の機会で紹介したいと思います。 現在、ファインディでは一緒に働くメンバーを募集中です。 興味がある方はこちらから herp.careers
アバター
こんにちは! Findy Team+ 開発チームでEMをしている ham です。 Findy Team+のバックエンドはRuby on Railsで開発しており、Rubyの静的解析ツールとして広く知られているRubocopをTeam+の開発でも積極的に活用しています。 ファインディでは「爆速開発」を掲げており、開発速度に直結するCI(継続的インテグレーション)の実行時間を非常に重視しています。具体的には、CIの実行時間は遅くとも10分以内、理想としては5分以内に完了することを目標にしています。 CIの実行時間短縮に向けた取り組みについては、以前公開した次の記事で詳しくご紹介していますので、ぜひご覧ください。 tech.findy.co.jp 今回、バックエンドのCIの実行時間を見直したところ、Rubocopの実行時間が5分以上かかっていることに気づきました。これは改善の余地があると感じ、実行時間短縮に取り組むことを決めました。 また、CIではRubocopの他にRSpecでテストを実行しているのですが、RSpecの実行時間の方がRubocopよりも圧倒的に長いためそちらの改善ばかりに気を取られて、Rubocopの実行時間は見過ごされていました。 なお、この時もテストは7分ほどかかっておりRubocopより長かったですが、こちらは並列実行と実行環境のスペックアップを駆使することで3分程度まで短縮できました。 そこで、今回はRubocopの実行時間短縮に取り組むことを決意し、その結果、5分以上かかっていた実行時間を1分未満に短縮できました! なぜRubocopの実行時間が長かったのか? CIにおけるRubocopの実行は、基本的に全てのRubyファイルに対して無条件に実行されていました。 そのため、プロジェクトの規模が拡大するにつれて、解析対象となるファイル数も増加し、比例して実行時間も伸びていったのが主な原因です。 改善内容 実行時間短縮のために、次の3つの変更を加えました。 無条件で全ファイル解析していたが、編集したファイルだけ対象とする Gitの差分情報を用いて、変更があったファイルのみをRubocopの解析対象とするようにしました。これにより、CIの実行ごとに解析するファイル数を大幅に削減できました。 ファインディではGitHub Actionsを使ってCIを実行しているため変更したファイルを取得できる tj-actions/changed-files を活用して対象ファイルを取得しました。 設定ファイルやGemがアップデートされたときは全て解析 前述の変更により通常時は変更ファイルのみを対象としますが、Rubocopの設定ファイル(.rubocop.ymlなど)やRubocop本体や関連Gemのバージョンがアップデートされた場合は全てのファイルを解析するようにしました。 これは、設定変更やバージョンアップによって、これまで問題なかった箇所が新たに指摘される可能性があるため、CIの信頼性を保つ上で重要な対応です。 ファイル指定だと除外設定が無視されたので無視されないように変更 Rubocopには、コマンド引数に対象ファイルを明示的に指定すると、.rubocop.ymlで指定されている除外設定(Exclude)が無視されるという仕様があり、本来は解析対象外のファイルまで解析されてしまうという問題が発生しました。 これを解決するため、 --force-exclusion オプションを利用できることがわかりました。こちらのオプションを指定することで対象ファイルを明示的に指定した場合でも除外設定が反映されるようになります。 bundle exec rubocop -f github --force-exclusion ${{ all_changed_files }} 結果 これらの改善を適用した結果、5分以上かかっていたRubocopの実行時間は1分未満にまで劇的に短縮されました! これにより、CI全体の実行時間が短縮され、開発者はより素早くフィードバックを得られるようになりました。Rubocopの指摘への対応も迅速に行えるようになり、コードの品質維持にも貢献しています。 まとめ CIにおける静的解析の実行時間は、テスト実行時間に比べると見過ごされがちですが、積み重なると開発体験を損なう要因となります。 今回のRubocopの実行時間短縮で行った次の対応は、実装難易度も低く簡単にできるものでしたが、大きな効果をもたらしてくれました。 無条件で全ファイル解析していたが、編集したファイルだけ対象とする 設定ファイルやGemがアップデートされたときは全て解析 ファイル指定だと除外設定が無視されたので無視されないように変更 もしあなたのプロジェクトでもCIのRubocop実行時間が長いと感じているのであれば、本ブログで紹介した内容が少しでも参考になれば幸いです。変更ファイルのみを対象とする工夫は、他の静的解析ツールにも応用できる可能性がありますので、ぜひ検討してみてください。 ファインディでは一緒に働くメンバーも絶賛募集中です。興味がある方はぜひこちらから ↓ herp.careers
アバター
こんにちは、ファインディでソフトウェアエンジニアをしている nipe0324 です。 先日、AWSさんの Coding Agent at Loft #2 〜 AI コーディング活用事例 Night - 効果的な組織導入と実践〜 (第2回目) で登壇させていただき、登壇内容を記事として書き起こしました。 この記事では、Devinと協働して 「個人のアウトプットを1.5倍にした実践3ステップ」 をご紹介します。 Devinと協働して個人のアウトプットは1.5倍に増加 2025年5月末時点では、Copilot Coding Agent、Codex、Jules などのリリースが続々とされており、自律型のコーディングエージェントが盛り上がってきています。 Devinに特化せず他のコーディングエージェントでも使える内容になっていますので、 自律型のコーディングエージェントの利活用に興味ある方 はご一読くださいませ。 ちなみに、生成AI界隈は動きが早いので、数ヶ月後はどうなるかわかりません。また、現時点での業務で試した結果の1事例として参考にしてください。 はじめに Devinと協働してアウトプットを1.5倍にした3ステップ ステップ1. Devinに教える 1-1. 小さなタスクから始める 1-2. Devinに開発のルールを教えていく ステップ2. Devinに慣れる 2-1. Devinのドキュメントを読む 2-2. 試行錯誤して勘所を掴む 2-3. Knowledgeを洗練させる 2-4. AI Friendlyな環境を整備する ステップ3. Devinと協働する 3-1. Devinに定型作業を任せる 3-2. Devinと一緒にタスクを進める 3-3. Devinにサブタスクを任せる おまけ: Devinの活用Tips Tips1. GitHub/AWSアカウント管理のワークフローを自動化 Tips2. 口調を変えるKnowledgeで親しみやすくする Tips3. PMが仕様把握のためにDevin Searchを利用 Tips4. AI駆動な仮実装 Devinに対する所感や協働して変わってきたこと まとめ はじめに Microsoftさんの最近の研究では、組織がAIトランスフォーメーションをしていくためには、次の3つのフェーズがあるようです。 Phase 1 Human with assistant :AIがアシスタントする Phase 2 Human-agent teams :エージェントがチームにジョインして、同僚として一緒に働く Phase 3 Human-led, agent-operated :人間がディレクションをして、エージェントが業務を遂行する。必要があれば人がチェックする AI Transformationの3段階(引用元: https://www.microsoft.com/en-us/worklab/work-trend-index/2025-the-year-the-frontier-firm-is-born ) その中で、今回の話はこの 「Phase2 エージェントと一緒に働く」 をトライしている話になります。 Devinと協働してアウトプットを1.5倍にした3ステップ ステップとしては次の3ステップで進めていきました。期間はDevinの導入から大体3ヶ月ぐらいかけて実施していきました。 ステップ1. Devinに「教える」 ステップ2. Devinに「慣れる」 ステップ3. Devinと「協働する」 個人的なポイントとしては、ステップ3までいくと、 コーディングエージェントの良さを感じ、日々のアウトプットをスケールしていけるという実感 をもてました。 ステップ1. Devinに教える 最初のステップは「Devinに教える」です。 Devinに小さなタスクを依頼しながら基本的な開発ルールを教えていくことで、 Devinが軽微な修正をそつなくこなせる ようになります。 1-1. 小さなタスクから始める まずは、文言変更や軽微なタスクを依頼してDevinの動きを知ります。 実際にDevinが開発している画面をみながら、Devinの開発環境・作業手順・やりとり方法などを押さえることで今後のDevinとのやりとりの土台をつくれます。 DevinとやりとりするWeb画面 アンチパターンとして、エージェントへの期待値が高すぎることで、最初から「急ぎのタスク」や「大きめなタスク」を依頼し精度がでなく幻滅してしまうことがあります。 人と同じで小さなタスクから開発に慣れてもらうことが大切です。Devinはチームに入った新人だと思い、焦らずまずは文言変更やドキュメント修正など小さなタスクから依頼していくことをオススメします。 1-2. Devinに開発のルールを教えていく 開発のルールは、会社やプロジェクトごとに違います。そのため、Devinに小さなタスクをお願いしながら開発ルールを教えていきます。 Devinには Knowledge という機能があります。Devinとの会話からルールをほぼ自動的に学び、Knowledgeとして保存することで、次回以降のタスク精度が高まります。 以下は、実際にDevinが学んだ開発ルールのKnowledgeの一部です。 ブランチ作成、PR作成、コミット作成、テスト実行など基本的な開発ルールをKnowledgeとしてDevinに学んでもらいました。 実際に学んだ開発ルールのKnowledge(抜粋) ステップ2. Devinに慣れる 次のステップとして、「Devinに慣れる」です。 Devinのドキュメントを読みながら、試行錯誤して依頼の勘所を掴みます。また、Knowledgeを洗練させ、AI Friendlyな環境を整えることで、 Devinがよくあるタスクで90点以上のタスク精度を実現できる ようになります。 2-1. Devinのドキュメントを読む Devin の Documentationの Essential Guidelines は必読です。Devinにうまく依頼するための重要なプラクティスが記載されています。 例えば、次のような内容が記載されています。 Devinは「ジュニアエンジニア」として扱うのが最適です。明確で十分な指示があれば、インターンや新人エンジニアが対応可能なタスクを任せることができます。 ベストプラクティス: 小さなタスクを並行して実行 :朝の始まりに複数の小さなタスクをDevinに割り当て、昼食時にレビューを行うと効率的です。 Slackでの迅速な対応 :Devinは30分程度で完了するタスクに適しており、長期間放置されがちなバックログの解消に役立ちます。 明確な成功基準のあるタスクに集中 :CIのパス確認や自動デプロイのテストなど、結果が明確に確認できるタスクが理想的です。 小さく始める :Devinの性能は長時間のセッション(≒10 ACU以上)で低下する可能性があるため、小規模なタスクから始めることを推奨します。 引用元: https://docs.devin.ai/essential-guidelines/when-to-use-devin の英語を翻訳 2-2. 試行錯誤して勘所を掴む Devinに様々なタスクを依頼しながら、依頼内容、依頼方法、タスク粒度の勘所を掴んでいきます。また、Devinのドキュメントを読んで学んだことを試しながら、より良い協働方法を模索していきます。 例えば、変数で指示をすることで汎用的なプロンプトを作成する、どの程度までの抽象的な指示ならタスクを遂行してもらえるか確認するなど試してみました。 そして、試行錯誤した結果、Linter対応や横展開系の対応、FeatureFlagの追加・削除、管理画面の軽微な改修、簡易なSentryのアラート対応などの定型作業は、ほぼ90点以上の精度でプルリクエストを作成できました。 Tipsとして、Devinの作成したプルリクエストの精度がいまいちなときは、Devinの設定画面で「Devinは承認を待たずに進める」のチェックをオフにするがおすすめです。複雑なタスクの場合、Devinの作業計画を承認してから作業を進めることができるので、作業計画段階で早めに方向性のすり合わせができ、「プルリクエストが出来上がった後のこれじゃない感」を大幅に減らせます。 「Devinは承認を待たずに進める」のチェックをオフにする 2-3. Knowledgeを洗練させる Devinとの試行錯誤を通しながら、DevinのKnowledgeのバリエーションを増やしていきます。 例えば、テーブルを扱う変更やビジネスロジックの作成、APIの実装、ドキュメンテーションの追加など、さまざまなケースのKnowledgeが溜まっていきます。 これにより、より多くのタスクを精度よく任せられるようになります。 しかし、KnowledgeがあってもDevinがその内容通りに動かないこともあります。 そのような場合は、うまくいかなかったやりとりをもとに「今回のやりとりを次回以降で活かすためにKnowledgeを更新して」と伝え根気強くKnowledgeを更新します。 それでも駄目なら、初期の依頼プロンプトに内容を盛り込んだり、現状のLLMの限界として諦めています。 2-4. AI Friendlyな環境を整備する 単純にDevinに頑張ってもらうだけでなく、AIが開発をしやすいコードベースや開発環境を整備することも大事です。 個人的に、AI Friendlyな環境として以下3つは特に重要だと考えています。 自動テスト、Linter、CIは必須 :Devin自身でフィードバックループが回せなくなるので作業の精度が下がります。また、レビュアーのレビュー時のガードレールにもなるので必須です。 コードの統一や暗黙のルールを明文化 :既存の実装を踏襲してもらうとDevinのタスク精度が高くなります。コードベース上で同じ目的のためにいろんな実装方式があるとDevinが迷ってしまうので、コードの統一感を高めています。また、暗黙知も明文化してKnowledgeとして教えます。 場合によっては開発ルールを変更する :Devinに何度伝えてもうまく対応できないことがあったので、ルールを変更しました。具体的には、コミットメッセージの文字数のルールを守れなかったため、開発ルールとしての制限を緩めることで対応しました。 ステップ3. Devinと協働する ここまででDevinへの依頼に慣れて、ある程度の難易度のタスクならほぼ満足できるようになってきます。 最後のステップでは、Devinに定型作業を任せたり、一緒にタスクを進めたりすることで、 個人のアウトプットを増加 させていきます。 3-1. Devinに定型作業を任せる 「これは9割以上の精度でDevinに任せられる」というタスクはDevinに任せていきます。定型作業のようなものは、ほぼ完璧にプルリクエストを仕上げてくれます。 最近では、個人のAIエージェントとの協働を通じてマインドも変化してきました。ある程度定型化されたタスクは「Devinに任せれば良さそう」いう考えになってきています。 例えば、軽微な修正やFeatureFlagの追加・削除、モデル作成、マイグレーションファイル作成、管理画面の簡易な対応などはDevinに任せて、その間に自分はDevinができない作業を進めることが多くなりました。 3-2. Devinと一緒にタスクを進める Devinは、CursorやClineなどローカルマシンで実行するエージェントに比べて相対的に作業時間は長くなります。しかし、タスクを分担して連携して開発できれば、大きな問題にはならないと考えています。 私がよく実施する「Devinと一緒にタスクを進める方法」は主に2つです。 よくあるDevinと一緒にタスクを進める方法 パターン①:タスクを分解し、Devinがメインで開発を担当し、難しい部分やレビューを自分が行う パターン②:横展開系のタスクで、最初の実装イメージは自分が作成し、その後の横展開はDevinに並列で実装を任せ、レビューを自分が行う 3-3. Devinにサブタスクを任せる たまに、メインでないサブタスクを裏でDevinに丸っと依頼して作ってもらうことをします。 Devinの公開APIを利用した自前ワークフローツール(※Roo Codeの Boomerang Tasks の簡易版のようなもの)を作成し、一連の依存関係のあるタスクリストをワークフローツールで実行しています。 これにより、1〜2時間ほどで一通りの対応(5〜8プルリクエスト)をDevinに実装してもらうことが可能になりました。 タスクの定義例。依存関係にそってタスクを実行してくれる 課題としては、個々のタスク精度にはまだ伸びしろがあります。また、メインタスクとサブタスクを頻繁に切り替える必要もでてくるため、かなり疲れることもあります。 改善案として、レビューの一部をエージェント化したり、ワークフローのオーケストレーターやタスク出しをエージェント化したりすることで、より丸ごと任せられるか試してみたいと考えています。 おまけ: Devinの活用Tips 最後におまけとして、Devinの活用Tipsをいくつかご紹介します。 Tips1. GitHub/AWSアカウント管理のワークフローを自動化 SREチームでは、SlackワークフローとDevinを組み合わせてGitHubとAWSのアカウント管理をほぼ自動化しています。 SlackワークフローでDevinに依頼 流れとしては次のとおりです。 前提として、Terraformでアカウント管理をしている。 Slackのワークフローで依頼をすると、@Devinメンションでワークフロー内容が記載される。 Devinが反応してアカウント管理(追加・更新・削除)のプルリクエストを作る。 SREがプルリクエストをレビューをする。修正がある場合プルリクエスト上でコメントをするとDevinが修正してくれる。 SREがプルリクエストをマージすると、Terraformによりアカウントが追加・更新・削除される。 SREの方に効果を聞いたところ、月末月初などユーザー申請の対応が五月雨にあり地味に時間がかかっていた工数が 感覚値で1日程度削減 できたようです。 Devinには、Slackからタスクを依頼をできる機能があるので、Slackのワークフローとの接続がとても簡単にできるところはいいところだと思っています。 Tips2. 口調を変えるKnowledgeで親しみやすくする DevinのKnowledgeで口調を指定することで、エンジニアがDevinに親しみを感じやすくしています。 Devinはデフォルトだと割とかしこまった感じの口調なのですが、Knowledgeを指定することで砕けた感じの会話ができるようになり、チームとDevinの親近感が醸成されました。(たぶん...) Devinの口調を変えるKnowledgeとDevinとのやりとり 実用的な観点ではDevinが Knowledge をどのぐらい理解して作業をするかを日々検証できるというメリットもあると思っています。 Tips3. PMが仕様把握のためにDevin Searchを利用 Devinには、コーディングだけでなくDevin Searchという自然言語で質問をして回答を得られる機能があります。現在はβ板なので無料で利用できます。 Devin Searchで仕様を確認 実際にPMからメール送信の条件に対する質問をされ、Devin Searchで検索したら100点の回答が得られました。 そのため、PMにDevinのアカウントを発行して、詳細の仕様が気になる場合はDevinに聞いてもらうという取り組みをしました。 Tips4. AI駆動な仮実装 まだ試行錯誤段階ですが、Devinをフル活用して、仮実装を感覚値で2~3日想定が1日で完了しました。また、仮実装を通して設計やコードの解像度が高まり、「実現性の検証」や「改善点の発見」を短期間で実現できました。 仮実装の流れは次のように進めました。 Devin Wikiで新しいリポジトリの主要機能や設計を把握 Devin Searchで影響範囲を設計とコードレベルで把握 Devin に大きめなタスクを渡して仮実装 Cursorを使いながら仮実装の細部を修正して動くようにする ある程度頭の中に設計イメージがあり、そのイメージをDevinに伝えて設計をしてもらい、仮実装を進めていくことで素早く検証を進めることができました。(※もちろん仮実装のコードは捨てて、本実装時に細部も含めてちゃんと作り込みます) Devinに対する所感や協働して変わってきたこと Devinとうまく協働することで、いままではチームのエンジニアが5~6名ぐらいだったのが、3名前後でも同程度のアウトプットをだせるようになっていきそうな感覚を受けています。 しかし、足元少しずつ顕在化しはじめているのですが、より少人数のチームでプルリクエストの数が増えているのでチームのレビュー負荷が高まっています。 具体的な解決策のアイデアはまだないですが、レビューのあり方をよりAI駆動によせていく必要があると思っています。 また、Devinと協働する中で個人の動きも少しずつ変わっています。 ミーティングが多くなってしまった日でも、Devinに実装をお願いすることで想定の進捗どおりに開発できた 一部のプロダクトマネジメント業務やデータ分析など業務の幅が広がった 生成AIの進歩は早いですが、新しい技術に一喜一憂しながら、楽しんでいこうと思っています。 まとめ 今回の記事では、 「Devinと3ヶ月協働して個人のアウトプットを1.5倍にした3ステップ」 についてご紹介しました。 Devinと協働する3ステップは次のとおりです。 ステップ1. Devinに教える :小さなタスクからはじめ、開発ルールを教える。 ステップ2. Devinに慣れる :Devinのドキュメントを読みながら試行錯誤をする。また、Knowledge拡充やAI Friendlyな環境を整備する。 ステップ3. Devinと協働する :Devinに定型作業を任せる。Devinと一緒にタスクを進める。Devinにサブタスクを全て任せる。 今後は次のようなことにトライしていきたいと考えています。 個人のアウトプット向上から、チームや組織のアウトプット増加につなげていきたい Devinに任せられる範囲を増やし、AI駆動開発をより推進したい また、セキュリティなどには気をつけつつも、DevinにとらわれずCopilot Coding Agent, Claude Code + GitHub Actionsなどいろいろと試しながら、より開発組織のスピードと品質を向上させていきたいと考えています。 ファインディでは生成AIやAIエージェントを試して開発生産性を高めることで、ユーザーにより価値を届けることを推進しています。 こうした新しい技術の利活用に興味がある方は、ぜひカジュアル面談にお越しくださいませ。 herp.careers
アバター
はじめに 皆さんこんにちは。ファインディに転職してまもなく1年を迎える、CTO室/SREチームの安達( @adachin0817 )です。今回は複数のWordPressサイトをShifterへ移行しましたので、その取り組みについてご紹介します。 Shifterとは ja.getshifter.io Shifterは、WordPressサイトを静的に変換・ホスティングできるマネージドサービスです。動的に動作するWordPressをShifterがビルドし、生成されたHTML/CSS/JavaScriptの静的ファイルをCDN経由で配信する構成となっています。主なメリットは次のとおりです。 セキュリティ強化:PHP実行環境を持たないため、WordPress本体やプラグインの脆弱性リスクが激減。 運用負荷の軽減:インフラやWordPressのバージョンアップ管理が不要。 表示速度の高速化:静的ファイルをCDN 経由で高速配信できるため、ユーザー体験の向上が期待。 自動バックアップ・簡単ロールバック:管理画面から任意のタイミングで復元可能。 Shifterに移行する背景 旧構成図 元々はAmazon Lightsail上の単一サーバーで複数のサイトを運用しており、画像ファイルやデータベースもすべて同一のインスタンス上で管理されていました。コーポレートサイト以外はPHPやプラグインのバージョン管理が行われておらず、明確な運用者もいなかったため、誰がメンテナンスしているのか分からない状況が続いていました。 また、コーポレートサイトに関しては情報システムチームが毎週のようにセキュリティアップデートを対応する必要があり、テーマのデプロイも自動化されておらず手動で行っていたため、運用負担が大きいという課題を抱えていました。 こうした背景に加え、会社全体でも定期的なバージョンアップ対応やセキュリティ強化に十分なリソースを確保することが難しく、改善が急務となっていました。そのような中、2024年に全社的な信頼性向上を目的として横断SREチームが発足しました。SREチームでは属人化していたWordPress環境を集約・統制することで、信頼性の向上を目指す取り組みが始まりました。 その解決策としてマネージドサービスへの移行を検討し、上記のメリットとコストが比較的安価のため、最終的にShifterを採用しました。次は、ローカル開発環境についてご紹介していきます。 開発環境の構築 ローカル開発環境では、コーポレートサイト以外の開発環境が整っていなかったため、Dockerを使って1から構築し直すことにしました。リポジトリ構成としては、WordPress本体をwordpress-coreリポジトリ(仮)で一元管理し、各サービスのテーマは個別のリポジトリで管理する方式を採用しました。これは、すべてを単一リポジトリに集約すると容量の肥大化や管理の複雑さを招くためです。 ※以下仮としています compose.yml ❯❯ cat docker-compose.yml volumes: php-fpm-sock: services: wp-nginx: container_name: wp-nginx build: docker/dev/nginx/ image: wp-nginx ports: - '80:80' - '443:443' volumes: - ./wordpress-core:/var/www/wp:delegated - ./site-corporate:/var/www/wp/corporate/wp-content/themes/site-corporate:delegated - ./site-enterprise01:/var/www/wp/enterprise01/wp-content/themes/site-enterprise01:delegated - ./site-enterprise02:/var/www/wp/enterprise02/enterprise/wp-content/themes/site-enterprise02:delegated - ./site-blog:/var/www/wp/blog/wp-content/themes/site-blog:delegated - php-fpm-sock:/var/run/php-fpm tty: true depends_on: - wp-app wp-app: container_name: wp-app build: docker/dev/app image: wp-app volumes: - ./wordpress-core:/var/www/wp:delegated - ./site-corporate:/var/www/wp/corporate/wp-content/themes/site-corporate:delegated - ./site-enterprise01:/var/www/wp/enterprise01/wp-content/themes/site-enterprise01:delegated - ./site-enterprise02:/var/www/wp/enterprise02/enterprise/wp-content/themes/site-enterprise02:delegated - ./site-blog:/var/www/wp/blog/wp-content/themes/site-blog:delegated - php-fpm-sock:/var/run/php-fpm tty: true depends_on: - wp-db wp-db: container_name: wp-db build: docker/dev/mysql image: wp-db command: --default-authentication-plugin=mysql_native_password ports: - '33066:3306' volumes: - ./mysql/db_data:/var/lib/mysql - ./mysql/init.sql:/docker-entrypoint-initdb.d/init.sql ShifterがPHP 8.1に対応していることから、Appコンテナ・Nginxコンテナ・MySQLコンテナも同じバージョンに揃えて構成しました。WordPressのバージョンや依存パッケージに差異があると、静的化後に想定外の動作になる恐れがあるためです。Shifterでは開発用のDocker環境も用意されていますが、できる限り本番と同等の構成になるよう意識して環境を整備しました。 さらに、Justfileを導入することで、hosts登録やmkcertによる自己署名SSL証明書発行、S3からのDBダンプ取得などのセットアップを自動化しました。複雑だった開発環境の構築手順が、今では just all コマンドを実行すれば素早く構築できるようになりました。ただ、運用していく中で、開発環境自体のバージョンアップもしなければなりませんが、今後GitHub Actionsで自動化していきたいと思っています。 Justfile 開発用ドメイン設定 リポジトリのclone mkcertの実行 wp-config.phpのコピー Docker Composeの起動から停止 DBのリストア setup-hosts: for hosts in "127.0.0.1 dev.site.co.jp" "127.0.0.1 dev.site.co.com" "127.0.0.1 dev-site2.com" "127.0.0.1 dev-site3.com"; do \ if ! grep -q "$hosts" /etc/hosts; then \ sudo bash -c "echo '$hosts' >> /etc/hosts"; \ fi; \ done setup-mkcert: cd ~/www/wordpress-core && \ brew install mkcert && \ mkcert -install && \ mkdir -p docker/dev/nginx/cert && \ cd docker/dev/nginx/cert && \ mkcert "*.hoge.co.jp" clone-repos: @echo "Cloning repositories..." repos="site-corporate site-blog site-enterprise01 site-enterprise02"; \ for repo in $repos; do \ dir="$HOME/www/$repo"; \ url="git@github.com:hoge/$repo.git"; \ if [ ! -d "$dir" ]; then \ git clone --depth 1 "$url" "$dir"; \ echo "Cloned $repo repository."; \ else \ echo "$repo repository already exists. Pulling latest changes."; \ cd "$dir" && git pull origin main; \ fi; \ done copy-wp-config: for dir in "corporate" "site-blog/blog" "site-enterprise01/enterprise01" "site-enterprise02/enterprise02"; do \ cd ~/www/wordpress-core/$dir && cp wp-config-dev.php wp-config.php; \ done start-docker: docker-compose up -d stop-docker: docker-compose stop down-docker: docker-compose down restore-db profile: rm -rf ./db_dumps aws --profile={{profile}} s3 cp s3://hoge/dev/MySQL/ ./db_dumps --recursive --exclude "*" --include "*.sql" for file in ./db_dumps/*.sql; do \ db_name=$(basename "$file" .sql); \ echo "Copying $file to wp-db container"; \ docker cp "$file" wp-db:/tmp/$(basename "$file"); \ echo "Dropping and creating $db_name"; \ docker exec wp-db mysql -u root -e "DROP DATABASE IF EXISTS \`$db_name\`; CREATE DATABASE \`$db_name\`;"; \ echo "Restoring $db_name from /tmp/$(basename "$file")"; \ docker exec wp-db sh -c "mysql -u root $db_name < /tmp/$(basename "$file")"; \ echo "Cleaning up /tmp/$(basename "$file") from wp-db container"; \ docker exec wp-db rm "/tmp/$(basename "$file")"; \ done rm -rf ./db_dumps all profile: just setup-hosts just clone-repos just copy-wp-config just start-docker just restore-db {{profile}} Shifter移行とトラブルシューティング Shifterへの移行にあたっては、公式ドキュメントにも記載されているとおり、All-in-One WP Migrationプラグインを使ったエクスポート・インポート方式が推奨されています。この方法を使えば、WordPressサイトを簡単に丸ごと移行できるのが大きなメリットです。実際に試してみたところ、基本的な移行作業はスムーズに完了しましたが、いくつか想定外の不具合も発生しました。以下にその内容を共有します。 独自テーマのページネーションが正しく動作しない コーポレートサイトのページネーションが正しく機能しないという事象が発生しました。Shifterでは、静的サイトを生成する際にWordPress REST API を通じて対象のURL一覧(JSON形式)を取得し、それをもとに静的化を行います。しかしながら、WordPressのアーカイブページのページネーションはデフォルト設定のままだと検出されず、静的化の対象から漏れてしまうことがあります。この問題に対しては、Shifterが提供する ShifterURLS::AppendURLtoAll を使うことで、手動で静的化対象のURLを追加が可能です。次のようにfunctions.phpに追加し、ページネーションURLを明示的に含めることで対応しました。 function.php function my_append_urls( $urls ) { $limit = get_option('posts_per_page'); $args = array( 'post_type' = > 'post', 'post_status' = > 'publish', 'posts_per_page' = > -1, 'fields' = > 'ids', 'no_found_rows' = > true, ); $the_query = new WP_Query($args); if ($the_query- > have_posts()) { $last_page = ceil(count($the_query- > posts) / $limit); for ($i = 2; $i <= $last_page; $i++) { $urls[] = home_url('/news/page/' . $i . '/' ); } } return $urls; } add_action( 'init' , function(){ add_filter( 'ShifterURLS::AppendURLtoAll' , 'my_append_urls' ); } ); 一部画像が表示されない All-in-One WP Migrationを使った移行ではスムーズに進みましたが、日本語ファイル名の画像が表示されないという問題が発生しました。Shifterへの移行後、原因を調査したところ、画像ファイル名が日本語の場合に限って読み込めないというバグを確認できました。数枚ほどだったので、対象のファイルを手動でリネームし、再アップロードをする必要がありました。 サブディレクトリでシェアボタンがShifterの仮ドメインになってしまう 既にCloudFrontにカスタムドメインが設定されている場合、Shifter 側のCDNに同じカスタムドメインを適用できません。このようなケースでは、Shifterで生成された静的コンテンツのみを対象に、CloudFront経由でカスタムドメイン配下のサブディレクトリに公開する必要があります。 /blog のようにサブディレクトリでの公開が必要な場合、 shifter-cli を使って --no-shifter-cdn オプションを指定することで、ShifterのCDN配信を無効化し、自前のCloudFront経由でのホスティングが可能になります。 CloudFrontのオリジンドメインにShifterが提供するCloudFront URLを指定し、キャッシュポリシーを無効化に設定することで、Shifterで生成された静的サイトをそのまま公開できます。最後にShifterでデプロイを実行することで、シェアボタンやOGPのリンク先にShifterの仮ドメインが含まれることはなくなりました。 ドメイン紐づけ $ npm install -g @shifter/cli $ shifter domain:attach --username hoge --password hoge --site-id hoge --domain hoge.com --no-shifter-cdn terraform/cloudfront.tf data "aws_cloudfront_cache_policy" "caching_disabled" { name = "Managed-CachingDisabled" } resource "aws_cloudfront_distribution" "hoge" { provider = aws.us-east-1 origin { domain_name = "hoge.cloudfront.net" origin_id = "blog" origin_path = "" custom_origin_config { http_port = 80 https_port = 443 origin_protocol_policy = "https-only" origin_ssl_protocols = [ "TLSv1.2" ] } ordered_cache_behavior { allowed_methods = [ "DELETE" , "GET" , "HEAD" , "OPTIONS" , "PATCH" , "POST" , "PUT" ] cache_policy_id = data.aws_cloudfront_cache_policy.caching_disabled.id cached_methods = [ "GET" , "HEAD" ] compress = true path_pattern = "blog/*" target_origin_id = "blog" viewer_protocol_policy = "https-only" } ordered_cache_behavior { allowed_methods = [ "DELETE" , "GET" , "HEAD" , "OPTIONS" , "PATCH" , "POST" , "PUT" ] cache_policy_id = data.aws_cloudfront_cache_policy.caching_disabled.id cached_methods = [ "GET" , "HEAD" ] compress = true path_pattern = "blog/" target_origin_id = "blog" viewer_protocol_policy = "https-only" } } これらトラブルシューティングを解決したことにより、次はテーマのデプロイ方法について説明したいと思います。 Shifter Github Plugin/Theme Installedでのテーマデプロイ https://github.com/getshifter/shifter-github ShifterではGitHub経由でテーマをデプロイするための公式プラグイン「Shifter GitHub Plugin/Theme Installed」が提供されています。このプラグインを利用することで、次の手順でテーマの更新が可能になります。 テーマをgit tagでバージョン付け git pushでタグを反映 テーマ一式を .zip に圧縮し、GitHubの Releases にアップロード Shifter側で該当リリースの.zip を取得し、テーマを最新化 もともとは複数のテーマをモノレポで管理しようとしていましたが、Shifterの仕様上、1テーマ、1プロジェクト(単一リポジトリ)が前提となります。さらに、GitHub Actionsを活用してリリースの自動化も実装しており、手動作業を減らして運用効率を向上させました。 github/workflows/release.yaml name : Tag and Release on : push : branches : [ main ] jobs : tag-and-release : name : Tag and Release runs-on : ubuntu-latest permissions : contents : write defaults : run : working-directory : "./" steps : - uses : actions/checkout@v4 - name : Fetch tags run : git fetch --tags - name : Get latest tag id : get_latest_tag run : | TAG=$(git tag --sort=-v:refname | head -n 1 || echo "0.0.0" ) echo "Latest tag: $TAG" echo "latest_tag=$TAG" >> $GITHUB_ENV - name : Calculate next tag id : calculate_next_tag run : | IFS='.' read -r MAJOR MINOR PATCH <<< "${{ env.latest_tag }}" NEXT_TAG="$MAJOR.$MINOR.$((PATCH+1))" echo "Next tag: $NEXT_TAG" echo "next_tag=$NEXT_TAG" >> $GITHUB_ENV - name : Create new tag run : | git tag ${{ env.next_tag }} git push origin ${{ env.next_tag }} - name : create archive env : PACKAGE_NAME : "hoge-theme" FILES_TO_ARCHIVE : "*" run : | sed -i -e "s/{release version}/${{ env.next_tag }}/g" ./style.css zip -r ${PACKAGE_NAME}.zip ${FILES_TO_ARCHIVE} - name : Upload to GitHub Release env : PACKAGE_NAME : "hoge-theme" GITHUB_TOKEN : ${{ secrets.GITHUB_TOKEN }} GOPATH : /home/runner/go run : | go install github.com/tcnksm/ghr@latest ${GOPATH}/bin/ghr \ -b "Release ${{ env.next_tag }}" \ -replace \ "${{ env.next_tag }}" \ "${PACKAGE_NAME}.zip" style.css Version: { release version } 現在の運用体制について Shifterのアカウント管理は情報システムチームが担当し、テーマの修正はエンジニアやSREチームが対応しています。また、記事の公開やコンテンツの更新は広報チームやマーケティングチームが行っており、各チームの役割が明確に分担されるようになりました。 移行してみて Shifter導入によって、SREチームだけでなく非エンジニアのメンバーでも運用できる環境が整いました。Shifterのダッシュボードはシンプルで直感的に操作でき、バックアップの復元も簡単です。また、静的ファイル化によりPHPの脆弱性リスクが排除され、HTMLとCSSのみで構成されることでセキュリティ面でも安心できるようになりました。さらに、CDNが標準で組み込まれているため、パフォーマンスの向上も実感しています。一方で気になったところは記事をリリースするまで、毎回Shifter側で静的化デプロイをしなければならないので、反映までに時間がかかるといったところです。 さらに、1つのShifterアカウントで複数のサイトを一元管理できる点も大きなメリットです。以前はサイトごとにユーザーアカウントを発行していたため管理が煩雑でしたが、現在は運用効率が大幅に向上し、ユーザー棚卸しも簡単になりました。また、デジタルキューブ様の導入事例でも紹介していただきましたので、ぜひご覧ください。 ja.getshifter.io まとめ 長らく技術的負債となっていたWordPress環境でしたが、SREチームを中心にShifterへの移行を進めたことで、信頼性の向上と一元管理を実現できました。自分自身、初めてShifterを導入するにあたり、静的化に伴う挙動の違いなど、試行錯誤する場面も多く苦労しました。しかし、今では導入して本当に良かったと感じています。今後は新たなメディアを立ち上げる際もShifterを利用し、SREチームを通じた管理フローを社内で徹底していきたいと思っています。 最後まで読んでいただきありがとうございました。SREチームは絶賛募集しているので、カジュアル面談ぜひお待ちしております!🙏 herp.careers 最後に aws.amazon.com AWS Summit Japan 2025で登壇することになりました!Security LakeとAIの活用についてお話します!6/26(木) 12:40~行いますので、ご都合合う方はぜひご参加をお待ちしております!
アバター
こんにちは。Findy Tech Blog編集長の高橋(@Taka_bow)です。 本記事は「 エンジニア達の人生を変えた一冊 」シリーズの第4弾となります。エンジニアとしてのキャリアや技術的な視点に大きな影響を与えた一冊とは?それぞれの思い入れのある本から、技術への向き合い方や成長の軌跡が垣間見えるかもしれません。 今回は佐藤さん、中村さん、甲斐さんの3名のエンジニアに、人生を変えた一冊を紹介していただきます。 まずはファインディのテクノロジーを統括するCTO佐藤さんからです!幅広い知見を持つ佐藤さんが、エンジニアとしての原点となった一冊とは?大学時代に出会った運命の本が、その後のキャリアをどう形作ったのでしょうか。 ■ 佐藤将高さん / CTO ■ ファインディ株式会社のCTOを務めています。トップバッターを務めさせていただきます! コンピュータの構成と設計 コンピュータの構成と設計 MIPS Edition 第6版 上 作者: David Patterson , John Hennessy 日経BP Amazon コンピュータの構成と設計 MIPS Editoin 第6版 下 作者: David Patterson , John Hennessy 日経BP Amazon コンピュータの構成と設計 作者: デイビッド・A・パターソン、ジョン・L・ヘネシー 出版社: 日経BP Amazon: 第6版 上 、 第6版 下 私が紹介する「コンピュータの構成と設計」は、コンピュータアーキテクチャの基礎から応用までを体系的に解説した書籍です。通称「パタ&ヘネ」として知られており、著者の両名はチューリング賞も受賞している著名な研究者です。 この本を読んだきっかけ 私が大学時代、恥ずかしながら「コンピュータについて何もわからない」状態で授業を受けていました。その後、大学院受験をするタイミングで試験内容にコンピュータアーキテクチャのセクションがあり、この本を何度も読み返し勉強を進めてきました。命令セットやプロセッサの種類を詳しく知ることが自分にとってどれほど重要なのか理解できていませんでしたが、この本との出会いが私のコンピュータに対する理解を大きく変えることになりました。私にとっては革命的な本です。 本の内容 この本は世界中の大学でコンピュータアーキテクチャの教科書として使われている名著で、もしかしたら大学で勉強したことがある方がいるかもしれませんね。 コンピュータアーキテクチャの内容をわかりやすく解説しており、コンピュータの歴史を踏まえながら命令やプロセッサなどの概念を理解しやすく説明しています。1996年の初版以来、私が読んだ時点の第5版ではMIPSアーキテクチャが中心でしたが、現在の6版では最新のアーキテクチャやトレンドも反映されています。 この本から影響を受けた点/学んだ点 この本を読んだことで、コンピュータの動作が頭の中でだんだんイメージできるようになりました。特に、Webサービスを提供する上では、クライアントと記憶媒体間のデータのやり取りを考慮しながらプログラムを組む必要があることを理解できました。サービスをより大規模に展開しようとすると、ハードウェアについての知識がますます重要になってくることを実感しました。また、I/Oやメモリに関する知識が深まったことで、プロダクトの課題発見に今でも役立てています。 特に印象に残った部分 世の中にある技術書は難解なものが多いと感じていましたが、この本は特に読みやすく、当時学生だった私にとってアーキテクチャの基礎を掴むのにとても役立ちました。 表面的なプログラミングだけでなく、その下層で何が起きているかを理解することで、より良いシステム設計ができるようになりました。例えば、CPU内部の論理設計から、メモリ階層・I/O・並列計算機までのコンピュータの動作原理を広く理解することは、エンジニアとしてのキャリアを積む中で常に私の基盤となっています。 全体的に図が多く、内部の動作をイメージしやすい良書です。 このような方におすすめ ソフトウェア以外にも、コンピュータサイエンス全般を幅広く学びたい方にお勧めの一冊です。特に、Webサービスやアプリケーション開発に携わるエンジニアの方々には、パフォーマンスチューニングやシステム設計の基礎知識として役立つでしょう。また、コンピュータサイエンスを学び始めたばかりの学生の方にも、その読みやすさから入門書として最適です。 また、理解が進んだ方には「上パタ&ヘネ」と言われる「Computer Architecture: A Quantitative Approach」という本もありますので、物足りない方はこちらもおすすめです。 佐藤さんの原点は「パタ&ヘネ」にあったんですね。大学時代に出会ったこの一冊が、現在CTOとして活躍する佐藤さんのコンピュータに対する深い理解の礎となったことがよく伝わってきます。技術書との出会いが、その後のキャリアを大きく左右することを実感させられます。 次は、Findy Team+のフロントエンド開発を担当する中村さんです!中村さんが選んだ一冊は、実際の開発現場で直面した課題から見つけ出した救世主のような本。Webセキュリティの世界への扉を開いた体験とは? ■ 中村大輝さん / フロントエンドエンジニア ■ こんにちは、 Findy Team+ の開発をしている 中村 です。 体系的に学ぶ 安全なWebアプリケーションの作り方 体系的に学ぶ 安全なWebアプリケーションの作り方 第2版 脆弱性が生まれる原理と対策の実践 作者: 徳丸 浩 SBクリエイティブ Amazon 体系的に学ぶ 安全なWebアプリケーションの作り方 作者: 徳丸 浩 出版社: SBクリエイティブ 私が紹介する「体系的に学ぶ 安全なWebアプリケーションの作り方」は、Webアプリケーションのセキュリティを体系的に解説した書籍です。通称「徳丸本」として知られています。 この本を読んだきっかけ エンジニアとして働き始めた1〜2年目の頃、当時私が担当していたWebサービスで脆弱性診断を受ける機会がありました。その結果、XSSやCSRFといった複数の脆弱性が指摘され、修正を求められる事態となりました。 1つ具体的な例を挙げると、そのWebサービスにはユーザーがレビューのようなものを投稿できる機能がありました。しかし、その入力欄に悪意あるスクリプトが埋め込まれ、他のユーザーがそのレビューを閲覧すると、JavaScriptが実行されるXSS(クロスサイトスクリプティング)が発生しました。この脆弱性により、ユーザーのクッキー情報が盗まれたり、不正な操作が実行されたりする可能性があり、非常に危険でした。これらの修正事項を解決するため、Webセキュリティに関する知識が必要となり、何をどう対策すれば良いのか悩んでいたときに出会ったのが「体系的に学ぶ 安全なWebアプリケーションの作り方」でした。 本の内容 この本は、Webアプリケーションの脆弱性を体系的に解説し、その対策方法を具体的に示しています。特に、XSSやCSRF、SQLインジェクションなど、代表的な脆弱性ごとにその仕組みと対策をわかりやすく解説しており、Webセキュリティに関する理解を深めることができます。また2011年の初版から、HTML5の普及に伴う改定などいくつかの改訂が重ねられた第2版まで現在出版されています。 この本から影響を受けた点/学んだ点 この本を読むことで、脆弱性とは単なる「エラー」ではなく、悪意ある攻撃者によって悪用される可能性があるとても重大なリスクであることを理解しました。また、脆弱性の種類ごとにどのような攻撃が行われるかを学び、実際のコードにどう対策を実装すればよいかを具体的に理解できました。 特に印象に残った部分 特に印象に残ったのは、XSS(クロスサイトスクリプティング)の項目です。当時私が担当したWebサービスでもXSSが報告され、その危険性を初めて実感しました。本書の例をもとに、エスケープ処理やCSP(コンテンツセキュリティポリシー)を適用することで安全性を高める方法を学び、実際の修正にも役立ちました。 このような方におすすめ Webアプリケーション開発に関わるすべてのエンジニアにお勧めです。特に、開発経験が浅く、Webセキュリティに自信がない方には非常に助けになる一冊です。 中村さんの「徳丸本」との出会いは、まさに現場のピンチを救った実践的な体験でしたね。エンジニアとして直面した実際の脆弱性問題が、セキュリティへの深い理解につながった瞬間が印象的です。技術書は時に、現場の課題を解決する強力な味方になるのですね。 最後は、2025年3月に入社したばかりの甲斐さんです!同じくFindy Team+のフロントエンド開発を担当する甲斐さんが選んだのは、コードの「変更しやすさ」という永遠のテーマに挑んだ一冊。大規模開発の現場で見つけた設計の知恵とは? ■ 甲斐和基さん / フロントエンドエンジニア ■ こんにちは、 甲斐 です! 2025年3月にファインディへ入社、フロントエンドエンジニアとして Findy Team+ の開発をしています。 現場で役立つシステム設計の原則 変更を楽で安全にするオブジェクト指向の実践技法 現場で役立つシステム設計の原則 ~変更を楽で安全にするオブジェクト指向の実践技法 作者: 増田 亨 技術評論社 Amazon 現場で役立つシステム設計の原則 変更を楽で安全にするオブジェクト指向の実践技法 作者: 増田 亨 出版社: 技術評論社 この本を読んだきっかけ エンジニアとして働き始めて2年目の頃、初めて大規模なコードベースのアプリケーションを担当することになりました。コードベースが巨大かつ複雑で影響範囲を調査するだけでも数時間かかるような状況に課題感を感じ、変更しやすいコード・設計に興味を持ったことがきっかけでこの本を手に取りました。 本の内容 システム設計について、ドメイン駆動設計とオブジェクト指向プログラミングを中心に、変更に強いコードを書くための設計・手法について具体的なコードを例に挙げて解説しています。ベースの話はオブジェクト指向に沿ったものですが、変更に強い構造を作るための手法や考え方は他のプログラミングパラダイムでも活用できるものになっています。 この本から影響を受けた点/学んだこと この本を読んだことで、高凝集・疎結合であることで変更しやすい構造になるということを知識としてだけではなく、具体的に実践するための手法・考え方をベースに設計を考えるようになりました。この考え方は直接的にではないですが、フロントエンド開発に主軸を移してもからもコンポーネント設計や共通ロジックの切り出しなどを検討する際に活かされています。 特に印象に残った部分 全体を通して「変更を楽にするため」の手法について触れていたことが印象に残っています。特にドメインオブジェクト(ドメインに紐づくデータとロジックを扱うことに特化したオブジェクト)を使用した関心ごとの分離について言及しており、コードの整理だけでなく業務ドメインに対する理解を深めることが良い設計をする上でも重要であることを学びました。 例として取り上げるサンプルコードの題材が発注システムなど実践的なものだったので、よくあるサンプルコードのような「定義はわかるけど実際にどう扱うのか?」がイメージしづらいようなことはなく読みやすかったです。 このような方におすすめ プログラミングを始めて、コードの書き方はわかったけど設計ってどうすればいいのか?現場のコードベースが変更しづらいけどなぜだろう?など設計・オブジェクト指向について具体的なユースケースを見つつ学びたい方におすすめです。 おわりに 今回ご紹介した3名のエンジニアたちが人生を変えた一冊は、それぞれの専門分野や関心領域を反映した多様な選択でした。コンピューターの基礎原理からWebセキュリティ、そしてシステム設計まで、エンジニアとしての成長過程で出会った本が、その後のキャリアや技術的視点に大きな影響を与えていることがわかります。 技術書との出会いは、単なる知識の獲得だけでなく、問題解決の糸口や新たな視点をもたらしてくれるものです。皆さんも、自分の技術的な課題や興味に合わせた一冊を見つけ、エンジニアとしての視野を広げてみてはいかがでしょうか。 ファインディでは、さまざまなバックグラウンドを持つエンジニアが活躍しています。技術にこだわり、より良いものを追求する仲間とともに働いてみませんか? 現在、ファインディでは新しいメンバーを募集中です。 興味のある方は、ぜひこちらからチェックしてみてください!
アバター
こんにちは。 ファインディ株式会社 で Tech Lead をやらせてもらってる戸田です。 現在のソフトウェア開発の世界は、生成AIの登場により大きな転換点を迎えています。 GitHub Copilotやチャットベースの開発支援ツールなど、生成AIを活用した開発支援ツールが次々と登場し、開発者の日常的なワークフローに組み込まれつつあります。 そのような状況の中で、このたび弊社からリモートMCPサーバーを公開することとなりました。 そこで今回は、リモートMCPサーバーを公開する上で実際に考慮したポイントを紹介します。 それでは見ていきましょう! 公開したリモートMCPサーバーの概要 インフラ構成 内部実装 認証情報 Streamable HTTP Transport まとめ 公開したリモートMCPサーバーの概要 MCPに関しては、前回の記事で詳しく説明していますので、公式ドキュメントと合わせてそちらをご覧ください。 tech.findy.co.jp modelcontextprotocol.io 今回公開したリモートMCPサーバーを使うことで、GitHub CopilotやDevinの利用状況を取得して可視化することが可能になります。詳細はリリースブログをご覧ください。 https://findy.co.jp/2843/ findy.co.jp なお、今回公開したリモートMCPサーバーのご利用は、2025/05/19現在、ファインディ株式会社のクライアント企業様限定となっております。 ご利用希望の方は、申込みフォームからお申し込みください。 docs.google.com インフラ構成 今回、弊社が公開したリモートMCPサーバーのインフラ構成は次のようになっています。 開発当初はAWS Lambdaを使うことを検討していたのですが、次の理由から採用を見送り、現状のシンプルな構成に落ち着きました。 実装内容をLambdaに合わせる必要がある 後述するSSE Transportを使う場合、クライアント、サーバー間のコネクションを維持する必要があり、その要件がLambdaと合わない 今回はdeploy時にDocker imageをECRにPushして、ECSを使ってサーバーを起動します。ログ出力はCloudWatchを使い、VPC経由でクライアント側からアクセス出来るようになっており、比較的よく見かけるシンプルな構成になっているかと思います。 今回のリモートMCPサーバーは、ファインディのSREチームが取り組んでいるセキュリティを担保したインフラ環境構築の一環として、HCP TerraformのPrivate Registryを活用した初の本番環境構築事例となりました。 このPrivate Registryに登録したモジュールは、Trivyによるセキュリティスキャンでエラーが出ない状態を担保しています。 一方で、全社的に必須とされている数多くの設定の中には、リモートMCPサーバーの特性上、一部適用が難しいものもありました。 特に、一部のWAFルールがリモートMCPサーバーの通信をブロックしてしまうケースが存在しました。 そのため、インフラ構築を進めながらPrivate Registryに登録したモジュールのカスタマイズも同時に行い、リモートMCPサーバーの要件を満たすインフラ環境を実現しました。 この取り組みにより、ファインディではより迅速かつセキュアにサービス提供ができる基盤が整ったと言えます。 内部実装 リモートMCPサーバーの内部実装の方法は、後述する認証情報の取得とTransportの利用が異なるだけで、基本的にローカルで実行するものと同じです。 具体的なコードを書くと、次のようになります。 import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js" ; import { SSEServerTransport } from "@modelcontextprotocol/sdk/server/sse.js" ; import express, { Request , Response } from "express" ; import { z } from "zod" ; const getMcpServer = ( name : string ) => { const mcpServer = new McpServer( { name : 'sample-mcp-server' , version : '1.0.0' , } , { capabilities : { logging : {} } } ); mcpServer.tool( "addition" , "足し算をする" , { a : z.number(), b : z.number() } , async ( { a , b } ) => ( { content : [ { type : "text" , text : `Hello ${ name } ` } , { type : "text" , text : String (a + b) } ] } ) ); return mcpServer; } ; async function main () { try { const app = express(); const transports: Record < string , SSEServerTransport > = {} ; app. get ( "/sse" , async ( req : Request , res : Response ) => { const transport = new SSEServerTransport( "/message" , res); const sessionId = transport.sessionId; transports[sessionId] = transport; const name = req. get ( "X-Hoge-Name" ); const { server } = getMcpServer( name ); await server.connect(transport); server.onclose = async () => { await server. close (); process .exit( 0 ); } ; transport.onclose = () => { delete transports[sessionId]; } ; } ); app.post( "/message" , async ( req : Request , res : Response ) => { const sessionId = req.query.sessionId as string | undefined ; if (!sessionId) { res. status ( 400 ). send ( 'Missing sessionId parameter' ); return ; } const transport = transports[sessionId]; if (!transport) { res. status ( 404 ). send ( 'Session not found' ); return ; } await transport.handlePostMessage(req, res, req. body ); } ); const PORT = process .env.PORT || 3001 ; app.listen(PORT, () => { console .log( `Server is running on port ${ PORT } ` ); } ); process .on( 'SIGINT' , async () => { for ( const sessionId in transports) { try { await transports[sessionId]. close (); delete transports[sessionId]; } catch (error) { console .error( `Error closing transport for session ${ sessionId } :` , error); } } process .exit( 0 ); } ); } catch (error) { console .error( 'Error starting server:' , error); process .exit( 1 ); } } main(). catch ( error => { console .error( 'Unhandled error:' , error); process .exit( 1 ); } ); SSE TransportでステートフルなMCPサーバーを実装する場合のサンプルコードとなります。 MCPサーバー自体の実装はローカル環境で実行するものと変わりませんが、MCPサーバーをExpressなどのサーバーに接続して起動している点が異なります。 つまるところ、サーバーに接続する箇所以外は基本的に同じ実装で良いので、サーバーの起動部分とMCPサーバーの実装部分を分けて実装しておくと、ローカルで実行するケースとリモートで実行するケースの両方に対応できるようになります。 また、SSE Transportを使って通信を行う場合、MCPクライアントとのセッション管理を行う必要があります。MCPサーバーと接続、切断した時や、サーバーそのものを停止した際にセッションに対する各種操作を実行する必要があります。 認証情報 MCPサーバーからAPIを実行する際にアクセストークンが必要なケースがあります。 ローカル環境でMCPサーバーを実行する場合は環境変数を使ってアクセストークン等を渡すのが一般的です。 しかし、リモートMCPサーバーの場合は環境変数に個人のアクセストークンを設定することは出来ません。リモートMCPサーバーの場合、動作する場所がローカル環境ではなく共通のサーバーになるからです。 こういったケースの場合、環境変数ではなくheaders経由でアクセストークンを渡すことが出来ます。 { " servers ": { " sample-mcp ": { " url ": " https://example.com/sse ", " type ": " sse ", " headers ": { " X-Hoge-Name ": " Fuga ", " X-Access-Token ": " <YOUR_ACCESS_TOKEN> " } } } } このようにMCPクライアント側の設定ファイルを記述することで、MCPサーバーと接続したタイミングにheaders経由でリモートMCPサーバーが必要な情報を受け取ることが出来るようになります。 Streamable HTTP Transport これまでのリモートMCPサーバーとの通信方式はSSE Transportを使うのが一般的でした。 しかし、PROTOCOL VERSION 2025-03-26からSSE Transportはdeprecatedになり、代わりにStreamable HTTP Transportを使うことが推奨されるようになりました。 modelcontextprotocol.io Streamable HTTP Transportに置き換わることで、次のようなメリットがあります。 ステートレスなMCPサーバーを実装できる プレーンなHTTPサーバーとして実装できる MCPクライアントとサーバーのコネクションを維持し続ける必要がなくなる 2025/05/19現在ですと、MCPクライアント側のStreamable HTTP Transportへの対応が出来ていないケースがあるため、しばらくはSSE Transportで通信できるようにする必要がありそうです。 弊社としましても、公開直後はSSE Transportでの提供を行い、折を見てStreamable HTTP Transportに移行していく予定です。 MCPサーバー側のTransport関連の実装は、公式SDKにサンプルコードがあるので、そちらを参考にしてみると良いでしょう。 github.com まとめ いかがでしたでしょうか?今回の記事がリモートMCPサーバーを公開する上での参考になれば幸いです。 今回公開したリモートMCPサーバーはα版という位置付けであり、今後も機能追加や改善を行っていく予定です。 2025/05/19現在、ファインディ株式会社のクライアント企業様限定の公開とはなっていますが、ご利用希望の方は是非、申込みフォームからお申し込みください。 docs.google.com 現在、ファインディでは一緒に働くメンバーを募集中です。 興味がある方はこちらから ↓ herp.careers
アバター
こんにちは。 ファインディ株式会社 で Tech Lead をやらせてもらってる戸田です。 現在のソフトウェア開発の世界は、生成AIの登場により大きな転換点を迎えています。 GitHub Copilotやチャットベースの開発支援ツールなど、生成AIを活用した開発支援ツールが次々と登場し、開発者の日常的なワークフローに組み込まれつつあります。 そのような状況の中で、MCPというプロトコルが話題となっていることは読者の皆さんもご存知かと思います。 そこで今回は、弊社の開発組織でのMCPサーバーの導入と実装、そして実績について紹介します。 それでは見ていきましょう! MCPとは 導入 実装 実績 動的にプロンプトのテキストを作成して返す Devinと連携する Figmaデータのlintを行う セキュリティ面の考慮 まとめ MCPとは MCP(Model Context Protocol)は、アプリケーションが大規模言語モデル(LLM)に情報やツールへのアクセス方法を提供する、新しいオープンプロトコルです。 USB-Cが様々なデバイスを標準的な方法で接続するように、MCPはAIモデルを多様なデータソースやツールへつなぐための、標準化された方法を提供します。 これがこれまでのやり方、特に従来のAPIとどう違うのかというと、AI連携の開発を大幅に簡素化し、より動的な機能を提供する点にあります。従来は、各サービスに対して個別のコードや設定が必要で、「それぞれのドアに個別の鍵が必要」な状況に似ていました。しかし、MCPは単一の標準化されたコネクタとして機能するため、一度の統合で複数のツールやサービスにアクセスできる可能性があります。 詳しくは次の公式ドキュメントをご覧ください。 modelcontextprotocol.io このMCPの規格に則って作成されたサーバーをMCPサーバーと呼びます。 導入 弊社でも例に漏れず、MCPサーバーの利用を開始しました。 Model Context Protocol servers で紹介されている公式MCPサーバーを開発リポジトリに追加するだけで、すぐに利用開始できます。 github.com GitHubのMCPサーバーを使って、Pull requestやIssueの情報を取得して、そのままLLMに渡すことができます。 github.com SentryのMCPサーバーを使って、不具合の詳細を取得して、そのままLLMに渡すことが可能です。その情報をそのままCopilotやAgentなどに解析してもらい、原因を特定した後に自動的に実装コードを修正してもらうことが出来ます。 github.com AWSのDocumentのMCPサーバーを使うことで、CopilotなどからAWSについての質問を投げて、ドキュメントの内容を検索して返してもらうことができます。その内容はそのままLLMに渡すことも可能です。 github.com 利用方法はとても簡単です。各種AIエージェントの設定ファイルに追記して、MCPサーバーを起動するだけです。 例えばVSCodeでGitHubのMCPサーバーを利用する場合、次のような記述を追加します。 { " servers ": { " github ": { " command ": " docker ", " args ": [ " run ", " -i ", " --rm ", " -e ", " GITHUB_PERSONAL_ACCESS_TOKEN ", " ghcr.io/github/github-mcp-server " ] , " env ": { " GITHUB_PERSONAL_ACCESS_TOKEN ": " <YOUR_ACCESS_TOKEN> " } } } } このように、既に多くのプロダクトやサービスでMCPサーバーは公開されており、開発者やプロダクト開発の生産性を向上させるための強力なツールとなっています。 実装 各社から提供されているMCPサーバーを活用していると、組み合わせ次第で色々なことが出来ることに気づきます。 そこで弊社の開発組織で活用できるようなMCPサーバーを自分たちで作ることにしました。 公式から開発用のSDKが提供されているのですが、今回は弊社が普段から使っているTypeScriptのSDKを使って実装しています。 github.com 公式のSDKを使うことで、MCPサーバー内部の実装に集中することが出来るので、利用しない手はありません。 次のコードは、与えられた2つの数値に対して計算を行い、その結果を返すMCPサーバーの実装例です。 import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js" ; import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js" ; import { z } from "zod" ; const mcpServer = new McpServer( { name : "Sample MCP Server" , version : "0.0.1" } ); mcpServer.tool( "addition" , "足し算をする" , { a : z.number(), b : z.number() } , async ( { a , b } ) => ( { content : [{ type : "text" , text : String (a + b) }] } ) ); mcpServer.tool( "multiplication" , "掛け算をする" , { a : z.number(), b : z.number() } , async ( { a , b } ) => ( { content : [{ type : "text" , text : String (a * b) }] } ) ); try { const transport = new StdioServerTransport(); await mcpServer.connect(transport); } catch (error) { console .error( 'Error starting server:' , error); process .exit( 1 ); } mcpServer.tool でtoolを追加します。 1つ目の引数でtoolの名前、2つ目の引数でtoolの説明、3つ目の引数でtoolのパラメータ、4つ目の引数でtoolの実装を定義しています。 また、用途や内容によってはMCPサーバーそのものを分けたいケースが出てきます。ですが、そのたびにリポジトリを切ってしまうと、同じような処理のコードを分散管理することになってしまいます。 そのため弊社では、以前から活用していたNxを利用して、MCPサーバーの実装をモノレポで管理するようにしました。 tech.findy.co.jp モノレポにすることで、複数のMCPサーバーから利用できる共通関数などを作成できます。外部APIを実行するClientなどが良い例です。このように弊社ではMCPサーバーの社内エコシステムを実現しています。 更にNxのgenerator機能を使うことで、モノレポとMCPサーバーの初期設定はコマンドを数回実行するだけで完了するようにしています。 Nxのgenerator機能については以前の記事で紹介しておりますので、併せてご覧ください。 tech.findy.co.jp このような整備を行うことで、社内のエンジニアがMCPサーバーを作るハードルを下げることができました。 実績 社内エコシステムの整備を行ったことにより、数名のエンジニアで、3日間で10個のMCPサーバーと30個のtoolを実装して、社内のエンジニアに配布することを実現できました。 今回はその中の一部を紹介します。 動的にプロンプトのテキストを作成して返す パラメータを受け取り、その内容に応じて動的にプロンプトのテキストを生成して返すMCPサーバーを実装しました。 開発チーム内で共通で利用したいプロンプトがあり、内容を動的に切り替えたい場合などに便利です。 例えば、他のMCPサーバーからデータを取得して、そのデータをこのMCPサーバーに渡すことで、外部データのテキストを利用してプロンプトを生成するといったことが実現できます。 このように、プロンプトのテキストを返し、それをそのままLLMで実行するような使い方がMCPサーバーでは可能です。 Devinと連携する 完全自律型AIエンジニアのDevinは、α版ではありますがAPIを公開しています。 docs.devin.ai そのAPIをMCPサーバーから実行することにより、Devinに関する情報を取得したり、セッションを作成、停止できるようにしました。 これによりエンジニア自身の作業中に、SlackやDevinの画面を直接確認せずともDevinの管理ができるようになりました。 このように、外部APIを実行して情報を取得したりデータ登録、削除を行うことがMCPサーバーでは可能です。 Figmaデータのlintを行う デザインプラットフォームのFigmaはAPIを公開しています。 www.figma.com そのAPIをMCPサーバーから実行して、対象のFigmaのデータを取得して、データの中身をチェックして、デザインルールに則っているかどうかlintのようなチェックを行うMCPサーバーを実装しました。 例えば、指定したフォント以外を使っている場合や、利用できるpx数ではない場合など、デザインルールに則っていない場合は警告を返すようなことが可能です。 このように、外部APIからデータを取得して、内容を解析したり加工して返すようなことがMCPサーバーには可能です。 セキュリティ面の考慮 外部APIを実行する場合、アクセストークンが必要になるケースが多いと思います。 このようなケースの場合、MCPサーバーを実行する際に環境変数を使ってアクセストークンを渡すことができます。 利用するアクセストークンの権限を絞り、環境変数でアクセストークンを隠蔽して実行することで、安全にMCPサーバーを管理、実装することが可能になります。 また、外部の公式以外のMCPサーバー、いわゆる野良MCPサーバーを利用する場合は注意が必要です。 悪意のあるMCPサーバーに対してアクセストークンなどを渡してしまうと、意図しない情報漏洩やデータの改ざんなどのリスクがあります。 そのため、外部の野良MCPサーバーを利用する場合は、内部実装を確認して実際に実行されている処理を確認することをオススメします。 まとめ いかがでしたでしょうか? MCPサーバーを実装するハードルとコストは非常に低く、それに対するリターンは非常に大きいため、コストパフォーマンスが非常に高いです。 是非一度、公式ドキュメントを参考にして簡単なMCPサーバーを実装してみてください。 現在、ファインディでは一緒に働くメンバーを募集中です。 興味がある方はこちらから ↓ herp.careers
アバター
このブログの内容をポッドキャストでも配信中! ソフトウェア開発現代史年表 Ver2.07 このブログの内容をポッドキャストでも配信中! はじめに DevOps誕生以前(〜2000年代前半) 2009年:DevOpsのはじまり Flickrの伝説的講演「10+ Deploys Per Day」 パトリック・ドボアと「DevOps」という言葉の誕生 2010年代前半:ジェズ・ハンブルと継続的インテグレーション(CI)から継続的デリバリー(CD)への発展 継続的インテグレーション(CI)とは何か 継続的デリバリー(CD)への発展 2010年代中盤:ジーン・キムとDevOpsの理論体系化 2010年代後半〜現在:DevOpsの普及と進化 Googleによる「SRE(Site Reliability Engineering)」の提唱 「You build it, you run it(作ったものは自分で運用する)」文化の定着 DORA(DevOps Research and Assessment)の設立と影響 クラウドネイティブ技術とDevOpsの融合 DevOpsの文化的側面の重視 DevOpsの本質と今後の展望 日本におけるDevOps:まだ「一部の人たちのモノ」状態 DevOpsとAI導入の共通障壁 【告知】ソフトウェア開発の伝説的パイオニアたちが来日! はじめに こんにちは。ソフトウェアプロセス改善コーチ兼アジャイルコーチ兼エンジニア兼Findy Tech Blog編集長の高橋( @Taka-bow )です。 30年以上前、エンジニアとしての第一歩を踏み出したばかりの私に、ある先輩が教えてくれた言葉があります。 「動いているコードには触れるな」 この言葉は、当時の日本のIT現場における「安定性最優先」の価値観を象徴しています。一度リリースされたシステムは「完成品」として扱われ、必要最低限の変更以外は避けるべきという考え方が主流でした。変更は常にリスクをはらんでおり、「動いているものを変えれば壊れる可能性がある」という恐れが、技術的な進化よりも優先されていたのです。 しかし、DevOpsの登場によって、この価値観は大きく変わりました。「変更を恐れる」文化から「変更を受け入れる」文化へ。「安定性のために変更を避ける」から「頻繁な小さな変更によって安定性を高める」へと変化しました。 つまり「動いているコードには触れるな」から 「常に改善し続けるコード」 へと進化したのです。 この価値観の転換は、単なる技術的な進化ではなく、ソフトウェア開発に対する根本的な考え方の変革です。 世界的には当たり前となりつつあるこの変革ですが、日本の状況はどうでしょうか。興味深いことに、日本では一部のWeb系企業やスタートアップを除き、DevOpsの波はまだ十分に広がっていません。 なぜでしょう? この疑問を探る前に、まずDevOpsがどのように生まれ、発展してきたのかを理解する必要があります。ソフトウェア開発現代史の視点から、この変革をもたらした先駆者たちの物語を紹介していきたいと思います。 下図の年表に示すように、DevOpsの歴史は2009年頃から始まります。 DevOps誕生以前(〜2000年代前半) 2000年代前半までのソフトウェア開発では、 開発と運用は完全に分断され、しばしば対立関係にありました 。開発チームの目標は「新機能を素早くリリース」、運用チームの目標は「システムの安定性維持」であり、この2つの目標はしばしば衝突していました。 開発チームは「とにかく早くリリースしたい」 → 変更を加えたがる 運用チームは「安定稼働が最優先」 → 変更を嫌う 結果として、開発は「リリース遅延」に苛まれ、運用は「障害対応」に追われる 変更のリスクを最小限にするために、厳格なリリースプロセスが敷かれ、開発者は運用チームから「勝手にデプロイするな」と釘を刺されていました。その結果、リリースサイクルは長期化し、開発の俊敏性が犠牲になっていったのです。 特に、大規模なリリースでは 手作業によるデプロイ が行われることが多く、設定ミスや環境差異が原因で障害が頻発していました。こうした問題を解決し、 開発と運用のギャップを埋める新たなアプローチ が強く求められていました。 2009年:DevOpsのはじまり Flickrの伝説的講演「10+ Deploys Per Day」 こうした状況に一石を投じたのが、2009年のO'Reilly Velocity 09 Conferenceで行われた、Flickrのエンジニアによる伝説的な講演です。当時Flickrのエンジニアだった ジョン・オールスパウ(John Allspaw)とポール・ハモンド(Paul Hammond) が「 10+ Deploys Per Day: Dev and Ops Cooperation at Flickr 」のタイトルで発表し、業界に衝撃を与えました。 この講演で彼らは、当時としては驚異的な「 1日に10回以上のデプロイ 」を実現している事実を明らかにしました。多くの企業が月に1回や四半期に1回のリリースサイクルに苦しんでいた時代に、これは革命的な実践だったのです。 彼らが強調したのは次のポイントです。 開発と運用の協力関係 - 両チームが共通の目標(ユーザー価値の提供)に向かって協力する 小さな変更の頻繁なデプロイ - 大きな変更を一度にリリースするのではなく、小さな変更を頻繁にリリースする 自動化の徹底 - テスト、デプロイ、モニタリングなど、あらゆる工程を自動化する 「失敗は避けられない」前提 - 失敗を防ぐのではなく、素早く検知して回復する能力を高める この講演により「高頻度デプロイは可能」との認識が広まり、多くの企業がFlickrの実践に触発されて自社のデプロイプロセスを見直すきっかけとなりました。この講演は、YouTubeで「10+ Deploys Per Day: Dev and Ops Cooperation at Flickr」として今でも視聴可能であり、DevOpsを学ぶ人々にとって必見の資料となっています。 このとき日本では:アジャイル導入の緩やかな歩み 2001年に発表された「アジャイルソフトウェア開発宣言(アジャイルマニフェスト)」は、「プロセスやツールよりも個人と対話を」「包括的なドキュメントよりも動くソフトウェアを」といった価値観を掲げ、後のDevOps誕生の重要な思想的背景となりました。しかし、2000年代前半のアジャイル導入期において、日本企業の多くはこの本質である「変化への適応」や「顧客との協働」といった考え方を十分に受け入れることができませんでした。日本の階層的な組織構造や詳細な計画を重視する文化が、アジャイルの柔軟性と相容れなかったのです。DevOpsはアジャイルの延長線上に生まれた考え方であり、この最初のアジャイル導入のチャンスを逃したことが、後のDevOps導入の遅れにも直接的な影響を与えることになりました。 パトリック・ドボアと「DevOps」という言葉の誕生 Flickrの講演と同じ2009年、「 DevOps 」という言葉が誕生しました。この言葉の背後には、ベルギーのITコンサルタント パトリック・ドボア(Patrick Debois) の興味深い物語があります。 *1 実は、ドボアの旅は2007年に始まっていました。ベルギー政府のデータセンター移行プロジェクトに携わっていた彼は、開発者とシステム管理者の間の対立に悩まされ、解決策を模索していました。 転機となったのは2008年8月、トロントで開催されたAgileカンファレンスでした。ソフトウェア開発者の アンドリュー・シェーファー(Andrew Shafer) が「Agile Infrastructure(アジャイルインフラストラクチャ)」というセッションを開催しましたが、参加者はたった一人、パトリック・ドボアだけでした。二人はこの出会いをきっかけに、開発と運用の間の壁を取り払うアイデアについて議論を始めました。 そして2009年、先述のFlickrの講演「10+ Deploys Per Day」に触発されたドボアは、エンジニアのナスラット・ナサー(Nasrat Nassar)からの「ベルギーで独自のVelocityイベントを開催してはどうか」というツイートをきっかけに行動を起こします。彼は「 DevOpsDays 」というイベントを開催することを決意しました。 この名前は、「development(開発)」と「operations(運用)」の最初の3文字を取り、「days(日)」という単語を追加したものでした。2009年10月30日に開催されたこのカンファレンスには、開発者、システム管理者、ツール開発者など多様な参加者が集まりました。 カンファレンス終了後、議論はTwitterに移り、より覚えやすいハッシュタグとして、ドボアは名前を「 #DevOps 」に短縮しました。これが「 DevOps(Development + Operations) 」という言葉の誕生であり、それ以来、このムーブメントはDevOpsとして知られるようになりました。 興味深いことに、スペルについては今でも意見の相違があります。主流の使用法は「DevOps」ですが、創始者のドボアを含む少数派は「Devops」を主張し、一部の人々は大文字を完全に排除して「devops」とすることを主張しています。 ドボアのX(当時はTwitter)アカウント(@patrickdebois)は初期のDevOpsムーブメントの中心的な情報源となり、#devopsというハッシュタグを通じて、世界中の実践者がアイデアを共有する場となっていきました。 このとき日本では:クラウド導入の機会損失 2006年から2010年にかけてのクラウド黎明期において、AWSをはじめとするクラウドサービスの登場は、単なるインフラの選択肢ではなく、DevOpsの実践を加速させる技術的基盤でした。「Infrastructure as Code」や「自動化」といったDevOpsの核心的な実践を可能にする環境が整いつつあったのです。しかし、日本企業の多くは「セキュリティ懸念」「既存システムへの投資保護」「クラウドの信頼性への疑問」などを理由に慎重な姿勢を取り続けました。この時期に積極的なクラウド活用を進めていれば、DevOpsへの移行も自然な流れとして実現できたはずです。この「待ちの姿勢」が、後のDevOps導入の遅れにも繋がることになりました。 なお、パトリック・ドボアは昨年2024年1月に行われたDevOpsDays Tokyo 2024の基調講演のため来日しています。 私は現地でお話を聞きましたが、このときのドボアは AIにとても興奮 しており、特にRAGについてのお話でした。 当時の私は、ドボア氏が何に興奮していたのか、何も分かっていませんでした……でも、今なら分かります。 改めてビデオを見返して、気になった発言をピックアップしました。 私がとても気に入っている概念のひとつに、こういう考え方があります。 エージェントの性能が向上すれば、私たちは直接「もの」を作るのをやめ、 「ものを作るもの」を作るようになります。 つまり、自動化の次の段階に進むのです。 多くの人が私にこう言いました。 「DevOpsでは乗り遅れた。でも今度のAIの波では遅れたくない。」 成熟したDevOps文化を持つ人々こそ、GenAIのアーリーアダプターになりつつあるのです。 私たちエンジニアは、常にやりすぎる傾向があります。 でも、それでいいのです。 今もっとも難しい新しいプログラミング言語? それはもしかすると「英語」か「日本語」かもしれませんね。 もっと、たくさん痺れる事を仰っているので、ぜひ本編YouTubeをごらんください。 2010年代前半:ジェズ・ハンブルと継続的インテグレーション(CI)から継続的デリバリー(CD)への発展 さて、話を戻しましょう。ここで登場するのが、DevOpsの歴史において重要な役割を果たした人物、 ジェズ・ハンブル(Jez Humble) です。彼はソフトウェアデリバリーの自動化と効率化に関する先駆的な研究と実践で知られ、継続的デリバリー(CD)の概念を体系化した第一人者として世界中のソフトウェア開発に多大な影響を与えました。 DevOpsの言葉が広まり始めた2010年代前半、まず理解しておくべき重要な概念が「 継続的インテグレーション(Continuous Integration, CI) 」です。CIは実はDevOpsより前から存在していた概念で、2000年代初頭にアジャイル開発の文脈で広まりました。 特に マーティン・ファウラー(Martin Fowler) と ケント・ベック(Kent Beck) がその重要性を提唱しています。 継続的インテグレーション(CI)とは何か 継続的インテグレーションの核心は、 「開発者が頻繁にコードを統合し、自動テストによって問題を早期に発見する」 という実践です。具体的には次のような特徴があります。 頻繁なコミット - 開発者は少なくとも1日1回、コードをメインブランチに統合する 自動ビルド - コードがコミットされるたびに自動的にビルドが実行される 自動テスト - ビルド後に自動テストが実行され、問題があれば即座にフィードバックが得られる 問題の早期発見 - 統合の問題を早期に発見することで、修正コストを低減する CIの実践により、「統合地獄(Integration Hell)」と呼ばれる、長期間分岐したコードを統合する際の困難を避けることができます。CIを実現するツールとしては、 Jenkins (旧Hudson)、 Travis CI 、 CircleCI などが広く使われるようになりました。 なお、CI(継続的インテグレーション)と密接に関係する実践として、TDD(Test-Driven Development:テスト駆動開発)があります。TDDは「テストを先に書き、そのテストをパスする最小限の実装を行う」という開発手法で、Kent Beckがエクストリーム・プログラミング(XP)の中で体系化し、広く普及させました。 テスト駆動開発 作者: Kent Beck オーム社 Amazon TDDによって作成される自動テスト群は、CIパイプラインにおける重要な構成要素となり、コードの品質を継続的に保証する基盤となります。TDDが主にコードレベルの品質を担保するのに対し、CIはチーム全体での統合品質を確保する役割を果たします。両者はいずれも「早期フィードバック」と「問題の早期発見」を重視しており、DevOps実践の基盤として不可欠な考え方です。 継続的デリバリー(CD)への発展 CIが「コードの統合と検証の自動化」に焦点を当てているのに対し、 継続的デリバリー(CD) はその先の「デプロイメントまでの自動化」にまで範囲を広げた概念です。この概念を体系化したのが、ジェズ・ハンブルとデビッド・ファーリー(David Farley)による「 Continuous Delivery: Reliable Software Releases through Build, Test, and Deployment Automation 」(2010年、日本語訳は2012年)の書籍でした。 継続的デリバリー 信頼できるソフトウェアリリースのためのビルド・テスト・デプロイメントの自動化 作者: Jez Humble , David Farley KADOKAWA Amazon この書籍では、「 継続的デリバリー(Continuous Delivery, CD) 」の概念が体系的に説明され、その核心は「 ソフトウェアをいつでも本番環境にリリース可能な状態にする 」ことにありました。 ジェズ・ハンブルとデビッド・ファーリーは、この書籍で継続的デリバリーを実現するために次のような実践を提唱しています。 デプロイメントパイプライン - コードの変更が自動的にビルド、テスト、デプロイされる一連のプロセス 自動化されたテスト - 単体テスト、統合テスト、受け入れテストなど、あらゆるレベルのテストを自動化 本番環境と一致したステージング環境 - 「開発環境では動くのに本番では動かない」問題を解消 ブルーグリーンデプロイメント - ダウンタイムなしでのデプロイを可能にする手法 これらの実践は現代のCI/CDパイプラインの基盤となり、ジェズ・ハンブルとデビッド・ファーリーの貢献によって「継続的インテグレーション(CI)」から「継続的デリバリー(CD)」へと、自動化の範囲が大きく拡大していきました。 このとき日本では:継続的デリバリー(CD)とゲートキーパー文化の相克 2010年代前半には、継続的デリバリー(CD)の波が世界中に広がりました。海外では多くの企業がデプロイパイプラインを構築し、頻繁なリリースを実現していました。一方、日本企業の多くは「手動リリース」や「長いリリースサイクル」、「厳格な承認プロセス」を維持し続ける傾向がありました。これは日本特有の品質管理への強いこだわりや、変更に対する慎重な姿勢が影響していると考えられます。「リリース=大イベント」「変更=リスク」という認識が根強く、DevOpsの核心である「小さな変更を頻繁に行う」文化への移行が進みにくい土壌があったのです。特に、多くの組織では品質保証部門が「ゲートキーパー」として機能し、リリース前の最終承認権を持つ構造が確立されていました。このゲートキーパー型の品質保証プロセスは、継続的デリバリーが目指す「自動化されたテストによる品質担保」という考え方と根本的に相容れず、プロセス重視の文化から脱却することが難しかったのです。 2010年代中盤:ジーン・キムとDevOpsの理論体系化 2010年代中盤、DevOpsムーブメントの中心人物として台頭したのが ジーン・キム(Gene Kim) です。彼は優れた技術者であると同時に、複雑な概念を分かりやすく伝える稀有な才能を持ち合わせていました。それまで現場レベルの実践知として広がっていたDevOpsの考え方を、組織全体で取り組むべき体系的な方法論として確立した功績は計り知れません。彼の著作は技術書の枠を超え、多くの組織でDevOps変革の指南書となっています。 2013年、ジーン・キムは、ケビン・ベア(Kevin Behr)、ジョージ・スパッフォード(George Spafford)と共に「 The Phoenix Project: A Novel about IT, DevOps, and Helping Your Business Win (邦題:The DevOps 逆転だ!)」を出版しました。 The DevOps 逆転だ! 作者: ジーン・キム , ケビン・ベア , ジョージ・スパッフォード 日経BP Amazon この小説形式の書籍は、架空の自動車部品メーカー「Parts Unlimited」のIT部門が、DevOpsの原則を採用することで危機を乗り越える物語を描いています。物語形式でありながら、DevOpsの本質的な考え方を分かりやすく伝え、多くの読者に影響を与えました。 続いて2016年には、 ジェズ・ハンブル 、パトリック・ドボア、ジョン・ウィリス(John Willis)と共に「 The DevOps Handbook: How to Create World-Class Agility, Reliability, and Security in Technology Organizations 」を出版し、DevOpsの実践をより体系的にまとめあげました。 The DevOps ハンドブック 理論・原則・実践のすべて 作者: ジーン・キム , ジェズ・ハンブル , パトリック・ボア , ジョン・ウィリス 日経BP Amazon この書籍では、 DevOpsの三つの原則(Three Ways) が提唱されています: フロー(Flow) - 開発からデプロイまでの流れを最適化する フィードバック(Feedback) - 問題を早期に発見し、素早く修正する 継続的学習と実験(Continual Learning and Experimentation) - 失敗から学び、継続的に改善する ジーン・キムの貢献により、DevOpsは単なる技術的プラクティスではなく、 組織文化の変革 として広く認識されるようになりました。 このとき日本では:DevOpsの理論と技術基盤の受容の遅れ ジーン・キムらがDevOpsの理論を体系化していた2013年以降、世界ではマイクロサービスアーキテクチャが注目され、Dockerの登場によりコンテナ技術が普及し始めました。これらの技術は、DevOpsの三つの原則(フロー、フィードバック、継続的学習)を実践するための技術的基盤でしたが、日本企業の多くはモノリシックなアーキテクチャと従来のデプロイモデルを維持し続けました。マイクロサービスへの移行には技術的な挑戦だけでなく、組織構造の変革(「2ピザチーム」など小規模で自律的なチーム編成)も必要でしたが、日本の階層的な組織文化はこうした変革に適応しにくい面がありました。「The Phoenix Project」や「The DevOps Handbook」といった書籍は日本語にも翻訳されましたが、その思想を実践に移す組織はWeb系企業の一部に限られていました。 2010年代後半〜現在:DevOpsの普及と進化 2010年代後半になると、DevOpsの考え方は業界全体に広がり、様々な形で進化していきました。 Googleによる「SRE(Site Reliability Engineering)」の提唱 2016年、Googleは「 Site Reliability Engineering: How Google Runs Production Systems 」の書籍を出版し、「 SRE(Site Reliability Engineering) 」の概念を広めました。 SRE サイトリライアビリティエンジニアリング ―Googleの信頼性を支えるエンジニアリングチーム オライリージャパン Amazon SREはDevOpsの思想を具現化し、システム運用と開発の連携を深めるための実践的なアプローチです。具体的には、ソフトウェアエンジニアリングの考え方をインフラや運用に適用し、システムの信頼性を向上させることを目的としています。 「You build it, you run it(作ったものは自分で運用する)」文化の定着 Amazonのエンジニアリング文化から生まれた「 You build it, you run it (作ったものは自分で運用する)」の考え方もこの時期に広く普及しました。これは開発者が自ら作ったシステムの運用にも責任を持つ考え方で、開発と運用の境界をさらに曖昧にするものです。この文化の下では、開発者はコードを書くだけでなく、デプロイ、モニタリング、障害対応など、システムのライフサイクル全体に関わることが求められるようになりました。 DORA(DevOps Research and Assessment)の設立と影響 Dr. Nicole Forsgren (開発生産性Conference 2024にて) 2016年、 ニコール・フォースグレン(Nicole Forsgren) 、 ジェズ・ハンブル(Jez Humble) らによって「 DORA(DevOps Research and Assessment) 」が設立されました。DORAはDevOpsの実践と組織のパフォーマンスの関係を科学的に調査・分析する組織として、毎年「 State of DevOps Report 」を発行し、世界中の何千もの組織からデータを収集・分析することで、DevOpsの実践がビジネス成果にどのように影響するかを明らかにしてきました。 特に、 「デプロイ頻度」「変更のリードタイム」「変更失敗率」「サービス復旧時間」 の4つの主要指標(Four Key Metrics)を確立し、これらがビジネスパフォーマンスと強い相関関係にあることを示しています。 2018年には、ニコール・フォースグレン、ジェズ・ハンブル、そしてジーン・キムによって書籍「 Accelerate: The Science of Lean Software and DevOps: Building and Scaling High Performing Technology Organizations (邦題:LeanとDevOpsの科学)」が出版され、DORAの研究成果が体系的にまとめられました。この書籍によってDevOpsの実践は「科学」として確立される重要な一歩を踏み出しました。 LeanとDevOpsの科学[Accelerate] テクノロジーの戦略的活用が組織変革を加速する (impress top gear) 作者: Nicole Forsgren Ph.D. , Jez Humble , Gene Kim インプレス Amazon DORAの10年にわたる研究と進化については、2025年4月に開催されたDevOpsDays Tokyo 2025にて私がお話した資料で詳しく解説していますので、そちらもぜひご参照ください。 DORAの研究は、DevOpsの実践が単なるトレンドではなく、科学的に裏付けられた効果的なアプローチであることを証明する上で、非常に重要な役割を果たしています。 クラウドネイティブ技術とDevOpsの融合 AWS、Google Cloudなどのクラウドサービスの活用が拡大し、「 クラウドネイティブ 」な開発が注目され始めました。これを支える技術として、コンテナ技術(Docker)やコンテナオーケストレーション(Kubernetes)が普及し、インフラとアプリケーションの管理がより柔軟かつ自動化されるようになりました。これらの技術はDevOpsの実践をさらに加速させる役割を果たしています。特に「 Infrastructure as Code(IaC) 」の考え方が広まり、インフラストラクチャの構成もコードとして管理されるようになりました。これによりインフラの変更も開発プロセスの一部として扱われるようになり、開発と運用の統合がさらに進みました。現代ではTerraform、AWS CloudFormation、Ansible、Puppet、ChefなどのIaCツールが広く使われるようになっています。 DevOpsの文化的側面の重視 技術的な進化と並行して、DevOpsの 文化的側面 も重視されるようになりました。成功したDevOps組織では次のような文化的特徴が見られます。 心理的安全性 - 失敗を非難せず、学びの機会として捉える文化 実験と学習の奨励 - 小さな実験を繰り返し、継続的に改善する姿勢 部門間の協力 - 開発、運用、セキュリティなど、従来は分断されていた部門間の壁を取り払う 共有責任 - 「これは私の担当ではない」考え方ではなく、全員がプロダクトの成功に責任を持つ これらの文化的側面がなければ、いくら優れたツールを導入しても、DevOpsの真の価値を実現できないという認識が広まっています。 DevOpsの本質と今後の展望 2009年に始まったDevOpsの旅は、今や業界標準となる考え方を生み出しました。現在、多くの開発チームが CI/CDを導入し、自動化されたデプロイパイプラインを運用 しています。開発者がコードをコミットすると、すぐにテストが走り、数分後には本番環境へデプロイされるようになりました。リリースのたびに夜を徹して作業したり、「障害対応のために今すぐオペレーションチームを呼べ」と叫んだりする文化は、徐々に過去のものになりつつあります。 しかし、DevOpsの本質は単なる「自動化」ではありません。 開発と運用の対立を超え、継続的に改善し続ける組織文化を作る ことこそがDevOpsの本質なのです。パトリック・ドボア、ジェズ・ハンブル、ジーン・キムをはじめとする先駆者たちが伝えたかったのは、ツールや技術の導入ではなく、根本的な組織文化の変革でした。 これからの開発者は、ツールやプロセスだけでなく、 組織全体の在り方に目を向ける必要があります。 そして、彼らが築いたこの文化をさらに進化させていくことが求められているのです。 日本におけるDevOps:まだ「一部の人たちのモノ」状態 DevOpsは世界的に広がりを見せていますが、日本においては普及のペースが異なる面もあります。 例えば、AgileやScrumのイベントと比べると DevOpsDays Tokyo の参加規模や盛り上がりが海外の同様のイベントと比較して小さいことからも、日本特有の課題があることがうかがえます。 また、2018年10月には「ソフト高速開発のDevOps推進協議会が2年余で解散」というニュースも報じられました。 日本でDevOpsの導入が遅れている理由を構造的・文化的・歴史的背景から分析すると、次のような要因が浮かび上がってきます。 要因 説明 「開発」と「運用」の切り分けの曖昧さ 日本の多くの企業では、「システム部門」が開発・運用・保守のすべてを抱える体制が一般的でした。このため、DevOpsが解決しようとした「開発と運用の断絶」問題が顕在化していなかったのです。しかし、これは必ずしも良い状況ではありませんでした。役割は分かれていなくても、責任やナレッジの分担があいまいで属人的になりやすいという弊害がありました。つまり、「DevとOpsが分かれていない」状態が「連携している」状態を意味するわけではなかったのです。 受託開発構造と多重下請けによる分業文化 日本では、ユーザー企業 → SIer(元請)→ サブ → 孫 という多重下請け構造が見受けられます。この構造だと、開発はA社、運用はB社、保守はC社という分断が起きやすく、DevOpsの前提である「共通の目標と責任」が成立しにくい環境にあります。DevとOpsが物理的にも契約上も分断されているため、DevOps導入が困難な状況が生まれているのです。 製造業モデルの異なる解釈:工程主義・完璧主義 日本のITは製造業の品質管理思想の影響を強く受けていますが、その解釈に課題がありました。本来、トヨタ生産方式は「継続的改善(カイゼン)」や「小さな改善の積み重ね」を重視し、これはDevOpsの思想と共通しています(実際、DevOpsの「リーン」の考え方はトヨタ生産方式が起源です)。しかし、多くの日本のIT現場では、製造業の手法を「工程の厳格な管理」「完璧な品質の追求」という側面だけを取り入れ、「リリース=出荷」「不具合=欠陥品」という感覚が強くなりました。このため、頻繁なリリース(Continuous Delivery)や早期フィードバックといったDevOpsの実践が「リスク」だと認識される傾向があるのです。皮肉なことに、トヨタ生産方式の本質を正しく理解していれば、DevOpsへの親和性はむしろ高かったはずなのです。 インフラのアウトソーシング文化 データセンターやネットワーク運用を外部委託(SIer、DC事業者など)するのが主流で、自社にインフラ運用の知見が蓄積しにくい環境があります。クラウドネイティブな文化が根付く前は、Opsの自動化やSRE的観点がなじみにくかったのです。これにより、DevOpsの「Infrastructure as Code」「SRE」文化が根付きにくい土壌が形成されていました。 ITが"事業の中核"ではないという認識 海外(特に米国)では、2000年代以降「ITは競争力の源泉」と位置づけられていましたが、日本ではITが依然として「業務支援」「コストセンター」と見なされ、IT部門が戦略の中心になりにくい状況がありました。このため、DevOpsが本来持つ「ビジネス成果を最大化するための仕組み」としての意義が伝わりにくかったのです。 しかし、近年日本企業においてもDevOpsへの関心が高まっています。その背景には次のような変化があります。 クラウドネイティブの普及 :AWS、Google Cloudなどのクラウドサービスの活用が拡大し、インフラのコード化や自動化の基盤が整いつつある 内製回帰の動き :特にメガベンチャーやSaaS企業を中心に、システム開発の内製化が進み、DevOpsの実践がしやすい環境が生まれている 開発生産性の重視 :Developer Productivity向上への注目が高まり、CI/CDパイプラインの構築など、開発効率を高める取り組みが増えている AI革命の波 :ChatGPTをはじめとする生成AIの登場により、ソフトウェア開発および運用の在り方が根本から変化しつつある。コードの生成、テストの自動化、運用時のログ分析や障害対応といった領域でAIが実用段階に入り、従来の手作業中心のプロセスは再構築を迫られている。AIは単なる補助ツールではなく、開発サイクル全体に組み込まれる存在となりつつあり、その文脈においてDevOpsも再定義され始めている。 この変化は一様ではありません。新興企業やIT専業企業ではDevOpsの実践が進む一方、大企業・公共系・受託構造の現場では依然として課題が残っています。 DevOpsとAI導入の共通障壁 冒頭のパトリック・ドボアは、1年前のDevOpsDays Tokyo 2024に於いて 「成熟したDevOps文化を持つ人々こそ、GenAIのアーリーアダプターになりつつある」と仰っており、事実そうなりました。 つまり、AI革命の波が押し寄せる中、DevOpsを受け入れられなかった組織は、同じ理由でAIの波にも乗り遅れる危険性があります。 日本でDevOpsを根付かせるためには、単なる「技術導入」ではなく「組織文化改革」として捉える必要があります。「DevOpsを導入する」=「ツールを導入する」ではなく、「職能や役割を超えてチームが協働する文化を育む」ことであり、この文化的転換が日本では特に重要です。 そのため、日本の組織構造を考慮した上で、DevOpsの本質を理解し、自分たちの組織に合った形で取り入れていくためには、次のようなアプローチが有効と思われます。 変化を恐れない文化の醸成 「失敗は学びの機会」という認識を組織全体で共有する 小さな変更から始め、成功体験を積み重ねることで自信をつける クロスファンクショナルチームの形成 部門の壁を越えた協働を促進し、共通の目標に向かって取り組む環境をつくる 開発・運用・ビジネス部門が一体となって価値を生み出す体制を構築する 長期的視点での投資判断 短期的ROIだけでなく、競争力維持の観点から技術投資を判断する 継続的な学習と実験の文化を育み、組織全体の適応力を高める DevOpsの歴史は、技術の進化だけでなく、人々の協力関係や組織文化の変革の歴史でもあります。その本質を理解し、自分たちの組織に合った形で取り入れていくことが、現代のソフトウェア開発者に求められています。そして、日本の文化や組織構造に合ったDevOpsの形を模索していくことも、私たち日本のエンジニアの重要な課題なのです。 ◆ AI時代の適応 ◆ AI時代は、変化に適応できる組織とそうでない組織の差がさらに拡大します。DevOpsの本質である「変化を恐れず、継続的に改善し続ける文化」は、この時代において競争力の源泉となります。 AIの導入に伴い、一部では大規模言語モデルのコストやセキュリティの観点から自社データセンターでのAI基盤構築が選択されています。しかし、これは単なるオンプレミスへの回帰ではなく、クラウドで培われたDevOps手法(IaC、コンテナ技術)を活用した新たな形態です。インフラの場所に関わらず、「自動化」「協働」「継続的改善」というDevOpsの価値はAI時代においてより重要になっています。 過去にDevOpsの波に乗り遅れた組織も、AI時代という転換点を変革の契機として活かすことができるのではないでしょうか。 【告知】ソフトウェア開発の伝説的パイオニアたちが来日! 2025年7月3日(木)・4日(金)9:30〜19:00、弊社主催「 開発生産性Conference2025 」にて、ソフトウェア開発の歴史を塗り替えた二人の巨匠が来日します。 アジャイルとDevOpsの世界的権威が同時来日する歴史的瞬間に、ぜひ立ち会ってください。 ケント・ベック(Kent Beck) — アジャイル開発の父、XPの創始者、TDDの提唱者。 ジーン・キム(Gene Kim) — DevOps革命の立役者、『The Phoenix Project』著者。 本ブログで紹介した「ソフトウェア開発の開拓者たち」が、目の前で語る貴重な機会です。彼らの思想と実践が世界のソフトウェア開発をどう変えたのか、そして日本のエンジニアが今後どう進むべきか—直接その声を聞ける、まさに一生に一度のチャンスです。 DevOpsの本質を理解し、AI時代を勝ち抜くための知恵を、開発現場の革命家たちから直接学びませんか? 参加登録はこちら : 開発生産性Conference2025公式サイト ファインディでは一緒に会社を盛り上げてくれるメンバーを募集中です。興味を持っていただいた方はこちらのページからご応募お願いします。 *1 : Edwards, D. (2014, May 16). The incredible true story of how DevOps got its name. New Relic. https://newrelic.com/blog/nerd-life/devops-name
アバター
こんにちは! Findy Team+ 開発チームでEMをしている ham です。 今年もRubyKaigi 2025に参加してきました。 私はコロナ後に三重県で開催されたときから4年連続で参加しているのですが、今年も興味深いセッションがたくさんあり、Rubyが着実に進化していることを感じることができました! 本記事では、その中の1つである「 The Ruby One-Binary Tool, Enhanced with Kompo 」で紹介された「 Kompo 」について、実際に試してみた結果と所感を紹介します。 Kompoとは 'Hello, world!'を返すスクリプト kompo-vfs Kompo Rails 最後に Kompoとは Kompoとは、READMEで「A tool to pack Ruby and Ruby scripts in one binary.」と紹介されている通り、Rubyスクリプトをバイナリにして配布できるツールです。 Rubyのスクリプトをバイナリにすることで、配布が容易になったり、実行環境にRubyのインストールが不要になるため配布先での実行が容易になります。 Kompoは2024年のRubyKaigiでも「 It's about time to pack Ruby and Ruby scripts in one binary 」のセッションで紹介されており、興味を持っていました。 2024年の時点ではRailsなどの大きなGemは実行できていないとのことだったのですが、2025年の発表ではRailsが動いており進化を感じました! 'Hello, world!'を返すスクリプト 今回のセッションでRailsが動作するようになったと発表されていたので、Webサーバーが動作するバイナリを作ってみることにしました。 とはいえ、最初から大きなものを作ろうとすると詰まる可能性が高いです。何事もスモールスタートが良いですね。 入門といえば 'Hello, world!'ということで、まずは'Hello, world!'を返却するRubyスクリプトでトライしました。 なお、ここからの内容は執筆時点(2025年4月)の情報なので最新版では変更されている可能性があります。 kompo-vfs Kompoは内部で仮想ファイルシステムを使っているとのことです。 当初は既存のライブラリで実現できないか検討したとのことですが、Kompoのやりたいことにマッチするものがなかったとのことで「 kompo-vfs 」を自作したそうです。 リポジトリを見ていただければわかりますが、こちらはRustで書かれていました。 Kompoを使うにはまずkompo-vfsをbuildしておく必要があるとのことです。 READMEに沿って作業します。 READMEには次のように記載されています。(※手順はKompoのREADMEに記載されています) ## prerequisites Install [kompo-vfs](https://github.com/ahogappa/kompo-vfs). #### Homebrew $ brew tap ahogappa/kompo-vfs https://github.com/ahogappa/kompo-vfs.git $ brew install ahogappa/kompo-vfs/kompo-vfs ### Building To build komp-vfs, you need to have cargo installation. $ git clone https://github.com/ahogappa/kompo-vfs.git $ cd kompo-vfs $ cargo build --release Set environment variables. $ KOMPO_CLI=/path/to/kompo-vfs/target/release/kompo-cli $ LIB_KOMPO_DIR=/path/to/kompo-vfs/target/release MacBookを使っているのでbrewの手順を試しましたがうまくいかなかったので、リポジトリをcloneする方法で実施しました。 mainブランチで試してみましたが、buildがエラーになりました。 % cargo build --release ... error: could not compile `kompo_storage` ( lib ) due to 2 previous errors; 1 warning emitted こちら色々解析したところ、MacBookには対応してなさそうだとわかりました。 そこでDockerを立ち上げてその中でbuildすることにしました。 Kompoの実行もDockerで実施した方が良さそうだったので、Rubyイメージから作成しました。 # Dockerfile FROM ruby:3.4.3 # Install dependencies RUN apt-get update && apt-get install -y \ git \ build-essential \ libssl-dev \ zlib1g-dev \ libyaml-dev \ libgmp-dev \ libreadline-dev \ pkg-config \ autoconf \ bison \ curl \ && apt-get clean \ && rm -rf /var/lib/apt/lists/* # Install latest Rust using rustup RUN curl --proto ' =https ' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y ENV PATH= "/root/.cargo/bin:${PATH}" # Set working directory WORKDIR /app # Copy project files COPY . /app/ # Install bundler and dependencies RUN gem install bundler && bundle install CMD [ " tail ", " -f ", " /dev/null " ] buildしてbashでコンテナに入り、改めてbuildしたら成功しました🙌 % docker build -t hello-world . % docker run -it --rm -v .:/app hello-world bash root:/app# cd kompo-vfs/ root:/app/kompo-vfs# cargo build --release ... Finished `release` profile [ optimized ] target ( s ) in 5 .40s Kompo 次に'Hello, world!'を返すRubyスクリプトを作成します。 # hello.rb p ' Hello, world! ' Kompoはgemが公開されていないのでリポジトリをcloneしてローカルで生成します。 色々試行錯誤したのちに気づいたのですが、登壇スライドの26ページによるとkompoは feature/use_rtld_next の方が最新と思われるのでそちらを利用します。 Dockerコンテナ内で gem build を実施して、無事 kompo-0.2.0.gem が生成できました。 root:/app/kompo# gem build kompo.gemspec WARNING: licenses is empty, but is recommended. Use an license identifier from https://spdx.org/licenses or 'Nonstandard' for a nonstandard license, or set it to nil if you don't want to specify a license. WARNING: open-ended dependency on mini_portile2 (>= 0) is not recommended use a bounded requirement, such as "~> x.y" WARNING: open-ended dependency on async (>= 0) is not recommended use a bounded requirement, such as "~> x.y" WARNING: You have specified the uri: https://github.com/ahogappa0613/kompo for all of the following keys: homepage_uri changelog_uri source_code_uri Only the first one will be shown on rubygems.org WARNING: See https://guides.rubygems.org/specification-reference/ for help Successfully built RubyGem Name: kompo Version: 0.2.0 File: kompo-0.2.0.gem 次に、Kompoをインストールします。 root:/app# gem install kompo/kompo-0.2.0.gem ... Successfully installed kompo-0.2.0 10 gems installed 時は来た!あとは梱包(Kompo)するだけです!実行には数分かかるので待ちます。 root:/app# kompo --help Usage: kompo [options] -e, --entrypoint=VAL File path to use for entry point. (default: './main.rb') -g, --use-group=VAL Group name to use with 'bundle install'. (default: 'default') --[no-]gemfile Use gem in Gemfile. (default: automatically true if Gemfile is present) --local-kompo-fs-dir=VAL --verbose Verbose mode. --dest-dir=VAL Output directry path. (default: current dir) --bundle-cache=VAL Specify the directory created by 'bundle install --standalone'. --ruby-version=VAL Specify Ruby version. (default: current Ruby version) --rebuild --repack root:/app# kompo -e hello.rb --local-kompo-fs-dir=kompo-vfs ... /usr/bin/ld: /app/kompo-vfs/target/release/libkompo_fs.a(529179467e613863-dummy_fs.o):(.rodata.WD+0x0): multiple definition of `WD'; /tmp/ccNwyWlB.o:(.rodata+0x0): first defined here /usr/bin/ld: /app/kompo-vfs/target/release/libkompo_fs.a(529179467e613863-dummy_fs.o):(.rodata.PATHS_SIZE+0x0): multiple definition of `PATHS_SIZE'; /tmp/ccNwyWlB.o:(.rodata+0x28): first defined here /usr/bin/ld: /app/kompo-vfs/target/release/libkompo_fs.a(529179467e613863-dummy_fs.o):(.rodata.PATHS+0x0): multiple definition of `PATHS'; /tmp/ccNwyWlB.o:(.rodata+0x30): first defined here /usr/bin/ld: /app/kompo-vfs/target/release/libkompo_fs.a(529179467e613863-dummy_fs.o):(.rodata.FILES_SIZE+0x0): multiple definition of `FILES_SIZE'; /tmp/ccNwyWlB.o:(.rodata+0x192c8): first defined here /usr/bin/ld: /app/kompo-vfs/target/release/libkompo_fs.a(529179467e613863-dummy_fs.o):(.rodata.FILES+0x0): multiple definition of `FILES'; /tmp/ccNwyWlB.o:(.rodata+0x192d0): first defined here collect2: error: ld returned 1 exit status /usr/local/bundle/gems/kompo-0.2.0/lib/kompo.rb:193:in 'Kernel#system': Command failed with exit 1: gcc (RuntimeError) エラーが発生しました。 kompo-vfsで、 WD や PATHS などの定義が重複しているようです。 Rustなんもわからんので雰囲気ですが、 kompo-vfs/kompo_fs/dummy_fs.c の中で WD や PATHS が定義されているので怒られた定義をコメントアウトして再buildしてやり直してみました。 // kompo-vfs/kompo_fs/dummy_fs.c // const char FILES[] = {}; // const int FILES_SIZE = 0; // const char PATHS[] = {}; // const int PATHS_SIZE = 0; // const char WD[] = {47,119,111,114,107,115,112,97,99,101,115,47,114,117,98,121,95,112,97,99,107,97,103,101,114,47, 0}; const char START_FILE_PATH[] = { 46 , 47 , 109 , 97 , 105 , 110 , 46 , 114 , 98 , 0 }; 再度Kompoを実行。今回は正常終了し、バイナリ( app )が生成されました🎉 root:/app# kompo -e hello.rb --local-kompo-fs-dir = kompo-vfs ... info: Finish kompo! root:/app# ls -l app -rwxr-xr-x 1 root root 76437456 Apr 24 02:15 app root:/app# ./app " Hello, world! " 最後に実行します。せっかくなのでRubyが入っていない環境で実行します。 Dockerfileはこちらを使いました。 FROM ubuntu:latest # Set working directory WORKDIR /app # Copy project files COPY . /app/ CMD [ " tail ", " -f ", " /dev/null " ] コンテナを立ち上げて、バイナリを実行します。 Rubyが入っていない環境で実行できました🎉 % docker build -t ubuntu-app . % docker run -it --rm -v .:/app ubuntu-app bash root@62df4e7aa257:/app# ./app " Hello, world! " Rails 簡単なRubyスクリプトでは実行できることがわかったので、次はRailsに挑戦です! ただ、結論は「色々試行錯誤したものの起動できず」でした... Kompoするところでエラーになったり、Kompoはできたが起動できなかったり、これ以上の解析は難しいので今回は諦めました🙏 最後に 今回は簡単なRubyスクリプトしかできませんでしたが、ワンバイナリで配布してすぐに実行できることはとても便利だと感じました。今後の更なる進化に期待です!! 5/13(火)に、「After RubyKaigi 2025〜ZOZO、ファインディ、ピクシブ〜」として、ピクシブ株式会社、株式会社ZOZO、ファインディ株式会社の3社でRubyKaigi 2025の振り返りを行います。 LTやパネルディスカッションなどコンテンツ盛りだくさんなのでぜひご参加ください!! https://pixiv.connpass.com/event/352852/ pixiv.connpass.com また、ファインディではこれからも新しいものを取り入れつつ、Rubyを積極的に活用してRubyとともに成長していければと考えております。 一緒に働くメンバーも絶賛募集中なので、興味がある方はぜひこちらから ↓ herp.careers
アバター
こんにちは。ファインディでソフトウェアエンジニアをしている nipe0324 です。 先日、愛媛県松山市で開催されていたRubyKaigi 2025 に参加してきました。 様々なセッションに参加し、他社のエンジニアと話す中で多くの刺激をうけました。特に印象深かったのは、Sansanさん、TwoGateさん、Timeeさんなどの企業がRubyの型を導入して運用していたことです。 本記事では、N番煎じですが、 Findy転職のRailsプロジェクトにSorbetを入れて型を試してみました。 Rubyの型に興味を持っているけどなかなか試せていないという方の参考になれば嬉しいです。 Sorbetとは? Sorbetのメリット・デメリット Sorbetを使ったRubyのコード例 型導入のモチベーション 課題:Interactorの入出力が不明確 検証した結果 検証の実施内容 Sorbetのセットアップ ActiveRecordのモデルに型を追加 GraphQLに型を追加 Interactorに型を追加 型を試してみた所感 最後に Sorbetとは? Sorbet( https://github.com/sorbet/sorbet ) はRubyのコードに型注釈をつけて、型エラーを検出できるツールです。 StripeやShopifyなどの企業で利用されています。 Sorbetを利用している企業例 Sorbetのメリット・デメリット Sorbetの主なメリットとデメリットは次のとおりです。 メリット 型エラーを事前に検出できる コードの可読性と保守性の向上 IDEのコード補完がより有効に使える リファクタリングが安全に行える ドキュメントとしての役割 デメリット 導入コストがかかる(コードの修正、型定義の作成) Rubyの動的な特性が一部制限される チーム全体に学習コストが発生する すべてのgemやライブラリが型に対応しているわけではない Sorbetを使ったRubyのコード例 Sorbetを使うと、型をインラインで定義できます。 このコード例では、 sig を使って、 greet メソッドが String 型の引数を受け取ることを宣言しています。そのため、数値(Integer)を渡すと型エラーが検出されます。 # typed: true class User extend T :: Sig sig {params( name : String ).void} def greet (name) " Hello #{ name }" end end User .new.greet( " Tom " ) # OK - 文字列を渡している User .new.greet( 3 ) # 型エラー - 数値を渡している 補足:Rubyの型チェッカー 現時点でRubyの主要な型チェッカーとして「Sorbet」と「Steep」があります。また、型定義の書き方として「RBI(Ruby Interface)」と「RBS」があります。 現時点の組み合わせとして、「SorbetとRBI」、「SteepとRBS」というように型チェッカーと型定義を組み合わせ使います。 型導入のモチベーション Findy転職のRailsアプリケーションでは、「GraphQL API」、「Interactor」、「Model」といったレイヤーで実装をしています。 Findy転職のRailsアプリケーションのレイヤー抜粋 Interactor層では、 collectiveidea/interactor という gemを使ってビジネスロジックを実現しています。 1つの操作(ユーザー登録やデータ検索など)を独立した「インタラクション」として実装でき、責務を分割しやすい特徴があります。 課題:Interactorの入出力が不明確 Interactor gemの内部では OpenStruct を使ってデータの受け渡しをしています。つまり、どんな値でも自由に設定できる柔軟性がある反面、入出力として何があるのか不明確になりやすいです。 Interactorのサンプルコードで問題点を確認してみます。 # イメージ実装 # Interactorの実装 class CreateJobDescriptionInteractor include Interactor def call job_description = JobDescription .new(create_params) if job_description.save # contextには何でも入れられるため # Interactorの返り値を知るには実装を読む必要がある context.job_description = job_description else context.fail!( error_messages : job_description.errors.full_messages) end end private def create_params # contextにどんな値が入るかは呼び出し元を見る必要がある context.params.slice( :name , :description , :job_type ) end end # 呼び出し元 # Interactorの中身を読まないと何が返されるか分からない result = CreateJobDescriptionInteractor .call( params : { title : ' 求人票 ' , description : ' 求人票の内容 ' , job_type : ' バックエンドエンジニア ' }) if result.success? result.job_description # job_descriptionが返るのは実装を読まないと分からない else result.error_messages # error_messagesが返るのも実装を読まないと分からない end この問題に対して、型を導入することで入出力を明確にし、コードの可読性と保守性を向上させることを目指しました。 検証した結果 Sorbetの 公式ドキュメント を見ながら、GraphQL、Interactor、Modelに対して型を定義してみました。 結果としては、次のとおりです。 ActiveRecordのモデル と GraphQLのAPI は tapioca を使うことで型定義(RBIファイル)をある程度自動生成できるため導入が簡単 Interactor は、gemの特性上、型との相性が悪く、PORO(Plain Old Ruby Object)などの設計の変更の実施が必要そう 検証の実施内容 Sorbetのセットアップ SorbetのGetting Started ( https://sorbet.org/docs/adopting ) を見ながらセットアップをしました。 まず、Gemfileに必要なgemを追加して bundle install を実行します。 # Gemfile gem ' sorbet ' , group : :development gem ' sorbet-runtime ' gem ' tapioca ' , require : false , group : [ :development , :test ] 次に、Tapiocaを使ってSorbetを初期化します。このコマンドで sorbet ディレクトリが作成され、プロジェクト内のGemに対して自動的に型定義(RBIファイル)が生成されます。 $ bundle exec tapioca init Sorbetによる型チェックを実行します。初回は多くの型エラーが出るので、修正していきます。 $ bundle exec srb tc 型エラーを修正して、型チェックが成功したら初期セットアップは完了です 👏 $ bundle exec srb tc No errors! Great job. ActiveRecordのモデルに型を追加 tapioca dsl コマンドを使うことで、ActiveRecordモデルやGraphQL-RubyのDSL(Domain Specific Language、特定領域向け言語)から自動的に型定義ファイル(RBIファイル)が作成できます。 $ bundle exec tapioca dsl Loading DSL extension classes... Done Loading Rails application... create sorbet/rbi/dsl/skill.rbi create sorbet/rbi/dsl/user.rbi create sorbet/rbi/dsl/job_description.rbi create sorbet/rbi/dsl/xxxx.rbi ... 例えば、JobDescriptionモデルがある場合、次のようなRBIファイルが自動的に作成されます。これによりモデルのプロパティに型情報が付与されます。 class JobDescription # ... sig { returns(:: String ) } def title ; end sig { params( value : :: String ).returns(:: String ) } def title= (value); end sig { returns( T :: Boolean ) } def title? ; end sig { returns( T .nilable(:: String )) } def title_before_last_save ; end では、実際に型チェックが機能するか検証してみます。 # typed: true をファイルの上部に追加し、型チェックの対象にします。さらに、型エラーの動作確認のためわざと不正な値を設定してみます。 # frozen_string_literal: true # typed: true class JobDescription < ApplicationRecord def type_check_error_method self .title = 1 # String型のプロパティにInteger型を設定(エラーになるはず) end end Sorbetを実行すると、予想通り型エラーが検出されました。👏 $ bundle exec srb tc app/models/job_description.rb:6: Assigning a value to value that does not match expected type String https://srb.help/7002 6 | self.title = 1 ^ Got Integer(1) originating from: app/models/job_description.rb:6: 6 | self.title = 1 ^ Errors: 1 GraphQLに型を追加 graphql-ruby を利用しているプロジェクトでは、GraphQLの型定義も tapioca dsl コマンドで自動生成されます。 # 自動生成されるRBIファイルのイメージ class CreateJobDescriptionMutation sig { params( title : :: String , company_id : :: Integer ).returns( T .untyped) } def resolve ( title :, company_id :); end end ActiveRecordモデルと同様に、型定義に違反した実装をすると型エラーが発生します。これによりGraphQLのリゾルバーやミューテーションにも型安全性を導入できます。 ただし、自動生成されたRBIファイルでは戻り値が T.untyped (型のない状態)になることがあるため、具体的な型を指定していく必要があると感じました。 Interactorに型を追加 Interactorでは、型定義をうまく行うことができませんでした。 Interactor gem で定義されている context が、 OpenStruct を使っておりデータが柔軟に設定できるがゆえに型定義がうまくできませんでした。 # frozen_string_literal: true # typed: true class CreateFooInteractor extend T :: Sig include Interactor # contextのアクセスのための型定義 # 上手く定義できず、`T.untyped`(型のない状態)になっている sig { returns( T .untyped) } attr_reader :context def call foo = Foo .new(create_params) if foo.save context.foo = foo else context.fail!( error_messages : foo.errors.full_messages) end end private def create_params context.params.slice( :title , :company_id ) end end 改善案としては、PORO (Plain Old Ruby Object) による実装に変えて、入出力の型を明確にすることで、型定義を記載するという方法が考えられます。 また、 https://github.com/maxveldink/sorbet-result にあるような Rustの Result 型に似た実装を導入するのも効果的です。Result型は「成功」か「失敗」のどちらかの状態を表現するもので、型安全な方法で結果を扱えるようになります。 型でガチガチにするとRubyの良さが失われてしまう懸念もあるため、段階的に導入しつつバランスを見ていく必要があります。 型を試してみた所感 今回既存のRailsプロジェクトでSorbetによる型を試しに導入しました。 感想としては、検討事項は他にもありますが、前向きに型導入を進めていこうと思いました。 ActiveRecordやGraphQLにほぼ自動的に型定義を追加できたり、段階的に型チェックを有効化できるので小さく始めやすい ローカル開発やCIで型チェック、GraphQLやテーブルスキーマ変更時の型更新のフローを整備する必要がある SorbetとSteepのどちらが良いかは好みによるので検討は必要がある など 最後に ファインディでは、一緒にRubyやRailsの開発をしてくれる仲間を募集しています。 興味のある方は、ぜひこちらからチェックしてみてください! herp.careers また、2025/5/13(火)に、「After RubyKaigi 2025〜ZOZO、ファインディ、ピクシブ〜」として、3社合同でRubyKaigi 2025の振り返りを行います。 オンライン・オフラインどちらもありLTやパネルディスカッションなどコンテンツが盛りだくさんなのでぜひご参加ください!! pixiv.connpass.com
アバター
はじめに こんにちは!ファインディでFindy Team+を開発している中嶋( @nakayama__bird )です! RubyKaigi 2025に参加してきました! 今回のRubyKaigiが初参加で楽しみ半分緊張半分だったのですが最高な3日間でした! Rubyを使う人、Rubyを作る人、そしてRubyで遊ぶ人などたくさんの出会いがあり、日頃の業務でRuby on Railsに触れるのとはまた違った視点でRubyを考えるきっかけとなりました。 複数のセッションに参加した中で特に関心を持ったruby.wasm、そしてruby.wasmを使ったスライド作成ツールgibier2を使ってみての感想をまとめていきたいと思います。 ruby.wasmとは? ruby.wasmとは、WebAssemblyという技術を使用してRubyのコードをブラウザ上で実行できる仕組みのことです。 Rubyをブラウザ上で実行できることで、環境構築をせずにRubyのコードを試せる環境の提供やフロントエンド実装への可能性を広げるといったメリットがあります。 github.com わかりやすい例だとruby.wasmのREADMEに掲載されている Try Ruby が挙げられます。ブラウザ上でRubyのコードが動くため、RubyをPCにインストールせずともどんな挙動になるのかを簡単に確認できます。 私自身、約1年半ほど前にコードを初めて書いた駆け出しエンジニアなのですが、ブラウザ上で動きが確認できるのは学習スタートのハードルが下がるという点でとても良いなと思いました。 また、ruby.wasmを活用した事例として、 Writing Ruby Scripts with TypeProf のセッションで、TypeProfをブラウザ上で試すことができる TypeProf.wasm が紹介されていました。 気軽に試してみて、もしエラーなどあれば報告してくださいというようなお話をしており、ruby.wasmが新しい技術を手軽に試せる環境として機能している点も大きな魅力だなと感じました。 TypeProf を ruby.wasm で完全ブラウザ上で動くようにしてみた。触ったらすぐにいろいろ粗が見つかるんで、気づいたらコントリビュートおねがいします https://t.co/4cF3Tm9QSm — Yusuke Endoh (@mametter) 2024年12月26日 x.com gibier2について gibier2は、ruby.wasmを使ったスライド作成ツールです。 dRuby on Browser Again! でトークセッションをした youchan さんが開発したもので、マークダウンでスライドを作成できます。 #RubyKaigi2025 Day1 の "dRuby on Browser Again!" のスライドを公開しました。ruby.wasmを読みこむのに少々時間がかかります。 https://t.co/oUKgrNdipp — よう (@youchan) 2025年4月18日 x.com github.com gibier2の前身として gibier があるのですが、こちらはRubyをJavaScriptにコンパイルする Opal を使用しているためスライド作成ツールという点では共通しているものの中身は別物です。 これまで私自身、スライドを作成する際にCanvaやGoogle スライドを使いGUI上で操作して作成していました。しかしながら箇条書きで内容整理をしながら登壇資料が作成できて便利そうということで、マークダウンを使ったスライド作成ツールが気になっていたため早速使ってみました! セットアップ 開発環境:ruby 3.3.6(RubyのWebAssemblyサポートは3.2.0以降 1 ) READMEを参考に実行しました。具体的なコマンドは、次の通りです。 リポジトリをcloneして bundle install します。 $ git clone https://github.com/youchan/gibier2 $ cd gibier2 $ bundle install その後、wasmディレクトリでのセットアップを行います。 README通りに bundle exec rbwasm build -o dist/ruby.wasm をしたところ失敗したため、先に cd wasm && bundle install を実行しました。 $ cd wasm && bundle install その後、wasmディレクトリで次のコマンドを実行します。 $ bundle exec rbwasm build -o ../dist/ruby.wasm $ bundle exec rbwasm pack ../dist/ruby.wasm --dir ./src::/src -o ../dist/app.wasm 初回のbuildには時間がかかるのしばらく待ちます。 ルートディレクトリに戻りRackアプリケーションサーバーを立ち上げます。 $ cd .. $ bundle exec rackup http://localhost:9161 を開くとサンプルのスライドが表示されます。 実際に使ってみる 基本的に slide.md にマークダウンで資料の内容を書いていきます。 <!-- slide.md --> ## RubyKaigi 2025行ってきました - ruby.wasmを知ることができた - gibier2を使ってみた - スピーカーとお話しできた スライドの背景画像は public/images 配下におきます。 それを public/css/custom.css で適宜設定します。 /* public/css/custom.css */ .page { background-image : url( "/images/new_background.png" ) ; color : #222 ; } 特にカスタマイズしないと、 # だとセンタリングされた状態、 ## だと見出しとして表示されます。 <!-- slide.md --> # #が1つの場合 <!-- slide.md --> ## #が2つの場合 マークダウンのためコードブロックやリンクの追加も可能です。 <!-- slide.md --> ## コードブロックやリンクの挿入も可能 <200b> `` `ruby def hello puts "Hello, World!" end <200b> ``` [Findy Tech Blog](https://tech.findy.co.jp/) まとめ RubyKaigiで関心を持ったruby.wasm、そしてruby.wasmを使ったスライド作成ツールgibier2を使ってみたという内容をまとめました。 発表を聞いてすぐにgibier2を試したのでぜひ youchan さんにお話を聞きたいなと思っていたところ、偶然にも弊社のDrink Upでお会いできました。セッション後、早速使ってみましたと伝えると喜んでもらえて私も嬉しい気持ちになりました。 このように、登壇者とコミュニケーションを取りやすいのもRubyKaigiの良いところです。 gibier2について、今回のRubyKaigiの登壇資料用として作ったため、汎用的なツールとしてはこれからだという話をしていました。 またGUI上でより細かなデザインをできるようにしたりPDFへ書き出せるようにしたりしたいなど、今後の展望についてもお話を聞くことができとても良い経験となりました。 またビルドの流れでREADMEに記載されている通りに実行したところうまく立ち上がらなかったため、Issueを作成してみました。 github.com OSSへの貢献したいなと考えていたので、RubyKaigiでの出会いをきっかけに実際に行動に移せてよかったです。 最後に 5/13(火)に、「After RubyKaigi 2025〜ZOZO、ファインディ、ピクシブ〜」として、ピクシブ株式会社、株式会社ZOZO、ファインディ株式会社の3社でRubyKaigi 2025の振り返りを行います。 オンライン・オフラインどちらもありLTやパネルディスカッションなどコンテンツ盛りだくさんなのでぜひご参加ください!! 今回のブログで取り上げたgibier2を使って登壇資料を作成予定なので、ぜひ見にきていただけると嬉しいです! pixiv.connpass.com また、ファインディではこれからも新しいものを取り入れつつ、Rubyを積極的に活用してRubyとともに成長していければと考えております。 一緒に働くメンバーも絶賛募集中なので、興味がある方はぜひこちらから ↓ herp.careers https://www.ruby-lang.org/ja/news/2022/12/25/ruby-3-2-0-released/ ↩
アバター
こんにちは、 Findy Freelance の開発をしているエンジニアの @2bo です。 先日、愛媛県で開催されたRubyKaigi 2025に参加してきました。ファインディのブースにお立ち寄りいただいた方、Rubyクイズに答えてくださった方、Drinkupに参加していただいた方、運営やSpeakerの皆様、ありがとうございました! おかげさまでとても楽しく過ごすことができ、興味深いセッションもたくさんありました。 本記事では、その中の1つである @sinsoku_listy さんの「 Automatically generating types by running tests 」で発表されていた「 RBS::Trace 」を早速、Findy FreelanceのRailsプロジェクトで試してみた結果と所感を紹介します。 RBS::Traceとは 実行手順 1. Gemfileへの追加 2. RSpecの設定 3. テストの実行 結果の確認 Inline RBS RBSファイル 参考: テストコード 結果からわかったこと Steepの導入 1. Gemfileへの追加 2. Bundle install 3. Steepの設定 4. VSCodeのSteep拡張のインストール 5. RBSファイルのエラーをコメントアウト VSCodeでの型情報の表示結果 所感 RBS::Trace導入のメリットと可能性 活用における注意点 RBSのプロジェクトへの導入について 最後に 参考 RBS::Traceとは RBS::Trace は、コード実行時にメソッドの引数と戻り値の型情報を収集し、自動的にInline RBSとしてコメントを挿入したり、RBSファイルを作成してくれるGemです。 実行手順 Findy FreelanceのRailsプロジェクトのテストでRBS::Traceを実行してみました。 執筆時点のバージョンは 0.5.1 です。 なお、今回は大枠の動作と結果を確認することが目的のため、対象は app/models/ 配下に絞っています。 次の手順で実行しました。 1. Gemfileへの追加 gem " rbs-trace " Gemfile追記後にbundle installを実行します。 $ bundle install 2. RSpecの設定 次に、RSpec用の設定ファイルを作成します。 RBS::Trace.new の引数 paths で対象のファイルを指定しています。 RBSファイルの格納先として、 sig/trace/ を指定しています。なお、この設定は任意です。 # spec/support/rbs_trace.rb RSpec .configure do |config| # RBSの出力対象とするファイルを指定 trace = RBS :: Trace .new( paths : Dir .glob( "#{ Dir .pwd } /app/models/**/* " )) config.before( :suite ) { trace.enable } config.after( :suite ) do trace.disable trace.save_comments # RBSファイルの格納先を指定 trace.save_files( out_dir : " sig/trace/ " ) end end 3. テストの実行 テストを実行します $ bundle exec rspec spec/models/ 結果の確認 テストを実行すると、Inline RBSが対象のファイルのメソッド定義の上に追記され、RBSファイルが生成されます。 それぞれの結果を次に示します。 なお、掲載しているのは例示用のコードで、実際のFindy Freelanceのコードや処理とはまったく関係ありません。 Inline RBS app/models/user.rb に次のようなInline RBSが生成されました。 class User < ApplicationRecord has_many :projects # @rbs () -> String def full_name format_name end # @rbs () -> Project? def main_project projects.find_by( active : true ) end # @rbs (Date) -> Project::ActiveRecord_AssociationRelation def projects_after_date (date) projects.where( ' start_date >= ? ' , date) end private # @rbs () -> String def format_name "#{ last_name } #{ first_name }" end end RBSファイル sig/trace/app/models/user.rbs に次のようなRBSファイルが生成されました。 class User def full_name : () -> String def main_project : () -> nil | () -> Project def projects_after_date : ( Date ) -> Project :: ActiveRecord_AssociationRelation def format_name : () -> String end 参考: テストコード RBS::TraceによるRBSの自動生成は、テストで実行された内容に依存するため、参考としてテストコードの例を掲載します。 RSpec .describe User do describe ' #full_name ' do subject { user.full_name } let( :user ) { create( :user , first_name : ' 太郎 ' , last_name : ' 山田 ' ) } it ' returns a string combining last_name and first_name ' do expect(subject).to eq( ' 山田 太郎 ' ) end end describe ' #main_project ' do subject { user.main_project } let( :user ) { create( :user ) } context ' when there is an active project ' do let!( :active_project ) { create( :project , user : user, active : true ) } let!( :inactive_project ) { create( :project , user : user, active : false ) } it ' returns the active project ' do expect(subject).to eq(active_project) end end context ' when there is no active project ' do let!( :inactive_project ) { create( :project , user : user, active : false ) } it ' returns nil ' do expect(subject).to be_nil end end end describe ' #projects_after_date ' do subject { user.projects_after_date(target_date) } let( :user ) { create( :user ) } let( :target_date ) { Date .new( 2025 , 4 , 1 ) } let!( :before_project ) { create( :project , user : user, start_date : Date .new( 2025 , 3 , 31 )) } let!( :on_date_project ) { create( :project , user : user, start_date : Date .new( 2025 , 4 , 1 )) } let!( :after_project ) { create( :project , user : user, start_date : Date .new( 2025 , 4 , 2 )) } it ' returns only projects starting on or after the specified date ' do expect(subject).to include (on_date_project, after_project) expect(subject).not_to include (before_project) end end end 結果からわかったこと 実行した結果、次のようなことがわかりました。 直接テストしていないが、テスト中に実行されるprivateメソッドの型情報も生成されている ApplicationRecordのサブクラスを返すメソッドは、具体的なクラス名で型情報が生成されている Railsのassociationやscopeメソッドには、型情報が生成されない これらはメソッド定義ではないため当然の結果である ActiveRecord::AssociationRelationのサブクラスのインスタンスを返すメソッドは、[具体クラス名]::ActiveRecord_AssociationRelationのように型情報が記載される これはRailsの仕様によるもので、そのようなクラスを動的生成しているためである 既に記載されているInline RBSは上書きされない RBSファイルの内容はテストを実行するたびに更新される Steepの導入 せっかくRBSファイルが生成されたので、型チェッカーである Steep もセットアップして型のあるRailsプロジェクトを疑似体感してみました。 ただし、RBS::TraceだけでRailsプロジェクトの型チェックすべてパスさせることはできないため、VSCodeで型情報を確認できるようにすることを目的としています。 次の手順でセットアップしました。 1. Gemfileへの追加 # Gemfile gem ' steep ' 2. Bundle install Gemfile追記後にbundle installを実行します。 $ bundle install 3. Steepの設定 設定ファイルとなる Steepfile 作成します。 D = Steep :: Diagnostic target :app do # RBSファイルの格納先を指定 signature " sig/trace " # Steepで型チェックする対象のファイルを指定 check " app/models " # 型チェック結果を全て抑制(無音)する configure_code_diagnostics( D :: Ruby .silent) end 4. VSCodeのSteep拡張のインストール steep-vscode をVSCodeにインストールします。 5. RBSファイルのエラーをコメントアウト RBSファイル内にエラーがあると、VSCodeのSteep拡張が動作しないため、エラーしている箇所をコメントアウトしました。本来は型定義を追加、修正するなどしてエラーを解消する必要がありますが、今回はVSCodeで型情報を確認できることが目的のため、このような対応をしました。 先のRBSファイルの例で言うと、RBS::Traceの実行だけでは Project::ActiveRecord_AssociationRelation クラスの型情報が生成されないため、 RBS::UnknownTypeName エラーになります。これをコメントアウトすることでエラーを握りつぶしています。 class User def full_name : () -> String def main_project : () -> nil | () -> Project # RBS::UnknownTypeName エラーになるためコメントアウト # def projects_after_date: (Date) -> Project::ActiveRecord_AssociationRelation def format_name : () -> String end VSCodeでの型情報の表示結果 メソッドの呼び出し箇所をホバーすると、型情報が表示されます。 また、入力の補完時にも型情報が表示されます。 所感 RBS::Trace導入のメリットと可能性 RBS::Traceは、RBSが全くないRailsプロジェクトにとって、型情報導入の最初の一歩として非常に有効だと感じました。テスト実行だけで型情報が自動生成されるため、手動で記載する手間が大幅に省けます。 Inline RBSだけの生成も可能なので、ドキュメント生成ツールとしても活用できそうです。これは人間にとって読みやすいだけでなく、GitHub Copilotなどの生成AIツールにも型情報を提供できるメリットがあります。生成AIがInline RBSから型情報を読み取れば、より正確なコード提案が得られるのではないかと期待しています。 将来的には、 RBS::Inline が RBS 本体に組み込まれる計画もあるようで、Inline RBSだけで型チェックができる日も来るかもしれません。 活用における注意点 型情報の出力結果はテストの実行内容に依存します。例えばStringとnilを返すメソッドがあっても、nilを返すケースのテストがなければ、型情報はStringだけになってしまいます。つまり、RBS::Traceを活用するには、テストの品質確保が前提となります。 また、Railsプロジェクト全体にRBSやSteepを導入した場合、相応のメンテナンス工数がかかると感じました。RBS::Traceだけでは、Rails自体が生成するクラスやメソッドの型情報は提供されないため、 rbs_rails などの併用や、手動での型情報メンテナンスも必要になるでしょう。 RBSのプロジェクトへの導入について 今回の試行から、Findy FreelanceプロジェクトへのRBSやSteep導入を決定したわけではありませんが、これらのツールとエコシステムの現状を実感できました。RBS::Traceのおかげで試すハードルが下がったのは大きな収穫です。開発者の @sinsoku_listy さんには感謝しています。 最後に RubyKaigi 2025では、Rubyの型に関するセッションがいくつかありました。 すべてを聞けてはいませんが、総じてRubyの型に関するエコシステムは今後もまだまだ進化していきそうだと感じました。 正直、私は今までちゃんとキャッチアップができていなかったのですが、RubyKaigiへの参加をきっかけに興味が強くなり、理解を深めるきっかけとなりました。 今後もRBS, Steep, Sorbetなどの型に関するエコシステムの進化をキャッチアップしつつ、プロジェクトに導入するかどうかを検討していきたいと思います。 5/13(火)に、「After RubyKaigi 2025〜ZOZO、ファインディ、ピクシブ〜」として、ピクシブ株式会社、株式会社ZOZO、ファインディ株式会社の3社でRubyKaigi 2025の振り返りを行います。 オンライン・オフラインどちらもありLTやパネルディスカッションなどコンテンツが盛りだくさんなのでぜひご参加ください!! pixiv.connpass.com ファインディでは、一緒にRubyやRailsの開発をしてくれる仲間を募集しています。 興味のある方は、ぜひこちらからチェックしてみてください! herp.careers 参考 RBS::Trace GitHub Automatically generating types by running tests (発表資料) Steep GitHub "型"のあるRailsアプリケーション開発 (発表資料)
アバター
こんにちは!ファインディ株式会社でエンジニアをしている みっきー です。 先日開催されたRubyKaigi 2025に参加しました。 去年はLTの登壇があり、準備で忙しかったので、今年はたくさんのRubyistと話したり、セッションを見られることをとても楽しみにしていました!! 今回は特に印象に残った「 On-the-fly Suggestions of Rewriting Method Deprecations 」というセッションについて紹介します。 自己紹介 deprewriter-ruby deprewriter-ruby gemの紹介 動かしてみた 作者のohbaryeさんに聞いてみた 最後に 自己紹介 私はFindy Team+でバックエンドエンジニアとして働いており、普段はRubyを使った開発をしています。 また、プライベートでは「 omochi gem 」というRuby gemを開発・メンテナンスしています。 そのため、今回のRubyKaigi 2025で「On-the-fly Suggestions of Rewriting Method Deprecations」というセッションを見つけたときは、すぐに興味を持ちました。非推奨メソッドの置き換えを自動化できるツールがあれば、ユーザーの移行をスムーズにサポートできると思ったからです。 deprewriter-ruby deprewriter-ruby gemの紹介 「On-the-fly Suggestions of Rewriting Method Deprecations」セッションでは、 ohbarye さんが開発した「 deprewriter-ruby 」というgemが紹介されました。このgemは、非推奨になったメソッドの呼び出しを検出し、新しいメソッドへの置き換え方法を自動的に提案してくれるツールです。 deprewriter-rubyの特徴は次の通りです: 設定ファイルによる柔軟な定義 : YAMLファイルで非推奨メソッドとその代替メソッドのマッピングを定義できます。 インラインの提案 : コードを実行すると、非推奨メソッドが使われている箇所で、代替メソッドへの置き換え方法が提案されます。 自動修正機能 : 提案された変更を自動的に適用することも可能です。 このgemは、Rubyの標準的な警告メカニズムを拡張して、単に「このメソッドは非推奨です」と警告するだけでなく、「このメソッドは非推奨です。代わりにこのように書き換えてください」という具体的な提案をします。 speakerdeck.com 動かしてみた セッション後、早速deprewriter-rubyを試してみました。 まず、次のようにインストールします # Gemfile gem " deprewriter " , github : " ohbarye/deprewriter-ruby " 次に、ライブラリコードで非推奨メソッドとその代替メソッドを定義します # Library code require " deprewriter " class Legacy def old_method (arg) puts " Using deprecated old_method with #{ arg }" end def new_method (arg) puts " Using new_method with #{ arg }" end extend Deprewriter deprewrite :old_method , to : ' new_method({{arguments}}) ' end そして、非推奨メソッドを含むコードを実行すると # User code legacy = Legacy .new legacy.old_method( " example argument " ) 環境変数を設定して実行することで、異なるモードで動作させることができます # ログモード - 非推奨メソッドの警告と提案を表示 $ DEPREWRITER=log ruby your_script.rb # 差分モード - 変更の差分を表示 $ DEPREWRITER=diff ruby your_script.rb # 書き換えモード - 自動的にコードを書き換え(注意して使用してください) $ DEPREWRITER=dangerously_rewrite ruby your_script.rb ログモードでは次のような警告と提案が表示されます $ DEPREWRITER=log bundle exec ruby user_code.rb Calling deprecated method: W, [2025-04-21T19:49:55.132998 #2137] WARN -- : DEPREWRITER: Legacy#old_method usage at user_code.rb:9 is deprecated. You can apply the diff below to resolve the deprecation. --- ./user_code.rb 2025-04-21 19:49:55.123740000 +0900 +++ ./user_code.rb 2025-04-21 19:49:55.123740000 +0900 @@ -6,7 +6,7 @@ # Call the deprecated method puts "Calling deprecated method:" -legacy.old_method("example argument") +legacy.new_method("example argument") # The deprewriter will detect this call and show a warning # suggesting to use new_method instead Using deprecated old_method with example argument 書き換えモードでは次のような警告と変更がおこなわれます。 $ DEPREWRITER=dangerously_rewrite bundle exec ruby user_code.rb Calling deprecated method: W, [2025-04-21T20:11:49.426183 #39447] WARN -- : DEPREWRITER: Dangerously trying to rewrite. It will rewrite a file to apply the deprecation and load the file Calling deprecated method: Using new_method with example argument Using deprecated old_method with example argument $ git diff diff --git a/user_code.rb b/user_code.rb index d1a2a2b..bfcab60 100644 --- a/user_code.rb +++ b/user_code.rb @@ -6,4 +6,4 @@ legacy = Legacy.new # Call the deprecated method puts "Calling deprecated method:" -legacy.old_method("example argument") +legacy.new_method("example argument") 手元の環境で demo を作成し、試してみました。 驚くほど簡単に非推奨メソッドの検出と提案が行われ、ユーザーがコードを更新する手間を大幅に削減できることを実感しました。 作者のohbaryeさんに聞いてみた セッション後、ohbaryeさんに直接話を聞く機会がありました。セッションの内容だけでなく、ツール開発のきっかけや普段の情報収集の方法についても興味深いお話を伺うことができました。 ohbaryeさんは Hacker News を定期的にチェックして、技術トレンドや面白いプロジェクトの情報を集めているそうです。 セッション内で紹介のあったPharoというプログラミング言語もHacker Newsで見かけたそうです。 また、PharoのDeprewriterに触れたきっかけで、deprewriter-rubyを作ったそうです。 「最近は日本語版もあるので、英語が苦手な方にもとっつきやすくなりました」と教えてくれました。さらに、情報収集の効率化について次のようなアドバイスもいただきました。 著名人や有名企業(Shopify、GitHubなど)のブログなど質の高い情報ソースはfeedlyというRSSリーダーを使って集めるようにしています。すべての未読記事を消化するのは大変なので、時間があるときに気になったものだけを読むようにしていますね。 このような効率的な情報収集の方法は、私も取り入れていきたいと思いました。 「必要だから作る」という実践的なアプローチにも共感し、自分のプロジェクトでも同じような姿勢で取り組んでいきたいと感じました。 ohbaryeさんに直接感想を伝えられたことで、オープンソースコミュニティの温かさも実感できた貴重な機会でした! 最後に deprewriter-rubyは、Rubyの非推奨メソッドの置き換えを自動化するという、一見小さな問題に焦点を当てたgemですが、その影響は大きいと感じました。 非推奨メソッドの変更は避けられないものであり、ユーザーの移行をいかにスムーズにサポートするかは、ライブラリ開発者にとって重要な課題です。 day3の Ruby Committers and the World 内で、Matzも後方互換を大切にしている話をしていました。 Matz「基本的にはなにもdeprecateしたくない。提案するのは彼らですが、互換性を壊すと怒られるのは僕なので… deprecateするにはマイグレーションパスを用意して、十分猶予期間を準備してからになるので数年から十年単位の話になる」 #rubykaigi #rubykaigiA — 黒曜@Leaner Technologies (@kokuyouwind) 2025年4月18日 x.com 現在のdeprewriter-rubyには、複雑なメソッド呼び出し(メタプログラミングなど)の書き換えができない、非推奨メソッドが呼び出されるたびに書き換え処理が実行されて効率が悪い、非標準のRubyライブラリに依存しているといった制限があります。 しかし、ohbaryeさんはこれらの課題に対して、より複雑なメソッド呼び出しへの対応、最初の呼び出し後の処理をスキップして最適化する、依存関係を減らしてスタンドアロン化するといった明確な改善計画を持っています。 このような継続的な改善への取り組みを見ると、今後さらに実用的なツールへと進化していくことが楽しみです!! RubyKaigi 2025では他にも多くの興味深いセッションがありました。 Rubyコミュニティの活発な技術共有やライブラリやツール開発の文化に、改めて感謝の気持ちを抱いた3日間でした。 5/13(火)に、 「After RubyKaigi 2025〜ZOZO、ファインディ、ピクシブ〜」 として、ピクシブ株式会社、株式会社ZOZO、ファインディ株式会社の3社でRubyKaigi 2025の振り返りを行います。 オンライン・オフラインどちらもありLTやパネルディスカッションなどコンテンツが盛りだくさんなのでぜひご参加ください!! ファインディでは、一緒に働く仲間を募集しています!! 興味を持っていただいた方はこちらのページからご応募お願いします。 herp.careers
アバター
こんにちは。Findy Tech Blog編集長の高橋(@Taka_bow)です。 この記事は これが私の推しツール!シリーズ の第3弾になります。今回も、 推しツール紹介 と題して、弊社エンジニア達が日々の開発業務で愛用しているツールやOSSを紹介していきます。 トップバッターは奥田さんです! ■ 奥田さん / PdM室 / GenAIイネーブルメント ■ データサイエンティストのだーさん ( @Dakuon_Findy ) です。2025年の1月よりファインディのプロダクトマネジメント室 GenAIイネーブルメントチームにデータサイエンティストとして参画しております。このチームでは、LLMを活用した各種プロダクトの強化や、社内オペレーションの改善に取り組んでいます。 Polars (Pythonライブラリ) Polarsの概要 Polarsは、高速かつ明示的なスキーマ定義を特長とするデータフレームライブラリです。大規模データであってもローカル環境でカラムの型の一貫性を保ったまま効率的に処理できるため、信頼性とスピードを両立したデータ分析が可能になります。 Polarsを使ってる様子 Polarsの推しポイント 処理が速い!型がある!の2点です。この2点がデータ分析における推しポイントとなる理由を以下で詳述します。 1. 処理速度 前職で経験したのですが、pandasで2時間かかっていた処理が、Polarsに書き換えた途端に5分少々で完了し、驚愕しました。処理が速すぎて「処理の間に別のコードをのんびり書こう」が成立せず、自分のタイピング速度が開発のボトルネックになっているような感覚になります。ひりつきますね。 次の図は公式が公開している、Parquet形式データ読み込みを含んだ処理速度のベンチマークです。例えば1つめのクエリ (Q1) ではpandasは25秒、Polarsでは約1.5秒と、約16.7倍の速度が出ており、全体を通じてPolarsの処理速度はpandasの約11倍 (Q6) 〜81倍 (Q5) の速度が出ていることが見て取れます。 Updated PDS-H benchmark results より引用 2. カラムごとに明確な型を持てる Polarsでは、スキーマを用いてカラムごとに明示的な型を定めることができます。たとえば「数値型 ( pl.Int64 )」とスキーマで決めたカラムに文字列が混ざっていると読み込み処理の段階で型エラーになります(もちろん、Object 型を使えば混在も可能ですが、それを明示しない限りはエラーで気づけます)。これは一見「融通が利かない」と思われがちですが、表記揺れに気づけたり、特定の型を前提とした処理を組んでもバグが起こりづらいというメリットがあります。 正常なスキーマでCSVを読み込んだ例 誤ったスキーマでCSVを読み込んだ場合のエラー Polarsがここまで快適なのは、単なる実装の工夫ではなく、そもそもの「設計思想」によるところが大きいと考えられます。 公式ドキュメント でも次のように述べられています: Philosophy The goal of Polars is to provide a lightning fast DataFrame library that: Utilizes all available cores on your machine. Optimizes queries to reduce unneeded work/memory allocations. Handles datasets much larger than your available RAM. A consistent and predictable API. Adheres to a strict schema (data-types should be known before running the query). Polars is written in Rust which gives it C/C++ performance and allows it to fully control performance-critical parts in a query engine. これを見てみると、私の推しポイントはまさにこの設計思想の上に成り立っていることがわかります。 “provide a lightning fast DataFrame library” → 推しポイントの「処理が速い」につながる話です。まさに "lightning" な速度です。 “Strict schema” → 推しポイントの型の話そのものです。明示的な型で処理を行えるという安心感は本当に大きいです。 つまり、「速い・型がある」が推しポイントになるデータフレームライブラリというのはPolarsが最初から目指していたものだった、というわけです。 たとえば長期の時系列データのように、機器の変更などで表記揺れが発生しやすくデータ量も非常に大きいケースでは、Polarsの「型」と「処理速度」の強みが特に活きます。そんな場面に出会ったときにはぜひPolarsのことを思い出してもらえると嬉しいです。 Polarsの設計思想にしっかり踏み込み、自分の手で試したからこその実感が伝わる記事ですね。速度と型、安全と快適さを追い求めるPolars愛が詰まったツール紹介でした! 次は、久木田さんです! ■ 久木田さん / プロダクト開発部 / バックエンド・SRE ■ Freelance開発チームの久木田です。ファインディ入社3年目です。 バックエンド開発からインフラ構築、SRE的業務にも携わっています。 HTTPie HTTPieの概要 HTTPieとはコマンドラインで使えるHTTPクライアントツールです。 curl の代替として使用できるものです。 コマンドが直感的でわかりやすく、レスポンスのJSONは自動的に色付けされて見やすく表示されます。まだβ版ですが、Webアプリやデスクトップアプリも提供されています。 HTTPieを使ってる様子 HTTPieは、レスポンスがJSONであれば自動で整形・色付けしてくれます。 公式サイトのインタラクティブなデモページでCLIコマンドを試すことができるので、ぜひそちらでも試してもらいたいです。 HTTPie CLI: HTTP & API testing client https://httpie.io/cli/run デスクトップアプリも使っており、こちらも使いやすく愛用しています。 デスクトップアプリ版は基本的にはGUIで操作するようになっており、リクエスト履歴の管理などがしやすくAPIテストを効率的にできます。 HTTPieの推しポイント CLIで使う場合 JSON出力の見やすさ 自動整形とシンタックスハイライトでAPIレスポンスの確認がしやすい オプションが簡潔 覚えるオプションが少なく、短く済むので直感的に使える デスクトップアプリの場合 使いやすいUI リクエストの作成や管理が簡単 コード生成機能 作成したリクエストをcurlコマンドやPython (requests)、JavaScript (fetch)など、他の言語やツールのコードに変換してくれる機能が便利 CLIとデスクトップアプリと場合によって使い分けできるので、API開発やテストでJSONデータを頻繁に扱う人に、HTTPクライアントツールとしておすすめです。 AWS Peacock Management Console AWS Peacock Management Consoleの概要 Chromeの拡張機能で、AWSコンソールを使いやすくするためのツールです。 AWSアカウントIDによってAWSコンソールのヘッダーの色を変更してくれます。 また、右上のログインユーザーの情報部分にアカウントのエイリアスも表示してくれます。 AWS Peacock Management Consoleを使ってる様子 拡張機能に追加してもらったあとに、オプションで設定をします。 適用したいアカウントIDを入力し、環境ごとに設定したい色を16進数のカラーコードで指定します。 指定することで次のように色が変わります。 アカウントのエイリアスの表示は自動でしてくれます。 Staging環境 Production環境 AWS Peacock Management Consoleの推しポイント 自分がログインしている環境を間違えない。 プロダクト・環境ごとにAWSの環境が違うのですが、ヘッダーの色分けによって自分が今どのアカウントにいるかを常に把握できます。 複数のAWSアカウントを使っている開発者にはぜひ導入してもらって、事故を減らしてもらえたらと思います。 直感的な操作感と環境ごとの安全設計にこだわったHTTPieとAWS Peacock Management Console。CLIとGUIを使い分けながら、開発の快適さを追求したツール紹介でした! 最後は金丸さんです! ■ 金丸さん / プロダクト開発部 / バックエンド ■ Findy Freelance バックエンド開発の金丸です。 オペレーション改善などのバックオフィス機能開発を中心として、Findy Freelanceのバックエンド開発に携わっております。 Warp Warpの概要 Warpは次の特徴を持つターミナルツールです。 - 強力なコマンド補完 - コマンド単位で入出力を管理するブロック - ショートカット、ペイン分割などのカスタマイズ可能 生成AIと連携したAgentモードも特徴の1つです。 (私自身はまだ使いこなせていません。。。) Warpの推しポイント 推しポイントは2つあります。 サジェストが強力 過去に実施したコマンドの傾向やエラー出力をもとに、次に実施するべきコマンドをサジェストしてくれます。 例: git add -> git commit -> push branch -> open pull request の流れを自動的に補完してくれる 上記のGIFでは git push でエラーが出た際に出力された git push --set-upstream origin hogehoge をサジェストしてくれています。 ブロック機能 Warpはコマンド実行単位でブロックが作成されます。 ブロック機能により、各コマンドに対応する出力結果が明確になり、理解しやすくなるという利点があります。 また、ブロック内の出力だけを選んでコピーしたり、検索したりする操作も簡単に実施できます。 特定コマンドの実行結果をコピーする 出力結果内から該当箇所を検索する なるほど、Warpの補完とブロック機能は確かに嬉しいですね。日々のターミナル操作がぐっと快適になる感じ、良さそうです。使い込むほどに良さがわかるツールですね。 おわりに 今回ご紹介した3名も、それぞれの開発スタイルに合わせてツールを選び抜き、日々の作業を快適にする工夫を重ねていましたね。スピードや操作性、ミスの防止など、それぞれが注目したポイントからはエンジニアとしてのこだわりが垣間見えます。道具選びひとつ取っても、開発生産性と心地よさを追求する姿勢が伝わってきました! ファインディでは、さまざまなバックグラウンドを持つエンジニアが活躍しています。技術にこだわり、より良いものを追求する仲間とともに働いてみませんか? 現在、ファインディでは新しいメンバーを募集中です。 興味のある方は、ぜひこちらからチェックしてみてください!
アバター
こんにちは。ファインディでデータサイエンティストをしています、sasanoshouta( @Edyyyyon )です。 今回は、社内向けに提供している「Findy Team+の使い方について、ざっくばらんに質問できる」社内Botを作成した際の取り組みと、そこから得た学びをご紹介します。 どんなBot? Bot作成の背景 社内PdMに舞い込んでくるプロダクトに関する問い合わせに対する業務効率化 Difyを社内用に解放しており、既にいくつかのBotがSlackワークスペースに産み出されていた どのように作成したか 作成した結果どうだったか 今後について 最後に どんなBot? まず、今回作成したBotの概要を、代表的な2つのユースケースを通じてご紹介します。 1つ目の代表的なユースケースとして、Botの名前の通り、 「Team+の使い方」に関する質問 ができます。 ユースケース1では、Team+のDevOps分析画面の使い方についての質問に、Botがどのように回答するかを紹介します。 後述しますが、こちらのBotを作る上で基にした情報はサポートページの内容をコンテキストとしている為、URLを付与して返答する事のできる質問に対しては基本的にURL付きで返答を返してくれます。 ユースケース1: デプロイしたはずのプルリクがDevOps分析に表示されない原因を教えて下さい デプロイしたプルリクがDevOps分析に表示されない場合、以下のような点をご確認いただくと原因を特定しやすくなります。 1. スタッツ管理で設定している正規表現が実際のブランチ名やタグ名に合っていない 例として、「^(main|master|pri)$」のように設定している場合、ブランチ名が「pri/~」などスラッシュを含む形だとマッチせず、デプロイ対象に含まれません。 → 正規表現を見直し、「^(main|master|pri.*)$」など適切な設定に修正いただくと改善されるケースがあります。 2. デプロイ(プルリク作成/タグ付け)を行ったメンバーが対象チームに所属していない、または表示対象OFFになっている → プロダクト画面上で、チームメンバーの設定や表示設定が正しく行われているかどうかご確認ください。 3. モニタリングのラベル設定が適切でない → 特定の文字列を含んだラベルのみを集計対象にしている場合は、ラベル付けのルールを再度ご確認ください。 上記を確認いただいても解決しない場合は、以下のサポートページや社内向けドキュメントURLもあわせてご参照ください。 ■サポートページ ・正規表現を使ったデプロイ頻度の集計例など https://hogehoge ・DevOps分析の概要 https://fugafuga もしご不明点や追加のご質問があれば、遠慮なくお知らせください。 2つ目の代表的なユースケースとして、単純なTeam+の使い方だけでなく 「特定のシチュエーションを想定した時にTeam+をどのように活用すると良いのか?」 と言うようなやや抽象度が高い質問や、壁打ちのような質問にも対応することができます。 ユースケース2: プロダクトオーナー、プロダクトマネージャーの育成観点でどんな活用方法が有効ですか?また重視するといい指標があれば教えてください。 プロジェクトオーナーやプロダクトマネージャーの育成には、以下のようなTeam+活用方法が考えられます。 1. プロジェクト全体を俯瞰できる情報共有 - 各担当メンバーのタスクを整理し、進捗と課題を可視化することで、オーナーおよびマネージャーが的確に優先度を判断しながら学習・意思決定できる環境を整えます。 2. 定量的なデータにもとづく振り返り - スプリント終了時やマイルストーンごとに、タスク消化数・リードタイム・タスク完了率などのデータを確認しながら振り返りを実施することで、チームビルディングの視点を持ちつつ育成が行えます。 3. コラボレーションを通じたスキル学習 - 例えば、生産性向上やユーザーインタビューなど多面的な業務に関わる際、それぞれ詳しいメンバーからレビューやフィードバックをもらえる仕組みを作り、学習機会を増やすことで、オーナーやマネージャーへ幅広いスキルを身につけさせることができます。 重視するとよい指標の例: • タスク完了率・計画対実績の差 • リードタイム(アイデアからリリースまでの期間) • タスクごとの担当メンバーのフィードバック量・質 • プロダクト改善提案数(チームやユーザーからの新たなアイデア) • チームメンバー間のコミュニケーションログやレビューの頻度 これらを定期的にモニタリングし、学習サイクル(学習→実践→振り返り)を短く回すことで、プロジェクトオーナーやプロダクトマネージャーが実践的に成長しやすくなります。 (中略) これらをプロダクトオーナーやプロダクトマネージャーの活動に取り入れることで、顧客への価値提供や開発組織の生産性向上につながりやすくなります。 Bot作成の背景 このBotを作るに至った背景は、以下のような理由からです。 社内PdMに舞い込んでくるプロダクトに関する問い合わせに対する業務効率化 Difyを社内用に解放しており、既にいくつかのBotがSlackワークスペースに産み出されていた 社内PdMに舞い込んでくるプロダクトに関する問い合わせに対する業務効率化 Team+には、日夜プロダクトの進化を支えるPdMが6名在籍しています。Team+はユーザー様に多様な機能を提供している性質上、様々な観点での問い合わせが届きます。 平均すると、1人1日2件程度は問い合わせをいただいており、これに対応する必要があります。 Difyを社内用に解放しており、既にいくつかのBotがSlackワークスペースに産み出されていた カレー屋さんBotをはじめ、ファインディ内では幅広い職種が使うことのできる多様なBotが稼働しています。これらのBotは社内向けにホスティングされたDifyで作成されており、希望者はDify上で色々なテストをすることができるので、気軽にBotを始めとしたツール作成にチャレンジをすることができます。 こうした背景と、今年よりプロダクトマネジメント室に所属するデータサイエンティストが 生成AI活用を推進するミッションを背負うようになった 事もあり、「 勝手にBot作ってみよう。面白いものができそうだよね 」と言うチャレンジから産まれました。 (余談ですが、同じやってみた系の取り組みとして同チームメンバーのだーさん( @Dakuon_Findy )が書いてくれた記事もありますので、是非ご覧になってみてください。) tech.findy.co.jp どのように作成したか 作り方自体は至ってシンプルです。以下の情報をコンテキストにしたBotを、簡単なプロンプトを添えてDify上に作成するだけです。 Team+のサポートページコンテンツ100ページ分 社内に蓄積された問い合わせFAQ100問分 プロンプトも非常にシンプルで、実際にはたった 4行 で構成されています。 あなたはTeam+というプロダクトについて知り尽くしているプロフェッショナルです。 ユーザーからの問い合わせに対して相応しいものをコンテキスト内から回答してください。 コンテキストの情報には、プロダクトのサポートページURLまたは社内ドキュメントのURLが添付されているはずです。 コンテキストの情報を元に回答が行える場合は、参照元のURLをユーザーへ提供してください。 たったこれだけで出来てしまいました。 作成した結果どうだったか Bot作成前、PdMに届いていた問い合わせの件数が新機能に関する問い合わせを除き ほぼゼロ にすることができました。 また、このBotは社内で職種問わず広く使われているのですが、以下のような声を多数いただくことができました。 今後について このように社内でも好評を得られていることから、プロダクトへの実装を進めようと考えています。 実際にプロダクト上への実装を行う為に、コンテキストの強化・Ops構築やハルシネーション対策など、生成AIを活用した機能ならではの考慮すべき点が論点として残っていますので、これらを解消していく予定です。 最後に ファインディでは、データサイエンティストを始めとするデータアナリスト、データエンジニアなどのデータ系職種の採用を強化中です。 こちらの記事を読んでいただき、取り組みに興味を持っていただいた方は是非カジュアル面談でお話しましょう。 herp.careers また、こちらの記事は「 プロダクトマネージャーLT Night 」と言う弊社主催のイベント内でも登壇させていただいた内容になります。 その際の登壇資料も掲載しておきますので、ぜひご覧ください。 speakerdeck.com
アバター
こんにちは。 ファインディ株式会社 で Tech Lead をやらせてもらってる戸田です。 現在のソフトウェア開発の世界は、生成AIの登場により大きな転換点を迎えています。 GitHub Copilotやチャットベースの開発支援ツールなど、生成AIを活用した開発支援ツールが次々と登場し、開発者の日常的なワークフローに組み込まれつつあります。 そのような状況の中で、「プロンプトの書き方がわからない」「プロンプトが思った動作をしてくれない」といった声をよく耳にします。 そこで今回は、生成AIに対するプロンプトの書き方のコツを紹介します。プロンプトの書き方のコツを知るだけで、生成AIの精度は驚くほど向上します。 それでは見ていきましょう! 長い文章よりも、簡潔な階層構造 記述の順序 対象と実行内容を絞る まとめ 参考文献 長い文章よりも、簡潔な階層構造 長文のプロンプトよりも、マークダウン記法などを利用して作成した階層構造のプロンプトの方が、生成AIの理解度が高い傾向にあるようです。 例えば、次のプロンプトを実行するとします。 buttonを作成して返すReact Componentを作ってください。propsにはテキストと無効フラグとクリック時のイベントを受け取ることが出来ます。テキストは必須です。無効フラグは任意で ` is ` のprefixを付けてデフォルト値は無しです。クリック時のイベントは任意です。 このプロンプトの実行結果は次のようになりました。 import React from 'react' ; type ButtonProps = { text : string ; isDisabled ?: boolean ; onClick ?: () => void ; } ; export const ButtonComponent: React.FC < ButtonProps > = ( { text , isDisabled = false , onClick } ) => { return ( < button type = "button" disabled = { isDisabled } onClick = { onClick } > { text } </ button > ); } ; プロンプトの意図が伝わっていない箇所が見られました。 isDisabled にデフォルト値が設定されてしまっています。 今度は、次のようにマークダウン記法の階層構造に書き直して実行してみます。 buttonを作成して返すReact Componentを作ってください。 - props - テキスト - 必須 - 無効フラグ - 任意 - ` is ` のprefixを付ける - デフォルト値は無し - クリック時のイベント - 任意 import React from 'react' ; type ButtonProps = { text : string ; isDisabled ?: boolean ; onClick ?: () => void ; } ; export const ButtonComponent: React.FC < ButtonProps > = ( { text , isDisabled , onClick } ) => { return ( < button type = "button" disabled = { isDisabled } onClick = { onClick } > { text } </ button > ); } ; isDisabled にデフォルト値が設定されなくなりました。この結果から、階層構造で書かれたプロンプトの生成結果の精度が上がることを理解できたかと思います。 このようにプロンプトに長文が書かれていた場合、文章を分解してマークダウン記法などの段落構造などに書き変えることをオススメします。 記述の順序 プロンプトやサンプルコードを記述する順番によって、生成AIが出力する内容が変わるケースがあります。 例えば、次のプロンプトを実行するとします。 次のコードを参考にして、UserSetting用のRepositoryクラスを作ってください ```typescript export interface UserSetting extends Entity { userId: string; notificationTarget: 'email' | 'slack'; } ``` ```typescript export interface IRepository<T> { findById(id: string): Promise<T | null>; } export interface Entity { id: string; createdAt: Date; updatedAt: Date; } ``` ```typescript export interface User extends Entity { name: string; email: string; role: 'admin' | 'user'; } export class UserRepository implements IRepository<User> { private users: User[] = []; async findById(id: string): Promise<User | null> { return this.users.find(user => user.id === id) || null; } async findAll(): Promise<User[]> { return [...this.users]; } async findByEmail(email: string): Promise<User | null> { return this.users.find(user => user.email === email) || null; } async findByRole(role: 'admin' | 'user'): Promise<User[]> { return this.users.filter(user => user.role === role); } } ``` ```typescript export interface Organization extends Entity { name: string; contractStatus: 'active' | 'inactive' | 'pending'; } export class OrganizationRepository implements IRepository<Organization> { private organizations: Organization[] = []; async findById(id: string): Promise<Organization | null> { return this.organizations.find(org => org.id === id) || null; } async isContract?(org: Organization): Promise<boolean> { return org.contractStatus === 'active'; } } ``` 実装内容が多い UserRepository を先に記述して、実装内容が少ない OrganizationRepository を後に記述しています。 このようなプロンプトの場合、先に書いた大きい方の内容が優先され、後に書いた小さい内容が無視されることがあります。 今回のケースだと、後述した OrganizationRepository の isContract? メソッドの例が無視される可能性があります。 実行結果は次のようになりました。 export class UserSettingRepository implements IRepository< UserSetting > { private userSettings : UserSetting [] = [] ; async findById ( id : string ): Promise < UserSetting | null > { return this .userSettings. find ( setting => setting. id === id) || null ; } async findAll (): Promise < UserSetting []> { return [ ... this .userSettings ] ; } async findByUserId ( userId : string ): Promise < UserSetting | null > { return this .userSettings. find ( setting => setting.userId === userId) || null ; } async findByNotificationTarget ( target : 'email' | 'slack' ): Promise < UserSetting []> { return this .userSettings. filter ( setting => setting.notificationTarget === target); } } 生成されたコードは全て this.userSettings に対するもののみで、 UserSetting のデータに対しての関数が生成されていないことがわかります。 次に、実行したプロンプトの OrganizationRepository に対する記述と UserSettingRepository に対する記述を入れ替えて実行してみます。 実行結果は次のようになりました。 export class UserSettingRepository implements IRepository< UserSetting > { private userSettings : UserSetting [] = [] ; async findById ( id : string ): Promise < UserSetting | null > { return this .userSettings. find ( setting => setting. id === id) || null ; } async findAll (): Promise < UserSetting []> { return [ ... this .userSettings ] ; } async findByUserId ( userId : string ): Promise < UserSetting | null > { return this .userSettings. find ( setting => setting.userId === userId) || null ; } async findByNotificationTarget ( target : 'email' | 'slack' ): Promise < UserSetting []> { return this .userSettings. filter ( setting => setting.notificationTarget === target); } async hasActiveNotification ( setting : UserSetting ): Promise < boolean > { return setting.notificationTarget !== null && setting.notificationTarget !== undefined ; } } UserSetting のデータを受け取ってチェックを行う hasActiveNotification が追加されていることがわかります。 この結果から、プロンプトの内容の記述順が生成結果に影響が出ることが理解できるかと思います。 対象と実行内容を絞る 生成AIからの結果の精度が低い場合、一度に多くの対象を選択しているケースがあります。 例えば、次のプロンプトを実行するとします。 次の変更を実行してください ## 変更対象 - 次の条件の全てに合致するファイル全て - ` app/lib/**/*.tsx ` に合致する - ` app/lib/**/*_spec.tsx ` に合致しない ## 変更内容 - ` window.alert ` を ` toast.error ` に変更する - ` toast ` は ` @findy/ui ` からimportする - propsで定義されているboolean型の項目の型定義を ` boolean ` から ` boolean | undefined ` に変更する 対象のファイル全てに対して複数の変更内容を実行するプロンプトになっています。 階層構造になっていますし、対象と内容を具体的に指定しており、特に問題ないように見えます。 しかし、このプロンプトを実行した場合、場合によっては実行結果の精度が落ちる可能性があります。 変更対象となっている app/lib 配下には多くのファイルが含まれている可能性があります。生成AIに対して一度の多くの対象を指定すると、対象を絞り込むことに対してリソースを使ってしまうため結果の精度が落ちてしまう傾向にあります。 このようなケースの場合、合致するフォルダの指定を app/lib/hoge/ 以下を見るようにプロンプトを変更して複数回の実行に分けると良いでしょう。 次の変更を実行してください ## 変更対象 - 次の条件の全てに合致するファイル全て - ` app/lib/hoge/**/*.tsx ` に合致する - ` app/lib/hoge/**/*_spec.tsx ` に合致しない ## 変更内容 - ` window.alert ` を ` toast.error ` に変更する - ` toast ` は ` @findy/ui ` からimportする - propsで定義されているboolean型の項目の型定義を ` boolean ` から ` boolean | undefined ` に変更する 変更対象のフォルダを指定して、一度に対象とするファイルを限定的にしました。このプロンプトを実行後、次は別のフォルダに対して同様のプロンプトを実行することで、生成AIの結果の精度が向上する可能性があります。 これで解決かと思いきや、更に改善の余地が見つかりました。変更内容が複数ある場合、プロンプトを分割して実行すると更に精度が上がります。今回のケースだと処理自体の変更と型定義の変更で異なる変更の指示が含まれています。 こういったケースの場合、プロンプトと実行を分割すると良いです。 次の変更を実行してください ## 変更対象 - 次の条件の全てに合致するファイル全て - ` app/lib/hoge/**/*.tsx ` に合致する - ` app/lib/hoge/**/*_spec.tsx ` に合致しない ## 変更内容 - ` window.alert ` を ` toast.error ` に変更する - ` toast ` は ` @findy/ui ` からimportする 次の変更を実行してください ## 変更対象 - 次の条件の全てに合致するファイル全て - ` app/lib/hoge/**/*.tsx ` に合致する - ` app/lib/hoge/**/*_spec.tsx ` に合致しない ## 変更内容 - propsで定義されているboolean型の項目の型定義を ` boolean ` から ` boolean | undefined ` に変更する 今回のケースのように複数の対象、内容の全てを一度に指定するのではなく、対象を絞って単一の変更内容を指示してプロンプトの実行そのものを分けることで、生成AIの結果の精度が向上する傾向にあります。 まとめ いかがでしたでしょうか? ちょっとした工夫や気遣いでプロンプトの精度が上がることがわかったかと思います。今後もトライアンドエラーを繰り返しながら、生成AIを活用するためのプロンプトの書き方を磨いていきましょう。 カスタムインストラクションでのプロンプトのコツも紹介しているので、是非こちらも参考にしてみてください。 tech.findy.co.jp 現在、ファインディでは一緒に働くメンバーを募集中です。 興味がある方はこちらから ↓ herp.careers 参考文献 Best practices for using GitHub Copilot in VS Code
アバター
ファインディ株式会社 でフロントエンドのリードをしております新福( @puku0x )です。 弊社では、フロントエンド系のリポジトリの多くに Nx を採用しています。 Nxは、Nx Cloudと連携することでCIの高速化やコストパフォーマンスの改善が期待できます。 直近のアップデートにより、Nx Agents(Nx Cloudが提供するサービスの1つ)の新機能「Assignment rules」が正式リリースされました。 nx.dev これまで解決が難しかったタスク割り当ての最適化が、いよいよ解決できそうです👏 今回はAssignment rulesの解説と、実際に使った場合の効果をご紹介しようと思います。 Nx Agentsとは? DTEの革命「Assignment rules」 Nx Agentsの課題 Assignment rulesの登場 Assignment rulesの効果 まとめ Nx Agentsとは? Nxには、Distribute Task Execution(DTE)というCI高速化の機能があります。 nx.dev DTEでは、ビルドやテストといったタスクを複数のCIマシンで分散実行します。単純なタスク別の並列化と比較して、CIマシンの数を増やした際の高速化の効率が良いという特徴があります。 Nx Agentsの動作イメージ(Nx公式ドキュメントより抜粋) DTEのマネージドサービスが「Nx Agents」であり、ファインディでは昨年Nx Agentsを導入し、CIの高速化に成功しました。 tech.findy.co.jp DTEの革命「Assignment rules」 Nx Agentsの課題 Nx Agentsを用いたDTEでは、CIの負荷に応じて利用するマシンの台数やスペックを動的に切り替えられます。 次の設定は、スペックが中程度のマシン3台を最小構成とし、Nxが検知したCIの負荷(=コードの変更に影響のあるプロジェクトの割合)をもとに、高スペックなマシンを混ぜつつ最大8台でCIを動かす例です。 distribute-on : 00-changeset-s : 3 custom-linux-medium-js 01-changeset-m : 3 custom-linux-medium-js, 2 custom-linux-large-js 02-changeset-l : 4 custom-linux-medium-js, 4 custom-linux-large-js # changesetは任意の数を定義できます # マシンスペックは https://nx.dev/pricing#resource-classes 参照 この機能はCIの高速化に役立つ一方、課題もありました。 コア数の多いCPUを活用できない (Nxの --parallel オプションはCI全体に適用されてしまう) 負荷の高いタスクがスペックの低いマシンで実行されるのを防げない 後者は特にメモリ不足によるエラーを発生させやすく、CIの再実行にかかる時間やコスト・開発者体験の低下が問題となっていました。 Assignment rulesの登場 そんな時に現れたのが「Assignment rules」です。 nx.dev この機能により、メモリ不足の最大の要因であった「負荷の高いタスク」をスペックの高いマシンに割り当てるといったことが可能になりました。 また、タスク単位で parallelism を設定することで、マシン内の最大タスク並列実行数を調整でき、コア数の多いCPUを有効活用できます。 設定の例をお見せします。 assignment-rules : - projects : - app1 # アプリケーション用のタスクを想定 targets : - build* # build や build-storybook が該当 - typecheck run-on : - agent : custom-linux-medium-js parallelism : 1 # メモリが足りなくなるため並列実行させない - agent : custom-linux-large-js parallelism : 3 # 高スペックなマシンの場合は並列実行させる - targets : - build - test - typecheck run-on : - agent : custom-linux-medium-js parallelism : 2 - agent : custom-linux-large-js parallelism : 4 - targets : - cspell - lint - stylelint run-on : - agent : custom-linux-medium-js parallelism : 6 - agent : custom-linux-large-js parallelism : 8 Assignment rulesの効果 比較対象として、4コアマシン×8台の場合を設定します。 参考: Nx Agentsを導入してフロントエンドのCIを約40%高速化しました - Findy Tech Blog Assignment rulesによってCI時間やコストがどれほど変化したかを見てみましょう。 次の表は、キャッシュヒット無しで全てのタスクが実行された場合のCI時間および想定されるCIのコストを表しています。 Before After(Assignment rules有効) CI時間 10m 31s 10m 54s コスト $0.968 $0.726 CI時間はそれほど変わらず25%ほど安くなりました。 マシンスペックを抑えた場合のメモリ不足を心配する必要が無くなったのも嬉しいところです。 まとめ Assignment rulesにより、Nx Agentsを用いたCIはさらに最適化されます。 個人的に parallelism での並列数設定は待望の機能でした。 (あとは各タスクの優先度を設定できたら最高なのですがNrwlの皆さんいかがですか...!?) CIの高速化・安定化は、チームのパフォーマンスを考える上で重要になります。Nxの機能の活用により、強固な開発の仕組みを構築できるでしょう。 今後もNxに目が離せませんね👍 ファインディでは一緒に会社を盛り上げてくれるメンバーを募集中です。興味を持っていただいた方はこちらのページからご応募お願いします。 herp.careers
アバター