この記事はKotlinFest 2025にて発表した内容をベースに執筆しています。
はじめに
多くのAndroidプロジェクトにおいて、長年運用されてきたJavaのレガシーコードは悩みの種です。「Kotlinファースト」の時代において、Javaコードは最新のUIツールキット(Jetpack Composeなど)や最新ライブラリの恩恵を受けにくいだけでなく、Null安全性などの言語レベルでのメリットも享受できません。
「とりあえずKotlinに変換しよう」とAndroid Studioの自動変換機能を使ったことがある方も多いでしょう。しかし、そこで生成されるコードは本当に「Kotlinらしい」コードになっているでしょうか?
本記事では、AI(LLM)を強力なパートナーとして活用し、単なる文法変換ではない、モダンで堅牢な「Kotlinらしいコード」へとリファクタリングする実践的なアプローチを紹介します。
自動変換の限界と「Java風Kotlin」
Android Studioの自動変換機能は非常に優秀ですが、あくまで「文法の変換」に主眼が置かれています。その結果、以下のような「Java風Kotlin」が生成されがちです。
-
Null安全性の欠如: 安易な強制アンラップ(
!!)が多用され、クラッシュのリスクが残る -
古いAPIの残留:
HandlerやViewModelProviderの古い記法など、推奨されないAPIがそのまま残る - 冗長な記述: Kotlinの強力な言語機能(スコープ関数、拡張関数など)が使われず、可読性が低い
これらを手動で修正するのは大変な作業です。そこで、AIへの指示を工夫することで、変換の質を劇的に向上させる手法を見ていきましょう。
AIへの指示を変える:コンテキストとベストプラクティス
AIに単に「JavaをKotlinに変換して」と頼むだけでは、Kotlinらしいには変換されにくいです。高品質なコードを得るためには、**「コンテキスト」と「ベストプラクティス」**を伝える必要があります。
1. 依存関係とコンテキストの共有
AIはプロジェクト全体の構造を知りません。そのため、依存するクラスやメソッドが見えない状態で変換を行うと、コンパイルエラーや誤った推測を含むコードが生成されます。
まず、「対象クラスと依存関係にあるクラスを調査してください」と指示し、必要な情報をAIに与えることが重要です。
改善例:ViewModelのデータ取得
Before (コンテキスト不足): プロパティに直接アクセスしようとする
val gender = viewModel.gender // エラー:実はGetter経由でUseCaseを呼ぶ必要がある
After (コンテキスト把握済み): 正しいGetterメソッドを使用する
val gender = viewModel.getGender()
2. 「Kotlinらしさ」の定義
「Kotlinらしくして」という曖昧な指示ではなく、具体的なベストプラクティスを言語化して伝えます。
-
Null安全:
!!を禁止し、?.や?:を使用する -
スコープ関数:
let,apply,runなどを適切に使い分ける -
条件分岐:
switchやif-elseの代わりにwhenや式としてのifを使う
改善例:可視性の制御
Java時代によくあった「Nullチェックをして、Viewを表示して、テキストをセットする」という冗長さは、スコープ関数で劇的に短くなります。
Before (Java風):
if (item.brandNameJp != null) {
binding.japaneseNameTextView.text = item.brandNameJp
ViewUtil.show(binding.japaneseNameTextView) // 古いユーティリティクラス
} else {
ViewUtil.hide(binding.japaneseNameTextView)
}
After (Kotlinらしいコード):
binding.japaneseNameTextView.let { textView ->
textView.text = item.brandNameJp
textView.isVisible = !item.brandNameJp.isNullOrEmpty()
}
Android KTXの isVisible 拡張プロパティを活用し、独自の ViewUtil も排除できています。
APIの近代化とコードの安全性
単なる言語機能だけでなく、Androidフレームワークの進化に合わせてAPIをアップデートさせる指示も有効です。「パフォーマンスや安全性に影響のある古いAPIは、モダンな代替APIに置き換えて」と指示に含めます。
非同期処理の近代化
Handler を使った古い遅延処理は、メモリリークの温床になりがちです。これをKotlin CoroutinesとLifecycle KTXを使った安全な実装へ誘導します。
Before (Handler):
Handler(Looper.getMainLooper()).postDelayed({
binding?.button?.performClick()
}, 1000)
After (Coroutines):
viewLifecycleOwner.lifecycleScope.launch {
delay(1000)
binding?.button?.performClick()
}
これにより、Fragmentのライフサイクルに合わせて自動的にキャンセルされる安全なコードになります。
ViewModel初期化の近代化
ViewModelの取得も、ViewModelProvider を直接使う古い方法から、Fragment KTXの委譲プロパティを使った簡潔な記法へ変更させます。
Before:
viewModel = ViewModelProvider(this).get(MyViewModel::class.java)
After:
private val viewModel: MyViewModel by viewModels()
品質の担保:AIとの付き合い方
AIは非常に強力ですが、完璧ではありません。生成されたコードには、ビジネスロジックの微妙なニュアンスの違いや、エッジケースの考慮漏れが含まれる可能性があります。
レビューのポイント
AIが生成したコードを受け入れる前に、必ず人の目でレビューを行います。
-
ロジックの等価性:
when式への変換などで条件漏れがないか - Null安全の妥当性: 本当にNullableで良いのか、あるいはNon-Nullであるべき箇所か
- 意図しないAPI変更: 必要な副作用まで削除されていないか
テスト駆動での安全網
リファクタリングにおける最大の恐怖は「デグレ(機能退行)」です。これを防ぐために、以下のフローを推奨します。
- AIにテストを書かせる: リファクタリング前のJavaコードに対して、現在の挙動を保証するユニットテストをAIに作成させる
- リファクタリング: コードをKotlinに変換・改善する
- テスト実行: 同じテストが通ることを確認する
このプロセスを経ることで、安心して大胆なリファクタリングを行うことができます。
まとめ
レガシーコードのリファクタリングは、単に言語を置き換える作業ではありません。AIに適切なコンテキスト、Kotlinの流儀、そしてAPI近代化の指針を与えることで、コードベースを真にモダンでメンテナンスしやすい状態へと生まれ変わらせることができます。
AIは「魔法の杖」ではありませんが、優れた「パートナー」です。最終的な品質責任はエンジニアが持ちつつ、AIの力を借りて開発体験を向上させていきましょう。