대규모 팀에 Kotlin 채택

새로운 언어로 전환하는 일은 어려울 수 있습니다. 천천히 시작하여 청크 단위로 이동하고 자주 테스트하여 팀을 조율해야 합니다. Kotlin은 JVM 바이트 코드로 컴파일되고 자바와 완벽하게 호환되므로 쉽게 이전할 수 있습니다.

팀 빌드

이전하기 전에 먼저 팀에서 Kotlin에 관한 공통된 이해 기준을 확립해야 합니다. 다음은 팀에서 신속하게 Kotlin을 학습하는 데 유용한 몇 가지 도움말입니다.

스터디 그룹 만들기

스터디 그룹을 만들면 효과적으로 Kotlin을 학습하고 기억하는 데 도움이 됩니다. 연구에 따르면 그룹 환경에서 배운 내용을 암송하면 습득한 지식을 강화하는 데 도움이 된다고 합니다. 스터디 그룹의 각 팀원을 위한 Kotlin 도서 또는 기타 학습 자료를 준비하여 매주 몇 장씩 학습하도록 하세요. 매번 모일 때마다 그룹에서 배운 것을 비교하고 질문이나 관찰사항을 논의해야 합니다.

강의 환경 조성

모두가 자신을 강사라고 생각하지는 않지만 강의 능력을 갖추고 있습니다. 기술 리더 또는 팀 리더부터 개별 참여자까지 모두가 성공적인 Kotlin 이전을 위한 학습 환경을 조성할 수 있습니다. 이를 위해 팀에서 한 사람을 지정하여 학습한 내용이나 공유하고 싶은 내용을 발표하는 정기적인 프레젠테이션을 마련하는 게 도움이 됩니다. 팀이 Kotlin에 익숙해질 때까지 지원자에게 매주 새로운 장을 발표하도록 요청하는 방식으로 스터디 그룹을 활용할 수 있습니다.

챔피언 지정

마지막으로 학습 활동을 주도할 챔피언을 지정합니다. 챔피언은 채택 프로세스를 시작할 때 주제 전문가(SME) 역할을 할 수 있습니다. Kotlin과 관련된 모든 실무 회의에 챔피언을 포함하는 것이 중요합니다. Kotlin에 관해 열의가 있고 실무 지식을 갖춘 팀원을 챔피언으로 지정하는 것이 좋습니다.

천천히 통합

천천히 시작하여 생태계 중 어떤 부분을 먼저 이전할지 전략적으로 사고하는 것이 중요합니다. 대체로 주력 앱보다는 조직 내 단일 앱에만 천천히 통합하는 전략을 적용하는 편이 가장 좋습니다. 선택한 앱의 이전과 관련하여 각 상황은 다양하지만 몇 가지 공통되는 시작 지점은 다음과 같습니다.

데이터 모델

데이터 모델은 몇 가지 메서드와 함께 여러 상태 정보로 구성되어 있을 수 있습니다. 데이터 모델에는 toString(), equals(), hashcode()와 같은 일반적인 메서드도 있을 수 있습니다. 이러한 메서드는 일반적으로 격리된 상태로 쉽게 전환되고 단위 테스트될 수 있습니다.

예를 들어 다음 자바 스니펫을 가정해보겠습니다.

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 한 줄로 바꿀 수 있습니다.

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

그런 다음, 이 코드를 현재 테스트 모음으로 단위 테스트할 수 있습니다. 여기에서는 한 번에 모델 하나씩, 주로 동작이 아니라 상태인 전환 클래스로 작게 시작하는 게 중요합니다. 이 과정에서 테스트를 자주 해야 합니다.

테스트 이전

고려할 또 다른 시작 경로는 기존 테스트를 변환하여 Kotlin으로 새로운 테스트 작성을 시작하는 것입니다. 이렇게 하면 앱과 함께 제공할 코드를 작성하기 전에 팀이 언어에 익숙해질 시간을 줄 수 있습니다.

유틸리티 메서드를 확장 함수로 이동

모든 정적 유틸리티 클래스 (StringUtils, IntegerUtils, DateUtils, YourCustomTypeUtils 등)는 Kotlin 확장 함수로 표현될 수 있으며 기존 자바 코드베이스에서 사용할 수 있습니다.

예를 들어 몇 가지 메서드가 있는 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 인터페이스를 자바 호출자에 제공하는 동시에 더 간결한 API를 증가하는 Kotlin 코드베이스에 제공할 수 있습니다.

이 작업을 실행하려면 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
}

이러한 함수는 이제 각 언어와 일치하는 규칙이 있는 자바 또는 Kotlin을 사용하여 호출할 수 있습니다.

Kotlin

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

Java

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

이전 완료

팀이 Kotlin에 익숙해지고 작은 영역을 이전한 후에는 프래그먼트, 활동, ViewModel 객체, 비즈니스 로직과 관련된 기타 클래스와 같은 더 큰 구성요소를 처리할 수 있습니다.

고려사항

자바에 특정 스타일이 있는 것과 마찬가지로 Kotlin에도 간결함에 기여하는 직관적인 자체 스타일이 있습니다. 그러나, 처음에는 팀에서 만드는 Kotlin 코드가 대체하는 자바 코드와 비슷하게 보일 수 있습니다. 이러한 현상은 시간이 지남에 따라 팀의 Kotlin 사용 경험이 많아지면 변합니다. 점진적 변경이 성공의 열쇠라는 것을 잊지 마세요.

다음은 Kotlin 코드베이스가 늘어남에 따라 일관성을 유지할 수 있는 몇 가지 방법입니다.

일반적인 코딩 표준

채택 프로세스 초기에 표준 코딩 규칙 세트를 정의해야 합니다. 타당한 Android Kotlin 스타일 가이드에서 벗어날 수 있습니다.

정적 분석 도구

Android 린트와 기타 정적 분석 도구를 사용하여 팀에 코딩 표준 세트를 시행합니다. 타사 Kotlin 린터인 klint도 Kotlin에 추가 규칙을 제공합니다.

지속적 통합

일반적인 코딩 표준을 준수하고 Kotlin 코드에 충분한 테스트 범위를 제공해야 합니다. 자동화된 빌드 프로세스의 이 부분을 만들면 일관성과 이러한 표준의 준수를 보장할 수 있습니다.

상호운용성

Kotlin은 대체로 자바와 원활하게 상호운용되지만 다음을 유의하세요.

Null 허용 여부

Kotlin은 컴파일된 코드에서 null 허용 여부 주석에 의존하여 Kotlin 측에서 null 허용 여부를 추론합니다. 주석이 제공되지 않으면 Kotlin은 기본적으로 null을 허용하거나 허용하지 않는 유형으로 취급될 수 있는 플랫폼 유형을 설정합니다. 그러나 주의하여 취급하지 않으면 런타임 NullPointerException 문제가 발생할 수 있습니다.

새로운 기능 채택

Kotlin은 여러 새로운 라이브러리와 구문을 제공하여 상용구를 줄이므로 개발 속도를 높이는 데 도움이 됩니다. 즉, 수집 함수, 코루틴, 람다와 같은 Kotlin의 표준 라이브러리 함수를 사용할 때는 신중하고 체계적이어야 합니다.

다음은 새로운 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가 null이 아닌 경우 foo.baz()foo.zap()을 실행하는 것이므로 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에는 위임을 통해 동작을 구현하는 지원이 기본 제공되어 구성을 간소화하는 데 도움이 됩니다.

이 동작은 인터페이스 구현이나 상속에 의존하여 테스트 중에 동작을 재정의하는 모의 프레임워크(예: Mockito)에 문제를 일으킵니다. 단위 테스트의 경우 Mockito의 Mock Maker Inline 기능을 사용 설정하여 최종 클래스와 메서드를 모의 처리할 수 있습니다. 또는 All-Open 컴파일러 플러그인을 사용하여 컴파일 프로세스의 일부로 테스트하려는 Kotlin 클래스와 클래스 멤버를 열 수 있습니다. 이 플러그인 사용의 주요 이점은 단위 및 계측 테스트에서 모두 작동한다는 것입니다.

추가 정보

Kotlin 사용에 관한 자세한 내용은 다음 링크를 확인하세요.