AI News HubLIVE
サイト内リライト7 分で読了

15万人が使うアプリのフロントエンドを引き継いだ – ほとんどがAI生成コード

社内AIアプリのフロントエンドを引き継いだ開発者が、コード品質の問題に直面しながらも、段階的な改善を進める物語。

ソースHacker News AI著者: koolcodez

少し前、ロンドンで旧友とコーヒーを飲んでいた。彼とはかつて、オプション取引のプロジェクトで一緒に仕事をした。複雑なドメインにもかかわらず、古くて肥大化したRedux Sagaを真の良いコードベースに書き換えたプロジェクトだ。

彼は今でも順調だと話し、私は今のチームで感じている困難を打ち明けた。コード品質やAIの粗製滥造について愚痴っている途中で、彼はこう尋ねた。

今、何人ユーザーがいる?

15万人。

我々はまだ数人のトレーダーしかいない。その数字なら、君のチームは間違っていないと言える。

もちろん文脈は重要だ。我々は今や異なる分野にいる。しかし、それでも私は線引きをどこにすべきか考えさせられた。いつ変更を受け入れ、いつ拒否するか。ビジネスにとって何が重要なのか。

正直な答えは、私にもわからない。しかし、この会話は時折思い出される。だから、現在のプロジェクトの状況と、私がどう対処しているかを共有したい。

出発点

約6ヶ月前、私は社内のAIアプリを構築するチームに加わった。この製品はビジネスの基盤であり、当然ながら経営陣から大きな注目を浴びている。

コードはお世辞にも良いとは言えない。意地悪で言っているわけではない。私もその一部に貢献したことを認める。小さなチームが素早く構築し、AIに大きく依存し、片付けに静かな週を一度も取れなかった結果だ。常に次の機能が要求され、人々が待っている。

このプロジェクトは2025年初頭に開始され、かなりAI生成コードに頼っていた。バックエンドエンジニアが機能をエンドツーエンドで実装し、フロントエンドにも手を出すことが多かったと聞いた。AIに関する興味深い言葉がある:

AI生成コードは、自分の専門分野に関するものでない限り、本当に素晴らしく見える。

とにかく、私が見つけたのはだいたい以下の通りだ。

ウェブアプリはTypeScript Reactアプリで、まだreact-app-rewiredを使っていた。知らない人のために言うと、それは中世にいるようなものだ。ビルドに約10分かかった。

リンティングはランダムなテンプレートから設定され、GitワークフローやCI/CDパイプラインに統合されていなかった。つまり、リンティングは事実上機能しておらず、エディタのリンティングも設定されていなかった。

少数の単体テスト。動作するエンドツーエンドのテストスイートはなし。

TypeScriptだが、多くの場所でanyを使用し、手書きのクライアント型は実際のレスポンスからずれていた。

サーバー状態管理なし。データを取得する各コンポーネントが独自にローディングとエラーハンドリングを実装していた。

常に40以上のプルリクエストがオープン。機能に数日間取り組むと、数百のコミット遅れになり、コンフリクト解決が常態化していた。

独自のイベントバスがあり、任意のコンポーネントがイベントを発火し、他の任意のコンポーネントがリッスンできる(Reduxの再実装?!)。もちろんイベント名は各登録でハードコードされ、「my-event」が「mY-event」にずれていく。

localStorageをキャッシュすべきでないものに使用しており、base64のアバター画像などが増大していた。ある時点でlocalStorageのサイズ制限に達し、プロダクションエラーが発生した。

巨大なコンテキストがすべてを保持。成果物、アップロードファイル、プリセット、統合、すべて同じ場所。

ファイルアップロード方法が4種類。それぞれ微妙に異なる(AI生成コードの匂いがする?笑)。

コアコンポーネントは数千行で、ほとんどがコールバック、エフェクト、状態、フラグで、最後に薄いUIレイヤーがある。

デザインシステムは貧弱。カスタムTailwindトークンがあちこちに散らばり、サイズ、色、スペーシングに一貫性がない。

時々、どうしてこれが動いているのか驚く。

これに対する明白な対応は何もしないことだ。動いているし、使われている。誰も根本的な変更を加えてリグレッションの責任を取りたくない。

冗談だ。どうでもいいなんて言えない。地獄のようなコードベースで毎日を過ごすことはできない。この分野は最先端で、プロジェクトは刺激的だが、そのリポジトリは楽しくない。仕事を楽しみ、素早く自信を持ってリリースしたい。

自分の働き方について一つ。私は仕事でAI生成コードに頼るのは無責任だと思う。チームの誰も理解できないコードを、これほど多くの人が依存する製品に出荷するのは、いつか負ける賭けであり、他の誰かが代償を払う。私はAIを常に使うが、ツールとして、ルールを設けて、やり取りを繰り返し、すべてを読んでから取り込む。

理解していないコードを、これほど多くの人が依存するものに出荷することはしない。頭の中に保持できる必要がある。それは原理主義ではなく、物事を悪化させずに素早く進む唯一の方法だ。

冒険の始まり

最初にやったことはViteへの移行だ。申し訳ないWebpack、あなたの時代は終わった。約1週間半かかり、ビルド時間を約10分から2分弱に短縮し、モジュールホットリロードによりclassNameの変更で全ページリロードが発生しなくなった。デモでは誰も気づかないが、6ヶ月後にはチーム全体に時間の節約をもたらした。

すぐに目に見える成果を出していないというプレッシャーは感じたが、幸いこの経営陣とは以前に仕事をしており、信頼されている立場だったので、多くの自由を持っていた(そして今も持っている)。

次に認証。ロジックがいくつかの無関係なコンポーネントに散らばっており、誰もその近くに触れたがらない典型だった。一箇所にまとめ、アプリ全体を一つのゲートの後ろに置き、アクセス、トークン、権限を扱っていた古いコードの山を削除した。過程でいくつかのリグレッションがあったが、都度修正し、その後は安定している。

次に型。バックエンドはすでにOpenAPIスキーマを公開していた(ありがたい)。そこで、手書きでクライアント型を書いて期待する代わりに、スキーマから型を生成し始めた。コツは、大規模な書き換えで全てを止めるのではなく、一度に数エンドポイントずつ段階的に採用することだ。この部分はそれ自体で記事に値するので、ここでは割愛する。

そして小さなことの数々。リンティングは有効になったが、CI/CDで有効にする前にクリアすべきバックログがまだある。テストを増やし、さらに重要なこととして、新しいコードの見た目に関する合意を得て、積み上がるのを防ぐ。プルリクエストを小さくし、コンポーネントからロジックをフックに移し、anyを禁止し、理由なきuseEffectを禁止する。目的は、残りをゆっくり修正しながら、乱雑さが広がるのを止めることだ。

良い人材が何人か入ってきて、同じ原則を共有してくれるようになってから、状況は好転し始めた。一人ではなく、支援されていると、状況は指数関数的に簡単になる。

モバイル地獄

私が入社して間もなく、新しい要件が降りてきた。モバイルアプリが必要で、すぐに必要だった(CEOが、大統領に会う途中で会社のAIを携帯で使えるというアイデアに興奮していた)。

Capacitorを使うことにした。これは素晴らしい、不満はない。しかし、プロダクションに持っていくために、いくつかの最高級のハックを導入せざるを得ず、思い出すたびに苦笑いする。

それはデモから始まった。何かを素早く見せる必要があり、モバイルではブラウザのfetchをオーバーライドしていくつかのモックを返すようにした。少なくともUIが機能することは見せられた。後でクリーンアップするつもりだった。

結局やらなかった。コードが積み重なり、大きくなりすぎた。今ではモバイルアプリからの全てのHTTPコールは独自のfetchバージョンでインターセプトされ、RPCコールに変換される。会社のファイアウォールを通る唯一の経路が一つのエンドポイントで、すべてがそこを通らなければならないからだ。

そしてストリーミング、チャットが依存する部分は、最もクレイジーな部分だ。モバイルでは、SSEはSwiftレイヤーでWebSocketに変換され、その一つのエンドポイント(ソケットのみサポート)を通り、ステートレスゲートウェイノードで再びSSEに変換され、ようやく最終インスタンスに到達する。旅を生き抜くために、あるものを別のものに変換し、また戻す。

その上に、不安定な接続に対する再接続ロジックやメッセージ欠落処理を追加すると、どれほど複雑になるか想像してほしい。楽しくない。

この儀式を避けるため、主にソケットに移行する具体的な計画を立てている。また、複数のSSE接続を同時に生かすのではなく、単一のストリームを持つためでもある(設計の問題だが、どうしようもない)。オーケストレーターとしてreact-socketを使う予定だ。オープンソースで、ストリームを扱っているなら見てみてほしい。

まだ道半ば

正直に言いたい。多くのことを始めたが、ほとんど完了しておらず、半分も来ていない。以下は進行中の作業であり、勝利宣言ではない。

デザインシステムはその一つ。並行してCSSファイルを作り、Tailwind 4に移行し、既存のスタイルシートの良いところを継承しつつ追加する。目標はコンポーネントを一つずつ移行することで、バッジ、トグル、モーダルスキンなど影響の少ないものから始めている。

ドキュメントインターフェースとしてStorybookを使い、コンポーネントごとに完全な説明とバリアントを提供している。まだ多くのコンポーネントが残っている。

もう一つ始めたばかりのことは、コア部分のリファクタリングだ。主要なドライバーであるAIチャット。

変更を加えると必ずリグレッションが発生する場所に来ている。慎重に扱わなければならないというだけでなく、ファイルに触れること自体がリスクだ。一箇所を小さく変更すると、別の場所で微妙な問題が発生し、2週間後にコードがプロダクションに出た後で気づくかもしれない。

最も明確な症状は、新しいチャットを開始すると、以前のチャットがまだ実行中の場合に以前の会話が漏れるバグだった。新しいチャットを開くと、前のチャットの一部が表示される。厄介だ。ストアにトップレベルのchatIdキーがあり、他のすべてがその下にネストされていれば、容易に防げたはずだ。

では計画は? はっきりとはわからない。まずは動作を変えずに小さな部分に分解し、その後何をどの順序で取り組むか決める。正直な要約:まだ多くの作業が待っている。

最後に

この記事はRedditの愚痴のように読めると気づいた。実際そうかもしれない。しかし、二つの理由で書き留めたかった。

一つはロンドンの会話だ。何度も思い返す。15万人のユーザーは現実的な答えだ。コードの苦痛を減らすわけではないが、問題を「これは許容できるか」から「修正のコストはいくらで、チームはそれを払う準備があるか」へと変える。我々は払っている。ゆっくりと、端々で、リリースを続けながら。ほとんどの日、それが正しい速度だと感じる。

二つ目は、これから私のようなチームに参加しようとしている人、あるいはすでにそのようなチームにいて、自分だけではないかと疑問に思っている人のためだ。あなただけではない。今やこうしたコードベースはたくさんある。出口は地味だ:一度に一つの境界、抽出された一つのフック、スキーマから生成された一つの型、Storybookに移行された一つのコンポーネント。どれもロードマップを印象的に見せない。しかし、すべて積み重なる。

そして、AIが仕事を奪うのではと心配してここに来たなら、業界はそういう方向ではないと思う。AIはより多くのコードを生み出している。誰かがそれを頭の中に保持できなければならない。