Android - TECH PLAY - TECH PLAY

TECH PLAY

Android

イベント

マガジン

技術ブログ

こんにちは、ラクス技術広報です。 2026年7月15日、主催イベント「RAKUS AI Conference 2026 Summer」を開催しました。本記事では、楽楽精算 開発3課の平川裕多さんが発表した「仕様駆動開発、導入半年。『本当に速くなってるの?』にデータで答える」について、技術広報がレポート記事でご紹介します。 この記事はこのような方におすすめです AI活用で実装は速くなった気がするのに、なぜか設計やレビューの負荷が増えていると感じているエンジニアの方 仕様駆動開発(SDD)の導入を検討している、あるいは導入したものの効果を数字で説明できずに悩んでいる方 「AIネイティブな開発」を、感覚ではなくデータで語りたいと考えているエンジニアの方 【目次】 「それ、本当に速くなってるの?」に答えられなかった半年 仕様駆動開発に"飛びついた"というのが実態でした 上司とメンバーからの"ツッコミ"と、1年分のデータを掘る決意 データを掘って初めて分かった、3つの指標の意外な共通点 指標①:時間 指標②:レビュー 指標③:バグ(事故) 3つの指標から見えてきたもの 正直に語られた課題と、「仕様を決める力」への投資 終わりに 「それ、本当に速くなってるの?」に答えられなかった半年 平川さんのチームが担当するのは、経費精算クラウドサービス「楽楽精算」のモバイルアプリです。iOS、Android、バックエンド、フロントエンドという複数のプラットフォームを、6名のエンジニアがアジャイルの2週間スプリントで開発しています。 ここ1〜2年でAI活用が本格化し、個人の実装スピードは体感としても数字としても間違いなく上がったといいます。コードを書く作業は、以前ほど開発のボトルネックではなくなりました。 ところがその裏で、3つの問題が起きていました。 意図のよく分からないコードが混ざるようになったこと レビューの負荷に偏りが出るようになったこと テストフェーズになって初めて「考慮漏れ」に気づく事故が多発するようになったこと 設計段階で気づかず、後工程で発覚するほど、修正のコストは高くつきます。 「早くはなったけど、何か別のものを払っている感覚があった」 この違和感から生まれたのが、「AIで早くなった裏で、本当は何を払っていたのか」という問いでした。 仕様駆動開発に"飛びついた"というのが実態でした この問いに対して、平川さんたちがたどり着いたのが仕様駆動開発(SDD)でした。ただし、最初からSDDを狙って導入したわけではなかった、と平川さんは振り返ります。 最初にやっていたのは、今まで手で書いていた設計書をAIに書かせて時短できないか、という「AI設計テンプレート」的な試みでした。それを1ヶ月ほど地道に作り込んでいたそうです。ちょうどそこに、世の中で「仕様駆動開発」という言葉が流行り始め、「これ、自分がやりたかったやつだ」と思ったといいます。慎重に比較検討して選んだというより、飛びついたという感覚の方が実態に近いと振り返ります。 飛びついたあとで、あらためて「なぜ他のやり方ではなくSDDだったのか」を整理しました。 Planモード :AIがタスクを組んでくれて便利だが、結局それを使うエンジニア個人の能力に依存する点で、直接指示と本質的に変わらない テスト駆動開発(TDD) :リファクタリングには強いが、そもそも仕様がブレていればテスト自体が空中分解してしまう Planモードの質もTDDのテストの質も、たどっていくと結局は「仕様」に行き着く。だったら一番上流の「仕様」そのものを中心に据えるのが筋が良い、という腹落ちだったとのことでした。 具体的には、マークダウンで構造化した自然言語の仕様書を使い、設計そのものをPRとしてレビューする運用を敷きました。とはいえ、自然言語の成果物にはコードのようなリンターもテストも効きません。「問題ない」と判断するにはしっかり読む必要があり、コストがかかります。仕様を構造化したり重複を減らしたりという地味なチューニングを、今も積み重ねている最中とのことでした。 上司とメンバーからの"ツッコミ"と、1年分のデータを掘る決意 SDDを始めてすぐ、突っ込まれる日々が始まりました。 上司からは「それ本当に早くなってるの」「設計に時間をかけている分、トータルで遅くなっているんじゃないの」という声。メンバーからは「設計フェーズが大変になった」「一番頭を使う部分が重くなった」という声が上がりました。 この2つのツッコミに、感覚で「いや、早くなっていますよ」と返しても説得力がありません。そう考えた平川さんは、1年分のデータを本気で掘り返して検証することにしました。 なお、平川さん自身「そもそもSDDを品質のために入れたわけではなく、狙いは実装を誰がやっても同じ質にして属人性をなくすことだった」と前置きしています。この後の検証結果は、当初の狙いとは別のところで平川さんたちを驚かせることになります。 データを掘って初めて分かった、3つの指標の意外な共通点 AIもアジャイルも定着した時期以降のデータに絞り、フェアな比較を心がけたうえで、3つの指標を見ていきます。 指標①:時間 実装フェーズの数字は、確かに速くなっていました。ただし、その「速さ」の正体を追うと、後工程にあった意思決定の負荷が、設計フェーズに前倒しされただけでした。たとえば「複数ある実装方針のどれを採用するか」という判断は、SDD以前は実装しながら決めることもありました。今はそれを設計のタイミングで行います。AIが選択肢を出してくれる分、考えるのは楽になった場面はあるものの、最終的にどれにするかを人間が決め、レビューやステークホルダーの合意を得る必要がある点は変わりません。SDD自体は時短策ではない、というのが平川さんの見立てです。 指標②:レビュー 1つのPRあたりの他者からのコメント数は、中央値がずっと1でほぼ横ばいでした。レビューの総量そのものは減っていません。ただし中身は変わっていました。実装PRで「この仕様どうなってるの?」という揉め事が減り、その議論が仕様レビューの場に前倒しされたのです。レビューが純粋なコード品質チェックに近づいたという意味では狙い通りですが、「楽になった」わけではなく、「議論する場所が移った」というのが実態に近い、と平川さんは説明します。 指標③:バグ(事故) バグの発生件数そのものは、劇的には変わっていませんでした。ただし2つの変化がありました。1つは、1件あたりの対応時間(※着手からテスト完了までのリードタイム)が17時間から11時間に短縮したこと。もう1つが、平川さんいわく「これが大きい」変化でした。以前は1スプリントで20件を超えるような"バグの大爆発"が起きることがあったのが、最大でも8件程度に収まるようになりました。事故の数ではなく、事故の振れ幅が小さくなったということです。 なお、この集計はテストまで完了したスプリントのみを対象にしており、サンプル数はまだ多くありません。平川さん自身、断定ではなく傾向として見てほしいと、数字の限界を率直に語っていました。 3つの指標から見えてきたもの 3つの指標を並べると、見えてくるものがあります。時間もレビューも、内容は移っただけで総量は変わらず、バグは件数こそ横ばいながら振れ幅が縮みました。 ここから導かれる結論を、平川さんはこう言い切ります。「SDDの本当の成果は、速さじゃない」。開発そのもののスピードは、AIをガムシャラに使っていた1年前とほとんど変わっていません。得られたのは、予測可能性でした。裏を返せば、以前ガムシャラに速度を出していた頃、代わりに払っていたのは、この予測可能性だったのです。 平川さんはこれを具体的なエピソードで語っていました。怖いのは、バグ修正にかかる時間そのものより、「何件出るか読めないこと」だそうです。2週間スプリントの7日目まで予定通り進み、残業もせず帰れていたとします。それなのにテストでバグがたくさん出ると、残り数日で焦って対応するか、別スプリントに送るかという判断に迫られ、計画が崩れます。SDDによって仕様の検討が上流に寄った結果、この「予想外の大爆発」が起きにくくなったのです。平均的な件数は大きく変わらなくても、最悪のケースが消えて振れ幅が縮み、立てた計画が、そのまま計画として機能するようになりました。 そしてこれは、働きやすさだけの話ではありません。事故で開発が止まらないということは、顧客に安定したペースで価値を届け続けられるということでもあります。予測可能性は、顧客への価値提供の土台でもある。平川さんはそう位置づけていました。 正直に語られた課題と、「仕様を決める力」への投資 SDDは時短の手法ではなく、決めごとの総量も変わりません。それでも品質と予測可能性への投資だった、というのが平川さんの結論です。実装スピードそのものは変わらなくても、速さの出方が変わりました。昔は事故が起きるかどうか読めないまま勢いで速度を出していたのに対し、今は上流で足場を固めてから、同じ速度を読める形で出している。アジャイルを捨てたわけでもなく、2週間スプリントという枠のなかで「決める位置」を前にずらしただけだ、という整理も印象的でした。 ここで終われば美談ですが、平川さんは課題も正直に語っていました。時間もレビューも総量は「移っただけ」で減ってはおらず、総量そのものをどう減らすかは宿題のままです。さらに、レビューを上流に寄せた結果、今度は仕様レビューの方が渋滞するという新しいボトルネックも生まれています。 興味深かったのは、仕様が設計段階で固まることで、そこからテストを作るのも楽になるという発見です。固まった仕様を起点にすれば、テスト設計やユニットテストを考える時間も減り、AIに任せられる部分も増えます。上流で固めた仕様を、テスト作成の自動化にそのまま流し込む。この接続を今まさに模索しているそうです。 またメンバーの「設計フェーズが大変」という声の実体は、仕様書を作ったあとのモブレビューではなく、その前段階、個人がローカルで仕様を練っている時間が最も頭を使う、というものでした。ここに「ループ」や「ハーネス」といった仕組みを当てはめ、機械的に拾える考慮漏れはモブレビュー前に潰しておきたいとのこと。ただし、モブレビューそのものは残したいとも話していました。人を育てる場であり、テックリード一人がすべてをレビューしなくても、メンバー同士でレビューが回るようになる効果もあるからです。自動化するのは生成の負荷にあたる部分で、人間の判断や育成の機会は残す。この線引きを大切にしているとのことでした。 この先の展望として、平川さんは「ループエンジニアリング」という考え方も紹介していました。海外のAI開発ツールの責任者が「もうAIに指示は出していない、自分の仕事はループを書くことだ」と話しているそうで、その考え方の提唱者とされる人物も「これは仕事が簡単になったわけではなく、レバレッジの効く点が移っただけ」と釘を刺しているとのことでした。これは平川さんが今回データで語った「決める場所が上流に移っただけ」と、驚くほど重なる指摘です。その人物はさらに、全部を自動ループに任せればプロダクトの品質は落ちるとまで話しているそうです。つまりループは「何が正解か」の判断までは代わってくれません。その「何が正解か」を上流ではっきりさせるのが、まさにSDDです。ループの時代が来るほど、その前段にある「仕様を決める力」の価値は上がっていく。開発をAIに委ねても、「何が正解かを決めるカロリー」だけは人間に残る、という見方を示していました。 予測可能性が手に入るということは、AIに安全に任せられる範囲が見えてくるということでもあります。読めないものは任せられませんが、振れ幅が小さく読めるものなら任せられます。その範囲を安全に広げていけば、いずれボリュームが増え、トータルのリードタイムも縮んでいくはずです。平川さんは、今回手に入れた予測可能性を、その先の自動化を安全に広げるための「足場」だと位置づけていました。 終わりに 時短にはなっていない、新しいボトルネックも生まれた。それでも正直に数字と向き合う姿勢そのものが、AIネイティブな開発組織のリアルなのだと感じます。「なんとなく速くなった気がする」で終わらせず、データで自分たちの仮説を裏切る勇気を持てるかどうか。仕様駆動開発を検討している方にとって、平川さんの検証プロセスそのものが参考になれば幸いです。 当日の発表資料はSpeakerDeckで公開しています。ぜひあわせてご覧ください。 発表資料 speakerdeck.com なお、8月下旬ごろに発表のアーカイブ動画をラクスエンジニア情報ポータルサイトにて公開予定です。 ラクスエンジニア情報ポータルサイト career-recruit.rakus.co.jp 「RAKUS AI Conference 2026 Summer」の他レポート記事 ・ AIを載せることはゴールではない。ラクスCTOと開発副本部長が語った、組織とプロダクトの変革 ・ 顧客の声から生まれた『AI返信補助機能』の開発プロセス ・ 楽楽精算AIエージェントを支える、LLMOpsとインフラの選択肢 ラクスでは、こうした「顧客志向」と「AIネイティブ」の両方を大切にしながら、地に足のついた検証を重ねる開発組織を、一緒に作っていく仲間を募集しています。ご興味を持っていただけた方は、ぜひ採用ページもチェックしてみてください。 最後までお読みいただきありがとうございました!
はじめにLINE Developersから利用できるLIFF(LINE Front-end Framework)を活用すれば、LINEアプリのユーザーを対象に、独自のサービススペースを開設できます。L...
こんにちは、トモニテ開発部 iOS エンジニアの村田です。 iOS エンジニアもしくは Android・クロスプラットフォームを含めてモバイルエンジニアの皆さんに対して気になっていることがあります。 みなさん AI 開発どんな感じでやってますか?どんなハーネス組んでますか? エンジニアリング業界では、単に指示を出して書いてもらう「バイブコーディング」の次の段階として、AI エージェントの自律化や「ハーネスエンジニアリング」「ループエンジニアリング」といった話題が急速に広がっています。 Web やサーバーサイド領域では、こうした最新の AI 活用の情報が活発に共有されている一方、iOS(モバイル)開発における実践的なナレッジはまだまだ各所に散らばっており個々で孤軍奮闘している印象を受けています。 私自身、久しぶりに iOS 開発に取り組んでいて、Claude Code と git worktree を用いた並行開発をする中で iOS 特有のハマりどころをいくつか感じました。 今回は「並行開発」という観点にフォーカスして、iOS で並行開発したときに感じた課題と対処方法を記載します。 開発環境 前提として、以下のような環境・仕組みで開発しています。 使用技術 AIエージェント : Claude Code エディタ : VSCode 並行開発 : Git Worktree 対象 : iOS アプリ(Swift / UIKit / SPM / .xcodeproj) ビルド方法 : XcodeBuildMCP(Claude 経由の MCP)・XcodeBuildMCP(CLI 版)・Xcode(GUI) ディレクトリ構成 <repo>-trees/ ← worktree develop/ ← メイン worktree(base ブランチ) feature-A/ ← タスクごとの worktree(並列) feature-B/ ← タスクごとの worktree(並列) refactor-C/ ← タスクごとの worktree(並列) ... 開発手順 タスク開始時に git worktree add で develop ブランチから新規 worktree を切る worktree 配下で Claude Code のセッションを開始し、要件定義 → 実装 → PR 作成など開発フローを進行 タスクが終わったら、その worktree ごと片付ける ---bin なぜ並行開発が欠かせないのか あちこちで語られている話なので、要点だけ。 AI に開発させると、人間の役割は「実装する人」から「複数のエージェントを監督する人」に変わります。「どこまで自律的に AI に開発させられているか」にも依りますが、設計・実装・ビルド・テストと多くのフェーズで人間の手が空くようになるため、1 タスクを直列で進めるよりも複数タスクを並行で回す方が効率よく進められます。 そこで注目を浴びたのが git worktree です。ブランチごとに独立した作業ディレクトリ(worktree)を持てるため、作業ファイルの競合を気にせず、安全に複数タスクを同時進行できます。 そうして git worktree を用いて iOS 開発を始めたのですが、いくつか課題が発生しました。 問題その1: DerivedData がストレージを食い尽くす 何が起きたか iOS のビルドでは DerivedData を生成します。DerivedData には Xcode がビルドのたびに吐き出す中間生成物(ビルドキャッシュ・インデックス・成果物など)が含まれており、デフォルトでは ~/Library/Developer/Xcode/DerivedData/ に生成されます。XcodeBuildMCP でビルドする場合は、 ~/Library/Developer/XcodeBuildMCP/workspaces/<worktree>-<hash>/DerivedData/<プロジェクト名>-<hash> に生成されます。 DerivedData は worktree の内部ではなく、ユーザー共通の場所にまとめて溜まるため、worktree の数に比例して独立したビルドキャッシュが蓄積されていきます。 私の環境では 1 worktree あたり数 GB〜20 GB 台、合計およそ 86 GB になっていました。 結果としてディスクの空き容量が枯渇し、スワップ領域も確保できなくなって、ビルドどころではなくなりました。 # Xcode(GUI)の既定 — プロジェクト名 + ハッシュ名 ~/Library/Developer/Xcode/DerivedData/ ├── <プロジェクト名>-a1b2c3d4efgh…/ ├── <プロジェクト名>-e5f6g7h8ijkl…/ ├── <プロジェクト名>-i9j0k1l2mnop…/ ├── <プロジェクト名>-q7r8s9t0uvwx…/ └── … # XcodeBuildMCP の既定 — worktree 名 + ハッシュ名 ~/Library/Developer/XcodeBuildMCP/workspaces/ ├── develop-a1b2c3…/DerivedData/ 19 GB ├── feature-A-d4e5f6…/DerivedData/ 21 GB ├── feature-B-g7h8i9…/DerivedData/ 11 GB ├── feature-C-j0k1l2…/DerivedData/ 8.8 GB └── …(他 4 worktree) 26 GB 計 86 GB キャッシュを削除しようと試みたところで Xcode の場合、DerivedData のフォルダ名がハッシュ化されていて、フォルダ名だけではどの worktree に対応するかわからない ビルドしなくてもインデックス更新などで更新日時が変わるため、「更新日が古い=不要」という判断も効かない といった理由により、使い終わった worktree のゴミだけを削除することが難しかったです。 かといって全部消すと、全 worktree がコールドビルドに逆戻りしてしまいます。 XcodeBuildMCP はデフォルトでフォルダ名に worktree 名が入るため対応関係は分かりやすいのですが、 git worktree remove (worktree の削除)をしても、この孤児フォルダが残り続ける点は Xcode の既定と同じです。 対処方針: DerivedData を worktree 配下に保存する 対応策として DerivedData の置き場所を worktree フォルダの直下( <worktree>/DerivedData )に固定すると、次のメリットが得られました。 どのキャッシュがどの worktree のものか、置き場所を見れば分かる git worktree remove すれば DerivedData も一緒に消える 開発時のライフサイクルは以下のようなイメージです。 DerivedData の置き場所を変更する方法 ① Xcode の GUI でビルドする場合 全プロジェクト一律でよい場合 : Xcode → Settings → Locations → Derived Data を Default から Relative に変える 特定のプロジェクトだけ有効にしたい場合 :対象プロジェクトを Xcode で開いた状態でメニューの File → Workspace Settings… ( .xcworkspace を開いていない場合は Project Settings… )を選び、 Derived Data を Workspace-relative Location に切り替える 後者の場合、実態としてはプロジェクト内の WorkspaceSettings.xcsettings ( xcuserdata 配下・通常は Git 管理外のユーザーローカル)の設定と同等のため、GUI を使わず次の内容を直接書いても実現できます。 <? xml version = "1.0" encoding = "UTF-8" ?> <!-- <project>.xcodeproj/project.xcworkspace/xcuserdata/<user>.xcuserdatad/WorkspaceSettings.xcsettings --> <! DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd" > <plist version = "1.0" > <dict> <key> DerivedDataLocationStyle </key> <string> WorkspaceRelativePath </string> <key> DerivedDataCustomLocation </key> <string> DerivedData </string> </dict> </plist> ② XcodeBuildMCP でビルドする場合 こちらは .xcodebuildmcp/config.yaml の derivedDataPath で決まります(worktree のルートからの相対パスで解決されます)。 # <worktree>/.xcodebuildmcp/config.yaml schemaVersion : 1 sessionDefaults : projectPath : "<プロジェクトパス>" scheme : "<スキーム名>" derivedDataPath : "./DerivedData" platform : "iOS" useLatestOS : false bundleId : "<バンドル ID>" 💡 どちらの方法でも DerivedData がリポジトリ配下に生成されるため、 .gitignore に DerivedData/ を追加する必要があります。 ただし「Xcode 版で特定のプロジェクトだけ Relative にしている場合」や「XcodeBuildMCP 版で .xcodebuildmcp/config.yaml を Git 管理していない場合」は、新しい worktree を作るたびにこれらの設定を行う必要があります。毎回手動で設定するのは面倒なので、Git の post-checkout hook で worktree の作成時に自動設定されるようにするのがおすすめです。 post-checkout hook で DerivedData の設定を自動化する git worktree add や git checkout を実行すると、 post-checkout という hook が発火します。hook は共通の Git ディレクトリ( git rev-parse --git-common-dir )に置かれ全 worktree で共有されるので、そこに設定することで新規 worktree 作成時の処理を仕込むことができます。 下記のような処理を .git/hooks/post-checkout に記述することで、新規 worktree 作成時に「DerivedData を各 worktree 配下( <worktree>/DerivedData )に保存する」ように設定できます。 なお以下の hook では ② の config.yaml を直接出力していますが、正の config.yaml をデフォルトブランチなどで管理し、post-checkout で cp する方式でも構いません。 #!/bin/bash # .git/hooks/post-checkout if [ " $3 " = "1" ]; then PROJECT = $( find . -maxdepth 1 -name " *.xcodeproj " | head -1 ) if [ -n " $PROJECT " ]; then # ① Xcode GUI 用: WorkspaceSettings に worktree 相対の DerivedData を書く DIR = " ${PROJECT} /project.xcworkspace/xcuserdata/ $( whoami ) .xcuserdatad " PLIST = " ${DIR} /WorkspaceSettings.xcsettings " mkdir -p " $DIR " [ ! -f " $PLIST " ] && plutil -create xml1 " $PLIST " plutil -replace DerivedDataLocationStyle -string WorkspaceRelativePath " $PLIST " plutil -replace DerivedDataCustomLocation -string DerivedData " $PLIST " # ② XcodeBuildMCP 用: config.yaml を worktree 直下に生成 if [ ! -f .xcodebuildmcp/config.yaml ]; then mkdir -p .xcodebuildmcp cat > .xcodebuildmcp/config.yaml <<'YAML' schemaVersion: 1 sessionDefaults: projectPath: "<プロジェクトパス>" scheme: "<スキーム名>" derivedDataPath: "./DerivedData" platform: "iOS" useLatestOS: false bundleId: "<バンドル ID>" YAML fi fi fi 問題その2: シミュレータを複数セッションが奪い合う 何が起きたか iOS 開発では、シミュレータ上での実操作が必要な場面が多々あります。 画面情報・遷移情報の取得 コード変更後の動作確認 E2E テストの実行 AI に並行開発させている中でこれらの動作をさせようとすると、1 台のシミュレータを複数のセッション(エージェント)が奪い合い、開発が滞る問題が発生しました。 操作の割り込み・競合: あるエージェントの検証中に、別のエージェントが起動・操作を行って画面を奪い合う 状態の破壊: 実行中のアプリ領域やデータが上書きされ、E2E テストや動作確認が誤判定される 処理の順番待ち: シミュレータの空き待ちが発生し、並行開発のスピード感が失われる 対処方針: worktree ごとに専用シミュレータを持たせる 対策として、 worktree ごとに専用シミュレータを持たせる運用にしました。 具体的にはシミュレータ名を <worktree 名> - <元の機種名> (例: feature-A - iPhone 16 Pro )のように紐付けます。これにより「このセッションが触っていいのはこの 1 台のみ」と明確にし、他のセッションで稼働中のシミュレータへの誤干渉を防いでいます。タスクが完了して役目を終えたシミュレータはシャットダウンし、別の worktree での開発で名前を変更して再利用する流れです。 💡 基本的には AI による並行開発を前提としていますが、人間が最終的な動作確認を行う場合にもメリットがあります。各 worktree 専用のシミュレータ上にビルド済みアプリがそのまま残っているため、スムーズに動作確認を進められます。 シミュレータの選定ルール ある worktree の開発で使うシミュレータの選定ルールは、以下のようにしました。 worktree 名で始まるシミュレータが起動済みなら、それをそのまま使う worktree 名で始まるシミュレータが未起動なら、起動して使う 無ければ、起動していないシミュレータを <worktree 名> - <元の機種名> にリネームして起動する これを実装に落とし込んだものが以下のコードです。 resolve_udid は ①→③ の順に「その条件に合うシミュレータがあるか」を確認していき、最初に見つかった 1 台を、その worktree に対応するシミュレータの UDID(デバイスの一意 ID)として採用します(見つかった時点で残りは確認しません)。あとはこの UDID を指定してビルドすれば、その worktree 専用のシミュレータに向けて実行できます。 wt = " $( basename " $PWD " ) " # worktree 名(例: feature-A)をシミュレータ名の接頭辞に使う # シミュレータ一覧を JSON で取得して devices_json にキャッシュする refresh_devices() { devices_json = $( xcrun simctl list devices available --json ) ; } # 未起動のシミュレータを起動する boot_sim() { xcrun simctl boot " $1 " 2 > /dev/null ; } # UDID から元の機種名を引く(既に worktree 名が付いていれば落とす。付け足すと再利用のたびに接頭辞が積み重なるため) sim_name() { printf ' %s ' " $devices_json " | jq -r --arg u " $1 " \ ' [.devices[][] | select(.udid == $u) | .name][0] // empty | sub("^.+ - "; "") ' } # 指定した起動状態(booted / unbooted)で、worktree 名で始まるシミュレータの UDID を返す(無ければ空文字) pick_named() { printf ' %s ' " $devices_json " | jq -r --arg wt " $wt " --arg want " $1 " \ ' [.devices[][] | select((.name | startswith($wt + " - ")) and (if $want == "booted" then .state == "Booted" else .state != "Booted" end)) | .udid][0] // empty ' } # 未起動(Shutdown)の空きシミュレータを1台返す(無ければ空文字) pick_spare() { printf ' %s ' " $devices_json " | jq -r \ ' [.devices[][] | select(.state == "Shutdown") | .udid][0] // empty ' } resolve_udid() { refresh_devices # ① worktree 名で始まるシミュレータが起動済みなら、そのまま使う udid = $( pick_named booted ) ; [ -n " $udid " ] && { echo " $udid "; return; } # ② worktree 名で始まるシミュレータが未起動なら、起動して使う udid = $( pick_named unbooted ) ; [ -n " $udid " ] && { boot_sim " $udid "; echo " $udid "; return; } # ③ 無ければ、空きシミュレータの接頭辞を「<worktree 名> - 」に付け替えて起動する spare = $( pick_spare ) [ -z " $spare " ] && { echo " 空きシミュレータがありません " >&2; return 1 ; } xcrun simctl rename " $spare " " $wt - $( sim_name " $spare " ) " boot_sim " $spare " echo " $spare " } 開発フローとクロージング処理 ここまでの処理を開発フローに沿って並べると次のようになります。 開始 : タスクごとに worktree を切り、専用の作業場を用意する 実装 : その worktree で Claude セッションを開始し、1 つの機能を実装する 動作確認 : その worktree 専用のシミュレータを起動し、ビルド & 実行・テストを行う レビュー : 実装が固まったら PR を作成する クロージング処理 : PR マージと同時に、worktree 削除・Issue クローズ・シミュレータのシャットダウンを行う 開発フローの最後には、クロージング処理を置いています。 close-feature のようなスキルにまとめ、PR マージ → Issue クローズ → worktree 削除 → 専用シミュレータのシャットダウンを一括で実行しています。 このクロージング処理をフローに組み込むことで、問題その1で挙げた DerivedData によるストレージ圧迫を防ぎつつ(worktree を削除すれば配下の DerivedData も一緒に消える)、起動したままのシミュレータが積み上がってメモリを圧迫するのも同時に抑えられます(シャットダウンした端末は、次のタスクで再利用される)。 問題その3: .xcodeproj のコンフリクトが多発する .xcodeproj を Git 管理しているとブランチの切り替えやマージのたびにコンフリクトが起きやすい、という問題があります。これ自体は昔からある iOS 開発の悩みですが、AI に複数タスクを並行開発させるようになってコンフリクトの頻度も増え、無視できないコストになったと感じています。 トモニテの PJ では実現できていませんが、下記の理由により XcodeGen を用いた YAML 管理への移行を検討しています。 1. 並行開発でのコンフリクト軽減 git worktree などを活用して複数ブランチでの並行開発を進める場合、 project.pbxproj のコンフリクトが多発する可能性があります。 特に AI の活用によって開発の速度や並行性が上がるほど、その発生頻度も多くなりがちです。 XcodeGen を導入して .xcodeproj を自動生成の成果物として扱い .gitignore に追加することで、 .xcodeproj でのコンフリクト問題を解消できると考えています。 2. AI と YAML の相性 Xcode のプロジェクト管理は GUI 操作を前提とした仕組みであり、AI エージェントとは相性が良くありません。これを project.yml による宣言的なテキスト管理に切り替えることで、「YAML での管理・変更 → コマンド実行( xcodegen generate )」という AI に適したフローで構成を変更できるようになると考えています。 3. コードレビューのしやすさ コード差分が複雑な pbxproj からシンプルな project.yml に変わることで、変更内容を容易に把握できます。 AI がコードを書き、人間がレビューするフェーズでは可読性の観点でもメリットがあると考えています。 まとめと残課題 ここまで git worktree を用いた iOS の並行開発で出会った問題点と、その対処方法を紹介してきました。 とはいえ、こうして並行タスクを回す仕組みを整えても、human-in-the-loop 的な体制では並行開発の効果も限界があると感じています。監督する人間のコンテキストスイッチがボトルネックになるため私の場合、同時に見られるのは 3〜4 タスクほどでした。これでは開発生産性も頭打ちになり、「プロダクトや事業をどう伸ばすか」という本質的な問いに集中できないと感じています。 この課題を突破するためには、やはり「ループエンジニアリング」など AI がより自律的に開発を回せるような仕組みを追求していくことが不可欠だと感じています。 関連リンク XcodeBuildMCP XcodeGen

動画

書籍