שימוש ב-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 + '\'' +
               '}';
   }
}

אפשר להחליף את מחלקת Java בשורה אחת של Kotlin, כמו בדוגמה הבאה:

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

לאחר מכן, תוכלו לבצע בדיקת יחידה (unit test) של הקוד הזה מול חבילת מקרים לבדיקה הנוכחית שלכם. התחילו בקטן עם מודל אחד בכל פעם, והעבירו מחלקות שהן בעיקר מצב ולא התנהגות. חשוב לבדוק את האפליקציה לעיתים קרובות במהלך הפיתוח.

העברת בדיקות

מקום נוסף להתחיל בו הוא הבדיקות: המירו בדיקות קיימות וכתבו בדיקות חדשות ב-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, אתם יכולים לספק את אותו ממשק Utils למבצעי קריאות ב-Java, וגם להציע API תמציתי יותר לבסיס הקוד של Kotlin שגדל.

כדי לעשות את זה, אפשר להתחיל בהמרה של המחלקה Utils ל-Kotlin באמצעות ההמרה האוטומטית שסביבת הפיתוח המשולבת (IDE) מספקת. פלט לדוגמה:

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 ככל שהוא גדל:

תקני תכנות נפוצים

חשוב להגדיר קבוצה סטנדרטית של מוסכמות קידוד בשלב מוקדם בתהליך ההטמעה. אפשר לחרוג ממדריך הסגנון של Kotlin ל-Android במקרים שבהם זה הגיוני.

כלים לניתוח סטטי

אפשר לאכוף את תקני הקידוד שהוגדרו לצוות באמצעות Android lint וכלי ניתוח סטטיים אחרים. ktlint, כלי של צד שלישי לניתוח קוד Kotlin, מספק גם כללים נוספים ל-Kotlin.

אינטגרציה רציפה (CI)

חשוב להקפיד על תקני קידוד נפוצים ולספק כיסוי מספיק של בדיקות לקוד Kotlin. אם משלבים את השלב הזה בתהליך אוטומטי של בניית גרסה, אפשר לוודא שהתהליך עקבי ושעומד בתקנים האלה.

יכולת פעולה הדדית

ברוב המקרים, Kotlin פועלת בצורה חלקה עם Java, אבל חשוב לזכור את הנקודות הבאות.

מאפיין המציין אם ערך יכול להיות ריק (nullability)

‫Kotlin מסתמכת על הערות nullability בקוד שעבר הידור כדי להסיק nullability בצד של Kotlin. אם לא מספקים הערות, Kotlin משתמשת כברירת מחדל בסוג פלטפורמה שאפשר להתייחס אליו כ-nullable או כ-non-nullable. אם לא תטפלו בסוגי הפלטפורמות בקפידה, עלולות להתרחש בעיות בזמן הריצה של 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()
}

בדוגמה הזו, הכוונה היא להפעיל את foo.baz ואת foo.zap אם nullableFoo לא ריק, וכך להימנע מ-NullPointerException. הקוד הזה פועל כמו שצריך, אבל קשה יותר לקרוא אותו בהשוואה לבדיקה של ערך null ולהמרת טיפוס חכמה, כמו בדוגמה הבאה:

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, שמסתמכות על הטמעה של ממשק או על ירושה כדי לשנות התנהגויות במהלך הבדיקה. בבדיקות יחידה, אתם יכולים להפעיל את התכונה Mock Maker Inline של Mockito, שמאפשרת ליצור מוקים של מחלקות ושיטות סופיות. אפשר גם להשתמש בתוסף מהדר All-Open כדי לפתוח כל מחלקה של Kotlin והחברים שלה שרוצים לבדוק כחלק מתהליך ההידור. היתרון העיקרי בשימוש בתוסף הזה הוא שהוא פועל גם עם בדיקות יחידה וגם עם בדיקות מכשור.

מידע נוסף

מידע נוסף על השימוש ב-Kotlin זמין בקישורים הבאים: