把 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 編寫新測試。這樣一來,在編寫您打算隨應用程式轉移的程式碼之前,您的團隊就有時間熟悉這種語言。

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

任何靜態公用程式類別 (StringUtilsIntegerUtilsDateUtilsYourCustomTypeUtils 等) 都可以表示為 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 = ...
val fooString = myString.foo()
...

Java

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

完成遷移

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

注意事項

跟 Java 有特定的樣式很類似,Kotlin 擁有獨特的語言習慣樣式,使其簡明易用。您一開始可能會發現,團隊產生的 Kotlin 程式碼看起來與它取代的 Java 程式碼更像。隨著團隊使用 Kotlin 經驗日益增加,這種情況會慢慢改變。請記住,逐步改變是成功的關鍵。

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

常見的程式設計標準

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

靜態分析工具

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

持續整合

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

互通性

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

是否可為空值

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

採用新功能

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

以下是 Kotlin 入門級開發人員遇到的極常見陷阱。假設有以下 Kotlin 程式碼:

val nullableFoo: 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 提供內建支援,可透過委派功能實作行為,協助簡化組合。

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

更多資訊

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