企業における Kotlin 導入・活用の推進

Read this post in other languages:

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

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

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

  1. Java 開発者向け Kotlin の基礎
  2. 実際のプロジェクトにおける Kotlin の評価手法
  3. 企業における Kotlin 導入・活用の推進

周知活動: 周りの開発者の心と支持を勝ち取る

これまでの取り組みにより、主力チームに Kotlin のメリットを十分に理解させることができました。 次は導入を拡大するための重要な段階に進みます。

この段階では、懐疑的な Java 開発者の心と支持を勝ち取ることが重要です。 ここでは以下のようなハード面とソフト面が結果を左右します。

ハード面: コード

  • コードで魅力を伝える

ソフト面: 開発者を支援してつながりを築く

  • オンボーディングを容易にする
  • 独学の資料を提供する
  • 社内に Kotlin コミュニティを作る
  • 焦りは禁物…

コードで魅力を伝える

Kotlin でアプリケーションを(再)作成して得た経験を深掘りし、そのメリットを具体的に分かりやすく示しましょう。

  • 説明するより実物を見せる: Java を Kotlin のスニペットと比較することで、Kotlin がもたらす簡潔さを示します。
  • 全体的な視点で支配的パラダイムに注目する: Kotlin は単に Java と異なっているわけではありません。Kotlin は安全性、最大限の可読性と保守性を可能にする簡潔さ、第一級市民としての関数、および拡張性を基礎としており、これらを組み合わせることで Java の根本的な欠点を解消しながら、Java エコシステムとの完全な相互運用性も実現しています。

以下にいくつか具体的な例を挙げます。

1. null 安全性: 多大な損失を生むミスがなくなる 

Java

//This is why we have the billion-dollar mistake (not only in Java…)
Booking booking = null; //🤨 This is allowed but causes:
booking.destination; //Runtime error 😱

Optional booking = null; //Optionals aren’t safe from null either: 🤨
booking.map(Destination::destination); //Runtime error 😱

Java には Optional やアノテーション(@NotNull など)のような null 安全性機能が追加されてきましたが、この根本的な問題は解決されていません。 また、プロジェクト Valhalla(null 非許容型と null 許容型)は Java に null 安全性を導入するものというよりも、より多くの選択肢を提供するものです。

Kotlin

//Important to realize that null is very restricted in Kotlin:
val booking:Booking? = null //…null can only be assigned to Nullable types ✅ 
val booking:Booking = null //null assigned to a Non-nullable types yields a Compilation error 😃 

booking.destination //unsafely accessing a nullable type directly causes a Compilation error 😃
booking?.destination //only safe access is possible  ✅

Kotlin の null 安全性が素晴らしいのは、安全であるだけでなく、使い勝手にも優れていることです。 以下は矛盾することを同時に実現させようとしている典型的な例です。

次のドメインがあるとします。

Kotlin

data class Booking(val destination:Destination? = null)
data class Destination(val hotel:Hotel? = null)
data class Hotel(val name:String, val stars:Int? = null)

Java

public record Booking(Optional destination) {

   public Booking() { this(Optional.empty()); }

   public Booking(Destination destination) {
       this(Optional.ofNullable(destination));
   }
}

public record Destination(Optional hotel) {

   public Destination() { this(Optional.empty()); }

   public Destination(Hotel hotel) {
       this(Optional.ofNullable(hotel));
   }
}

public record Hotel(String name, Optional stars) {

   public Hotel(String name) {

       this(name, Optional.empty());

   }

   public Hotel(String name, Integer stars) {
       this(name, Optional.ofNullable(stars));
   }
}

オブジェクトの構築

Java

//Because Optional is a wrapper, the number of nested objects grows, which doesn’t help readability
final Optional booking = Optional.of(new Booking(
      Optional.of(new Destination(Optional.of(
            new Hotel("Sunset Paradise", 5))))));

Kotlin

//Since nullability is part of the type system, no wrapper is needed: The required type or null can be used. 
val booking:Booking? = Booking(Destination(Hotel("Sunset Paradise", 5)))

ネストしたオブジェクトの全探索

Java

//traversing a graph of Optionals requires extensive unwrapping
final var stars = "*".repeat(booking
                          .flatMap(Booking::getDestination)
                          .flatMap(Destination::getHotel)
                          .map(Hotel::getStars).orElse(0)); //-> "*****"

Kotlin

//Easily traverse a graph of nullable types with: ‘?’, use  ?: for the ‘else’ case.
val stars = "*".repeat(booking?.destination?.hotel?.stars ?: 0) //-> "*****"

ネストしたオブジェクトの展開

Java

//extensive unwrapping is also needed for printing a leaf 
booking.getDestination()
       .flatMap(Destination::getHotel)
       .map(Hotel::getName)
       .map(String::toUpperCase)
         .ifPresent(System.out::println);

Kotlin

//In Kotlin we have two elegant options:
//1. we can again traverse the graph with ‘?’
booking?.destination?.hotel.?name?.uppercase()?.also(::println)

//2. We can make use of Kotlin’s smart-cast feature
if(booking?.destination?.hotel != null) {
   //The compiler has checked that all the elements in the object graph are not null, so we can access the elements as if they were non-nullable types
   println(booking.destination.hotel.uppercase())
}

Java で null 安全性がサポートされていないことは開発者の大きな悩みの種であり、防御的なプログラミングや複数の異なる null 許容性コンストラクトの混在(あるいはそのよなコンストラクトの不在)を招き、冗長さが増す原因になります。 さらに、NullPointerExceptions はアプリケーションがクラッシュする原因の約 3 分の 1 を占めています(JetBrains ブログ)。 このような実行時の障害は、Kotlin のコンパイル時 null チェックによって完全に防げます。 そのため、今でも null 安全性は Kotlin に移行する主な動機となっています。

2. コレクションは敵ではなく味方である

Spring の作者である Rod Johnson 氏は、Kotlin を試すきっかけとなったのは null 許容型ではなく、複雑すぎる Java Streams API だったと最近のインタビュー「Creator of Spring: No desire to write Java」(Spring の作者: Java を書く気はない)で述べています。

以下の例は、Java Sreams API がひどく複雑になっている各種の原因と Kotlin がどのようにすべての問題を解決しているのかを示しています。

Java

public record Product(String name, int... ratings){}
List products = List.of(
  new Product("gadget", 9, 8, 7), 
  new Product("goody", 10, 9)
);

Map<String, Integer> maxRatingsPerProduct =

   //🤨 1. Stream introduces indirection
   products.stream()
     //🤨 1. Always to and from Stream conversion
      .collect(
        //🤨 2. Lacks extension methods, so wrappers are required
        Collectors.groupingBy(
           Product::name,
            //🤨 2. Again…
           Collectors.mapping( groupedProducts ->
                //🤨 3. (too) low-level types, arrays, and primitives cause extra complexity
                //🤨 4. No API on Array, always wrap in stream 
               Arrays.stream(groupedProducts.ratings())
                   .max()
                      //🤨 5. Extra verbosity due to Optional
                      .orElse(0.0),
               //🤨 6. No named arguments: what does this do?
                Collectors.reducing(0, Integer::max)
              )
            ));

Kotlin

//😃 rich and uniform Collection API - even on Java collections - due to extension methods
val maxRatingsPerProduct = products.
      .groupBy { it.name }
      .mapValues { (_, groupedProducts) -> //😃 destructuring for semantic precision
            //😃 built-in nullability support, and the same API for 
            //arrays like other Collections
            groupedProducts.flatMap { it.ratings }
                  .maxOrNull() ?: 0 
            }
      }

Kotlin ではコレクションフレームワークが統一されているため、異なるコレクション型同士の変換は非常に簡単です。
Java

int[] numbers = {1, 3, 3, 5, 2};

Set unique = Arrays.stream(numbers).boxed().collect(Collectors.toSet());

Map<Integer, Boolean> evenOrOdd = unique.stream()
       .collect(Collectors.toMap(
               n -> n,
               n -> n % 2 == 0));

Kotlin

val numbers = arrayOf(1, 3, 3, 5, 2)

val unique: Set = numbers.toSet() //😃 simply call to to do the conversion

val evenOrOdd: Map<Int, Boolean> = unique.associateWith { it % 2 == 0 }

最終的には以下のような効果が得られます。

  • 機能豊富で直感的なコレクション API: パイプラインはネストしたコレクターの呼び出し内に存在せず、英語のように左から右に読み取れます。
  • ボイラープレートと複雑さの減少: Collectors.groupingBy、Stream、Optional、Arrays.stream のいずれも存在しません。
  • メンタルモデルの統一化: ListSetArray、またはプリミティブ配列のどれから始めても、同じコレクション演算子に到達します。
  • 苦にならないパフォーマンス: コンパイラーがやむを得ない場合にのみボクシング/アンボクシングを挿入するため、開発者は通常のコードを書くことができます。
  • null 安全性の組み込み: コレクション API は、主に orNull(...) 接尾辞を持つ各種ヘルパーによって null 許容性をサポートしています。
  • 慣用的なラッパーによる違和感のない Java との相互運用性: 拡張メソッドがあるため、「両方のいいとこ取り」ができます。つまり、Java のコレクションに加えて機能豊富なコレクションを使用できるのです。

要するに、Kotlin はフィルタリング、マッピング、グループ化といった日常的に使用するコレクションのタスクを第一級のコンポーザブル関数に格上げするため、必要なことをその実現方法に縛られることなく表現できます。

3. チェック例外を使用しなくても安全性の高い Kotlin コード

Java はチェック例外を現在でもサポートしている数少ない言語の 1 つです。 元々は安全性を保証する機能として実装されたものですが、その約束はいまだ果たされていません。 その冗長さ、何も行わないか例外を RuntimeException として再スローする無意味な catch ブロック、ラムダとの連携の悪さはコードの安全性を高めるどころか、障害をもたらすいくつかの要因となっています。

Kotlin は他のほぼすべてのプログラミング言語(C#、Python、Scala、Rust、Go など)と同様に、回復不可能な状況でのみ例外を使用するという実証済みのパラダイムに従っています。

次の例はチェック例外が安全性を一切高めず、コードにもたらしている障害を明確に示しています。

Java

public String downloadAndGetLargestFile(List urls) {
   List contents = urls.stream().map(urlStr -> {
               Optional optional;
               try {
                   optional = Optional.of(new URI(urlStr).toURL());
                   //🤨 Within lambdas checked exceptions are not supported and must always be caught...
               } catch (URISyntaxException | MalformedURLException e) {
                   optional = Optional.empty();
               }
               return optional;
           }).filter(Optional::isPresent)       //Quite a mouthful to get rid of the Optional...
           .map(Optional::get)
           .map(url -> {
               try (InputStream is = url.openStream()) {
                   return new String(is.readAllBytes(), StandardCharsets.UTF_8);
               } catch (IOException e) {
                   //🤨… or re-thrown, which is annoying, I don’t really care about IOE
                   throw new IllegalArgumentException(e);
               }
           }).toList();
   //🤨 An empty List results in a NoSuchElementException, so why is it not checked? The chance that the List is empty is as high as the other two cases above...
   return Collections.max(contents);
}

Kotlin

//😃 safe return type
fun downloadAndGetLargestFile(urls: List): String? =
   urls.mapNotNull {    //😃 convenient utility methods to rid of null
       //😃 try catch is possible, yet runCatching is an elegant way to convert an exception to null
       runCatching { URI(it).toURL() }.getOrNull()
   }.maxOfOrNull{  //😃 safe way to retrieve the max value
       it.openStream().use{ it.reader().readText() }  //😃 convenient extension methods to make java.io streams fluent
   }

4. 第一級市民としての関数

Kotlin は関数を第一級市民として扱っています。Java 経験者はその意味と重要性に疑問を持つかもしれません。

主な違いは Java の限定的なアプローチにあります。Java の機能は主にラムダを介した呼び出し側に重点を置いていますが、宣言側は冗長で直感性に劣る関数型インターフェースに結び付いたままです。 Kotlin ではボイラープレートなしで関数を定義、受け渡し、返却、作成できるため、関数型プログラミングの表現力がさらに強化され、自然になります。

Java

public void doWithImage(
      URL url, 
      //🤨 Function interfaces introduce an indirection: because we don’t see the signature of a Function we don’t know what a  BiConsumer does, unless we look it up
      BiConsumer<String, BufferedImage> f) throws IOException {
        f.accept(url.getFile(), ImageIO.read(url));
}

//🤨 Same here
public void debug(Supplier f) {
   if(isDebugEnabled()) {
      logger.debug("Debug: " + f.get());
   }
}

//🤨 calling no-argument lambdas is verbose
debug(() -> "expensive concat".repeat(1000));

Kotlin

fun doWithImage(
   url: URL,
  //😃 Kotlin has a syntax for declaring functions: from the signature, we see what goes in and what goes out 
   f:(String, BufferedImage) -> Unit) = 
      f(url.file, ImageIO.read(url))

  //😃 same here: nothing goes in, a String goes out
fun debug(msg: () -> String) {
   if(isDebugEnabled) {
            logger.debug(msg())
   }
}

//😃 convenient syntax to pass a lambda: {}
debug{"expensive concat".repeat(1000)}

Kotlin では明確で簡潔な構文で関数を宣言できます。シグネチャーだけで入出力パラメーターをすぐに理解できるため、外部の関数型インターフェースを確認しに行く必要はありません。

Java はその関数型プログラミングの機能を冗長で複雑なシグネチャーを持つ大量の java.util.function.* インターフェース一式を通して「流出」させているため、多くの場合は関数型プログラミングが使用しにくくなっています。 これに反して Kotlin は関数を第一級市民として扱っています。このようなインターフェースは開発者からは見えなくなっているものの、Java のアプローチと完全な相互運用性を維持しています。

結果的に Kotlin のほうが関数をはるかに単純明快かつ直感的に使用できるため、この強力なプログラミングの概念を自作のコードで活用するハードルが大幅に低くなります。

5. 手間のいらないコルーチンによる並行性

Java では高いスループット、1 つのリクエスト内での並列処理、またはストリーミングが必要な場合、Spring WebFlux、Vert.X、Quarkus などのフレームワークで利用できる Reactor や RxJava などのリアクティブライブラリしか選択肢がありません。

これらのライブラリは複雑であり、関数型プログラミングを強いられるという問題があることが知られています。 そのため、習得が困難であるだけでなく、アプリケーションに負荷がかかっている状況で悲惨な結果を招く可能性のあるエラーを非常に簡単に発生させてしまいます。 リアクティブプログラミングが主流になったことがないのはこれが原因だと思われます。

注意: 仮想スレッドはリアクティブライブラリと若干重複する部分がありますが、後者を置き換えるものではありません。 仮想スレッドはノンブロッキング I/O を提供していますが、並行処理やリアクティブストリームなどの機能は提供していません。 主要なフレームワークが構造化並行性とスコープ値をサポートするようになれば、それらによって並行処理も可能になるでしょう。 リアクティブストリームの場合は、常にリアクティブライブラリに頼ることになります。

では、あなたが Spring Boot を使用する Java 開発者であり、1 つのリクエスト内で並行呼び出しを行いたいと仮定しましょう。 以下のようなコードが完成しているものとします。

@PostMapping("/users")
@ResponseBody
@Transactional
public Mono storeUser(@RequestBody User user) {
   Mono avatarMono = avatarService.randomAvatar();
   Mono validEmailMono = emailService.verifyEmail(user.getEmail());
   //🤨 what does ‘zip’ do?
   return Mono.zip(avatarMono, validEmailMono).flatMap(tuple -> 
       if(!tuple.getT2()) //what is getT2()? It’s the validEmail Boolean…
        //🤨 why can I not just throw an exception?
         Mono.error(new InvalidEmailException("Invalid Email"));
       else personDao.save(UserBuilder.from(user)
                                          .withAvatarUrl(tuple.getT1()));
     );  
  }

このコードは実行時の動作という観点からは申し分なく機能しますが、予期せず複雑極まりない作りになっています。

  • コードが呼び出しチェーン全体にわたって Mono/Flux に支配されており、すべてのドメインオブジェクトが強制的にラップされています。
  • zipflatMap などの複雑な演算子があちこちで多用されています。
  • 例外のスローなどの標準的なプログラミング構文を使用できません。
  • コードの動作の意図が著しく損なわれています。このコードは MonosflatMap に重点を置いているため、動作の観点から実際に何が起こっているのかが不明瞭になっています。

幸いなことに、Kotlin コルーチンという強力な解決策があります。 コルーチンは言語レベルでのリアクティブな実装として組み立てられます。 したがって、両方の長所を組み合わせることができます。

  • 以前と同じように逐次的なコードを書くことができます。
  • コードは実行時に非同期または並行して実行されます。

上記の Java コードは以下のようにコルーチンに変換されます。

@GetMapping("/users")
@ResponseBody
@Transactional
suspend fun storeUser(@RequestBody user:User):User = coroutineScope {
   val avatarUrl = async { avatarService.randomAvatar() }
   val validEmail = async { emailService.verifyEmail() }
   if(!validEmail.await()) throw InvalidEmailException("Invalid email")
   personRepo.save(user.copy(avatar = avatarUrl.await()))
}

Kotlin の suspend キーワードにより、構造化されたノンブロッキング実行が明確で簡潔に実現されています。 async{}await() と併用することで、深くネストしたコールバックや、MonoCompletableFuture といった複雑な構文を使用しなくても並行処理を行えます。

そのため、複雑さが緩和され、開発者の快適さと保守性が向上し、完全に同じパフォーマンス特性を維持できます。

注意: Java ベースのすべての主要ウェブフレームワークがコルーチンをサポートしているわけではありません。 Spring はこの点で優れており、Micronaut も同様です。 Quarkus によるコルーチンのサポートは現時点では限定的です。

6. Java も進化しています!

Java はレコードやパターンマッチングなどの機能によって進化しており、Amber、Valhalla、Loom などのプロジェクトも予定されています。 この着実な進化は JVM を強化し、エコシステム全体に利益をもたらしています。

ただし、ここには落とし穴があります。これらの「新しい」Java 機能のほとんどは、Kotlin 開発者が長年活用してきたものです。 null 安全性、値クラス、トップレベル関数、デフォルト引数、簡潔なコレクション、および第一級関数はどれも Kotlin の設計に組み込まれているものであり、より統一され、開発者が使いやすいように提供されています。 そのため、多くの場合は Kotlin コードのほうがクリーンかつ安全で、はるかに生産性が高く感じられます。

しかもそれだけではありません。Kotlin は Java のイノベーションの恩恵も受けています。 仮想スレッドや Loom などの全体的な JVM レベルの進歩や Valhalla のパフォーマンス強化は、Kotlin にもシームレスに適用されます。

要するに、Java は進化していますが、Kotlin は開発者が必要とする最新のツールを最初から提供するように設計されているのです。そのため、安全・モダンかつ将来を見据えた選択肢と言えます。

7. Kotlin の進化のメリット

古い言語とモダンなプログラミング言語と比較した場合、前者は必然的に古くから引きずっている問題があります。 大量の既存コードベースをサポートしながら言語を更新するには、言語設計者に固有の課題が強いられます。 Kotlin には以下に挙げる 2 つの重要なメリットがあります。

先人の偉業に学ぶ: Kotlin の最初の設計チームは新しい言語をゼロから開発せず、主要プログラミング言語の実証されたパラダイムを集約し、まとまりのある 1 つの言語に統一しました。 このアプローチにより、より広範なプログラミングコミュニティが進化の過程で蓄積してきた知識を最大化しました。

Java の欠点から学ぶ: Kotlin の設計者は Java の落とし穴を観察し、堅実なソリューションを基礎から開発することができました。

Kotlin の進化に対する理解を深めるには、Kotlin の最初の設計チームに携わった Andrey Breslav がアムステルダムで開催された KotlinDevDay で「Shoulders of Giants: Languages Kotlin Learned From – Andrey Breslav」(先人の偉業に学ぶ: Kotlin の知識源となった数々の言語 – Andrey Breslav)という素晴らしい講演を行っています。

ソフト面: 開発者の Kotlin 導入を支援して開発者同士のつながりを築く

1. オンボーディングを容易にする

表現力豊かな Java と Kotlin のコードスニペットを比較する目的は、Kotlin への興味をそそることにあります。 ただし、コードだけでは Java 開発者の心と支持を得るには不十分です。 導入を促進してスムーズなスタートを切れるよう、次の項目を提供しましょう。

  • サンプルプロジェクト: Java と Kotlin の両方のコードをすぐに比較できるプロジェクトがあれば、移行時に役立つ実践的な参考資料になります。
  • 組み込みの品質チェック: SonarQube、ktlint、detekt などのツールを事前に構成しておくことで、最初からクリーンで一貫性があり、保守可能なコードが促進されます。 これにより、一貫したリントルール、テストフレームワーク、ライブラリ、および CI パイプラインを適用し、チーム全体の手間を減らすことができます。
  • 指導とサポート: 開発の早期段階で新しいチームの指導、質問への回答、およびハンズオン形式でのアドバイスを提供できる経験豊富な Kotlin エンジニアを配備します。
    • これは特に重要です。そのような段階を過去に経験した別チームの開発者から数時間の指導を受けるだけで、多くの損害や技術的負債を防ぐことができます。

持続的な Kotlin への熱意を育むには、ほんの少しのサポートや指導が最も効果的です。

2. 独学の資料を提供する

特に Java から移行する場合、Kotlin の基本は独学できます。 事前にリソースを提供しておくことで、Kotlin で生産性が向上するまでの道のりが容易になり、敷居が低くなります。

注意: 独学は基礎をつかむのに効果的な学習方法ではありますが、いくつかの欠点もあります。 その 1 つは本人任せであることです。日々忙殺されていれば、独学を省略したくもなります。 さらに、慣用的な Kotlin を正しく適用し、その微妙な違いを理解している開発者からのフィードバックを得ることもできません。 独学コースを終えた後は、Java ライクな Kotlin を書いてしまう可能性が高くなります。これは幾分かのメリットをもたらすものの、この言語の能力を最大限に活用することはできません。

的確な指導を受けられないのであれば、従来型のコースが非常に役立ちます。 従来型のコースの場合、参加する必要性があります。同レベルの他の学習者とやり取りし、経験豊富な開発者から質問への回答を得られるため、最初に移行する段階で慣用的でない Kotlin の使用が少なくなり、より早く言語に慣れることができます。

3. 社内に Kotlin コミュニティを作る

Kotlin の専門知識を最速で全社的に高める手段の 1 つは、社内にコミュニティを作り、その育成を最優先することです。

  • 社内 Kotlin コミュニティを立ち上げる
    • Kotlin コミュニティに投資する意欲のある 3~6 人以上の開発者からなる中核チームを作りましょう。 また、各開発者が上司から時間を与えられ、その活動に対する評価を得られるようにしてください。
    • チームが結成されたら、Kotlin コミュニティから有名な講演者を招待し、全社的なキックオフを開催しましょう。 これにより、Kotlin への移行意欲に火が付き、勢いが増します。
    • 毎月または隔週の定期ミートアップを設定し、勢いが収まらないようにしましょう。
    • 質問、コードスニペット、イベントメモをリアルタイムに残せる共有のチャットチャンネルや Wiki を作成しましょう。
  • (外部)講演者を招待する
    • Kotlin を本番環境に出荷したことのあるエンジニアを招待し、率直な現場の話を共有してもらいましょう。
    • 技術面を深掘りした講演(コルーチン、KMP、関数型プログラミング)と比較的高度なケーススタディ(移行戦略、ツールのヒント)を交互に行いましょう。
  • 他の社内プロジェクトで得られた知識を伝授する
    • 成功したこと、失敗したこと、客観的に評価できる結果など、プロジェクトリーダーに Kotlin の知識を披露してもらいましょう。
    • このような知見は、新しいチームが選定できる「Kotlin 戦略集」に含めることができます。
  • チームの開発者にスポットを当てる
    • 誰もが裏技、ライブラリ、または克服した失敗を紹介できる 5~10 分程度のライトニングトークセッションを開催しましょう。
    • 貢献者を公の場で称えましょう。全社会議や社内ニュースレターで声援を送ることで、取り組みと知識の共有を促進できます。
  • フィードバックループを厳守する
    • セッションが終了するたびに、分かりやすさや有用性についての簡単なアンケートを取りましょう。その後、アンケートの結果を基に今後のアジェンダを調整します。
    • 組織の職務をローテーションすることで、コミュニティが 1 人のスキル保持者に依存しないようにし、長期的な柔軟性を維持しましょう。

注意: 上記の提案の多くは簡単なようですが、 コミュニティの勢力を維持するために必要な労力は過小評価してはなりません。

4. 焦りは禁物…

既存文化を変えるには時間がかかります。 自分にとって革新的なツールに熱中すると、文化を押しつけることになり、逆効果になる危険性があります。 冒頭で述べた「説明するより実物を見せる」を念頭に、上記のすべての活動によって導入プロセスを促進するのが効果的です。

この連載の次の記事

開発者の説得から意思決定者の説得にトピックを移します。 次の記事では、Kotlin を導入するための説得力のあるビジネスケースを実際のデータと客観的に評価できる結果に基づいて作る方法をご紹介します。 開発者にとってのメリットを経営陣の議論に発展させる方法、生産性向上をコスト削減に関連付ける方法、さらには Kotlin が単なる技術改良ではなく、チームと会社の両方にとって戦略的な手段である理由を証明する方法について学びます。

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

Urs Peter

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

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

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

Alyona Chernyaeva

Alyona Chernyaeva

image description

Discover more