대규모 팀에 Kotlin 채택

새로운 언어로 이전하는 것은 어려울 수 있습니다. 팀의 성공을 위해 천천히 시작하고, 작은 단위로 이전하고, 자주 테스트하세요. Kotlin은 JVM 바이트 코드로 컴파일되며 Java와 완전히 상호 운용되므로 이전이 더 쉽습니다.

팀 빌드

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

스터디 그룹 만들기

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

강의 환경 조성

모두가 자신을 강사라고 생각하지는 않지만 강의 능력을 갖추고 있습니다. 기술 리더 또는 팀장부터 개별 참여자까지 모두가 성공을 위한 학습 환경을 조성할 수 있습니다. 이를 위해 팀에서 한 사람을 지정하여 학습한 내용이나 공유하고 싶은 내용을 발표하는 정기적인 프레젠테이션을 마련하는 게 도움이 됩니다. 팀이 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?)

그런 다음 현재 테스트 모음에 대해 이 코드를 단위 테스트할 수 있습니다. 한 번에 하나의 모델로 작게 시작하고 동작보다는 주로 상태인 클래스를 이전합니다. 이 과정에서 자주 테스트합니다.

테스트 이전

테스트에서 시작할 수도 있습니다. 기존 테스트를 변환하고 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
}

이러한 함수는 이제 각 언어와 일치하는 규칙이 있는 자바 또는 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 린트 및 기타 정적 분석 도구를 사용하여 팀에 설정된 코딩 표준 모음을 적용합니다. 서드 파티 Kotlin 린터인 ktlint는 Kotlin에 관한 추가 규칙도 제공합니다.

지속적 통합

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

상호 운용성

Kotlin은 대부분 Java와 원활하게 상호 운용되지만 다음 사항에 유의하세요.

null 허용 여부

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

새로운 기능 채택

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

다음은 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가 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은 상속보다 구성을 우선시하기 위해 이 선택을 합니다. 구성을 간소화하기 위해 Kotlin에는 위임을 통해 동작을 구현하는 기본 지원이 있습니다.

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

추가 정보

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