toriR blog

生成AIで2000行のJavaScriptをリファクタリングするも、結局1から作り直すことにした理由

2000行のJavaScriptを生成AIでリファクタリングしようとした結果、AIの限界と問題に直面。最終的に1から作り直すことにしたその理由とは?実際のプロセスと学んだ教訓を解説します。
refactering

はじめに

〜生成AIによるリファクタリングの可能性と限界〜

最近、生成AI(人工知能)がソフトウェア開発に役立つツールとして注目されています。特に、リファクタリングという作業において、AIはコードを自動で改善したり、整理したりする手助けをしてくれると期待されています。しかし、AIによるリファクタリングには限界もあるというのが現実です。

まず、リファクタリングとは、コードをもっと使いやすく、わかりやすく改善する作業のことです。コードの動きや機能自体は変えずに、ただコードの構造を見直して整理したり、冗長な部分を削除したりします。これを行うことで、将来的にコードが壊れにくくなり、開発がスムーズに進みやすくなります。

AIを使う最大のメリットは、作業を速く進めることや、人間が見逃しがちな部分を見つけて修正することです。しかし、AIにも難しいことがあり、特に大きなコードになると、AIがうまく理解できなかったり、正しく修正できないことがあります。

この記事では、2000行のJavaScriptコードをAIを使ってリファクタリングしようとした結果、AIが得意な部分と、うまくいかない部分を紹介します。AIを上手に使うためには、適切なツールを選ぶことや、AIだけに頼らず人間の力も活用することが大切だと分かりました。

AIは「すべての問題を解決する魔法の道具」ではありません。AIを使いこなすためには、どのように役立てていけば良いかを知ることが重要です。この記事では、その方法について具体的に説明します。

2000行のJavaScriptをAIと一緒に分割する準備手順

背景と課題

  • 生成AIが作った2000行の main.jsはモノリシックで可読性が低い
  • 保守が難しく、関数の再利用も困難
  • AIを使って効率的に分割したいが、壊れないかが不安

前提環境

  • フロントエンドは Vite(ES Modulesベース)
  • モジュールは import/export を用いた構成にする
  • Node.js + npm 利用可能

使用ツールとその役割

ツール 目的
Vite ビルドと実行、モジュール解決
ESLint 文法・構文のチェック
madge モジュール依存関係の可視化
ChatGPT等 分割・再構成のアシスタント

人間が決めること

  • 機能ごとの責任分担(DOM, API, 状態, イベント, ユーティリティなど)
  • モジュール構成案
  • モジュールの粒度と命名規則
  • 最小の「main.js」(エントリーポイント)の設計

AIに任せること

  • 指定したモジュールに該当する関数群の抽出
  • export / import の整合性の補完
  • ESLintで通るコードスタイルへの変換
  • 必要に応じた関数のrename提案

madgeでの可視化と比較

  • リファクタリング前に madge --image before.svg src/
  • リファクタリング後に madge --image after.svg src/
  • 循環依存や集中度の変化を比較

ESLintでの一貫性チェック

  • 未使用import, export漏れを検出
  • import順序、重複、グローバル変数依存をチェック

生成AIに与えるルール例(Cursor用)

// setup/prompt-rules.md
# モジュール分割ルール
- ES Modules構文(import/export)を使用すること
- 各モジュールは単一責任を原則とする(API, DOM, STATE, EVENTS, UTILS)
- 名前付きエクスポートを原則とする
- main.js は init 関数から始まるentry pointとする
- モジュール名とファイル名を一致させる(例: api.js → export関数名: fetchData)
- import順序は: Node標準 > 外部ライブラリ > 相対パス の順に並べる
- コメントはJSDoc形式で記述すること

実際に行ったリファクタリング

最初に、2000行のコードをリファクタリングしようとしましたが、思ったように進みませんでした。ESLintが最初から通らない状況で、修正を繰り返しても編集が収束せず、時間が無駄になってしまうのを感じました。特に、ESLintのエラーが解消されるまでに予想以上の時間がかかり、これは先が見えない作業だと感じたため、作業方針を変更しました。

そこで、問題の核心に迫るために、まずなぜうまくいかないのかを理解することに切り替え、再度生成AIを使ってその分析を行いました。この過程で、生成AIの限界を実際に体験し、どのような状況でAIがリファクタリングをうまく行えないかが見えてきました。

修正方針と実行

修正方針を立て、最も簡単で独立した関数を外部に切り出し、最初に初期化関数init()や変数定義部を整理しようとしました。これらの変更は理論的には非常に簡単で、モジュールの分割が進むはずでしたが、ここでも問題が発生しました。ESLintが依然として通らず、修正が収束しないという状態が続きました。

具体的な問題点:

  1. 依存関係が多すぎる

    初期化関数や変数定義部を切り出しても、コード全体で使われている依存関係が複雑すぎて、分割後に動作が崩れてしまう部分がありました。AIが提案したコードの修正では、依存関係が適切に解決されていないため、正しく動作しませんでした。

  2. ESLintのエラーが解消されない

    コードのモジュール化を進めたものの、ESLintによって強制された命名規則やimport/exportの整合性が守られていなかったため、エラーが発生し続けました。AIはそのエラーを自動的に修正することができなかったのです。

  3. AIによるリファクタリングが無駄に時間をかけた

    生成AIにリファクタリングを任せたものの、動作が正しいかどうかの確認が行われなかったため、AIが最適な解決策を提案するのに時間がかかり、最終的にはエラーが続いてしまいました。

生成AIによるリファクタリングが苦手な理由

  1. 大規模コードの理解限界
    • トークン制限:生成AIは、1回の推論で処理できるトークン数(文字数)の制限があるため、大規模なコードベース(2000行以上)を一度に理解することができない。コードの一部しか見られないため、全体の文脈を把握しづらい。
    • 依存関係の把握:コード全体の依存関係を正確に理解するのが難しい。特に複雑なモジュール間の相互作用や変数のスコープ、関数の相互依存を正しく把握するのは容易ではない。
  2. 破壊的な変更
    • AIは通常、「動作するコード」を生成するが、リファクタリングの際には既存の動作を崩さないようにする必要がある。AIはその区別をうまく行えないため、動作を壊すような変更を加えてしまうことが多い。
    • 例えば、関数の署名や引数を変更しても、呼び出し元のコードがそれに適応できるか確認することができないため、破壊的な変更が発生する。
  3. コンテキストと意図の理解不足
    • コードの意図設計思想を深く理解することができない。AIはコードの「動作」を模倣することはできても、その背後にあるビジネスロジックや目的を把握するのは難しい。
    • リファクタリングは「より読みやすく」「保守性を高める」ことが目的であり、AIはそのような抽象的な目的を理解するのが苦手。
  4. 部分的な最適化
    • AIはコードの一部分に最適な変更を加えることが得意だが、全体としてのバランスを取るのが苦手。リファクタリングでは、全体のパフォーマンスや可読性を保ちながら変更を加える必要があるが、部分的に最適化を行って全体を破壊してしまうことがある。
  5. コードスタイルの統一
    • AIは、個々のスタイルガイドやチーム固有のコーディング規約を正確に反映するのが難しい。リファクタリングにおいては、コードスタイルを統一し、規約に則った書き方にすることが求められるが、AIは一貫性を保つのが難しい。
    • ESLintやPrettierでスタイルを自動的に整えることはできても、AIはより深いスタイルの一貫性を保証することが難しい。
  6. エラー検出とテストの不足
    • AIはコードを生成したりリファクタリングしたりする際に、実行やテストを実際には行わないため、新たに発生する可能性のあるエラーや副作用を把握できない。
    • コードが動作するかどうかの確認を自動テストで事前に行わないと、リファクタリング後に不具合が発生するリスクが高い。
  7. 非同期処理や複雑な状態管理の問題
    • 特に非同期処理(Promisesやasync/await)や複雑な状態管理(ReduxやVuex)のコードをリファクタリングする際、AIが全てのタイミングや状態遷移を把握し、適切な変更を行うのは難しい。非同期な挙動が絡むコードの理解は非常に高度で、エラーが発生しやすい。
  8. コード分割とモジュール化の難しさ
    • 生成AIは、コードを小さな部品に分割し、適切にモジュール化する能力が限られている。リファクタリングにおいては、関数やクラスの適切な分割や、不要なコードの削除を行う必要があるが、AIがそのバランスを取るのは難しい。
  9. 動的型付け言語の複雑さ
    • JavaScriptやPythonのような動的型付け言語では、型の推測が困難であり、リファクタリング後に型の不整合が生じる可能性が高い。AIは型情報が欠如している状態でコードをリファクタリングするため、予期しないエラーを引き起こすことがある。
  10. ステークホルダー間の合意形成
  • リファクタリングには、チーム内での合意形成や、ビジネスニーズに基づく判断が必要だが、AIはそのような組織的な調整や意図の理解を行うことができない。人間による判断を補完する形で動くAIは、個別の要求に合わせたリファクタリングが難しい。

  • 以上の理由により、リファクタリングは諦めて1から作り直すことにしました。

結論

生成AIがリファクタリングにおいて苦手な理由は、コードの全体的な理解意図の保持、さらに動作確認と最適化のバランスを取る難しさにあります。特に、コードベースが大きくなると、AIはすべての依存関係や変更が及ぼす影響を適切に管理できず、破壊的な修正をしてしまうことが多くなるのです。

これらのポイントを踏まえて、AIを利用する際には部分的な最適化小規模なリファクタリングにとどめ、大規模なリファクタリングには人間の判断を必要とするという姿勢が重要です。

End-to-Endで開発をする場合でももっと生成AIを活用できるはずです。欠点を知った上で仕掛け作りで生産性を向上させる方法です。これも充分生成AIと壁打ちすることで光明が見えてきました。これはまた別の機会で。

Article Info

created: 2025-04-20 08:12:34
modified: 2025-05-06 17:54:55
views: 11
keywords: javascript リファクタリング 生成AI コード分割 モジュール化 依存関係 AI活用 Cursor 保守性 コードの最適化 自動化 テスト

Articles