記事のサマリー(TL;DR)
- サイボウズのkintoneチームが、Java製・総行数50万行超のkintoneサーバーサイドのレガシーコードを対象に、AIエージェント(Claude Code、モデルはClaude Opus 5の1Mコンテキスト版)による再実装を5パターン実験しました。
- 「既存の振る舞いを維持して技術的負債を除去してください」のように再実装を全面的にAIに委ねた実験(実験1〜3)は軽微な修正にとどまり、うまくいきませんでした。一方、ゴールが具体的でかつ道筋が見通せるスコープで指示した実験(実験4、5)はうまく再実装でき、実験5の成果は実際にコードベースへマージされました。
- 実験5で有効だった「レガシーコンポーネントをラップしたインターフェイスを切り出し、その内部実装のみをAIに再実装させる」というパターンは、他の十数か所にも横展開され、いずれも結合テストを通過した上でコードベースにマージされています。
詳細
実験の前提条件
記事の筆者はkintoneチームの前田氏です。利用したエージェントはClaude Code、モデルはClaude Opus 5(1Mコンテキスト版)です。AIへの指示や情報は増やすほど求める実装が生成されやすくなると考えられるものの、調整の手間がかかるため、今回は指示を必要最小限にして実験しています。また、試行回数はどの実験でも1回としています。
モジュールの再実装
今回のリライト対象はkintoneのサーバーサイドで、Javaで書かれており総行数は50万行を超えています。サーバーサイド全体をそのままAIに渡すのは現実的ではないため、再実装の対象はkintone内の「モジュール」としました。モジュール単位に絞ることで、製品全体への影響がなくなりリスクが下がるほか、コードレビューや自動テスト(モジュールに対する結合テスト)が可能になり、デグレの有無を迅速にAIへフィードバックできるとしています。
kintoneでは、アプリ設定機能ならアプリ設定モジュール、アプリ機能ならアプリモジュールというように、機能と実装が対応する形でモジュール分けされています。モジュールの内部実装は完全にモジュール内に閉じており、定められた公開インターフェイス以外は他モジュールから依存されない構造です。そのため、公開インターフェイスの振る舞いを変えない限り、内部実装をどれだけ変更してもモジュールの機能的な役割の面では問題ないとしています。
実験1:整った実装+全てAIにお任せ
最初に再実装したのは、約1,000行でクリーンアーキテクチャを参考にユースケースレイヤーやエンティティ、ゲートウェイを導入して役割分担が整理された「モジュールA」です。新しい手法を導入する際によく使われるモジュールとされています。プロンプトは「既存の振る舞いを維持しつつモジュールA内のコードを再実装し、技術的負債を取り除いてください」で、こちらから特に指示せずAIに全て任せました。
結果はほぼ何も変わらず、タイポの修正や、重複した数行のコードが関数に切り出される程度にとどまりました。モジュールAの内部実装はkintoneの中でも整っている部類のため、書き換えるほどの技術的負債が検出されなかった可能性があるとしています。
実験2:レガシーな実装+全てAIにお任せ
モジュールAのような役割分担が導入されておらず、レガシーな部類に入る約3,500行の「モジュールB」でも再実装を試しました。プロンプトは実験1と同じにした結果、実験1と同様に重複コードの切り出しといった軽微な修正にとどまりました。既存の振る舞いを維持しつつ技術的負債をなくすよう指示すると、機械的かつ軽微な修正しか行われない可能性があるとしています。
実験3:レガシーな実装+ゴールを指示
次に試したプロンプトは「既存の振る舞いを維持しつつモジュールBの内部実装を再実装し、内部設計がモジュールAと同等になるようにしてください」です。再実装後のゴールについて最低限の情報を与えています。
結果は、usecase、entity、gatewayといったサブディレクトリが作られ、ファイルがそれらのディレクトリへ移動されるだけで終わりました。ディレクトリ構成は模倣されたものの、ユースケースやエンティティの定義に沿って既存のクラス群が再構成されることはありませんでした。クラス群の再構成まで求めるには、さらなる情報が必要だとしています。
実験4:整った実装をKotlinで書き換え
まったく異なる方向性の再実装も試しています。プロンプトは「モジュールAをKotlinで書き直してください」です。結果として、Javaのクラスがほぼ一対一で対応する形でKotlinのクラスに書き換えられた実装が生成されました。生成されたコードを確認したところ問題なく処理が再現されているようで、今回はコンパイルやテストは通していないものの、実際に書き換える際のベースとして使えそうな印象だったとしています。
モジュールより小さい範囲での再実装
上記の再実装をモジュール単位で行えたのは、モジュールの内部実装がそのモジュール内に閉じていたためです。外に公開する振る舞いと内部実装が分かれていて、内部実装が隔離されていれば、モジュール単位でなくても再実装させられるとしています。
kintoneの一部コードでは、レガシーコンポーネントへの依存を制限するため、レガシーコンポーネントをラップしたインターフェイスを定義し、そのインターフェイスに依存する形に修正しています。そのインターフェイスの実装には元のレガシーコンポーネントが使われており、インターフェイスが外に公開する振る舞いを定義し、レガシーコンポーネントを使った部分が内部実装という関係になっています。この内部実装であれば、AIによる再実装が可能なはずだと考えたとしています。
実験5:レガシーなコンポーネント実装+ゴールを指示
「○○というレガシーコンポーネントを使わずに、インターフェイスを再実装してください」という指示を出したところ、レガシーコンポーネントを使わず振る舞いを新たに書き下した実装が生成されました。この実装はコードレビューを経て、実際にkintoneのコードベースにマージされています。
マージ前には、インターフェイスの振る舞いに対する結合テストが通ることを確認しており、デグレのリスクを抑えながら進められたとしています。このテストは、AIと人手を使って事前に用意した、様々な入力パターンを網羅したものです。テストがあることで、実装を置き換える際の人間の作業は、AIが再実装したコードをレビューするのみになったとしています。
なお、元のレガシーコンポーネントに依存したコードは数十行で、置き換わったコードも数十行程度です。コード量としては小さいものの、AIによる再実装がマージまで到達した点で大きな成果だとしています。現在では新実装に置き換わり、レガシーコンポーネントを使った旧実装は不要になっています。
実験5の横展開
実験5がうまくいったことを受け、同様のパターンを他の十数か所にも横展開し、AIによる再実装を実施しました。これらも全てうまくいき、再実装されたコードはコードベースにマージされています。マージまでの流れは実験5と同様で、インターフェイスと内部実装の分離、インターフェイスに対する振る舞いのテスト用意、AIによる再実装、レビューとテスト通過の確認、という手順です。
実験5のプロンプトは必要最小限だったため、横展開する際のプロンプト調整コストもコンポーネント名を変える程度の小さなものだったとしています。一つ一つの置き換えは数十行程度ですが、レガシーコードを理解して人手で再実装するのは難しいとしています。実際、AIが生成したコードをレビューしていて「これは違うのでは」と感じたものの、人間側のレガシーコードの理解が不足していただけで、よく処理を追うと正解だったケースもあったとしています。数十行とはいえレガシーコードを読み解くのは簡単ではなく、複数箇所での再実装には心理的な抵抗も大きいとした上で、AIに再実装を任せられるようになったことで、人間側の認知的な負担はコードレビュー程度で済んだとしています。
分析とまとめ
5回の実験を通じて、うまくいった実験4、5では、何を生成すればよいかがAIへの指示から機械的に導き出せる状態になっていたとしています。実験4では既存のクラス設計をそのまま再利用することでJavaからKotlinへの書き換えが機械的に可能でした。実験5では内部実装が数十行と小さく、レガシーコードを丹念に追って振る舞いを再現できました。ゴールが具体的(Kotlinで実装する、特定のレガシーコンポーネントに依存しない)であることに加え、ゴールまでの道筋がある程度見通せるスコープで指示を出したことが重要だったと考えられるとしています。
一方、うまくいかなかった実験1、2、3ではゴールまでの道筋を見通すのが難しい状態でした。実験1、2は再実装を完全にAIに委ねており、実験3ではモジュールAを参考にというゴールを示したものの、既存クラスをどう分解するかまで見通すのは難しかったとしています。
筆者は、インターフェイスを切り出したことがレガシーコンポーネントを使った旧実装を捨てることにつながった点を、kintoneにとって良い発見だとしています。kintoneは大規模なプロダクトであり、インターフェイスやモジュールを適切に切ることが開発を進める上で不可欠であるとした上で、これらを単位として必要最小限の指示だけでAIが再実装してくれる可能性があることが分かったとし、今後もkintoneの改善にAIをどう活用できるか考えていきたいと述べています。
なお、記事の注釈では、レガシーコンポーネントは長年の開発の積み重ねで様々な処理を持ち、様々な利用者から依存されているため、実装自体が読みづらく、利用者が多いため変更しづらく、どのような振る舞いを提供しているかも把握しづらいという問題があり、これに対処するためコード上でレガシーコンポーネントに直接依存させない改善を行っている、と補足されています。