
プログラミング
イベント
マガジン
技術ブログ
はじめに こんにちは、プラットフォーム部の 勝間田 です! 今回は書籍紹介記事の第2弾です! 昨年投稿した第一弾の記事は👇にあります。 tech.stmn.co.jp 今回はそれぞれ職種の異なる4人が各々GWで読んでよかった書籍について、紹介させていただきます! この記事で何か学びになったり、書籍を読むきっかけになったら嬉しいです! SREの知識地図——基礎知識から現場での実践まで 勝間田が紹介する本は、「SREの知識地図」という書籍です! gihyo.jp 私自身、今年からSRE業務に携わることになったため、SRE関連の書籍を探していたところこの本に出会いました! 読んでみて、SLIやSLO、The Four Golden SignalsといったSRE業務でよく使われる用語の意味をしっかりと学習することができました。用語の意味を理解したことで、普段何気なく活用していたDatadogなどの監視ツールも、より使いやすくなった気がします。 また、この書籍を読んでからシステムの「攻めと守り」や、エラーバジェットについて意識するようになりました。弊社ではまだ明確にSLO等を定義できているわけではないので今後の課題ですが、意識づけができたのはよかったかなと思っています! さらに、普段チームで行っているSRE業務が正しく行えているかの「答え合わせ」ができたのもよかったです。例えば、障害発生時に作成しているポストモーテムについては、本に記載されている通り「再発防止」や「被害の最小化」に向けた具体的なネクストアクションまで落とし込めていることが確認できたので、これは今後も自信を持って継続していきたいです。 弊社ではAIを活用すべく、開発チームの形も大きく変わりました。 本書ではチームトポロジーについても触れられており、4つのチームタイプと3つの主要なインタラクションモードが図解でわかりやすく解説されていました。色々なパターンのSREについて知ることができたので、自組織にあった動きができるよう精進していきたいです! SRE業務を始めることになり、SLIやSLOなどの基礎用語からしっかり理解したい方におすすめです! エンジニアリング組織論への招待 こんにちは、名古屋でEMをしているあさしん( @asashin227 )です。 私がお勧めするのは、「エンジニアリング組織論への招待」です。 gihyo.jp 日々エンジニアリングの現場で直面する様々な不合理に対して『エンジニアリング組織論への招待』は、ビジネスや組織、コミュニケーションの構造的な課題として捉え直し、どのように向き合うべきかを説明しています。 この本を読んだことで「不確実性をいかに最小化するか」、「不確実性を受け入れたまま、いかに前に進めるか」という視点を知ることができました。 プロジェクトを進める中で、「ここが分からないので進めません」「経験がないから難しいです」といったメンバーからの相談を受けることがあります。 本書を読んだことで、未知の領域に対しても「次に進むための構造的な視点」を持って向き合えるようになりました。 現代はAIの進化により、実装方法(How)に頭を悩ませるシーンが劇的に減りました。今私たちが集中すべきなのは、「どのような課題を解決し、どのような価値を創るのか」という本質的な問いです。 このような時代だからこそ、「未知を既知に変えていくプロセス」そのものの重要性が増しています。自分の知らない領域に飛び込むことを恐れず、仮説を持って挑戦し続ける。このマインドセットこそが、技術力以上に求められる現代の重要なソフトスキルではないでしょうか。 本書は、組織論の解説書ではなく、不確実なこのAI時代でエンジニアとして、もしくはリーダーとして「いかに思考し、行動するか」のマインドセットの下地を与えてくれる一冊です。 現状に閉塞感を感じている方や、新しい挑戦に踏み出す勇気が欲しい方に、ぜひ手に取っていただきたいです。 デザインの伝え方 はじめまして、プロダクトデザイナーの hikky です。 最近はコードを書く機会も増えてきたので、エンジニアによる書籍紹介に混ぜてもらいました🐢 私が紹介するのは、オライリー「 デザインの伝え方 」です。 www.oreilly.co.jp AIエージェントの発展でデザイン領域に踏み込むエンジニアの方も増えてきましたが、デザインに直接関わらない場合でも、自分が書いたコードに承認をもらう場面は誰にでもあると思います。そこで切っても切り離せないのが「コミュニケーション」です。本書はその根っこにある考え方を学べる一冊となっています。 本書が教えてくれることはシンプルで、コミュニケーションにおいて「 聞く・伝える・信頼を築く 」がいかに大事か、ということです。文字にすれば当たり前なのですが、その当たり前が一番難しい。 たとえば「聞く」一つとっても、相手に「ちゃんと自分の話を聞いてくれている」と感じてもらえているか、スムーズに本音を引き出せているか、といった観点があります。「伝える」についても、専門用語は同じ知識を持つ人同士では効率の良い言葉ですが、そうでない相手にはノイズになり得ます。立場の違う相手にどう届けるかという視点が必要なのです。 本書を通じて、エンジニアやビジネスサイドのメンバーは自分と違う立場で物事を見ており、それを前提に意図を汲み取ろうというマインドが強くなりました。違うからこそ、お互いを尊重して歩み寄ることがコミュニケーションには欠かせません。AIがどれだけ進化しても、人と人との対話は変わらず残り続けます。 他職種のメンバーともっとうまく連携して、良いものを届けていきたいと感じている方は、ぜひ手に取ってみてください! 書くスキルも設計スキルも飛躍的に上がる! プログラムを読む技術 GW中は首を痛めて、左をほとんど向けなかった、とんとんぼです。 私は最近、「 書くスキルも設計スキルも飛躍的に上がる! プログラムを読む技術 」という書籍を読んでいました。 bookplus.nikkei.com この本は2024年に発売され、当時も一度手に取ったのですが、最近のAIの普及などを受け、改めて読み返してみることにしました。 多くのプログラミング書は「いかにコードを書くか(例:〇〇の実装方法、XX実践入門など)」に焦点を当てたものがほとんどで、「コードの読み方」に特化した本は稀に思えます。しかし、実際の業務ではコードを書くよりも読む時間のほうが圧倒的に長く、比重も大きいのが現実です。さらに、近年では AI がコードを生成してくれるようになったため、提示されたコードの正誤や意図を正しく理解する力はこれまで以上に重要なスキルになっていると感じています。 この本の優れた点は、「理論」と「実践」が明確に分けられていることです。 前半では、コードを読む際の視点や意識すべきポイントについて理論と少しのサンプルコードから学び、後半では、そこで得た知識を活かして実際にさまざまなコードを読み解いていく構成になっています。 サンプルコードには、Python が採用されているため、読みやすく、実際の仕事でのコードでも実践しやすいのも魅力です。 最後に 最後までお付き合いいただきありがとうございました! それぞれ異なる職種のメンバーによる選書はいかがでしたでしょうか? 記事をまとめていて、自分自身も手を伸ばしてみたくなる書籍がありました。 AIで簡単に情報が手に入る時代ですが、本ならではの説得力や納得感を今回改めて感じました。 もし気になる書籍がありましたらぜひ読んでみてください! herp.careers
はじめに こんにちは、カート決済部カート決済サービスAブロックの 道場 です。ZOZOTOWN内のカート機能や決済機能の開発、保守運用を担当しています。 現在、ZOZOTOWNのカート決済画面はリプレイスが進行中です。既存システムとリプレイス後のシステムが並行して開発される中、既存システムへのさまざまな機能改修を、リプレイス側にも取り込む必要があります。その際、条件の組み合わせが膨大になるテストを手動で網羅的に実施することが現実的でなく、特に注文金額の計算結果の正確性を人間が1件ずつ確認するには大きなコストがかかっていました。 本記事では、Claude CodeとPlaywright CLIを組み合わせて、自然言語によるE2Eテストを自動化した仕組みをご紹介します。Confluence(Atlassian社が提供するナレッジ共有ツール)に自然言語でテスト手順を記述することでAIが自律的にブラウザを操作し、計算検証も含めてE2Eテストを完結させています。コードを書かずにテストを作成・実行できるため、テスト自動化の属人化解消にもつながりました。 目次 はじめに 目次 背景・課題 リプレイスに伴う二重開発とテストの課題 なぜ従来のE2E自動化では足りなかったのか AIエージェント駆動のE2Eテストシステム 全体アーキテクチャ Playwright CLIによるブラウザ操作 Agent Skillsによる操作手順の定義 テストケースの設計と期待値の保証 Confluenceベースのテストケース管理 計算が必要なテストの期待値保証 テスト実行の6つのStep テスト支援ツールの構築 atlassian-cli:Confluence操作のCLI zozo-sql-server-cli:SQL Serverクエリ実行CLI AIエージェントが必要なツールを自ら作る 従来のテスト自動化との比較 実践から得られた知見 テストケースの実績 実践を通じた気づき まとめ 背景・課題 リプレイスに伴う二重開発とテストの課題 冒頭の通り、ZOZOTOWNのカート決済画面ではリプレイスが進行中です。既存システムとリプレイス後のシステムが並行して動作する期間中、既存システムに対するさまざまな機能改修をリプレイス側へ取り込む必要があります。 これらの改修をすべて取り込み、条件の組み合わせが爆発的に増加するテストケースを検証する工数が大きな課題となりました。 たとえば、ある案件の機能を取り込む場合、以下のような因子が絡み合います。 ユーザーの属性(性別・年齢 等) 購入商品の種類・金額 割引・クーポンの有無 ポイント利用の有無 キャンペーン期間の内外 これらを組み合わせると、1つの案件だけで 100件以上のテストケース が発生することもありました。さらに、各テストケースでは 注文フローの複数画面 (配送・支払い選択、注文の確認 等)で表示値の確認が必要です。そして、 PC用の画面とスマートフォン(以下、SPと表記します)用の画面がそれぞれ存在 するため、検証量は実質的にさらに倍になります。 カート決済画面では、注文金額の計算ロジックにさまざまな要素が関わっており、前述の通り案件ごとに条件の組み合わせが大きくなりがちでした。さらに、期待値は複雑な計算式で決まるため、人間が1件ずつ手計算したうえで画面の表示と照合するには多くの時間がかかっていました。 なぜ従来のE2E自動化では足りなかったのか ZOZOTOWNでは、手動テストに加えて品質管理部によるコードベースのE2E自動テストも活用しています。しかし、そのような従来のコード記述型の自動テストを使ったアプローチでは以下の課題がありました。 プログラミングスキルへの依存 :CSSセレクタやロールを使った要素特定のコードを書く必要があるため、開発者でなければ作成・保守が難しい UI変更への追従コスト :UIの変更に応じて、要素特定の方法やテスト内容のメンテナンスが必要になる テストコードの属人化 :記述・保守できる人が限られるため、特定の開発者への依存が生じる 実現したかったのは、 テスト手順を自然言語で書くだけで、AIが要素を自動で見つけて操作し、計算検証まで完結する仕組み です。そのためのアプローチとして、Claude CodeのAgent SkillsとPlaywright CLIを組み合わせた自動化システムを構築しました。 AIエージェント駆動のE2Eテストシステム 全体アーキテクチャ 構築したシステムの全体像は以下の通りです。 各コンポーネントの役割は次の通りです。 コンポーネント 役割 Confluenceページ テストデータ・手順・期待値を自然言語で記載したテストケース管理の場 エージェント ( zozotown-qa-tester ) テストの実行フローを定義するClaude Codeエージェント Agent Skills ZOZOTOWNの操作手順やCLIの使い方をMarkdownで定義した再利用可能なリファレンス 計算サービス(TypeScript) 期待値を算出するための計算ロジック実装 Playwright CLI コマンドでブラウザを操作するCLIツール atlassian-cli Confluenceの読み取りと、エビデンスを含めた結果の記載を行う自作CLI zozo-sql-server-cli SQL Serverへのクエリ実行と結果の画像化を行う自作CLI Claude CodeのエージェントがConfluenceからテストケースを読み取ります。Agent Skillsを参照しながらPlaywright CLIでブラウザを操作し、結果をConfluenceに書き戻します。 Playwright CLIによるブラウザ操作 Playwright CLI は、ブラウザ操作をコマンドで実行できるCLIツールです。テストコードを書く代わりに、コマンド1つでブラウザを操作できます。Playwright MCPもありますが、CLIの方がトークン使用量を節約できるため選択しています。 特徴的なのは スナップショット機能 です。ページを開くと、Playwright CLIはページの構造をYAML形式で取得します。このとき各要素には ref 番号が付与されています。AIはこのスナップショットを読んで要素を特定し、 ref 番号を使って操作します。 # ref番号を使って要素をクリック playwright-cli click e42 --session = pc # テキストを入力 playwright-cli fill e15 " test@example.com " --session = pc # スクリーンショットを取得 playwright-cli screenshot --output screenshots/cart-top.png --session = pc CSSセレクタやロールを明示的に指定しなくても、AIがスナップショットを解釈して要素を特定できます。そのため、セレクタベースの実装に比べると、軽微なUI変更には追従しやすくなります。 PCとSPの切り替えは設定ファイルで行います。 // playwright-cli.json(PC用) { " browser ": { " launchOptions ": { " headless ": false } , " isolated ": false , " contextOptions ": { " viewport ": { " width ": 1400 , " height ": 1080 } } } } // playwright-cli-sp.json(SP用) { " browser ": { " launchOptions ": { " headless ": false } , " isolated ": false , " contextOptions ": { " viewport ": { " width ": 430 , " height ": 932 } , " userAgent ": " Mozilla/5.0 (iPhone; ...) Safari/604.1 ", " isMobile ": true , " hasTouch ": true } } } PCテストとSPテストは 別セッションで同時に実行できる ため、テスト時間の短縮にも貢献します。 Agent Skillsによる操作手順の定義 Agent Skillsでは、Claude CodeのSkill機能を活用してZOZOTOWN固有の操作手順を定義しています。コードベースのPlaywrightにおけるPage Object Modelに相当する役割を、Markdownによる自然言語の手順書で担うイメージです。 操作手順は次のように自然言語で記述します。 # ログイン手順リファレンス ## 手順 1. 以下のページを開く - PC: ` /_member/login.html ` - SP: ` /sp/_member/login.html ` 2. ` メールアドレス ` 入力欄にメールアドレスを入力する。 3. ` パスワード ` 入力欄にパスワードを入力する。 4. ` ログイン ` ボタンをクリックする。 テストケースに「テストユーザーAのアカウントでログインする」と書けば、エージェントがこのリファレンスを参照して手順を実行します。操作をリファレンスとして標準化しておくことで、 誰が書いたテストケースでも同じ操作が再現できます 。 今回定義した主要なリファレンスは次の通りです。 login-flow.md :ログイン手順(PC / SP対応) add-to-cart-flow.md :商品をカートへ投入する手順 order-flow.md :注文フロー(カートTOP → 配送・支払い選択 → 注文確認 → 注文完了) sql-execution-flow.md :SQL Serverへのクエリ実行手順 テストケースの設計と期待値の保証 Confluenceベースのテストケース管理 テストケースはConfluenceページで管理しています。ページの構成は次の通りです。 セクション 内容 要件 テスト対象の機能仕様 因子と水準 テストに関わる条件の洗い出し(ホワイトボックス観点) デシジョンテーブル 条件の組み合わせパターン テストデータ 環境URL、ユーザー情報、商品情報 テストケース 手順、パラメータ、期待値、実行結果、エビデンス テスト実行後は、Claude Codeがこのページに結果(OK / NG)とスクリーンショットを自動で書き込みます。 実際に実施したテストケースの例を紹介します。 注文金額に関わる計算ロジックの検証テスト :注文の確認画面に表示される金額が、計算サービスの算出結果と一致することを検証します。前述の因子を組み合わせた数十件のパターンを定義しています。 テストの手順は、Confluenceページに次のように自然言語で記述されています。 1. カートを空にする 2. パラメータ(商品)に記載されている商品をカートに入れる 3. 注文へ進み、パラメータ(支払い方法)の支払い方法を選択して注文確認画面を表示する 4. 表示されている計算結果の値が OrderAmountCalculationService.getの値と 一致していることを確認する 5. viewportのスクリーンショットを取得する 6. パラメータ(ポイント利用)に記載のポイントを利用する 7. 表示されている計算結果の値が上記計算サービスの値と一致していることを確認する ... この手順をClaude Codeが読み取り、Agent Skillsを参照しながらブラウザを操作します。 計算が必要なテストの期待値保証 計算結果の検証は、今回の取り組みで最も重要なポイントです。 課題 :注文金額に関わる複雑な計算結果を、人間が手計算して期待値と照合するには大きな工数が必要です。特に、割引・クーポン・ポイント利用・税率が絡み合う計算は、ミスが発生しやすく時間もかかっていました。 解決策 :Playwrightテスト用リポジトリにTypeScriptで計算サービスを実装し、あらかじめ期待値を算出しておきます。Claude Codeはテスト計画の作成時に計算サービスを呼び出し、期待値をプランに出力してから、ブラウザの表示値と照合します。 // ZOZOCARD還元ポイントを計算するクラス export class ZozocardRewardPointCalculationService { private static readonly POINT_RETURN_RATE = 0.05 ; public get ( goodsPriceWithoutTax : number , quantity : number , taxRate : number ): number { // ZOZOCARD 還元ポイントの計算処理... } } この計算処理は、システムと同じ仕様をもとにClaude Codeで生成した 独立した実装 になっています。システム側の実装コードをそのまま流用すると、同じバグを共有してしまいます。仕様を別実装することで、 システム側とテスト側の独立性 を保っています。これにより、期待値とシステムの表示値を照合したときに、単なる一貫性チェックではなく、システム側の実装が仕様どおりかを検証できます。実際に、このテストを通じてシステム側の実装が仕様を正しく考慮できていないケースを検知できた事例もありました。 期待値の検証フローは次の通りです。 Claude Codeはテスト計画を作成する段階で計算サービスを実行し、全テストケースの期待値を事前に算出します。テスト実行時には、ブラウザで取得した表示値と事前に算出した期待値を照合します。 テスト実行の6つのStep エージェント定義ファイル( zozotown-qa-tester.md )では、テスト実行を次の6つのStepで定義しています。 --- name: zozotown-qa-tester description: ZOZOTOWN の QA テストを実行するエージェント skills: - playwright-cli - zozotown-operations - confluence-page-operations - atlassian-cli - zozo-sql-server-cli --- ## テスト実行フロー ### 1. テストケースの確認 Confluenceページからテストケースを取得し、 対象の開発環境・前提条件・手順・期待結果を読み取る。 ### 2. テストケースプランの作成 テストデータ・期待値(計算サービスの実行結果)・実行手順を整理し、 ` test-plans/ ` ディレクトリにMarkdownファイルとして出力する。 **ユーザーの承認を得てからテスト実行に進む。** ### 3. テスト準備 ブラウザを起動し、ログインや初期データのセットアップを行う。 ### 4. テスト実行 各ステップを ` zozotown-operations ` のリファレンスに従って実行する。 手順が定義されていない操作は、実際にブラウザで確認して新しいリファレンスを作成する。 ### 5. 結果の記録 実行結果(OK / NG)を判定し、スクリーンショットを撮影して Confluenceページに結果とエビデンスを書き込む。 ### 6. 結果の報告 ユーザーに実行結果のサマリを報告する。 特に重要なのは Step 2のテストケースプランの作成とユーザー承認 です。AIは非決定的に動作するため、テストケースの解釈が意図と異なる可能性があります。実行前に計画を提示してユーザーに確認することで、 解釈のズレを事前に検出 できます。 また、Step 4の「リファレンスに手順がない操作は自ら作成する」という仕組みにより、エージェントが新しい操作手順を発見するたびにリファレンスファイルが自動的に追加されていきます。使うほどにリファレンスが充実し、テスト作成が楽になっていく仕組みです。 実際のテスト実行では、テスト計画の確認とPC / SPセッションの並列実行をターミナル上で確認できます。 テスト支援ツールの構築 atlassian-cli:Confluence操作のCLI Confluenceのテストケースページを詳細に処理するため、atlassian-cliを作成しました。Atlassian MCPもありますが、スクリーンショットを添付できないため、REST APIをラップしたCLIです。 テスト実行フローでの使用例を示します。 # Confluence のテストケースページを取得 atlassian-cli confluence get-page 348678105 --body-format atlas_doc_format # テスト結果のスクリーンショットをアップロード atlassian-cli confluence upload-attachment 348678105 \ --file ./screenshots/confirm-pc.png # テスト結果をページに追記 atlassian-cli confluence update-page 348678105 \ --body-file ./test-results/result.json \ --page-version 41 zozo-sql-server-cli:SQL Serverクエリ実行CLI 注文完了後のDBデータを検証するため、zozo-sql-server-cliも作成しました。注文データが正しく保存されているかをSQLで確認し、 結果をHTMLテーブルとして描画してPuppeteerでスクリーンショット化 する機能が特徴です。 # SQL クエリを実行してテーブル形式で表示 zozo-sql-server-cli \ " SELECT total_amount, discount_amount FROM orders WHERE order_id = 12345 " # クエリ結果をスクリーンショット(HTMLテーブルとして描画)として保存 zozo-sql-server-cli \ " SELECT total_amount, discount_amount FROM orders WHERE order_id = 12345 " \ --screenshot ./screenshots/order-db.png このスクリーンショットをそのままConfluenceのエビデンスとして添付することで、DB検証の証跡も自動的に記録できます。 AIエージェントが必要なツールを自ら作る atlassian-cliとzozo-sql-server-cliは、いずれもClaude Codeを活用して作成しました。 テスト自動化を進める中で「Confluenceにスクリーンショットを添付したい」「DBの検証結果を画像として保存したい」といったニーズが生まれました。これらをCLIとしてClaude Codeに実装してもらい、短期間で必要な機能を揃えることができました。 AIエージェントに必要なツールをAI自身が作れる という点は、自動化のエコシステムを大幅に加速させます。 従来のテスト自動化との比較 従来のコードベースのE2E自動テストと、今回構築したClaude Code + Playwright CLIのアプローチを比較します。 観点 コードベースのPlaywright Claude Code + Playwright CLI テストケースの形式 TypeScript / JavaScriptコード Confluenceページ(自然言語) 要素の特定方法 CSSセレクタ / ロール スナップショットのref番号(AIが自動特定) 期待値の検証 ハードコードされたアサーション 計算サービス + AIによる照合 UI変更への耐性 低い(セレクタ・ロールの変更対応が必要) 高い(スナップショットベースで柔軟に対応) 作成に必要なスキル プログラミング ドメイン知識 + 自然言語 最も大きな違いは、 テストコードの記述・保守スキルがなくてもE2Eテストを作成・実行できる 点です。 Confluenceでテスト手順を書く際には「テストユーザーAでログインする」「XXXの商品をカートに入れる」といった日常的な言葉で記述できます。Agent Skillsのリファレンスにログインやカート投入の手順が定義されているため、この自然言語の指示だけでAIが正確に操作を再現します。 また、計算検証の自動化により、人手では高コストだった期待値照合をAIが実行できるようになりました。開発者は別の案件の開発を進めながら、Claude Codeにテストを並行して実行させることができます。 実践から得られた知見 テストケースの実績 実際に実施したテストの実績は次の通りです。 テスト対象 テストケース数 対象画面 プラットフォーム 案件A(計算ロジックの検証) およそ20件 注文フローの各画面 PC / SP 案件B(条件の組み合わせ検証) およそ50件 注文フローの各画面 PC / SP 手動でのフローと、今回構築したAIエージェント活用後のフローを比較すると次のようになります。 人が行うのは、テスト計画のレビューのみです。数十件のテストケース × 複数ページ × PC / SPの全テストをClaude Codeに任せられました。案件Aでは詳細な計算結果を、案件Bでは肥大化する条件の組み合わせを検証でき、人手による手計算や確認にかかる工数を大きく減らせました。 実践を通じた気づき Agent Skillsの粒度設計 :ログインやカート投入、注文フローというような1つの手順として指示する粒度がちょうどよく、再利用しやすいです。細かすぎるとリファレンスが増えすぎて管理が難しくなり、粗すぎると他のテストケースで使いにくくなります。 テスト計画承認フローの効果 :「2. テストケースプランの作成」でAIが作成した計画をレビューすることで、テストケースの解釈ミスを事前に検出できた事例がありました。コーディング時もそうですが、私はClaude Codeのプランモードをよく利用します。何をするかを綿密に考えさせたものを自分が確認することで、あとはそれを実行するだけになり、質が高くなると感じています。 自己改善するリファレンス :未定義の操作に遭遇した際、エージェントが実際にブラウザで操作して手順を確認し、新しいリファレンスファイルを自動作成する仕組みは実用的でした。テストを重ねるほどリファレンスが充実し、環境を育てていくことで後のテスト作成が楽になっていきます。 まとめ 本記事では、Claude CodeとPlaywright CLIを組み合わせた自然言語E2Eテストの構築と実践をご紹介しました。Confluenceに自然言語でテスト手順を記述するだけでAIが自律的にブラウザを操作し、計算検証も含めてE2Eテストを完結させることができました。 膨大な組み合わせテストの自動化・計算検証の正確性担保・テスト自動化の属人化解消という課題を同時に解決し、開発者が別の案件を進めながらテストを並行完了できる体制が実現しました。今後は他の画面への展開や、定期的な実行によるリグレッションの検知などを検討していきたいと考えています。 ZOZOでは、一緒にサービスを作り上げてくださる方を募集中です。ご興味のある方は、ぜひ採用ページをご覧ください。 corp.zozo.com
Developer Engagementブロックの @ikkou です。2026年4月22日から24日の3日間にわたり北海道は函館市の 函館サーモン・まるなまアリーナ で「 RubyKaigi 2026 」が開催されました。 日本Rubyの会「RubyKaigi 2026」特別ライトアップ 今回の函館開催にあわせ、通常の白色のみの五稜郭タワーのライトアップが、Rubyをイメージした特別色のレッドにライトアップされていました。 ZOZOは今年もプラチナスポンサーとして協賛し、スポンサーブースを出展しました。 technote.zozo.com 本記事では、前半はWEARのバックエンドエンジニアが気になったセッションを紹介します。後半では、ZOZOの協賛ブースの様子と各社のブースにおけるコーディネートを写真中心に報告します。 ZOZOとWEARとRubyKaigi WEARのバックエンドエンジニアが気になったセッション A Faster FFI そもそもFFIとは? 現状のFFIの課題 なぜFFIは遅いのか? 改善1 改善2 FFXの仕組み 最終的なパフォーマンス ruby.wasm can also enable JavaScript to call Ruby libraries. The Less-Told Story of Socket Timeouts なぜopen_timeoutが必要だったのか net/httpのtimeoutライブラリ依存問題 resolv_timeout + connect_timeoutでは代替できない open_timeoutのAPI仕様 試してみる Ruby 3.4: fast_fallback ON Ruby 3.4: fast_fallback OFF Ruby 4.0: open_timeout fast_fallback ON + open_timeout fast_fallback OFF + open_timeout 同時指定はエラー 全体の対比 おわりに Autoresearching Ruby Performance with LLMs Autoresearchとは なぜ「ループ」が重要なのか 4つのループパターン Ralphループ Autoresearchループ Factoryループ Sidekiqでの実証実験 実験の背景 "マージされないコードを生成することの意味" PRレビューとは何か? 4つのレッスン Lesson 1: 「自動調査」であって「自動変更」ではない Lesson 2: 自分がオーナーで Architect でないものに Autoresearch を適用しない Lesson 3: ループはビター・レッスンを実践する Lesson 4: 人間のゲートをソフトウェアゲートに変換する Software Factory とその課題 まとめ おわりに Exploring RuboCop with MCP The Journey of Box Building Ruby Boxとは何か Ruby Boxの仕組み Ruby Boxが生まれた歴史 おわりに ZOZOブースの紹介 協賛企業ブースのコーディネートまとめ RubyKaigi 2026 アフターイベント〜初参加LT・スポンサー4社のパネル〜を開催します おわりに ZOZOとWEARとRubyKaigi 私たちが運営する ファッションコーディネートアプリ「WEAR by ZOZO」 のバックエンドはRuby on Railsで開発されています。2013年にVBScriptで構築されたシステムでしたが、2020年頃からコードフリーズし、Rubyへのリプレイスを開始しました。現在もリプレイスを進めながら、新規の機能もRubyで開発しています。また、Matzさんを技術顧問としてお迎えし、毎月Matz MTGと称したオンラインミーティングを実施しています。 ZOZOとRubyKaigiの関係は、ZOZOの前身であるVASILY時代の RubyKaigi 2017 に遡ります。コロナ禍を経て再開した RubyKaigi 2022 からはWEARのバックエンド開発を担うチームが中心となって協賛とスポンサーブースの出展を続けています。 RubyKaigi2017参加レポート(全日分)とスライドまとめ RubyKaigi2018参加レポート RubyKaigi 2019参加レポート〜sonots登壇セッション & エンジニア8名による厳選セッション RubyKaigi 2022参加レポート 〜エンジニアによるセッション紹介〜 RubyKaigi 2023参加レポート 〜エンジニアによるセッション紹介〜 RubyKaigi 2024 参加レポート RubyKaigi 2025 協賛&参加レポート WEARのバックエンドエンジニアが気になったセッション 今年はWEARチームから6名のバックエンドエンジニアがRubyKaigiに参加しました。本パートでは各エンジニアが特に気になったセッションを個々の視点で紹介します。 A Faster FFI chika です。私からはAaron Patterson( @tenderlove )氏の「 A Faster FFI 」を紹介します。 このセッションでは、「RubyはC言語より速くなるか?」という問いからスタートし、具体的にはRubyのFFIを高速化し、ネイティブのC言語(C拡張)よりもRubyを速く実行できるか? というのがメインの議題でした。 余談ですが、Aaron氏はRubyKaigi 2026の外国人登壇者の中でおそらく唯一日本語でスピーチする方です。流暢な日本語に加えて時折ジョークも交え、大変ユニークなセッションであり毎年楽しみに拝聴しています(最初の挨拶では「外国人みたいな名前ですが、外国人です」と日本語でジョークを言っていて面白かったです)。 そもそもFFIとは? FFIとはForeign Function Interfaceの略称で、一般的にはRubyのような高水準言語からC言語やRust, Zigなどで書かれた外部の関数を呼び出すための「概念」のことを指します(FFI自体は、あるプログラミング言語から他のプログラミング言語で定義された関数などを利用するための機構)。 Rubyでは主にlibffiというライブラリやfiddle gemを介して利用され、プラットフォームごとの呼び出し規則の違いを吸収してくれます。 例:FFIを使ったhello world #include <stdio.h> void hello ( void ) { printf ( "Hello, World from C! \n " ); } require ' ffi ' module Hello extend FFI :: Library ffi_lib File .expand_path( ' libhello.dylib ' , __dir__ ) attach_function :hello , [], :void end Hello .hello cc -shared -o libhello.dylib hello.c && ruby hello.rb #=> Hello, World from C! FFIはネイティブC拡張に比べ、CRuby, JRuby, TruffleRubyといった異なるRuby実装でも、ほぼそのまま動くというポータビリティの観点でもメリットがあります。 現状のFFIの課題 一見するとRubyからC言語の関数が直接呼び出せるのはパフォーマンスや移植性などの観点から便利そうに見えますが、実際のところFFIは滅多に使われておらず、その理由としてパフォーマンスが悪いという点が挙げられていました。 ベンチマーク比較では、既存のC拡張とFFIを比較すると、ネイティブC拡張の方が約2.4倍も高速に動作しているとのことでした(FF愛(FFI)してない…)。 なぜFFIは遅いのか? FFIが遅い主な原因として、以下の三点を挙げていました。 余分なフレームプッシュ Rubyから目的のC言語の関数を最終的に呼び出すまでに、呼び出し規則の変換などを行う「中間的な関数」がFFIやVM内部で何層も呼び出される。そのたびにコールスタックに余分なフレームが積まれる(プッシュされる)ため、関数呼び出しそのもののオーバーヘッドが大きくなってしまう。 値の渡し方の違い Rubyはスタックを使って値を渡すが、C言語は(x86_64やARM64などの環境において)CPUのレジスタを使用して値を受け取る。そのため、C言語の呼び出し規約(ABI)に合わせて、スタックからレジスタへ値をコピーして詰め直す操作が発生する。 タイプ変換 RubyのオブジェクトをC言語が理解できる型に変換し、Cからの戻り値を再びRubyオブジェクトに変換するオーバーヘッドが発生する。 改善1 この遅い原因を解消するために、JITコンパイラを用いてそれらのコストを削減するアプローチを試みていました。そして、Rubyで書かれたFFIのためのJITコンパイラ「FJIT」が誕生しました。これはfiddle gemなど複数のgemの機能を活用して直接マシンコードを生成することで、既存のFFI実装に比べて約2倍ほど高速化することが可能となりました。 改善2 さらにもう1つの解決案として提案されたのが、CRuby内蔵のJITコンパイラであるZJITと、新しいトランスレータである FFX でした。 FFXの仕組み FFXは、Rubyで書かれたFFI拡張のコードを読み込み、自動的にC拡張のコードに変換します。この時、生成されるCコードの中にはZJIT向けのヒント(具体的には、生成されるCコードの中に「この関数の引数はこういう型である」といったメタデータをZJITが読み取れる形で配置する)が意図的に埋め込まれます。ZJITはこのヒントから関数の引数や戻り値の型情報を認識することで、最適化された高速なマシンコードを動的に生成することが可能となります。 最終的なパフォーマンス これらの改善を導入しベンチマークを測定したところ、なんとC拡張よりも約1.4倍速く動作するという結果になりました。結果的に、「RubyはCよりも速くなる?」という問いに対しては、「(JITとFFXの力を借りれば)イエス」となり、つまり「RubyはCよりも速い」という結論で締め括られていました(笑) FFIの「遅いから使わない」という常識が覆りつつあり、ZJITやFFXの成熟によってRubyからCライブラリを気軽に・高速に呼べる未来が近づいていると感じました。 ruby.wasm can also enable JavaScript to call Ruby libraries. 小島( @KojimaNaoyuki )です。私からはShigeru Nakajima( @ledsun )さんの「 ruby.wasm can also enable JavaScript to call Ruby libraries. 」を紹介します。 www.docswell.com このセッションでは、ruby.wasmのこれまで積み重ねられてきた改善を振り返りつつ、なぜまだ実務での採用事例が少ないのかの分析とそれを解消するために「JSからRubyを呼び出す」機能を強化していく方針が話されていました。 ruby.wasmとは、ブラウザ環境でRuby実行を可能にするものです。JavaScriptと相互で連携しながらRubyをブラウザ環境で実行できます。 ruby.wasmはこれまでRubyからJavaScriptを呼び出す改善を多く実施しています。しかし、現状、本番環境で利用されている例はあまりないそうです。その原因としてこのセッションでは以下の2点について言及されていました。 ruby.wasmバイナリが大きすぎる JSからRubyを呼ぶサポートが弱い ruby.wasmバイナリが大きすぎることについては https://cdn.jsdelivr.net/npm/@ruby/head-wasm-wasi@2.9.3-2.9.4/dist/ruby+stdlib.wasm を例に出して話されていました。こちらのファイルは非圧縮で31.80MiB、圧縮すると8.84MiBになるそうです。そして、BtoCのブラウザアプリケーションではダウンロードサイズが重要ですが、BtoBの場合はダウンロードサイズは大きな問題ではない可能性があるとセッションでは話されていました。 JSからRubyを呼び出すサポートが弱いことについては、RubyからJSを呼び出す場合とJSからRubyを呼び出す場合を比較して扱いやすさの違いを言及していました。以下がセッションで使用されていた表です。 Feature R → J J → R eval Yes Yes Method Call Yes call Property Access Yes N/A Object Conversion Yes string Load Ruby Scripts browser No 出典: ruby.wasm can also enable JavaScript to call Ruby libraries. | ドクセル (最終閲覧日:2026/05/07) こちらを見ると確かにJSからRubyを呼び出す場合にサポートされていないものが多い印象ですね。現状JSからRubyを呼び出すにはevalメソッドを利用して実行できますが、rubyの実行結果の戻り値を扱う適切なAPIが不足していたりと課題があるようです。 これらの現状を受けて、本番環境で利用されている例が少ないのは「JSからRubyを呼ぶ」機能が不十分であるからではないかと分析されていました。そして、もしJSからRubyを呼ぶことが簡単にできたなら、ロジックの二重実装を防ぐことができるため有用ではないかとおっしゃっていました。ロジックをRubyに統一できれば修正も容易になり保守性も向上しそうですね! 「JSからRubyを呼ぶ」機能を強化していくためにはRubyオブジェクトをラップするRbValueを拡張する必要があり、このプロジェクトを「Ruby’s Blanket」と呼ぶと発表がありました。ちなみに、「Ruby’s Blanket」という命名は函館出身の人気バンドGLAYの楽曲に由来しているともおっしゃっていました。 セッションでは、目指していきたい理想の姿も紹介されており、以下のような使用感を想定しているようです。 vm . evalFile ( “ [ dog . rb ]( http: //dog.rb)”); const dog = vm .eval ( “Dog . new” ) ; dog . vow () ; dog . vow () ; const count = dog . count_of_vows . toInt () ; 出典: ruby.wasm can also enable JavaScript to call Ruby libraries. | ドクセル (最終閲覧日:2026/05/07) JSネイティブなプロパティアクセスやメソッド呼び出しでRubyプログラムを扱っているところを示しています。すごく直感的にJSからRubyコードを呼び出せていますね! セッションの最後にはコーディングエージェントの活用についても語られていて、以前よりも難しい課題に取り組むことができていると語られていました。「Ruby Committers and the World」や「Matz Keynote」でもAI活用が議題に上がっており、多くのコミッターがAIを駆使してRubyの改善に当たっているそうです。 個人的には、言語実装のような「世界的にサンプルが少なそうな低レイヤーの領域」はAIが不得意な分野だと思い込んでいたため、これほど多くのコミッターが活用している事実に驚かされました。 Ruby本体や周辺ライブラリの開発が、AI活用によって今後さらに加速していくのが非常に楽しみです! このセッションを通して「JSからRubyを呼ぶ」機能が充実することで、Rubyの活用の幅が広がる未来を感じました。実際のプロダクトのブラウザ上でRubyが動いているところを目にすることを期待しています! The Less-Told Story of Socket Timeouts WEARのバックエンド開発を担当しているaao4seyです。私からは、 @coe401_ さんの「 The Less-Told Story of Socket Timeouts 」を紹介します。 このセッションでは、Ruby 4.0で Socket.tcp / TCPSocket.new に新しく加わった open_timeout の導入経緯と、その背後にあるsocketライブラリのタイムアウトの歴史が語られていました。本記事では open_timeout が必要になった経緯を発表内容を踏まえて整理し、続いて各Rubyバージョンで実際にタイムアウトの挙動がどう変わるのかを手元で観察した結果を紹介しようと思います。 なぜopen_timeoutが必要だったのか net/httpのtimeoutライブラリ依存問題 発表者のもとに「net/httpのtimeoutライブラリ依存を外したい」という相談が届いたことが、 open_timeout 導入のきっかけでした。スライド内でも紹介されていますが、Ruby 2.7の開発時代にもnet/httpのような標準ライブラリが、標準ではないライブラリに依存しないほうが良いのではという趣旨の提案がなされていました。 加えて、timeoutライブラリは内部で Timeout::State::GLOBAL_STATE という共有状態を持っているため、non-main Ractorからこれにアクセスすると Ractor::IsolationError が発生してしまう状況でした。 resolv_timeout + connect_timeoutでは代替できない Ruby 4.0開発時点で、 Socket.tcp / TCPSocket.new には名前解決のタイムアウトを指定する resolv_timeout と、接続確立のタイムアウトを指定する connect_timeout がすでに用意されていました。これらを組み合わせればtimeoutライブラリの代替になりそうにも思えますが、実際にはメソッド全体の絶対上限時間を制御できません。 ここで前提として1つ補足しておくと、Ruby 3.4で Socket.tcp / TCPSocket.new にはHappy Eyeballs Version 2 (HEv2 / RFC 8305)が fast_fallback という名前でデフォルト有効として導入されました。HEv2は複数候補のIPアドレスに対して名前解決と接続試行を並行実行するアルゴリズムです(HEv2 自体の解説は同僚が「 RubyKaigi 2025 協賛&参加レポート 」で書いているので、本記事ではそちらに委譲します)。なお、Ruby 3.4までは fast_fallback: false 経路の resolv_timeout がAPI onlyで実装されておらず、本記事の対象であるRuby 4.0で両経路で機能するように修正されています。 その上で、たとえばfast_fallback有効で resolv_timeout: 2000ms , connect_timeout: 1000ms を指定した場合、IPv6が先に解決されてIPv4が解決待ちのまま接続試行が始まると、 connect_timeout の1000msを過ぎても resolv_timeout の期限まで待機が続きます。結果、全体タイムアウトは2つの合計でも connect_timeout の値でもなく、 resolv_timeout の値である2000msに支配されてしまいます。 HEv2のConnection Attempt Delay (250ms)が絡むケースでは「 connect_timeout: 1000ms を指定しても全体は1250ms待つ」、fast_fallback無効では「IP数 × connect_timeout 」と、いずれもユーザーが指定した値とは別の値で全体時間が決まってしまいます。 open_timeoutのAPI仕様 これらの背景を受けて、発表者ご自身が open_timeout を Socket.tcp / TCPSocket.new に追加する提案をされました。 open_timeout はtimeoutライブラリと同じく「名前解決〜接続確立の全体」を1つの期限で管理するオプションです。 Socket .tcp( " ruby-lang.org " , 80 , open_timeout : 1 ) 設計上の論点として、 open_timeout を resolv_timeout / connect_timeout と併用された場合にどう振る舞うかを整理する必要がありましたが、複雑になりすぎてしまうという課題があり、最終的に open_timeout は connect_timeout / resolv_timeout との同時指定を禁止し、同時指定時は ArgumentError を投げる仕様となったそうです。 発表内では実装の細かい部分が fast_fallback ありなしごとに説明されていました! スライドのp.224以降に記載されています。 試してみる ここまで見てきた経緯を踏まえて、 open_timeout がRuby 4.0で動作するのかの検証をしてみます。また、私自身はRuby 3.4で導入された fast_fallback 機能もこの発表内で知ったので、こちらの挙動も合わせて確認してみることにしました。 実験のため /etc/hosts に応答しない3つのIPを割り当てた test-multi-local.wear.jp を用意しました(記載のIPはdummyです)。 192.168.xx.1 test-multi-local.wear.jp 192.168.xx.2 test-multi-local.wear.jp 192.168.xx.3 test-multi-local.wear.jp 接続先には応答しないポート(50000)を指定し、SYNを送っても応答が返ってこない状況を作ります。 Ruby 3.4: fast_fallback ON この場合、HEv2のアルゴリズムに従い以下のような挙動になるはずです。 約250ms間隔(Connection Attempt Delay)で次のIPへ並行に試行を発射している 期待結果: (IP数-1) × 250ms + connect_timeout = 1.5 秒 で全体タイムアウト 検証スクリプト require " socket " t = Time .now begin Socket .tcp( " test-multi-local.wear.jp " , 50000 , connect_timeout : 1 , fast_fallback : true ) { |s| s.close } rescue => e puts "#{ e.class } : #{ e.message }" ensure puts " elapsed= #{ Time .now - t } s " end 実行結果(Ruby 3.4.9) Errno::ETIMEDOUT: Operation timed out \- user specified timeout elapsed=1.51281s tcpdumpの結果抜粋 10:41:18.580 SYN → .1 ← IP1 開始 (t=0) 10:41:18.836 SYN → .2 ← IP2 開始 (t≒0.256) 10:41:19.086 SYN → .3 ← IP3 開始 (t≒0.506) 想定どおり、約1.5秒でtimeoutエラーになることが観測できました! また、tcpdumpでSYNパケットをざっくり観測した結果、約250msごとに異なるIPへSYNパケットが飛んでいることも見て取れました。 Ruby 3.4: fast_fallback OFF fast_fallback: false を渡すと、Ruby 3.3以前と同じ直列フォールバックの挙動に戻ります。以下のような挙動になるはずです。 各IPに対して connect_timeout: 1 で1秒ずつ順番に試行 期待結果: IP数 × connect_timeout = 3 秒 で全体タイムアウト 検証スクリプト 前述のスクリプトの Socket.tcp 呼び出し行を以下のように変更します。 - Socket.tcp("test-multi-local.wear.jp", 50000, connect\_timeout: 1, fast\_fallback: true) { |s| s.close } + Socket.tcp("test-multi-local.wear.jp", 50000, connect\_timeout: 1, fast\_fallback: false) { |s| s.close } 実行結果(Ruby 3.4.9) Errno::ETIMEDOUT: Operation timed out \- user specified timeout elapsed=3.013505s tcpdumpの結果抜粋 10:40:20.753 SYN → .1 ← IP1 開始 (t=0) 10:40:21.760 SYN → .2 ← IP2 開始 (t≒1.0) 10:40:22.764 SYN → .3 ← IP3 開始 (t≒2.0) 想定通り、fast_fallbackを無効にするとIP1 → IP2 → IP3と1秒ずつ順番に試行され、合計約3秒で全体タイムアウトすることが観測できました。 Ruby 4.0: open_timeout ここまでの結果から、 connect_timeout だけではメソッド全体の上限時間を制御できないことが見えました。Ruby 4.0で導入された open_timeout を使うと挙動がどう変わるかを見ていきます。 fast_fallback ON + open_timeout fast_fallback ONで open_timeout が機能するか試します。 期待結果:open_timeoutに指定した1秒でタイムアウト 検証スクリプト 前述のスクリプトの Socket.tcp 呼び出し行を以下のように変更します。 \- Socket.tcp("test-multi-local.wear.jp", 50000, connect\_timeout: 1, fast\_fallback: true) { |s| s.close } \+ Socket.tcp("test-multi-local.wear.jp", 50000, fast\_fallback: true, open\_timeout: 1) { |s| s.close } 実行結果(Ruby 4.0.3) IO::TimeoutError: user specified timeout for test-multi-local.wear.jp:50000 elapsed=1.004653s 想定通り、約1.0秒で IO::TimeoutError が発生し、 open_timeout: 1 で指定した時間とほぼ同じ時間でタイムアウトすることが観測できました! fast_fallback OFF + open_timeout fast_fallback OFFで open_timeout が機能するか試します。 期待結果:open_timeoutに指定した1秒でタイムアウト 検証スクリプト 前述のスクリプトの Socket.tcp 呼び出し行を以下のように変更します。 \- Socket.tcp("test-multi-local.wear.jp", 50000, connect\_timeout: 1, fast\_fallback: true) { |s| s.close } \+ Socket.tcp("test-multi-local.wear.jp", 50000, fast\_fallback: false, open\_timeout: 1\) { |s| s.close } 実行結果(Ruby 4.0.3) Errno::ETIMEDOUT: Operation timed out \- user specified timeout for 192.168.xx.1:50000 elapsed=1.007804s こちらも想定通り、約1.0秒で user specified timeout が発生し、 open_timeout: 1 で指定した時間とほぼ同じ時間でタイムアウトすることが観測できました! fast_fallback の設定有無に関わらず open_timeout が機能していることがわかりました。 同時指定はエラー open_timeout は connect_timeout / resolv_timeout との同時指定が禁止されており、 Socket.tcp の入口で ArgumentError が即座に発火します。 Socket .tcp( " test-multi-local.wear.jp " , 50000 , connect_timeout : 1 , open_timeout : 1 ) { |s| s.close } # => ArgumentError: Cannot specify open_timeout along with connect_timeout or resolv_timeout 全体の対比 ここまで観測した結果を並べると以下のようになります。 Ruby 設定 elapsed 3.4.9 fast_fallback: false , connect_timeout: 1 約 3.01 秒 3.4.9 fast_fallback: true , connect_timeout: 1 約 1.51 秒 4.0.3 fast_fallback: false , open_timeout: 1 約 1.01 秒 4.0.3 fast_fallback: true , open_timeout: 1 約 1.00 秒 connect_timeout だけを指定したケースでは、ユーザーの指定値とは別の値で全体時間が決まっていたのに対し、 open_timeout を指定すれば 指定値ぴったりで打ち切られる ようになりました。 おわりに 本パートではセッションの内容である open_timeout の導入経緯と、実際に fast_fallback や open_timeout の挙動を観測した結果を紹介しました! open_timeout の実装により、接続試行全体のタイムアウトが管理しやすくなったと感じます。実際のセッションではRuby 2.0の開発時代から今に至るまでのsocketライブラリのタイムアウト導入の歴史も詳細に説明されており、各時代ごとにどんな課題がありどう解決してきたのかを知ることができとても興味深かったです。また、このセッションに限らずですが、RubyKaigi全体を通して発表者の方のモチベーションを垣間見ることができたのも自分にとって良い刺激となりました。 Autoresearching Ruby Performance with LLMs 小山 です。私からはNate Berkopec( @nateberkopec )さんの「 Autoresearching Ruby Performance with LLMs 」を紹介します。 Berkopec氏は、Speedshop代表でありPumaメンテナー、Railsパフォーマンスコンサルタントとして広く知られています。本セッションは Autoresearch という先行ツールを参考に、AIエージェントを使って、Rubyのパフォーマンス問題を自動で調査する取り組みとそれにより得られた示唆の発表でした。 発表資料は以下のリポジトリで公開してくださっているためご参照ください。 github.com Autoresearchとは Autoresearchは、2026年3月にAndrej Karpathy( @karpathy )氏が公開したLLM実験自動化ツールです。仕組みはシンプルです。 AIエージェントがコードへの変更を提案・適用する 一定時間ベンチマークを実行する ベースラインより良ければコミット、悪ければリバート 1に戻る これを無限に繰り返します。PyTorchと単一のメインファイル以外に依存関係がなく、1時間あたり約12実験、一晩で約100実験を回せます。Berkopec氏がこのコンセプトをRubyのパフォーマンス改善に応用できるかを探ったのが今回の発表です。 なぜ「ループ」が重要なのか セッションで繰り返し強調されたのが ループ という概念です。 AIエージェントは本質的にはループにすぎないと、Berkopec氏は次のコード例で説明しています。 messages = [user_prompt] loop do reply = llm.call(messages, tools : TOOLS ) break puts(reply.text) unless reply.tool_call? result = run_tool(reply.tool_name, reply.arguments) messages << reply messages << tool_result(result) end このループに どのようなゲート(通過条件)を置くか が、ループの性格を決めます。それがセッション全体の一貫したテーマでした。 4つのループパターン Berkopec氏は、ゲートの種類によってループを4種類に分類しました。 ループ ゲート シグナル 成果物 Agents LLM 自己停止 離散 最終的な返答 Ralph ビルド+テスト 離散 グリーンなコミット Autoresearch ベンチマーク差分 連続 改善した diff Factory 多数のチェック 多変数 マージ可能な PR Ralphループ while :; do cat PROMPT.md | claude-code ./build \_ and \_ test || continue git add \- A git commit \- m " ralph: passing build " git push done ビルドとテストをゲートにしたループです。テストが通った変更だけをコミットし続けます。バグへの対処に向いています。 Autoresearchループ best \= benchmark loop do change \= agent.propose\_optimization apply(change) score \= benchmark if score \> best git\_commit(change.summary) best \= score else git\_revert end end ゲートがブール値(pass/fail)ではなく 連続値(ベンチマークスコアの改善量) である点がRalphとの違いです。 Factoryループ backlog.each do |spec| loop do code \= agent.implement(spec) gates \= scenarios.map { |s| s.run(code) } break if gates.all?(& :pass? ) end ship(code) end スペックからコードを生成し、複数のゲートを全て通過したら出荷するパターンです。StrongDM社の「 Software Factory 」が参照されていました。 Sidekiqでの実証実験 セッション中に紹介された実験の1つが、 pi-autoresearch を用いた、Sidekiqを対象にした自動最適化です。 実験の背景 Sidekiqの Processor::Counter はアトミックなカウンターで、16行程度のシンプルな実装です。Autoresearchエージェントはここにストライプドロック(各スレッドが自前の状態を持ち、書き込みを安くする仕組み)を適用する変更を提案しました。 また、別のPRでは Time.now.to_i を Process.clock_gettime に置き換えることも提案されました。 Comparison: Process.clock_gettime: 17294486.8 i/s Time.now.to_i: 12698329.6 i/s - 1.36x slower Timeオブジェクトを生成せずに整数を直接返すことで1.36倍高速化できます。 "マージされないコードを生成することの意味" これらの変更についてBerkopec氏は「OSSメンテナーは実際にはこれをマージしないだろう」と正直に述べていました。その上で、なぜマージできないコードを生成するのか、という問いを投げかけました。 "Why generate code that you can't merge?" この問いに対して、セッションは PR レビューの目的を再定義するパート へと展開しました。 PRレビューとは何か? 変更を却下する理由として、Berkopec氏は以下を挙げました。 バグがある → LLMはある程度得意 トレードオフがある →「十分に速い」とはどのレベルか? 複雑すぎる → Flog/Flayスコア、ABCスコア、LOC リスクが高すぎる → キャッシュ無効化など(Autoresearchはキャッシュが大好きで、しかもバグりやすい) テストを通るが... → Autoresearchは良いテストがある前提で動く GPL 違反など → GitHub外の法的・コンプライアンスチェック 要するに 「マージできるか」を判断する能力こそがArchitectである という主張です。 4つのレッスン セッションを通じて提示された4つのレッスンをまとめます。 Lesson 1: 「自動調査」であって「自動変更」ではない Stan Lo氏(Shopify)の取り組みが紹介されました。Ruby-lspのCI時間を33%削減、rubydexのインデックス速度を10%〜50%改善するといった成果を、AIの提案を自身がレビューした上で実現しています。AIの出力を人間が検証・理解して取り込む姿勢が重要だという例です。 Lesson 2: 自分がオーナーで Architect でないものに Autoresearch を適用しない 理解・検証・修正できるコードにのみ適用すべきです。 Lesson 3: ループはビター・レッスンを実践する Richard Suttonさんの「苦い教訓(Bitter Lesson)」(人間の知識を組み込むより、計算のスケールによって探索・学習する方が長期的に優れるという教訓)をループは体現しています。ループは人間のゲートを担保するバージョンに過ぎず、ゲートをソフトウェアに置き換えることで探索をスケールさせられます。 Lesson 4: 人間のゲートをソフトウェアゲートに変換する 「遅い」という感覚を赤/緑の状態・バグチケットに変換するプロセスがパフォーマンス改善の本質です。たとえAutoresearchが失敗しても、ゲートを定義する過程でソフトウェアの品質自体が上がります。 Software Factory とその課題 セッション後半では、 Software Factory (StrongDM社の提唱するコンセプト)が紹介されました。 "Code must not be written by humans. Code must not be reviewed by humans." — StrongDM's "Software Factory" これは人間のレビューを完全に排除し、多数の自動ゲートを通過したコードのみを出荷する考え方です。セッションではこれを「dark factory」と表現し、Berkopec氏は多くの未解決の問いを並べて紹介しました。 形式手法・プロパティベーステストは現実的にスケールするか? 既存の大規模コードベース(ブラウンフィールド)に適用できるか? LOCの肥大化をどう防ぐか? LLMがトレーニングデータの著名な最適化を「再発明」するだけにならないか? 決定論的なゲートとLLMジャッジをどう組み合わせるか? Berkopec氏はこれらに対して現時点で答えはないとしながらも、「ゲート設計こそが重要になる」という方向性を示しました。 まとめ Berkopec氏のセッションを一言でまとめると、 「AI に自律的なループを回させることはできるが、それを意味のある成果に変えるにはゲート設計という人間のスキルが必要」 というメッセージでした。 発表で推奨されていた実践ステップは以下の通りです。 自分が十分に理解・レビューできるコードにのみ適用する(Stan Lo氏のアプローチ) 成熟した小さなモジュールから始める ゲートが最小で済む場所からスタートする ゲートを増やせば人間のレビューを減らせる Ralph(離散)・Autoresearch(連続)・Factory(多ゲート)のいずれかのループを試してみる おわりに 今年のRubyKaigiはAIに関するセッションの多さが印象的でした。普段の業務で自身もAIを活用しています。本セッションで紹介されていたゲートの設計・導入に注力して、パフォーマンス改善はもちろんのこと、それ以外のあらゆる業務課題の自動化をより推進していきたいと思いました! Exploring RuboCop with MCP 伊藤です。今年もRubyKaigiに参加してきました! 私からは @koic さんの「 Exploring RuboCop with MCP 」をご紹介します。 speakerdeck.com 本セッションは「I. MCP Ruby SDK」「II. RuboCop x MCP」の2部構成で、前半では @koic さんがコミッターを務める公式の MCP Ruby SDK ( mcp gemとして公開)の設計や、Streamable HTTPでのセッション管理・Sampling・Elicitationまでが解説されました。後半は、RuboCop 1.85.0で実験的に追加された組み込みMCPサーバーの紹介でした。 ここでは特に後半「RuboCop x MCP」を題材に、WEARのバックエンドエンジニアとして実際に手元で動かしてみた感想を中心にご紹介します。 rubocop --mcp は、RuboCop 1.85.0から導入されたサブコマンドで、AIエージェント(Claude CodeなどのMCPクライアント)からRuboCopを呼び出せるようにする組み込みMCPサーバーを起動します。stdio transportで通信し、以下の2つのツールを公開します。 rubocop_inspection :オフェンス検査(読み取り専用) rubocop_autocorrection :オートコレクト適用(破壊的) なお、RuboCopではコーディング規約違反のことを「オフェンス(offense)」と呼びます。以降本記事でもこの用語を使います。 rubocop --lsp の内部で使われている診断機構( RuboCop::LSP::Runtime )をそのまま再利用しているのが特徴で、出力されるオフェンスはLSPの Diagnostic 形式(JSON)です。Clang形式のテキストよりも構造化されているため、AIエージェントがそのままパースして扱いやすい形になっています。 ここからは実際に手元で動かしてみます。 rubocop --mcp を使うには、RuboCop本体に加えて mcp gemが必要です(RuboCopは mcp をruntime dependencyとして持っていないため)。既存のGemfileのdevelopmentグループに以下を追加します。 group :development do gem ' mcp ' end 次にClaude Codeに .mcp.json でRuboCopのMCPサーバーを登録します(Claude Codeが設定を読み、起動時に bundle exec rubocop --mcp をサブプロセスとして立ち上げてくれます)。 { " mcpServers ": { " rubocop ": { " command ": " bundle ", " args ": [ " exec ", " rubocop ", " --mcp " ] } } } Claude Code上で /mcp を叩くと、登録した rubocop サーバーから2つのツールが認識されているのが確認できます。 ❯ /mcp ──────────────────────────────────────────────────── Tools for rubocop 2 tools ❯ 1\. RuboCop's inspection read-only 2\. RuboCop's autocorrection destructive それでは実際に rubocop_inspection を呼んでみます。オフェンスを意図的に仕込んだ以下のサンプルファイル sample.rb を用意して、検査を呼んでもらいます。 # frozen_string_literal: true def greet ( name ) message = " Hello, " +name+ " ! " puts message end greet( " WEAR " ) 「 sample.rb をRuboCopで検査して」とお願いすると、Claude Codeが rubocop_inspection ツールを呼び、結果を以下のように整形して提示してくれました。 ❯ sample.rb をRuboCopで検査して Called rubocop (ctrl+o to expand) ⏺ sample.rb の検査結果: 9 件の offense が検出されました(すべて自動修正可能)。 ┌─────┬─────────────────────────────┬─────────────────────────────┐ │ 行 │ Cop │ 内容 │ ├─────┼─────────────────────────────┼─────────────────────────────┤ │ 2 │ Layout/SpaceInsideParens │ 括弧内の余計なスペース(2 │ │ │ │ 箇所) │ ├─────┼─────────────────────────────┼─────────────────────────────┤ │ 3 │ Style/StringConcatenation │ \+ 連結より文字列補間を推奨 │ ├─────┼─────────────────────────────┼─────────────────────────────┤ │ 3 │ Style/StringLiterals │ "Hello, " → │ │ │ │ シングルクォート推奨 │ ├─────┼─────────────────────────────┼─────────────────────────────┤ │ 3 │ Style/StringLiterals │ "\!" → シングルクォート推奨 │ ├─────┼─────────────────────────────┼─────────────────────────────┤ │ 3 │ Layout/SpaceAroundOperators │ \+ 演算子の前後にスペース無 │ │ │ │ し(2 箇所) │ ├─────┼─────────────────────────────┼─────────────────────────────┤ │ 4 │ Layout/IndentationConsisten │ インデントが不揃い │ │ │ cy │ │ ├─────┼─────────────────────────────┼─────────────────────────────┤ │ 7 │ Style/StringLiterals │ "WEAR" → │ │ │ │ シングルクォート推奨 │ └─────┴─────────────────────────────┴─────────────────────────────┘ すべて rubocop \-A で自動修正できます。自動修正を実行しますか? 「自動修正を実行しますか?」というフォローアップ付きで、続けて rubocop_autocorrection ツールへの切り替えまで自然に提案してくれているのが分かります(行番号がLSPの0-indexed値のまま表示されているのは、RuboCopが返すLSP Diagnosticの値をClaude Code側で1-indexedに変換せずに表示しているためです)。 このとき裏で rubocop_inspection から返ってきている生のJSONは、紙面の都合で code_actions を省略し、検出された9件のうち代表2件のみ抜粋すると以下のようになっています。 { " files ": [ { " path ": " sample.rb ", " offenses ": [ { " range ": { " start ": { " line ": 2 , " character ": 10 } , " end ": { " line ": 2 , " character ": 11 } } , " severity ": 3 , " code ": " Layout/SpaceInsideParens ", " codeDescription ": { " href ": " https://docs.rubocop.org/rubocop/cops_layout.html#layoutspaceinsideparens " } , " source ": " RuboCop ", " message ": " Layout/SpaceInsideParens: Space inside parentheses detected. ", " data ": { " correctable ": true , " code_actions ": [ /* 省略 */ ] } } , { " range ": { " start ": { " line ": 3 , " character ": 12 } , " end ": { " line ": 3 , " character ": 30 } } , " severity ": 3 , " code ": " Style/StringConcatenation ", " codeDescription ": { " href ": " https://docs.rubocop.org/rubocop/cops_style.html#stylestringconcatenation " } , " source ": " RuboCop ", " message ": " Style/StringConcatenation: Prefer string interpolation to string concatenation. ", " data ": { " correctable ": true , " code_actions ": [ /* 省略 */ ] } } ] } ] , " summary ": { " target_file_count ": 1 , " offense_count ": 9 } } LSPの診断と同じ形式なので、各オフェンスにcop名・該当範囲・cop個別のドキュメントURL・自動修正可否( correctable )まで構造化された形で含まれます。さらに省略した code_actions には実際のautocorrectで挿入する文字列(例: Style/StringConcatenation なら "Hello, #{name}!" への置換)や、 # rubocop:disable を入れて該当行で無効化する案もLSPの WorkspaceEdit 形式で含まれており、エージェントから機械的に扱えるようになっています。 rubocop_autocorrection ツールに切り替えれば、そのまま破壊的に修正をかけることもできます。 WEARのバックエンドではすでにRuboCop+各種pluginを大規模に運用しているので、生成AIによる開発を取り入れる際にも「RuboCopを通っている」という決定的なゲートはそのまま使い続けたい、と日頃から思っていました。本セッションは、まさにその「決定的なツール(RuboCop)」と「確率的なLLM」を rubocop --mcp というかたちで素直に橋渡ししてくれる発表で、聞きながら自分の開発フローに組み込む絵が具体的に浮かびました。 セッション後半で @koic さんが投げかけられていた "What happens when combined?"(決定的なツールと確率的なLLMを組み合わせると何が起きるか)という問いも印象的でした。例えば、現状の .rubocop.yml で無効化しているcopをLLMに一時的に有効化させ、レビュー観点として「いま無効になっているcopをあえて見たらどう見えるか」を提示してもらう、といった使い方は、ツール単体だと出てこない発想です。Streamable HTTPでのSamplingやElicitationまで含めて、まだ「探索が始まったばかり」という締めくくり通り、これからRuboCop×MCPでどんな試みが出てくるかとても楽しみです。 The Journey of Box Building 坂元( @sakam0cchan )です。私からはSatoshi Tagomori( @tagomoris )さんの「 The Journey of Box Building 」というセッションを紹介します。 speakerdeck.com このセッションは、Ruby 4.0で実験的に導入される新機能「Ruby Box」をテーマにしたKeynoteです。Boxの基本概念から、実行中のRubyプログラムの中でBoxを特定する際の難しさ、そしてその実装に至るまでの背景などが語られていました。 私自身が初めてRubyKaigiに参加し、そして初めて聞いたKeynoteがこの内容だったこともあり、これからの3日間がどんなものになるかとてもわくわくしました。そんな思いも込めて、内容を共有します。 Ruby Boxとは何か Ruby Box(RubyKaigi 2025までは「Namespace」と呼ばれていた機能)は、Ruby 4.0で導入された実験的な機能です。 Ruby Boxは、これまでRubyのプロセス全体で共有されていたクラスやモジュール、モンキーパッチなどを、別の空間(Box)として分離し読み込み・実行できる革新的な機能となっています。たとえば、 something.rb に書かれたクラスを、メイン環境とは独立したBoxの中に読み込むイメージです。 # something.rb class Something def hello = " hello from Something " end # main.rb box = Ruby :: Box .new box.require( ' something ' ) s = box:: Something .new p s.hello # => "hello from Something" Boxの中で読み込んだ Something は box::Something としてだけアクセスでき、Boxの外からは見えません。これにより、ライブラリやモンキーパッチをプロセス全体へ漏らさずに使えるのがBoxのコアアイデアです。セッションの中では、以下のような用途がBoxの利用シーンとして挙げられていました。 一部のコードにて有効なモンキーパッチの適用 テスト内でのモック処理 異なるバージョンのライブラリに依存する複数のアプリケーションを、同じプロセス内に同居させる どちらにも同じリクエストを送ることで、異なるバージョンのライブラリでの挙動を比較する このように、Boxは「同じプロセス内での隔離された環境」を提供することで、これまでRubyで難しかったことを可能にする機能として期待されています。 Ruby Boxの仕組み 本セッションでは、Ruby Boxを実現する上で必要不可欠となる「現在、実行中のコードがどのBoxに属しているか」を判定する仕組みが詳細に解説されていました。 Ruby Boxでは、「1つのファイルを、別のBoxで同時に読み込む」といったことが可能です。つまり、ファイル自体は特定のBoxに縛られていないため、プログラムが実行されるたびに、Ruby自身が「自分は今、どのBoxの中で動いているのか?」を動的に特定し続けなければなりません。 Boxの特定・決定のプロセスは、以下のようなステップで行われているとのことでした。 現在実行中のフレームを特定する まず、自分がいま実行しているControl Frameを見つける LOCALな変数スコープを探す そこから、ブロックや例外処理といった非ローカルなものを無視し、ファイル全体、クラス、メソッドといった「LOCAL(局所的)」なスコープを持つフレームまでさかのぼる Boxの情報を引っ張り出す そこで、見つけたLOCALなフレームの中に保存されている情報をたどり、そこに紐付いている「Box情報」を取得する ここで一番興味深かったのは、Control Frameとローカル変数などを管理しているENVとの関係性です。それぞれはRubyを実行する上で、現時点でどのコードが動いているのかを把握するためのものです。Rubyのメモリ上では、Control FrameとENVの2つが両端から内側に向かって伸びていく特殊な構造となっているそうです。 この二つが衝突すると、Stack Overflowが発生するという話をこのセッションで初めて知り、今まであまり意識していなかったRubyの内部構造について、もっとより知って勉強したいと改めて感じました。 Ruby Boxが生まれた歴史 さらに、印象に残っているもう1つのトピックとして、Ruby Boxが生まれた歴史についての話があります。開発者のTagomoriさんがRuby Boxの開発を始めたきっかけは、RubyKaigi 2023で発表された 「Multiverse Ruby」のセッション でした。その内容が、過去に自身が抱えていた課題と重なると感じたことで、一気にモチベーションが高まったそうです。 さらに、その会期中に発表者の方と直接話す機会があり、当初は「Hako」という命名にして函館のRubyKaigiで発表しよう、という話もあったそうで…。そして実際に今、こうして発表に至っている、なんと胸が熱くなる展開でしょうか。 こんなエピソードを通して、「みなさんにも、ふとモチベーションが湧き上がる瞬間があるかもしれない。あなたにとっての『Multiverse Ruby』があるかもしれない。そんなプロセスも含めてRubyKaigiを楽しんでほしい」という言葉が印象的でした。最初のセッションでその一言を聞いたとき、これから始まる3日間のRubyKaigiにとてもわくわくしたあの瞬間を、今でも覚えています。そんな開発の裏エピソードも含め、とても印象的なセッションでした。 おわりに 本セッションは「新機能の紹介」というよりも、どんなふうに実現されているのか、さらにその機能が生まれた瞬間を聴ける発表で、聴いた後の余韻が長く残るキーノートでした。 WEARのバックエンドはRuby on Railsで開発・リプレイスを進めており、monkey patchが絡む箇所のスコープ管理に苦労する場面が今後出てくるかもしれません。Boxのように「実行時の境界」を表現できるようになれば、たとえばライブラリの差し込みやテスト環境固有の上書きを、より見通しよく扱えるようになる可能性を感じました。Ruby Boxはまだ実験的な機能ではありますが、今後も引き続き注目していきたいです! ZOZOブースの紹介 こんにちは。Developer Engagementブロックの wiroha です。ここからはZOZOや各社の協賛ブースの様子を紹介します。 今回は出発当日の朝、管制トラブルの影響で飛行機の運航に乱れが出ている状況からのスタートとなりました。搭乗予定の便も遅延か欠航かの見通しが立たなかったため、早々にキャンセルして新幹線へ切り替えたことが功を奏し、4〜5時間かかったものの比較的早めの時間に函館入りできました。また直前の地震の影響で北海道では配送の遅延が起きていたようですが、幸いブースの荷物は無事に届いており、予定どおり運営できそうでほっとしました。 今回のブースはファッション×AIの2つの新しい体験をご用意しました。ひとつは「 Apps in ChatGPT 」、もうひとつはWEARアプリ内の「 着回し提案 」機能です。 ファッション×AIの新機能を体験しよう! 「 Apps in ChatGPT 」の体験では、技術カンファレンスに合うコーディネートを相談する方や、自分の写真を撮って「もっとおしゃれにするにはどうしたらいい?」と質問する方など、それぞれ自由な対話を楽しんでいる様子が見られました。 「Apps in ChatGPT」を体験中の様子 「 着回し提案 」は特定のアイテムを選択すると、ファッションに特化したAIがユーザーの好みに合わせた着回しを提案します。「知らなかった、便利そう!」という感想を多くいただきました。 「着回し提案」を体験中の様子 いずれかの体験をしていただいた方に、ZOZOらしいノベルティの「 洗濯ネット 」をプレゼントしました。「ちょうど今日洗濯しようと思ってました」「洗濯ネットが欲しくて体験しに来ました」といった声があり、日常的に使えるアイテムとして多くの方に喜んでいただけました! ZOZOらしいノベルティの洗濯ネット 協賛企業ブースのコーディネートまとめ assu_ です。函館へはすんなり辿り着く──はずでした。伊丹空港で突如届く欠航の知らせ、満席の函館便、迫られる決断。滑り込んだ新千歳行き、やけくそで高い寿司を買い、特急北斗で函館へ。家を出てから実に13時間、極寒の函館に到着──虚無の心を救ったのは、函館駅前のハセガワストア。波乱の移動を経て辿り着いた会場には、同じように大変な思いをした人も少なくありませんでした。 そんな会場から、前回の RubyKaigi 2025 同様、素敵なコーディネートの協賛企業の皆様を撮影させていただきました! デザインスポンサーのGaji-Laboさん Committer限定のMA-1は、希少性と刺繍の2026デザインがとてもクール。 北海道らしいイラストをあしらったフーディーの note さん。 パンダをモノクロにすると北海道らしくなったという GMO Flatt Security さん。 CM でおなじみのキャラクターにより個性的なデザインの ファインディ さん。 シンプル胸元とカタカナ背中の対比が良い、ネットプロテクションズさん。 襟シャツに絞り付きの裾、男性も女性も着こなし上手だったギフティさん。 ロゴのラインが今っぽい、視認性が高くセンスの良いWEDさん。 誇り高きデザインにより、長い Ruby歴 を表しているKOMOJUさん。 桜が満開の函館に一番ぴったりなカラーと、法被で華やかなwithさん。 Wellness Sponsorらしいブラックで “ W ”orkoutしやすそうなhacomonoさん。 北海道・函館らしいデザインや、変化する気温に対応しやすそうな形が目立った回でしたね。お忙しい中ご協力いただいたブースの皆さん、本当にありがとうございました! RubyKaigi 2026 アフターイベント〜初参加LT・スポンサー4社のパネル〜を開催します RubyKaigi 2026 アフターイベント〜初参加LT・スポンサー4社のパネル〜 5月13日(水)にRubyKaigi 2026スポンサー企業の株式会社ZOZO、株式会社リブセンス、株式会社TOKIUM、株式会社マイベストでRubyKaigi 2026のアフターイベント「 RubyKaigi 2026 アフターイベント〜初参加LT・スポンサー4社のパネル〜 」を開催します。 RubyKaigi 2026に参加した方も、参加できなかった方も、ぜひお気軽にご参加ください! mybest.connpass.com おわりに 世界中のRubyistが函館の地に集まりました! ZOZOは毎年RubyKaigiに協賛し、ブースを出展しており、今年も多くの方々との交流を通じて有意義な時間を過ごすことができました。実行委員会の皆様、そして温かく迎えてくださった函館市の皆様に感謝申し上げます。来年も再び素晴らしい時間を共有できることを楽しみにしております! 技術カンファレンスでは恒例のスポンサーパネルへのサイン! RubyKaigi 2027 #rubykaigi 🗓️ 14..16 Apr 2027 🥭 Miyazaki, Japan — RubyKaigi (@rubykaigi) 2026年4月24日 そして、次回の開催地は私たちZOZOの宮崎オフィスがある宮崎県です。宮崎在住エンジニアも数名在籍しているので、地元を生かしたイベントなどができると良いですね。それではまた来年、RubyKaigi 2027でお会いしましょう。現場からは以上です! ZOZOでは、来年のRubyKaigi 2027を一緒に盛り上げるエンジニアを募集しています。ご興味のある方は以下のリンクからご応募ください。 corp.zozo.com

























