Upgrade to Pro — share decks privately, control downloads, hide ads and more …

AIとの協業で実現!レガシーコードをKotlinらしく生まれ変わらせる実践ガイド

 AIとの協業で実現!レガシーコードをKotlinらしく生まれ変わらせる実践ガイド

2025/11/1にKotlin Fest 2025で発表した登壇資料です。
https://2025.kotlinfest.dev/timetable/1719042600_a/

株式会社ZOZO
ZOZOTOWN開発本部
ZOZOTOWN開発2部 Androidブロック
にしみー (@nishimy432) / 清水(西上) 知里

#KotlinFest

Avatar for ZOZO Developers

ZOZO Developers PRO

November 01, 2025
Tweet

More Decks by ZOZO Developers

Other Decks in Technology

Transcript

  1. © ZOZO, Inc. 3 アジェンダ • レガシーコードの課題 • Kotlinの利点 •

    リファクタリングの課題 • AIによるコードの置き換え • プロンプトの改善とコードの変化 • AIによる品質の保持
  2. © ZOZO, Inc. レガシーコードが引き起こす具体的な問題 Kotlinファーストの恩恵を受けられないJavaコードベース 最新技術の利用不可 Googleの「Kotlinファースト」方針により、主要な新機能が利用できない (例) Jetpack Compose、Coroutinesなど

    Null安全性の欠如 言語レベルでのNull安全性がなく、コードの信頼性が開発者の注意力に依存し、NullPointerExceptionのリ スクが常につきまとう 開発効率の低下 コードの複雑さが原因で、新機能の追加やバグ修正に想定以上の時間がかかり、ビジネスの要求ス ピードに応えられない 6
  3. © ZOZO, Inc. 10 Googleの「Kotlinファースト」 GoogleはAndroid開発においてKotlinを第一に推奨 Google は、Google I/O 2019

    にて Android 開発での Kotlin ファーストを強化すると発表し、 その取り組みを続けてきました。 Kotlin は、表現力に優れた簡潔なプログラミング言語であり、 よくあるコードエラーを減らします。 また、既存のアプリに統合するのも簡単です。 これから Android アプリを作成するという場合は、Kotlin を採用して、 そのトップクラスの機能を活用することをおすすめします。 Android’s Kotlin-first approach: https://developer.android.com/kotlin/first Jetpack Compose などの多くの最新 API は Kotlin専用
  4. © ZOZO, Inc. 自動変換の問題点 変換後のコードは「Javaの文法で書かれたKotlin」になりがち 冗長な表現の残存 Java特有の冗長な表現がそのまま残る(例: get(), set() の呼び出し)

    言語機能の不活用 Kotlinの強力な言語機能(拡張関数, スコープ関数など)を活かせない 低可読性の維持 結果として、可読性が低いままになってしまう 12
  5. © ZOZO, Inc. 目指すべき「Kotlinらしい」コードとは? 私たちが目指すコードの3つの柱 1. 簡潔性と可読性 データクラス、型推論、名前付き引数を活用し、意図が明確なコード 2. 安全性

    Nullable/Non-nullを厳格に区別し、NullPointerExceptionのリスクを排除したコード 3. 表現力 (イディオム) 高階関数、スコープ関数、拡張関数を使いこなし、宣言的に記述されたコード 13
  6. © ZOZO, Inc. Kotlinらしさの欠如 言語機能を活かしきれず、Javaライクな記述が残存 Java的なNullチェック KotlinのNull安全機能(?. や ?:)を適切に使えていない スコープ関数の誤用

    let / apply / run のセマンティクスをせず、不適切なコンテキストで使用する 冗長な記述 Kotlin KTX(拡張プロパティ)の isVisible を使わず、Javaライクな View.VISIBLE を使用する 18
  7. © ZOZO, Inc. 安全性・正確性の問題 Null安全の破綻や、コンテキスト不足による誤り 強制アンラップ(!!) AIがNull安全を諦め、安易に !! を使用しNullPointerExceptionのリスクを残す 不正確なNullability

    Non-nullで良い箇所をnullableにする(またはその逆) コンテキスト不足 プロジェクト固有の関数を取得し切れず、存在しない関数を呼び出す 19
  8. © ZOZO, Inc. 改善アプローチ AIの出力を「Kotlinらしい」コードに導くためのアプローチ 1. 依存関係の調査 AIが知らないコンテキスト(プロジェクト固有のクラス)を事前に調べさせる 2. ベストプラクティスの明示

    「Kotlinらしい」の定義を具体的に言語化し、守るべきルールとして指示する 3. モダンAPIへの置換指示 古いAPIを避け、モダンな代替APIへ置き換えるよう明示的に指示する 22
  9. © ZOZO, Inc. 改善1: コンテキスト不足 課題 • AIがプロジェクト固有の依存関係や他クラスで定義された変数や関数のアク セス修飾子を把握していない •

    結果として、コンパイルエラーになるコードを生成する 対策プロンプト • 変換前に「調査」を指示 • 先にAIにコンテキストを把握させる 対象クラスと依存関係にあるクラスを調査してください。 23
  10. © ZOZO, Inc. コード変化 (1): コンテキスト不足 シンプルプロンプト // プロパティに直接アクセスしようとする val

    gender = viewModel.gender プロンプト改善後 // publicなgetメソッドを正しく使用 val gender = viewModel.getGender() 24 ViewModel側のコード fun getGender(): Gender { return getGenderUseCase.execute().getGender() }
  11. © ZOZO, Inc. コード変化 (1): コンテキスト不足 25 ViewModel側のコード fun getGender():

    Gender { return getGenderUseCase.execute().getGender() } シンプルプロンプト // プロパティに直接アクセスしようとする val gender = viewModel.gender プロンプト改善後 // publicなgetメソッドを正しく使用 val gender = viewModel.getGender() Gender は UseCaseから取得する 値のため、gender に直接アクセス 不可
  12. © ZOZO, Inc. コード変化 (1): コンテキスト不足 シンプルプロンプト // プロパティに直接アクセスしようとする val

    gender = viewModel.gender プロンプト改善後 // publicなgetメソッドを正しく使用 val gender = viewModel.getGender() 26 ViewModel側のコード fun getGender(): Gender { return getGenderUseCase.execute().getGender() }
  13. © ZOZO, Inc. 改善2: Kotlinらしさの欠如 課題 「Kotlinらしい」の定義が曖昧なため、AIがJavaライクなコードを生成する • 強制アンラップ(!!)を多用 •

    Java時代のサードパーティ製ライブラリをそのまま使用 対策プロンプト • ベストプラクティスを具体的に列挙 ベストプラクティスの適用: 以下のKotlinの機能を積極的に活用し、 コードの可読性、安全性、簡潔性を最大化してください。 27
  14. © ZOZO, Inc. 改善2: Kotlinらしさの欠如 実際にプロンプトに含めた機能 • データクラス (data class)

    • スコープ関数 ◦ let, apply, run, also, withの適切な利用 • null安全性の徹底 ◦ 安全呼び出し演算子(?.)の使用 ◦ エルビス演算子(?:)の使用 ◦ 強制アンラップ(!!)の排除 • 高階関数とラムダ式 (特にコレクション操作) • when式 • sealedクラスやsealedインターフェース • 拡張関数 28
  15. © ZOZO, Inc. コード変化 (2-1): Kotlinらしさ シンプルプロンプト private fun removeVisibility(id:

    Long): Brand { val brand = mAdapter?.delete(id) checkItemSize() return brand!! } プロンプト改善後 private fun removeVisibility(id: Long): Brand? { return adapter?.delete(id)?.also { updateItemSize() } } 29 呼び出し側 removeVisibility(id)?.let { brand -> // 処理 }
  16. © ZOZO, Inc. コード変化 (2-1): Kotlinらしさ シンプルプロンプト private fun removeVisibility(id:

    Long): Brand { val brand = mAdapter?.delete(id) checkItemSize() return brand!! } 30 強制アンラップ プロンプト改善後 private fun removeVisibility(id: Long): Brand? { return adapter?.delete(id)?.also { updateItemSize() } } 呼び出し側 removeVisibility(id)?.let { brand -> // 処理 }
  17. © ZOZO, Inc. コード変化 (2-1): Kotlinらしさ シンプルプロンプト private fun removeVisibility(id:

    Long): Brand { val brand = mAdapter?.delete(id) checkItemSize() return brand!! } 31 プロンプト改善後 private fun removeVisibility(id: Long): Brand? { val brand = adapter?.delete(id) updateItemSize() return brand } 呼び出し側 removeVisibility(id)?.let { brand -> // 処理 }
  18. © ZOZO, Inc. コード変化 (2-2): Kotlinらしさ シンプルプロンプト Optional.ofNullable(item.brandNameJp) .ifPresentOrElse( {

    s -> binding.japaneseNameTextView.text = s ViewUtil.show(binding.japaneseNameTextView) }, { ViewUtil.hideAway(binding.japaneseNameTextView) } ) プロンプト改善後 binding.japaneseNameTextView.apply { item.brandNameJp?.let { name -> text = name isVisible = true } ?: run { isVisible = false } } 32
  19. © ZOZO, Inc. コード変化 (2-2): Kotlinらしさ シンプルプロンプト Optional.ofNullable(item.brandNameJp) .ifPresentOrElse( {

    s -> binding.japaneseNameTextView.text = s ViewUtil.show(binding.japaneseNameTextView) }, { ViewUtil.hideAway(binding.japaneseNameTextView) } ) プロンプト改善後 binding.japaneseNameTextView.apply { item.brandNameJp?.let { name -> text = name isVisible = true } ?: run { isVisible = false } } 33 サードパーティ製 ライブラリの利用 独自ユーティリティ クラスの利用 ViewUtilの処理 public static void show(@Nullable View view) { if (view == null) { return; } view.setVisibility(VISIBLE); }
  20. © ZOZO, Inc. コード変化 (2-2): Kotlinらしさ シンプルプロンプト Optional.ofNullable(item.brandNameJp) .ifPresentOrElse( {

    s -> binding.japaneseNameTextView.text = s ViewUtil.show(binding.japaneseNameTextView) }, { ViewUtil.hideAway(binding.japaneseNameTextView) } ) プロンプト改善後 binding.japaneseNameTextView.apply { item.brandNameJp?.let { name -> text = name isVisible = true } ?: run { isVisible = false } } 34
  21. © ZOZO, Inc. コード変化 (2-3): Kotlinらしさ シンプルプロンプト val stateChanged =

    Optional.of(mode) .filter { shouldShowForAllFavorite(it) } .map { addForAllFavoriteColumn() } .orElseGet { removeForAllFavoriteColumn() } プロンプト改善後 val stateChanged = when { shouldShowForAllFavorite(mode) -> addForAllFavoriteColumn() else -> removeForAllFavoriteColumn() } 35
  22. © ZOZO, Inc. コード変化 (2-3): Kotlinらしさ シンプルプロンプト val stateChanged =

    Optional.of(mode) .filter { shouldShowForAllFavorite(it) } .map { addForAllFavoriteColumn() } .orElseGet { removeForAllFavoriteColumn() } プロンプト改善後 val stateChanged = when { shouldShowForAllFavorite(mode) -> addForAllFavoriteColumn() else -> removeForAllFavoriteColumn() } 36
  23. © ZOZO, Inc. コード変化 (2-4): Kotlinらしさ シンプルプロンプト fun createInstance(args: Args?):

    SearchBrandItemFragment { val bundle = Bundle() bundle.putSerializable(SearchBrandItemViewModel.BUNDLE_KEY_ARGS, args) val fragment = SearchFavBrandItemFragment() fragment.arguments = bundle return fragment } プロンプト改善後 fun createInstance(args: Args?): SearchFavBrandItemFragment { return SearchBrandItemFragment().apply { arguments = Bundle().apply { putSerializable(SearchBrandItemViewModel.BUNDLE_KEY_ARGS, args) } } } 37
  24. © ZOZO, Inc. コード変化 (2-4): Kotlinらしさ シンプルプロンプト fun createInstance(args: Args?):

    SearchBrandItemFragment { val bundle = Bundle() bundle.putSerializable(SearchBrandItemViewModel.BUNDLE_KEY_ARGS, args) val fragment = SearchFavBrandItemFragment() fragment.arguments = bundle return fragment } プロンプト改善後 fun createInstance(args: Args?): SearchFavBrandItemFragment { return SearchBrandItemFragment().apply { arguments = Bundle().apply { putSerializable(SearchBrandItemViewModel.BUNDLE_KEY_ARGS, args) } } } 38
  25. © ZOZO, Inc. 改善3: 古いAPIの利用 課題 AIが古いAPIとモダンなAPIの区別をつけられない Handler, 古いViewModelProvider, 非推奨APIが残存する

    対策プロンプト 「近代化」を明示的に指示 原則として近代化: パフォーマンスに影響のある古いAPIは、 原則としてモダンな代替APIに置き換えてください。 39
  26. © ZOZO, Inc. コード変化 (3): モダンAPI シンプルプロンプト Handler(Looper.getMainLooper()) .postDelayed({ binding?.editFavBrand?.performClick()

    }, DELAY_AFTER_DELETE) プロンプト改善後 viewLifecycleOwner.lifecycleScope.launch { delay(DELAY_AFTER_DELETE) binding?.editFavBrand?.performClick() } 40
  27. © ZOZO, Inc. コード変化 (3): モダンAPI シンプルプロンプト Handler(Looper.getMainLooper()) .postDelayed({ binding?.editFavBrand?.performClick()

    }, DELAY_AFTER_DELETE) プロンプト改善後 viewLifecycleOwner.lifecycleScope.launch { delay(DELAY_AFTER_DELETE) binding?.editFavBrand?.performClick() } 41
  28. © ZOZO, Inc. コード変化 (3): モダンAPI シンプルプロンプト viewModel = ViewModelProvider(this)[SearchFavBrandItemViewModel::class.java]

    プロンプト改善後 private val viewModel: SearchFavBrandItemViewModel by viewModels() 42 ViewModelの初期化方法
  29. © ZOZO, Inc. 46 レビュー項目 1: 構文・記法を改善 Javaライクな構文が、Kotlinらしい宣言的な構文に改善されているか 条件式の書き方を修正した箇所 例:

    switch 文 → when 式 ループ処理の改善 例: forループ → forEach, map, filter などの高階関数 式ベースへの変換 例: if 文による変数代入 → if 式による直接代入
  30. © ZOZO, Inc. 47 レビュー項目 2: null安全性の強化 プロンプトで指示した「Null安全」が徹底されているか Platform Typesの排除

    Javaからの戻り値(例: String!)に、? か !! ではない、適切な型を明示したか nullabilityの明示化 不必要にnullableだった変数をnon-nullにした箇所 強制アンラップ(!!)の削除 プロンプト指示の通り、!! が完全に排除されているか
  31. © ZOZO, Inc. 48 レビュー項目 3: ベストプラクティスを適用 スコープ関数やデータクラスなど、Kotlinの機能が適切に使われているか コンストラクタの改善 例:

    apply の使用 関数型プログラミング 高階関数、ラムダ、メソッド参照等の活用 スコープ関数の活用 let, apply, run の適切な使い分け
  32. © ZOZO, Inc. 49 レビュー項目 4: APIの変更 AIがどのAPIを「古い」と判断し、「新しい」ものに置き換えたか 古いAPIから新しいAPIに置き換えた箇所 例:

    Handler.postDelayed → lifecycleScope.launch { delay(DELAY_AFTER_DELETE) } 非推奨APIの削除・置き換え 例: ViewModelProvider(this) → by viewModels()
  33. © ZOZO, Inc. 50 レポート項目 5: 潜在的なリスクと注意点 AI自身にリスクを分析させ、重点的なレビュー箇所を特定 動作が変わる可能性のある箇所 ロジックの変更により、意図しない動作変更(デグレ)が発生する可能性のある箇所を指摘

    パフォーマンスへの影響 コレクション操作の変更(例: Sequence化)など、パフォーマンスに影響を与えうる箇所 テストが必要な箇所 上記の変更点に基づき、特に手厚い単体テストやQAテストが必要となる箇所を提案
  34. © ZOZO, Inc. 51 事前にユニットテストを作成する AIリファクタリングとテスト駆動開発(TDD)の組み合わせ テスト駆動開発の進め方 1. AIを使ってリファクタリング前のコードに対しユニットテストを書く 2.

    AIがリファクタリングした後も同じユニットテストが通るか確認する 3. デグレしていないことを保証する AI活用のポイント ユニットテストの作成自体もAIに支援させることで、リファクタリングのプロセ ス全体を高速化できる
  35. 54