LINE Creators Studio 개발에 사용하는 Kotlin 소개

시작하기 전에

안녕하세요, 저는 LINE Fukuoka 개발 팀에서 일하는 Freddie Wang입니다. 저는 현재 LINE Creators Studio라는 새로운 Android 앱 개발을 맡고 있습니다. LINE Creators Studio는 누구나 자기만의 스티커를 만들 수 있도록 도와주는 스티커 제작 툴입니다. LINE Creators Studio로 만든 스티커는 LINE Store에서 판매도 할 수 있습니다.

이 블로그에서는 LINE Creators Studio 앱 개발에 전적으로 사용하고 있는 프로그래밍 언어인 Kotlin에 대해 이야기해보려 합니다. 왜 Kotlin을 주요 언어로 선택했는지 설명하고 Kotlin의 어떤 기능을 주로 사용하고 있는지 소개하겠습니다.

참고: LINE Creators Studio는 현재 일본에서만 제공되고 있으며 다른 지역에도 곧 배포될 예정입니다.

Kotlin의 장점과 핵심 기능

Google I/O 2017에서 Google은 Android Studio 3.0부터 Kotlin을 공식 지원한다고 발표한 바 있습니다. 2016년 말 LINE Creators Studio 개발 프로젝트를 킥오프했을 당시 저희는 이 프로젝트를 단기간에 MVP(Minimum Viable Product) 프로젝트로 키워야 하는 과제를 안고 있었습니다. Kotlin 1.0이 출시된 지 한참 지난 후였지만 저희 팀에는 Kotlin을 사용해 본 사람이 아무도 없었습니다. 그래서 개발을 시작하기 전에 먼저 Kotlin을 사용하면 어떨지 알아보았는데, 그 결과 아래에서 설명하는 이유 때문에 Kotlin을 사용하기로 결정했습니다.

Kotlin을 선택하게 된 배경

Java와의 100% 호환성

Kotlin의 가장 큰 매력은 Kotlin 코드와 Java 코드를 한 프로젝트에서 함께 쓸 수 있고 기존의 Java 라이브러리를 모두 사용할 수 있다는 것입니다. 우리 프로젝트에는 레거시 Java 코드는 없지만 Dagger 2, Retrofit, RxJava 같은 Java 호환 라이브러리를 사용하고 싶었습니다.

간결한 구문

Kotlin은 문제 해결을 위해 설계된 언어입니다. Kotlin의 주요 목표 중 하나는 깔끔한 코드를 Java보다 쉽게 쓰는 것입니다. 이것이 Android 애플리케이션을 개발할 때 최우선적이고 필수적인 요인입니다.

줄어든 종속성

Kotlin은 Guava처럼 크기가 큰 Java 라이브러리를 대체할 수 있는 컴팩트한 런타임 라이브러리를 가지고 있습니다. 서버나 데스크톱 환경에서는 대용량 라이브러리가 큰 문제가 아니지만 Android에서는 문제를 일으킬 수 있습니다. Android 앱을 개발할 때는 65K 메서드 제한이 있기 때문에 대용량 Java 라이브러리의 사용을 피해야 합니다. Kotlin의 stdlib 라이브러리(버전 1.1.3-2)는 메서드 개수가 6306개라서 메서드 개수에 따른 영향이 Guava 라이브러리보다 적습니다.

이전 버전의 Android 기기 지원

Kotlin 1.0은 Java 6을 기준으로 하기 때문에 버전 2.3 이상인 Android 기기를 지원할 수 있습니다. 이 또한 Android 개발자에게 중요한 요인입니다.

Kotlin의 핵심 기능

Nullability

Kotlin 기능 중에 저희가 가장 마음에 들어하는 건 nullability 지원입니다. Kotlin 코드에서는 nullable 변수를 사용하고 Kotlin 컴파일러로 변수들이 올바르게 nullable로 지정되었는지 확인할 수 있습니다. Nullable 변수를 사용하면 보다 깔끔하고 안전하며 읽기 쉬운 코드를 작성할 수 있습니다. 다음은 null 변수를 사용한 간단한 코드 샘플입니다.

var output: String
output = null //compile complains

var output: String?
output = null //OK

//don't need to check null in every field
val name = bob?.department?.head?.name

Lambda expression

또 다른 유용한 기능은 lambda expression(람다 식)입니다. Java 8은 lambda를 지원하지만 Java 8은 최신 Android 기기에서만 지원됩니다. 반면 Kotlin은 lambda를 지원할 뿐 아니라 대부분의 Android 기기에서 실행됩니다. 그래서 저희는 LINE Creators Studio의 UI 코드에 lambda expression을 적극 사용하고 있습니다. 이렇게 하면 너무 많은 listener를 구현하거나 ButterKnife 같은 다른 view-binding 라이브러리를 사용할 필요가 없습니다.

예를 들어, 아래처럼 custom back 버튼을 만들 수 있습니다. Java 코드보다 훨씬 보기 좋지 않나요?

backButton = imageButton {
    id = R.id.my_back_button
    imageResource = R.drawable.ic_back
    onClick {
        onBackPressed()
    }
}

Extension

Extension 또한 Kotlin의 대표적인 유용한 기능입니다. Extension을 사용하면 base class를 생성하지 않고도 Android 프레임워크를 확장할 수 있습니다. 한 예로, Fragment class의 extension 함수를 생성하면 이 함수로 한 Fragment를 다른 Fragment와 스와핑할 수 있습니다. 각 Fragment별로 BaseCustomFragment class를 만드는 대신 모든 Fragment가 이 Fragment extension을 사용하도록 할 수 있습니다.

fun Fragment.addFragment(fragment: Fragment) {
    fragmentManager.beginTransaction()
            .setCustomAnimations(R.anim.slide_left_in,
                                 R.anim.slide_left_out, 
                                 R.anim.slide_right_in,
                                 R.anim.slide_right_out)
            .add(R.id.container, fragment)
            .addToBackStack(fragment::class.java.simpleName)
            .hide(this)
            .commit()
}

//In another Fragment, don't need to write a BaseCustomFragment class
onClick { addFragment(AnotherFragment()) }

아래 코드는 conversion extension을 사용해서 Boolean을 정수로 변환하는 간단한 예시입니다. 이 extension은 기존 값을 JSON 타입 데이터로 변환해야 할 때 유용합니다. Boolean 값을 손쉽게 정수로 바꿀 수 있습니다.

fun Boolean.toInt(): Int {
    return if (this) 1 else 0
}

fun Int.toBoolean(): Boolean {
    return this != 0
}

//In another data class, we can use the extension like this
data.intValue = true.toInt()
data.booleanValue = 1.toBoolean() 

Named argument

Kotlin을 사용하기 전에는 여러 개의 파라미터(주로 4개 이상)를 사용하는 함수가 모든 이에게 골칫거리였습니다. 하지만 Kotlin의 named argument를 사용하면 훨씬 더 읽기 쉬운 코드를 쓸 수 있습니다. 저희는 LINE Creators Studio에서 곡선 트리밍 툴을 제공하기 위해 Catmull-Rom spline 함수를 사용하는데 이 함수는 7개의 파라미터를 필요로 합니다. 만약 named argument를 사용하지 않는다면 코드가 훨씬 길고 복잡해질 겁니다.

fun catmullRomControlPoint(controlPoint: PointF, 
                           prevPoint: PointF,       
                           currentPoint: PointF,
                           nextPoint: PointF, 
                           delta1: Double, 
                           delta2: Double, 
                           alpha: Float) {
...
}

//When not using named arguments
catmullRomControlPoint(controlPoint1, 
                       point0, 
                       point1,
                       point2, 
                       delta1, 
                       delta2, 
                       alphaValue)

//When using named arguments
catmullRomControlPoint(controlPoint = controlPoint1, 
                       prevPoint = point0, 
                       currentPoint = point1,
                       nextPoint = point2, 
                       delta1 = delta1, 
                       delta2 = delta2, 
                       alpha = alphaValue)

Data class

다른 언어와 달리 Java는 struct를 지원하지 않기 때문에 Java 개발자들에게 POJO(Plain Old Java Object)를 생성하는 작업은 짜증나고 번거로운 일입니다. 많은 Java 개발자들이 이 일을 조금이라도 쉽게 하기 위해 lombok을 사용하는데, Kotlin의 data class를 사용하면 더 쉽게 할 수 있습니다. 아래 보이는 것처럼 data class를 생성하기만 하면 됩니다. 그러면 Kotlin 컴파일러가 자동으로 getter와 setter를 생성합니다. Kotlin 컴파일러는 equals(), hashCode(), copy() 같은 기본 함수도 생성할 수 있습니다.

//Just create the data class in one line, and that's it!
data class User(var name: String, var email: String, var age: Int)

또한 gson의 annotation도 사용할 수 있습니다.

data class User(@SerializedName("userName") var name: String, 
                @SerializedName("userEmail") var email: String, 
                var age: Int)

Anko

Kotlin은 언어 기능 외에도 여러 유용한 라이브러리를 제공합니다. LINE Creators Studio에 주로 사용하는 라이브러리는 stdlibAnko(Android용)입니다. Anko는 Kotlin의 라이브러리 중 하나인데, Android layout을 위한 DSL(Domain Specific Language), SQLite를 위한 파싱, Android SDK를 위한 helper 함수 등 여러 핵심 기능을 제공합니다. 이런 기능들은 모두 Android 개발자가 보다 쉽고 빠르게 작업하도록 도와줍니다.

저희가 가장 많이 사용하는 기능은 Anko Layouts입니다. 이 기능이 제공하는 DSL을 사용하면 기존의 XML 방식보다 더 쉽게 Android UI를 만들 수 있습니다. Anko DSL을 사용해서 아래처럼 UI 페이지를 생성할 수 있습니다.

//anko DSL
class MainActivityUi : AnkoComponent<MainActivity> {
    lateinit var aButton: Button
    override fun createView(ui: AnkoContext<MainActivity>): View = with(ui) {
        relativeLayout {
            aButton = button {
                textResource = R.string.ok
                onClick {
                    toast(“click button”)
                }
            }
        }.lparams(width = matchParent, height = wrapContent) {
            alignParentBottom()
        }
    }
}


Anko Layouts를 사용하면 다음의 이점을 누릴 수 있습니다.

  • XML보다 뛰어난 유연성
  • 성능 개선
  • Anko는 모든 UI 뷰를 프로그래밍 방식으로 생성하기 때문에 아래처럼 lambda expression을 사용해서 DSL에 조건을 추가할 수 있습니다. 하지만 XML을 사용하면 ViewStub를 사용하거나 toolTipLayoutRelativeLayout에 동적으로 추가해야 합니다. 이런 이유 때문에 LINE Creators Studio 프로젝트에서는 XML을 써서 레이아웃을 작성하지 않습니다. 예외적으로 XML을 사용하는 경우는 값을 지정할 때뿐입니다. 또한, 테스트 결과에서도 Anko DSL을 사용하지 않던 이전 Android 기기보다 UI 생성 시간이 단축된 것으로 나타났습니다.

    relativeLayout {
        ...
        if (shouldShowToolTip) {
            toolTipLayout()
        }
    }
    

    또 한 가지 Anko DSL을 사용하면 좋은 점은 UI 프리뷰 툴에 의존하지 않고도 UI 레이아웃을 수월하게 생성할 수 있다는 것입니다. Anko는 Android Studio와 IntelliJ를 위한 UI 프리뷰 툴을 제공하기는 합니다. 그런데 이 툴은 Android Studio 2.4 이상 버전에서만 동작하는 반면 저희는 Android Studio 2.3을 사용하고 있습니다. 그렇다 하더라도 저희에게는 큰 문제가 아닙니다. Anko DSL의 도움을 받으면 구조적인 구문을 사용하는 자체 UI 레이아웃을 쉽게 만들 수 있습니다.

    Anko는 비동기적인 작업 수행에 유용한 함수를 여럿 제공합니다. 예를 들어, doAsync() 함수를 사용해서 bitmap을 이미지 뷰로 로딩합니다.

    doAsync {
        val bitmap = loadImage()
        uiThread {
            imageView.imageBitmap = bitmap
        }
    }
    

    Mockito를 사용한 유닛 테스팅


    Java와 달리 Kotlin의 클래스는 모두 기본적으로 final인데, Mockito는 open으로 선언된 클래스만 mocking할 수 있습니다. 이는 유닛 테스트(unit test)를 어렵게 만들 수 밖에 없습니다. 어떤 프로토콜이 클라이언트와 서버 사이에서 제대로 작동하는지 테스트한다고 가정했을 때, 프로토콜의 내부 동작은 특정 데이터 모델에 따라 달라지기 때문에 Mockito를 사용해서 응답 데이터의 반환을 mocking할 수 없습니다. 다행히 몇 가지 해결책이 있습니다.

  • Mockito 2 사용. 이 경우 여전히 org.mockito.plugins.MockMaker 파일을 사용해야 합니다.
  • Kotlin 1.0.6 버전부터 제공되는 all-open 플러그인 사용
  • 코드에 인터페이스를 최대한 사용
  • 저희는 프로젝트를 mocking 가능하게 해서 유닛 테스트를 수행하기 위해 결국 기존의 클래스 기반 소스 코드를 인터페이스 기반으로 수정하게 되었습니다. 그런데 수정한 덕분에 얻은 혜택이 하나 더 있습니다. 소스 코드에서 “bad smell”이 나는 것을 방지할 수 있게 된 것입니다.

    마치며

    LINE Creators Studio 프로젝트를 시작하기 전에는 Kotlin에 대해 의심과 우려가 있었습니다. 하지만 Kotlin 코드를 쓰면 쓸수록 올바른 결정을 내렸다는 확신이 생겼습니다. 지금은 모든 팀원이 Kotlin 코드를 좋아하게 되었고 팀의 생산성도 크게 향상했습니다. 여러분도 Kotlin을 아직 시도해보지 않았다면 한번 써 보실 것을 권합니다. 아마 후회하지 않으실 겁니다.