把 Kotlin 引進到大型團隊

改用新語言是一項艱鉅的任務。為確保團隊順利遷移,請緩慢起步,分批遷移,並頻繁測試。Kotlin 會編譯為 JVM 位元碼,並與 Java 完全互通,因此遷移作業更輕鬆。

建立團隊

遷移前的第一步,是使團隊對基準的理解完全一致。以下是幾個有助於提高團隊學習速度的提示。

組成讀書會

讀書會是促進學習、鞏固記憶力的有效方式。研究表明,在群組中背誦學到的內容,有助於鞏固對這些內容的記憶。請為讀書會的每位成員指定 Kotlin 圖書或其他學習教材,然後要求所有成員每週閱讀幾章內容。在每次會面時,所有成員應比較自己學到的知識,並討論任何問題或觀察結果。

形成教學文化

儘管並非所有人都自視為老師,但每個人都可以傳授知識。從技術或團隊主管到每位貢獻者,每位成員都能促進學習氛圍,從而為確保獲得成功出一分力。促進學習氛圍的方法之一,就是定期舉行研討會,並指定團隊的某位成員就所學內容或想分享的心得發言。您可以善用讀書會,要求志工每週發表新章節的內容簡報,直到您認為團隊能夠適當掌握這種語言為止。

指定帶領者

最後,請指定帶領著來引領學潮。在您開始採用程序時,這個人可以做為「主題專家」(SME)。請務必讓此人加入與 Kotlin 相關的所有練習會議。理想情況下,此人熱衷於 Kotlin 事務,而且具備一些工作知識。

逐步整合

緩慢起步,並從戰略上思考先整合生態系統的哪些部分,這是關鍵。通常,最好的做法是將這項功能限定在機構內某個應用程式中,而限定在不是旗艦應用程式中。雖然在遷移所選應用程式時,每種情況都不盡相同,但以下舉出幾個可著手的常見方面。

資料模型

您的資料模型可能由大量狀態資訊和少量方法組成。資料模型也可能包含常用的方法,例如 toString、equals 和 hashCode。您通常可以遷移這些方法,並單獨進行單元測試。

例如,假設有以下 Java 程式碼片段:

public class Person {

   private String firstName;
   private String lastName;
   // ...

   public String getFirstName() {
       return firstName;
   }

   public void setFirstName(String firstName) {
       this.firstName = firstName;
   }

   public String getLastName() {
       return lastName;
   }

   public void setLastName(String lastName) {
       this.lastName = lastName;
   }

   @Override
   public boolean equals(Object o) {
       if (this == o) return true;
       if (o == null || getClass() != o.getClass()) return false;
       Person person = (Person) o;
       return Objects.equals(firstName, person.firstName) &&
               Objects.equals(lastName, person.lastName);
   }

   @Override
   public int hashCode() {
       return Objects.hash(firstName, lastName);
   }

   @Override
   public String toString() {
       return "Person{" +
               "firstName='" + firstName + '\'' +
               ", lastName='" + lastName + '\'' +
               '}';
   }
}

您可以使用一行 Kotlin 取代 Java 類別,如下所示:

data class Person(var firstName: String?, var lastName: String?)

接著,您可以用這個程式碼對目前的測試套件進行單元測試。每次從一個模型著手,並遷移主要是狀態而非行為的類別。在此過程中,務請頻繁進行測試。

遷移測試

另一個著手途徑是從測試開始:轉換現有測試,並以 Kotlin 編寫新測試。這樣一來,在編寫您打算隨應用程式轉移的程式碼之前,您的團隊就有時間熟悉這種語言。

將公用程式方法移至擴充功能函式

您可以將靜態公用程式類別 (例如 StringUtils、IntegerUtils、DateUtils 或 YourCustomTypeUtils) 表示為 Kotlin 擴充功能函式,並從現有 Java 程式碼集使用這些函式。

例如,假設 StringUtils 類別具備下列幾種方法:

package com.java.project;

public class StringUtils {

   public static String foo(String receiver) {
       return receiver...;  // Transform the receiver in some way
   }

   public static String bar(String receiver) {
       return receiver...;  // Transform the receiver in some way
   }

}

這些方法隨後可能會用於您應用程式的其他位置,如以下範例所示:

...

String myString = ...
String fooString = StringUtils.foo(myString);

...

使用 Kotlin 擴充功能函式,您就可以為 Java 呼叫端提供相同的 Utils 介面,同時為不斷擴大的 Kotlin 程式碼集提供更精簡的 API。

一開始,您可以先使用 IDE 提供的自動轉換功能,將這個 Utils 類別轉換為 Kotlin。輸出的範例大概如下:

package com.java.project

object StringUtils {

   fun foo(receiver: String): String {
       return receiver...;  // Transform the receiver in some way
   }

   fun bar(receiver: String): String {
       return receiver...;  // Transform the receiver in some way
   }

}

接下來,請移除類別或物件定義,在各個函式名稱前面加上適用此函式的類型,然後用它來參照函式內部的類型,如以下範例所示:

package com.java.project

fun String.foo(): String {
    return this...;  // Transform the receiver in some way
}

fun String.bar(): String {
    return this...;  // Transform the receiver in some way
}

最後,在來源檔案的頂端加入 JvmName 註解,讓編譯後的名稱與應用程式的其餘部分相容,如以下範例所示:

@file:JvmName("StringUtils")
package com.java.project
...

最終版本大概如下所示:

@file:JvmName("StringUtils")
package com.java.project

fun String.foo(): String {
    return this...;  // Transform `this` string in some way
}

fun String.bar(): String {
    return this...;  // Transform `this` string in some way
}

請注意,您現在可以使用 Java 或 Kotlin 呼叫這些函式,而這些函式包含符合各種語言的慣例。

Kotlin

// ...

val myString: String = "hello"
val fooString = myString.foo()

// ...

Java

...
String myString = ...
String fooString = StringUtils.foo(myString);
...

完成遷移

當團隊熟悉 Kotlin,且您已遷移較小的項目後,就可以繼續處理較大的元件,例如片段、活動、ViewModel 物件及與商業邏輯相關的其他類別。

注意事項

就像 Java 有特定的樣式,Kotlin 也有獨特的慣用樣式,使其簡明易用。不過,您一開始可能會發現,團隊編寫的 Kotlin 程式碼看起來與它取代的 Java 程式碼更像。隨著團隊使用 Kotlin 經驗日益增加,這種情況會慢慢改變。循序漸進是成功的關鍵。

當 Kotlin 程式碼集不斷擴大時,您可以採取下列做法來確保一致性:

常見的程式設計標準

在採用程序早期,請務必定義一組標準的程式設計慣例。您可以視情況偏離 Android 的 「Kotlin 樣式指南」。

靜態分析工具

請使用 Android lint 和其他靜態分析工具,強制執行團隊設定的程式設計標準。ktlint 是第三方 Kotlin Linter,也為 Kotlin 提供了其他規則。

持續整合

請務必遵守常用程式設計標準,並為您的 Kotlin 程式碼提供足夠的測試涵蓋範圍。在自動化建構程序中採用這個做法,有助於確保一致性和遵循這些標準。

互通性

Kotlin 大部分與 Java 順暢地互通,但請留意以下幾點。

是否可為空值

Kotlin 利用已編譯的程式碼中的是否可為空值註解,來推斷 Kotlin 端是否可為空值。如果未提供註解,Kotlin 預設為某種平台類型,即可視為可為空值或不可為空值的類型。如果未謹慎處理平台型別,可能會發生執行階段NullPointerException問題。

採用新功能

Kotlin 提供許多新程式庫和語法糖,藉此減少樣板,提升開發速度。即便如此,使用 Kotlin 的標準程式庫函式時請小心謹慎、有條不紊。這些函式包括集合函式、協同程式和lambda。

以下是 Kotlin 新手開發人員常遇到的陷阱。請參考以下 Kotlin 程式碼:

val nullableFoo: Foo? = Foo()

// This lambda executes only if nullableFoo is not null
// and `foo` is of the non-nullable Foo type
nullableFoo?.let { foo ->
    foo.baz()
    foo.zap()
}

這個範例中的用意是,如果 nullableFoo 不是空值,則執行 foo.baz 和 foo.zap,因此避免使用 NullPointerException。雖然這個程式碼可以正常執行,但相較於空值檢查和智慧型層級轉換,這個程式碼沒那麼容易讀取,如以下範例所示:

val nullableFoo: Foo? = null
if (nullableFoo != null) {
    nullableFoo.baz() // Using !! or ?. isn't required; the Kotlin compiler infers non-nullability
    nullableFoo.zap() // from guard condition; smart casts nullableFoo to Foo inside this block
}

測試

在 Kotlin 中,類別及其函式預設會關閉擴充功能。 您必須明確開啟要變成子類別的類別和函式。Kotlin 選擇透過繼承提高撰寫速度。為簡化撰寫作業,Kotlin 內建支援,可透過委派實作行為。

此行為會為模擬架構 (例如 Mockito) 帶來問題。在測試期間,這些架構利用介面實作或繼承來覆寫行為。如要進行單元測試,您可以啟用 Mockito 的 Mock Maker Inline 功能,藉此模擬最終類別和方法。您也可以使用 All-Open 編譯器外掛程式,開啟要在編譯過程中測試的任何 Kotlin 類別及其成員。使用這個外掛程式的主要優點,是能用於單元測試和檢測設備測試。

更多資訊

如要進一步瞭解如何使用 Kotlin,請參閱下列連結: