Slide 1

Slide 1 text

AIとの協業で実現! レガシーコードをKotlinらしく 生まれ変わらせる実践ガイド 株式会社ZOZO
 ZOZOTOWN開発本部 ZOZOTOWN開発2部 Androidブロック
 にしみー (@nishimy432) / 清水(西上) 知里 Copyright © ZOZO, Inc. 1

Slide 2

Slide 2 text

© ZOZO, Inc. 株式会社ZOZO ZOZOTOWN開発本部 ZOZOTOWN開発2部 Androidブロック にしみー/nishimy432 2024年3月- 株式会社ZOZO Android版ZOZOTOWNアプリ開発を担当 2

Slide 3

Slide 3 text

© ZOZO, Inc. 3 アジェンダ ● レガシーコードの課題 ● Kotlinの利点 ● リファクタリングの課題 ● AIによるコードの置き換え ● プロンプトの改善とコードの変化 ● AIによる品質の保持

Slide 4

Slide 4 text

© ZOZO, Inc. レガシーコードの課題 4

Slide 5

Slide 5 text

© ZOZO, Inc. レガシーコードの問題点 長年にわたる機能追加や仕様変更により、技術的負債が蓄積したJavaベースのレ ガシーコードがまだ残っている ➢ コードは複雑化し、可読性やメンテナンス性が著しく低下 ➢ 特にAndroid開発においては、Googleの「Kotlinファースト」方針への追従 が困難に・・・ 5

Slide 6

Slide 6 text

© ZOZO, Inc. レガシーコードが引き起こす具体的な問題 Kotlinファーストの恩恵を受けられないJavaコードベース 最新技術の利用不可 Googleの「Kotlinファースト」方針により、主要な新機能が利用できない (例) Jetpack Compose、Coroutinesなど Null安全性の欠如 言語レベルでのNull安全性がなく、コードの信頼性が開発者の注意力に依存し、NullPointerExceptionのリ スクが常につきまとう 開発効率の低下 コードの複雑さが原因で、新機能の追加やバグ修正に想定以上の時間がかかり、ビジネスの要求ス ピードに応えられない 6

Slide 7

Slide 7 text

© ZOZO, Inc. Kotlinの利点 7

Slide 8

Slide 8 text

© ZOZO, Inc. 安全性の劇的な向上 KotlinはJavaの課題を解決し、より堅牢な開発を実現する Null安全 型システムレベルでNullableとNon-nullを区別。 NullPointerExceptionをコンパイル時に検出・回避できる 型システム スマートキャストや型推論により、冗長なキャストや型チェックを削減し、コードの安全性を高める 8

Slide 9

Slide 9 text

© ZOZO, Inc. 高い可読性と表現力 簡潔かつ直感的な構文が、メンテナンス性の高いコードを生み出す データクラス ボイラープレートコードを自動生成し、モデルクラスを簡潔に定義 名前付き引数 引数の意味を明確にし、デフォルト引数と組み合わせることでビルダーパターンなどを代替可能 高階関数とラムダ スコープ関数や拡張関数と組み合わせ、コードの意図を明確にする 9

Slide 10

Slide 10 text

© 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専用

Slide 11

Slide 11 text

© ZOZO, Inc. リファクタリングの課題 11

Slide 12

Slide 12 text

© ZOZO, Inc. 自動変換の問題点 変換後のコードは「Javaの文法で書かれたKotlin」になりがち 冗長な表現の残存 Java特有の冗長な表現がそのまま残る(例: get(), set() の呼び出し) 言語機能の不活用 Kotlinの強力な言語機能(拡張関数, スコープ関数など)を活かせない 低可読性の維持 結果として、可読性が低いままになってしまう 12

Slide 13

Slide 13 text

© ZOZO, Inc. 目指すべき「Kotlinらしい」コードとは? 私たちが目指すコードの3つの柱 1. 簡潔性と可読性 データクラス、型推論、名前付き引数を活用し、意図が明確なコード 2. 安全性 Nullable/Non-nullを厳格に区別し、NullPointerExceptionのリスクを排除したコード 3. 表現力 (イディオム) 高階関数、スコープ関数、拡張関数を使いこなし、宣言的に記述されたコード 13

Slide 14

Slide 14 text

© ZOZO, Inc. もう一つの課題: レビュー負担 リファクタリングはレビューのコストが高い 広範囲のリファクタリングは、レビューの負担が非常に重い 「Kotlinらしさ」の基準がチーム内で統一されていないと、レビューで手戻りが 発生しやすい 変換漏れや、意図しない動作変更のリスクも伴う 14

Slide 15

Slide 15 text

© ZOZO, Inc. AIによるコードの置き換え 15

Slide 16

Slide 16 text

© ZOZO, Inc. シンプルなプロンプト 16 JavaからKotlinに置き換えてください。

Slide 17

Slide 17 text

© ZOZO, Inc. 生成されたコードの「惜しいところ」 一見動くように見えるが、多くの問題点を内包している ● Kotlinらしさの欠如 ● 安全性・正確性の問題 ● 古いAPIの利用 17

Slide 18

Slide 18 text

© ZOZO, Inc. Kotlinらしさの欠如 言語機能を活かしきれず、Javaライクな記述が残存 Java的なNullチェック KotlinのNull安全機能(?. や ?:)を適切に使えていない スコープ関数の誤用 let / apply / run のセマンティクスをせず、不適切なコンテキストで使用する 冗長な記述 Kotlin KTX(拡張プロパティ)の isVisible を使わず、Javaライクな View.VISIBLE を使用する 18

Slide 19

Slide 19 text

© ZOZO, Inc. 安全性・正確性の問題 Null安全の破綻や、コンテキスト不足による誤り 強制アンラップ(!!) AIがNull安全を諦め、安易に !! を使用しNullPointerExceptionのリスクを残す 不正確なNullability Non-nullで良い箇所をnullableにする(またはその逆) コンテキスト不足 プロジェクト固有の関数を取得し切れず、存在しない関数を呼び出す 19

Slide 20

Slide 20 text

© ZOZO, Inc. 古いAPIの利用 モダンな代替APIへ置き換えられず、負債が残る 古い非同期処理 Handler.postDelayed などを使用(Coroutinesに置き換えない) 古い初期化 ViewModelProvider を直接使用(by viewModels を使わない) 非推奨API すでに deprecated になったAPIをそのまま使用し続ける 20

Slide 21

Slide 21 text

© ZOZO, Inc. プロンプトの改善とコードの変化 21

Slide 22

Slide 22 text

© ZOZO, Inc. 改善アプローチ AIの出力を「Kotlinらしい」コードに導くためのアプローチ 1. 依存関係の調査 AIが知らないコンテキスト(プロジェクト固有のクラス)を事前に調べさせる 2. ベストプラクティスの明示 「Kotlinらしい」の定義を具体的に言語化し、守るべきルールとして指示する 3. モダンAPIへの置換指示 古いAPIを避け、モダンな代替APIへ置き換えるよう明示的に指示する 22

Slide 23

Slide 23 text

© ZOZO, Inc. 改善1: コンテキスト不足 課題 ● AIがプロジェクト固有の依存関係や他クラスで定義された変数や関数のアク セス修飾子を把握していない ● 結果として、コンパイルエラーになるコードを生成する 対策プロンプト ● 変換前に「調査」を指示 ● 先にAIにコンテキストを把握させる 対象クラスと依存関係にあるクラスを調査してください。 23

Slide 24

Slide 24 text

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

Slide 25

Slide 25 text

© 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 に直接アクセス 不可

Slide 26

Slide 26 text

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

Slide 27

Slide 27 text

© ZOZO, Inc. 改善2: Kotlinらしさの欠如 課題 「Kotlinらしい」の定義が曖昧なため、AIがJavaライクなコードを生成する ● 強制アンラップ(!!)を多用 ● Java時代のサードパーティ製ライブラリをそのまま使用 対策プロンプト ● ベストプラクティスを具体的に列挙 ベストプラクティスの適用: 以下のKotlinの機能を積極的に活用し、 コードの可読性、安全性、簡潔性を最大化してください。 27

Slide 28

Slide 28 text

© ZOZO, Inc. 改善2: Kotlinらしさの欠如 実際にプロンプトに含めた機能 ● データクラス (data class) ● スコープ関数 ○ let, apply, run, also, withの適切な利用 ● null安全性の徹底 ○ 安全呼び出し演算子(?.)の使用 ○ エルビス演算子(?:)の使用 ○ 強制アンラップ(!!)の排除 ● 高階関数とラムダ式 (特にコレクション操作) ● when式 ● sealedクラスやsealedインターフェース ● 拡張関数 28

Slide 29

Slide 29 text

© 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 -> // 処理 }

Slide 30

Slide 30 text

© 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 -> // 処理 }

Slide 31

Slide 31 text

© 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 -> // 処理 }

Slide 32

Slide 32 text

© 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

Slide 33

Slide 33 text

© 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); }

Slide 34

Slide 34 text

© 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

Slide 35

Slide 35 text

© 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

Slide 36

Slide 36 text

© 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

Slide 37

Slide 37 text

© 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

Slide 38

Slide 38 text

© 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

Slide 39

Slide 39 text

© ZOZO, Inc. 改善3: 古いAPIの利用 課題 AIが古いAPIとモダンなAPIの区別をつけられない Handler, 古いViewModelProvider, 非推奨APIが残存する 対策プロンプト 「近代化」を明示的に指示 原則として近代化: パフォーマンスに影響のある古いAPIは、 原則としてモダンな代替APIに置き換えてください。 39

Slide 40

Slide 40 text

© 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

Slide 41

Slide 41 text

© 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

Slide 42

Slide 42 text

© ZOZO, Inc. コード変化 (3): モダンAPI シンプルプロンプト viewModel = ViewModelProvider(this)[SearchFavBrandItemViewModel::class.java] プロンプト改善後 private val viewModel: SearchFavBrandItemViewModel by viewModels() 42 ViewModelの初期化方法

Slide 43

Slide 43 text

© ZOZO, Inc. AIによる品質の保持 43

Slide 44

Slide 44 text

© ZOZO, Inc. レビューの必要性 結局は人が責任を持つ必要がある AIが生成したコードが100%正しい保証はない ビジネスロジックの微妙なニュアンスや、エッジケースの考慮が漏れる可能性が ある 人の目での確認は必ず必要 44

Slide 45

Slide 45 text

© ZOZO, Inc. AIの活用 クラスのサイズが大きいとレビューが大変… ➔ 解決策: AIに「特に確認が必要な箇所」をレポートとして出力させる ➔ レビューの焦点を絞り込み、人間の負担を軽減する 45

Slide 46

Slide 46 text

© ZOZO, Inc. 46 レビュー項目 1: 構文・記法を改善 Javaライクな構文が、Kotlinらしい宣言的な構文に改善されているか 条件式の書き方を修正した箇所 例: switch 文 → when 式 ループ処理の改善 例: forループ → forEach, map, filter などの高階関数 式ベースへの変換 例: if 文による変数代入 → if 式による直接代入

Slide 47

Slide 47 text

© ZOZO, Inc. 47 レビュー項目 2: null安全性の強化 プロンプトで指示した「Null安全」が徹底されているか Platform Typesの排除 Javaからの戻り値(例: String!)に、? か !! ではない、適切な型を明示したか nullabilityの明示化 不必要にnullableだった変数をnon-nullにした箇所 強制アンラップ(!!)の削除 プロンプト指示の通り、!! が完全に排除されているか

Slide 48

Slide 48 text

© ZOZO, Inc. 48 レビュー項目 3: ベストプラクティスを適用 スコープ関数やデータクラスなど、Kotlinの機能が適切に使われているか コンストラクタの改善 例: apply の使用 関数型プログラミング 高階関数、ラムダ、メソッド参照等の活用 スコープ関数の活用 let, apply, run の適切な使い分け

Slide 49

Slide 49 text

© ZOZO, Inc. 49 レビュー項目 4: APIの変更 AIがどのAPIを「古い」と判断し、「新しい」ものに置き換えたか 古いAPIから新しいAPIに置き換えた箇所 例: Handler.postDelayed → lifecycleScope.launch { delay(DELAY_AFTER_DELETE) } 非推奨APIの削除・置き換え 例: ViewModelProvider(this) → by viewModels()

Slide 50

Slide 50 text

© ZOZO, Inc. 50 レポート項目 5: 潜在的なリスクと注意点 AI自身にリスクを分析させ、重点的なレビュー箇所を特定 動作が変わる可能性のある箇所 ロジックの変更により、意図しない動作変更(デグレ)が発生する可能性のある箇所を指摘 パフォーマンスへの影響 コレクション操作の変更(例: Sequence化)など、パフォーマンスに影響を与えうる箇所 テストが必要な箇所 上記の変更点に基づき、特に手厚い単体テストやQAテストが必要となる箇所を提案

Slide 51

Slide 51 text

© ZOZO, Inc. 51 事前にユニットテストを作成する AIリファクタリングとテスト駆動開発(TDD)の組み合わせ テスト駆動開発の進め方 1. AIを使ってリファクタリング前のコードに対しユニットテストを書く 2. AIがリファクタリングした後も同じユニットテストが通るか確認する 3. デグレしていないことを保証する AI活用のポイント ユニットテストの作成自体もAIに支援させることで、リファクタリングのプロセ ス全体を高速化できる

Slide 52

Slide 52 text

© ZOZO, Inc. まとめ 52

Slide 53

Slide 53 text

© ZOZO, Inc. AI協業リファクタリングの要点 AIを「Kotlinらしい」コード生成のパートナーにするために 品質管理 AIの出力を鵜呑みにせず、レポートを活用して 検証したりテストを作成してデグレを防ぐ 明確な指示(プロンプト) 依存関係の事前調査など、AIが知らない情報を 与える コンテキストの提示 「Kotlinらしさ」を具体的に定義し、ベストプラ クティスとモダンなAPIの使用を明示する 53

Slide 54

Slide 54 text

54