Kotlin の導入を組織全体に拡大する

Read this post in other languages:

Urs Peter(シニアソフトウェアエンジニア、JetBrains 認定 Kotlin トレーナー)によるゲスト投稿です。Urs は、より体系的な Kotlin スキルの構築を希望する開発者を対象とする Kotlin Upskill Program(Kotlin スキルアッププログラム)も Xebia Academy で開講しています。

この記事は、実際のチームである開発者の好奇心をきっかけに会社全体が変革するまでの Kotlin の導入経緯を追った「Java 主体の環境で Kotlin の導入を成功させるための究極ガイド」連載記事の第 5 回です。

この連載に含まれる各回のタイトル:

  1. Java 開発者向け Kotlin 入門ガイド
  2. 実際のプロジェクトにおける Kotlin の評価手法
  3. 企業における Kotlin 導入・活用の推進
  4. 意思決定者の「Kotlin 採用」を後押しするために
  5. Kotlin の導入を組織全体に拡大する

大規模な Kotlin の導入を実現する成功要因

Kotlin に対する開発者の賛同と経営陣の支持を得ることは重要な節目ではありますが、ゴールにはなりません。本当の挑戦が始まるのは、Kotlin との共存か Kotlin への移行が必要な既存の Java コードベースに向き合ってからです。このハイブリッド環境で効果的に計画を進めるにはどうすればよいのでしょうか。

既存のコードベースを管理するには、組織の目標や現実的な運用に合った戦略を練ることが重要です。以下では実際に成功した実績のある手法を紹介します。

アプリケーションのライフサイクルに応じた戦略

各種のアプリケーションにはそのライフサイクルの段階に応じた異なる手法が必要です。以下の 3 つの異なるカテゴリを見てみましょう。

サポート終了対象のアプリケーション

戦略: 何もしない。

アプリケーションのサポート終了が予定されている場合は、移行のためにやるべきことはありません。そのようなシステムは Java のままにし、最も重要な部分に注力しましょう。アプリケーションの寿命が残っていることを理由に移行コストが正当化されることは絶対にありません。

新しいシステム

戦略: Kotlin をデフォルトとする。

Kotlin の導入が完了した組織では、グリーンフィールドプロジェクトは必然的に Kotlin で開始されます。導入プロセスが進行中の場合、多くの場合は Java と Kotlin のどちらかを選択できます。賢く選びましょう ;-)

稼働中のアプリケーション

戦略: 現実的な機能重視の移行を行う。

稼働中のアプリケーションについては入念な考慮が必要です。書き直すこと自体を目的に書き直すことをプロダクトオーナーに訴求するのはかなりの大仕事です。代わりに、移行作業と新機能の開発を組み合わせるようにしてください。このやり方なら、具体的なビジネス上の価値を提供しながらコードベースをモダン化できます。「既存の Java アプリケーションを拡張 / 変換する」のセクションでは、すでに異なる角度の取り組みを説明しました。

Java から Kotlin への変換手法

Java から Kotlin への変換手法にはいくつかの選択肢があり、それぞれに明確な妥協点があります。

1. 完全な書き直し

最適な適用対象: 小規模なコードベース 

課題: 大規模なシステムでは時間がかかる

コードベースをゼロから書き直すと、最もクリーンで慣用的な Kotlin コードを得られます。この手法はマイクロサービスなどの小規模なコードベースに最適です。大規模なコードベースの場合、この手法は概して法外な費用がかかる傾向があります。

2. IDE による自動変換と手直し

最適な適用対象: リファクタリング専用の時間を割り当てられる中規模なコードベース

課題: 手動調整が必須 

IntelliJ IDEA の Convert Java to Kotlin(Java から Kotlin に変換)機能では、慣用的とは程遠い逐語的な変換が行われます。以下の例を見てみましょう。

例 1: 

Java

record Developer(
       String name,
       List languages
) {}

生の自動変換結果:

Kotlin

@JvmRecord
data class Developer(
   val name: String?,
   val languages: MutableList?
)

この変換には以下のいくつかの問題があります。

  • すべてが null 許容になる(過度に防御的)。
  • Java のコレクションが Kotlin のデフォルトの読み取り専用リストではなく、MutableList になる。

jspecify のアノテーションを使用した変換の改善:

幸い、Java のすべての型を Kotlin の null 許容型に変換する場合、@NonNull/@Nullable アノテーションを使用した解決策があります。さまざまな解決策がありますが、最新の解決策は jspecify を使用することです(最近になって Spring でも正式にサポートされるようになりました)。

   org.jspecify
   jspecify
   1.0.0


implementation("org.jspecify:jspecify:1.0.0")

jspecify を使用すると、Java コードに @Nullable および @NonNull アノテーションを付与できます。

例 2: 

Java

import org.jspecify.annotations.NonNull;
import org.jspecify.annotations.Nullable;

record Developer(
       @NonNull String name, 
       @NonNull List languages,
       @Nullable String email
) {}

以下のように自動変換の結果が大幅に最適化されます。

Kotlin

@JvmRecord
data class Developer(
   //😃 non-null as requested
   val name: String, 
   //😃 both, collection and type are non-null
  val languages: MutableList,
   //😃 nullable as requested
  val email: String?, 
)

自動変換手法の制限事項:

jspecify アノテーションを使用しても複雑な Java パターンは適切に変換されません。セクション「3. チェック例外を使用しなくても安全性の高い Kotlin コード」で例示した以下の自動変換済みのコードを考察してください。

Kotlin

fun downloadAndGetLargestFile(urls: MutableList): String? {
     //🤨 using Stream, instead of Kotlin Collections
     val contents = urls.stream().map { urlStr: String? ->
     //🤨 still using Optional, rather than Nullable types
     //🤨 var but we want val!
     var optional: Optional
     //🤨 useless try-catch, no need to catch in Kotlin
     try {
         optional = Optional.of(URI(urlStr).toURL())
     } catch (e: URISyntaxException) {
           optional = Optional.empty()
     } catch (e: MalformedURLException) {
           optional = Optional.empty()
     }
     optional
   }.filter { it!!.isPresent() }//🤨 discouraged !! to force conversion to non-null
    .map {it.get() }
    .map { url: URL ->
      //🤨 useless try-catch, no need to catch in Kotlin
      try {
        url.openStream().use { `is` -> 
           String(`is`.readAllBytes(), StandardCharsets.UTF_8)
        }
      } catch (e: IOException) {
         throw IllegalArgumentException(e)
       }
    }.toList()
   //🤨 usage of Java collections…
   return Collections.max(contents)
}

自動変換の結果は、求めている慣用的なものからは程遠いものです。

Kotlin

fun downloadAndGetLargestFile(urls: List): String? =
   urls.mapNotNull {
       runCatching { URI(it).toURL() }.getOrNull()
   }.maxOfOrNull{ it.openStream().use{ it.reader().readText() } }

自動変換でたたき台を得ることはできますが、本当に慣用的な Kotlin を実現するには大幅な手直しと慣用的な Kotlin の知識が必要です。

3. AI 支援による変換

最適な適用対象: 堅牢なテストインフラストラクチャを伴うより大規模なコードベース

課題: 手動レビューが必要

AI は基本的な自動変換よりは慣用的な結果を生成できる可能性はありますが、成功するには入念な準備が必要です。

前提条件:

  1. 包括的なテストカバレッジ: LLM の動作は予測不可能なため、AI ハルシネーションを検出できる信頼性の高いテストが必要です。
  2. よく練られたシステムプロンプト: 標準に沿った慣用的な Kotlin 変換を実現するための詳細な指示を作成する必要があります。こちらのシステムプロンプトをたたき台にするとよいでしょう。
  3. 広範囲にわたるコードレビュー: AI の出力は徹底的なレビューによって論理的・慣用的な正しさを確保する必要があります。大規模なコードベースの場合、このレビューには多大な精神的な負荷がかかる可能性があります。

提案されたシステムプロンプトを使用して変換をガイドすると、以下のようにかなり満足のいく結果を得られます。ただし、それでも完璧ではありません。

Kotlin

fun downloadAndGetLargestFile(urls: List): String? {
   val contents = urls
      .mapNotNull { 
            urlStr -> runCatching { URI(urlStr).toURL() }.getOrNull() 
      }.mapNotNull { url -> runCatching { 
            url.openStream().use { it.readAllBytes().toString(UTF_8)
         } 
      }.getOrNull() }

   return contents.maxOrNull()
}

4. 大規模な自動変換

最適な適用対象: システムによる変換が必要な非常に大規模なコードベース

現在、Java コードベースから慣用的な Kotlin への大規模な変換を可能にするツールは公式には存在しません。ただし、Meta と Uber はバックエンドアプリケーションでも十分に効果を発揮した手法により、Android コードベースでこの課題への取り組みを成功させました。 以下のドキュメントと講演では、Meta と Uber によるこの課題への取り組みを深く理解することができます。

Meta の手法:

Meta

Uber の手法:

Uber

どちらの会社も手動変換や単純な自動化に頼ることなく、システム的に繰り返し可能なプロセスを作成することで成功しました。ルールベースの手法により、大量のコード行全体にわたって一貫性と品質が確保されています。

重要: Java から Kotlin の大規模な変換により、組織レベルでの課題が発生します。信頼性を確保するには、生成されたコードに対する「ヒューマンインザループ」のレビューが必要です。ただし、計画が慎重に行われなかった場合、自動変換によって大量のプルリクエストが発生し、エンジニアに多大な負荷がかかる可能性もあります。そのため、組織的な影響を慎重に考慮する必要があります。 

大規模な Kotlin 導入を成功させるには、コードの変換だけでは不十分であることを覚えておきましょう。チームの専門知識を集積し、コーディング標準を確立し、組織に長期的な価値をもたらす持続可能なプロセスを作成することが必要です。

どの手法をどのタイミングで使用すべきかを簡単に要約します。

  • 小規模なアプリケーションであるか、いずれにしても書き直している最中である。
    Kotlin で書き直す(1)
  • リファクタリング専用の時間を割り当てられる中規模のコードベースである。または Kotlin を学習中のチームがいる。
    IntelliJ IDEA の自動変換 + 手直し(テストから着手する)(2)
  • 繰り返しのパターンがあり、良質なテストのある中規模・大規模なコードである。
    AI 支援による手法(3)
  • 組織レベルで多数のサービスにわたって Kotlin に移行する。
    具体的な計画に基づいた大規模な自動変換(プラットフォーム主導)(4)

Kotlin の可能性を最大限に引き出す

Kotlin は開発者の生産性をすぐに向上させます。その簡潔な構文、安全性機能、および豊富な標準ライブラリにより、多くの開発者は一般的に独学や実習を通じて数週間で質の高いコードを書けるようになります。ただし、指導がなければ、多くの人は可変構造、冗長なパターン、慣用的な機能の見逃しなど、Java ライクに Kotlin を使用してしまう落とし穴にはまってしまいます。

レベルアップする

不変性、式指向のコード、DSL、およびコルーチンによる並行性の構造化(仮想スレッドの有無を問わない)を取り入れる次のレベルに到達すると、多くの開発者は行き詰ってしまいます。

この段階では、外部のトレーニングが独学よりもはるかに効果が高いことに気付きました。Kotlin を長年経験している開発者でも、多くの場合は慣用的なパターンを採用し、この言語の可能性を最大限に引き出すための集中的なコーチングを活用しています。

Kotlin Heroes

Kotlin に移行するかしないか: どのような会社にしたいのか?

結局のところ、Kotlin を導入するかどうかの判断にはエンジニアリングの文化が反映されます。以下の項目を重視していますか?

  • 従来の手法(Java): 保守的、形式的、安定。
  • 進歩的な手法(Kotlin): 実用的、現実的、適応的。

どちらにもメリットがあります。Java でも目標を達成できるでしょう。しかし、Kotlin なら開発者の満足度が高まり、バグも減り、より良い結果が得られると考えられます。Kotlin の方が優れているかどうかではなく、組織が改善のために投資する容易があるかどうかが問題なのです。

あの最初の Kotlin のテストから組織全体での導入までの道のりは必ずしも容易ではありませんが、適切な手法をとれば驚くほど予測可能になります。小さく始めて価値を証明し、コミュニティを構築し、よく考えて拡大しましょう。

現在と未来の開発者に感謝されるはずです。

オリジナル(英語)ブログ投稿記事の作者:

Urs Peter

Urs は経験豊富なソフトウェアエンジニア、ソリューションアーキテクト、カンファレンス講演者、トレーナーであり、主に Kotlin と Scala が関わるレジリエントで拡張可能、かつミッションクリティカルなシステムの構築に 20 年以上携わってきました。

コンサルタント業の傍ら、Kotlin と Scala の言語コースからマイクロサービスやイベント駆動型アーキテクチャといったアーキテクチャ関連トレーニングに至る多種多様なコースのトレーナーと作成者としても情熱的に活動しています。

元々人との交流を好む性格であり、ミートアップやカンファレンスで他の開発者と知識を共有し、刺激し合うことを楽しんでいます。Urs は JetBrains 認定 Kotlin トレーナーです。

Alyona Chernyaeva

Alyona Chernyaeva

image description