AI駆動開発は「AIに全部任せる開発」ではない | 実機トラブルから見えた、本当に必要な技術力
AIを使えば、コードを書く速度は大きく上がります。
要件を伝えれば実装案を出してくれる。
テストを書いてくれる。
エラーを渡せば修正案も考えてくれる。
実際、私たちの開発でもAIをかなり深く取り入れています。
一方で、AI駆動開発を続けていると、だんだん別の難しさが見えてきます。
それは、
「コードを書くこと」と「システムを正しく理解すること」は別
だということです。
特に厄介なのが、正常に開発できていたはずのシステムで、突然、
- 動作が重くなる
- 実機だけで問題が起きる
- 特定条件でしか再現しない
- 原因らしいコードが見つからない
といったトラブルが起きたときです。
AIに実装を任せることはできます。
しかし、AIにすべてを任せたままシステムが複雑化すると、問題が起きたときに「どこから調べればよいのか分からない」状態になりやすい。
今回は、実際に経験したReact Nativeアプリのトラブルをもとに、AI駆動開発で重要になる「トラブルシューティング力」について考えてみます。
AIが書いたコードは、一つひとつ見ると正しい
今回起きた問題は、React Nativeで開発していたiOSアプリで、
タブを切り替えているうちに、徐々にアプリが重くなっていく
というものでした。
起動直後は正常です。
しかし、何度も画面を切り替えていると、タップしてから画面が表示されるまで数秒かかるようになります。
しかも、
- Simulatorではほぼ再現しない
- iPhone実機では再現する
- APIをモックにしても発生する
- TestFlightでも発生する
という状態でした。
最終的には、Push通知用のToken取得処理が大量に多重実行されていることが原因だと分かりました。
問題となっていた処理自体は、概念的には非常に単純です。
await Notifications.getExpoPushTokenAsync({ projectId })
この処理が、イベントリスナーとの組み合わせによって自己再入し、実機上で大量に滞留していました。
しかし、興味深いのはここからです。
コードを一つひとつ見ると、それほどおかしくなかったのです。
Push Tokenが変わったら再登録する。
二重登録しないようガードする。
起動時にもTokenを登録する。
それぞれ単体では自然な実装です。
問題は、それらが組み合わさったときに起きました。
AI駆動開発で怖いのは「局所的に正しいコード」
AIは、比較的小さな単位の要求にとても強いです。
例えば、
Push Tokenが変更されたらサーバーへ再登録して
と頼めば、その処理を書いてくれます。
重複登録されないようにして
と頼めば、重複防止ロジックを追加します。
起動時にもTokenを取得して
と頼めば、初期化処理を追加します。
それぞれの変更を見ると正しい。
テストも通るかもしれません。
レビューでも問題が見つからないかもしれません。
ところが、
Aという機能がBを呼び、BがOSを経由してCというイベントを発火し、Cが再びAを呼ぶ
という全体の因果関係になると、一気に難易度が上がります。
今回も、
JavaScript
↓
Expo Native Module
↓
iOS
↓
APNs
↓
Native Event
↓
JavaScript Listener
↓
最初へ戻る
という流れになっていました。
コードの中だけを検索しても、この因果関係すべてが書かれているわけではありません。
AIが苦手というより、
そもそもリポジトリだけを読んでも分からない問題
だったのです。
「AIに原因を聞く」だけでは解けない
こういう問題が起きると、
このコードのどこが悪い?
とAIに聞きたくなります。
もちろん、AIは大量の仮説を出してくれます。
今回も、
- Reactの再レンダーではないか
- Navigationではないか
- APIではないか
- GCではないか
- イベントデータの同期処理ではないか
- 端末の発熱によるサーマルスロットリングではないか
といった候補が考えられました。
どれも、それなりにもっともらしい。
ここにAI駆動開発の落とし穴があります。
AIは仮説を作るのは非常に得意です。
しかし、
その仮説のうち、どれが本当なのかを決めるには計測が必要です。
AIが10個の可能性を出したところで、事実が増えたわけではありません。
むしろ仮説だけ増やし続けると、調査対象が広がってしまいます。
トラブルシューティングで重要なのは「何を測るか」
そこで今回、原因コードを探し続けることを一度やめました。
代わりに、
原因を判定できる計測環境を作る
ことにしました。
例えば、
- タブを押してから描画まで何msかかったか
- JSスレッドがどれくらい停止しているか
- GCは何回発生しているか
- Native APIは何回呼ばれているか
- 10周目と30周目で悪化しているか
- 通知機能のどの処理を有効にすると再現するか
といった情報です。
さらに、操作条件も固定しました。
人間が手で画面を操作すると毎回条件が変わるので、Maestroで同じ操作を30周させます。
端末温度の影響を見るため、冷却した状態と温かい状態でも比較します。
そのうえで、
「通知全部ON」
「通知全部OFF」
「Push Token系だけON」
「イベント同期だけON」
と条件を分けました。
結果として、Push Token取得系だけで症状が再現することが分かりました。
つまり、AIに、
「原因を当ててもらった」のではありません。
「原因を判定できる環境を作ってもらった」のです。
AIはトラブルシューティングでも強い。ただし使い方が違う
ここは誤解したくないところです。
AIはトラブルシューティングに向いていないわけではありません。
むしろ非常に強力です。
ただ、
「エラーを貼って答えを聞く」だけではもったいない。
今回、AIが特に役立ったのは、
- 計測コードを書く
- 計測項目を増やす
- 機能フラグを分割する
- 仮説を整理する
- テストコードを書く
- 計測結果を比較する
- 再発防止ルールをコード化する
といった部分でした。
原因となったコードの修正自体は小さなものでした。
しかし、その原因を特定するためには、大量の計測コードが必要でした。
以前であれば、
「ここまで計測環境を作るなら、とりあえず怪しいところを直してみよう」
となっていたかもしれません。
しかしAIがあることで、観測装置を作るコストが大幅に下がっています。
これはAI駆動開発における、かなり大きな変化だと思います。
AI駆動開発だからこそ、開発者の知識が必要になる
「AIで開発するなら、技術知識はいらなくなる」
という話を聞くことがあります。
簡単なアプリであれば、そういう場面も増えていくと思います。
しかし、業務システムや長期間運用するサービスでは、むしろ逆です。
AIによってコードを書く量と速度が上がるほど、
人間には、より上位の知識が求められる
ようになります。
例えば今回であれば、
- 非同期処理
- Promise
- Native Bridge
- iOSのPush通知
- イベントリスナー
- 再入
- タイムアウト
- キャッシュ
- GC
- パフォーマンス計測
- 実機とSimulatorの違い
といった知識が、原因を切り分ける際に必要になります。
すべてのコードを暗記している必要はありません。
AIに聞けば調べられます。
しかし、
「何が怪しいか」を考えるための地図
は必要です。
知らない概念は、そもそも疑うことができません。
AIに全部任せると「ブラックボックス」が育つ
AI駆動開発でもう一つ注意したいのがこれです。
開発初期は、AIに指示するとどんどん機能が増えていきます。
実装も速い。
テストも通る。
非常に気持ちよく開発できます。
しかし、仕組みを理解しないまま、
次はこれを追加して
次はこれを直して
このエラーも直して
と繰り返していると、
自分自身が理解していないシステムが高速に成長していく
ことがあります。
正常に動いている間は問題ありません。
本当に困るのは、トラブルが起きたときです。
AI自身も、そのシステムを最初から最後まで一貫して設計したわけではありません。
セッションが変わればコンテキストも変わります。
その状態で問題が発生すると、
人間もAIも全体像を把握していない
という状況になり得ます。
これがAI駆動開発で避けたい状態です。
AI駆動開発では「実装」より「検証」に投資する
AI時代の開発では、実装コストが大きく下がります。
だから私は、その分を、
検証側へ移すべき
だと考えています。
具体的には、
- 自動テスト
- 静的解析
- E2Eテスト
- ログ
- メトリクス
- パフォーマンス計測
- Feature Flag
- CI/CD
- コードレビュー
- アーキテクチャルール
- 自動チェック用フック
などです。
今回も、問題を直しただけでは終わらせませんでした。
Native APIを直接呼ばない。
高コスト処理の前に重複ガードを置く。
イベントハンドラから、自分自身を再発火させるAPIを呼ばない。
といったルールを追加しました。
さらに、一部は文章として残すだけではなく、AIがルールに反するコードを書こうとした段階で止める仕組みにしています。
AIは高速にコードを書きます。
だからこそ、
AIに注意してもらうのではなく、間違えられない仕組みにする。
これが重要になります。
トラブルシューティングもAI駆動にする
AI駆動開発というと、
AIにコードを書かせること
ばかりが注目されます。
しかし、実務ではそれだけでは足りません。
本当に重要なのは、
トラブルシューティングそのものもAI駆動にすること
だと思います。
AIに、
原因を当てて
と頼むのではありません。
そうではなく、
この仮説を確認するために何を測ればいい?
仮説AとBを区別するにはどんな実験が必要?
その計測コードを書いて
この2つの結果から何が棄却できる?
再発防止を自動チェックにできない?
と使っていく。
この使い方になると、AIはかなり強力です。
AI時代に必要なのは「コードを書く力」だけではない
今回のトラブルを通して改めて感じたのは、
AI駆動開発でも、技術力は不要にならない
ということです。
ただし、求められる技術力の種類が変わっています。
以前は、
「この処理をどう実装するか」
が中心でした。
これからは、
「このシステムはどこで壊れる可能性があるか」
「どんな情報があれば原因を特定できるか」
「どの仮説を先に潰すべきか」
「同じ問題を二度起こさないために、どこを自動化するか」
といった能力が、より重要になると思います。
AIがコードを書く。
AIがテストを書く。
AIが計測装置を書く。
AIがログを分析する。
そのうえで人間が、
何を見るべきか、何を信じるべきか、どこまで確認すれば安全かを設計する。
これがAI駆動開発における、人間とAIの現実的な役割分担ではないでしょうか。
まとめ
AI駆動開発は、
「AIにすべて任せれば開発者が楽になる方法」
ではありません。
正確には、
AIによって実装・検証・調査の速度を大幅に上げる開発手法
だと思います。
その速度を活かすには、人間側にも、
- システム全体を見る力
- 仮説を立てる力
- 計測する力
- 結果を疑う力
- 再発防止を設計する力
が必要です。
AIにすべてを任せ、動いているから大丈夫と考えていると、トラブルが起きた瞬間にシステム全体がブラックボックスになります。
一方で、開発者が基本的な知識とトラブルシューティングの経験を持ち、そのうえでAIを使えば、
人間だけでは現実的でなかったほど大量の計測・検証を短時間で行える
ようになります。
AI駆動開発で重要なのは、
AIに間違えさせないことではありません。
間違いが起きても、
すぐに検知できる。
原因を切り分けられる。
そして同じ問題を二度起こさない。
そこまで含めて設計することが、これからのAI駆動開発の技術力なのだと思います。