[Kotlin] Kotlin으로 Java/Kotlin 모두 사용 가능한 유틸성 라이브러리 만들기

유틸성 라이브러리의 필요성

프로젝트를 진행하다 보면, 공통적으로 필요한 기능들이 정말 많다는 것을 알 수 있다. 그리고 이런 공통의 기능들을 사용하기 좋게 기능들을 모아둔 것을 '유틸성 라이브러리'라고 한다. 아마도 국내에서 많은 개발자들이 쓸법한 유틸성 라이브러리라 하면 스프링이나 전자정부 프레임워크에서 제공하는 유틸성 라이브러리일 것이다. 물론 대부분의 경우 이런 라이브러리를 다운받아 사용하면 된다. 하지만 직접 라이브러리를 만들어서 사용하면 관리에 용이하기도 하고, 무엇보다 성취감도 생기기 때문에 이 기회에 직접 라이브러리를 만들어보자고 생각했다.

첫번째 난관 : static 키워드의 부재

코틀린에서는 static 키워드를 제공하지 않는다. 그런 까닭에 코틀린에서는 상수나 정적인 메소드를 선언해야할 경우, 별도의 방식을 이용해야 한다. 그 중 하나가 바로 클래스 외부에서 선언을 하는 것이다. 모든 것이 반드시 클래스 내부에 선언 하도록 되어있는 자바 언어와는 큰 차이점이 있는 부분이다.

package dev.retrotv.data.utils

import java.io.File

public class FileUtils {

    // Java에서는 모든 것이 class 내부에서 선언되어야 한다
    public static boolean isNull(File file) {
        return file == null;
    }
}

Java에서의 정적 메소드 선언

/*
 * Java에서 Kotlin Top-level 영역에 선언한 변수/메소드를 사용하기 위해서는,
 * 반드시 JVM Signature를 명시적으로 표기해야만 한다
 */
@file:JvmName("FileUtils")
package dev.retrotv.data.utils

import java.io.File

fun isNull(file: File?): Boolean = file == null

Kotlin에서의 정적 메소드 선언

위의 코틀린 언어처럼 선언을 하면 JavaKotlin에서는 각각 다음과 같이 호출이 가능하다.

// Java
void checkFile(File file) {
    FileUtils.isNull(file);
}

// Kotlin
fun checkFile(file: File) {

    // FileUtils.isNull(file) 형식으로는 호출되지 않는다
    isNull(file)
}

코틀린에서는 내가 예상한것과는 다르게 메소드를 호출했다는 점에 의아함을 느끼긴 했지만, 일단 기능에는 문제가 없었기 때문에 당장은 넘어가기로 생각했다. 그리고 그 다음 문제는 바로 이 안일한 생각 때문에 터지고야 말았다.

두번째 난관 : Top-level 선언의 문제점

JVM 기반의 언어는 중간 단계 코드를 생성할 때, 어떤식으로든 클래스가 존재하는 형태로 바이트 코드를 컴파일 한다. 그러나, 코틀린이란 언어를 통해 보면 Top-level에서 선언한 상수나 메소드들은 특정 클래스에 속해있지 않는 형태인 것처럼 되어있기 때문에, 클래스명을 통해 상수나 메소드를 구분할 수 없다.

이렇게 되면 생기는 문제가, 특성에 따라 유틸 클래스를 구분해 놓았는데 실제로는 거대한 하나의 파일에 모든 상수와 메소드가 모여있는 것과 다름없는 형태가 된다는 점이다. 이것이 잘 이해가되지 않는다면, 아래의 사진을 보도록 하자.

위의 사진은 내가 생성한 KDoc 문서의 메소드명 부분을 캡쳐한 것이다. ByteUtils, StringUtils, NumberUtils 등... 내가 생성한 유틸 파일은 여러가지인데 하나의 문서 내부에 모든 메소드가 최상위 레벨 문서에 모두 다 때려넣어져 있는 것을 볼 수 있다. 물론 메소드를 사용하는데는 문제가 없지만, 이런식으로 생성되는 문서는 보기에 좋지 않다. 또 하나의 문제라면, 하나의 파일에 존재하는 메소드를 다른곳에서 호출할 수 없다는 문제도 있다.

최종적인 결론 : object 키워드 사용하기

코틀린에서는 상수나 정적인 메소드를 만드는 방법이 한가지 더 존재한다. 그것은 바로 object 키워드를 사용하는 것이다.

object Utils {
    ...
}

object 키워드를 사용하면, 클래스를 정의함과 동시에 객체를 생성한다. 단 일반 클래스와는 달리 생성자는 가질 수 없다.

object FileUtils {

    // Java에서 static 메소드처럼 사용하려면 @JvmStatic 어노테이션을 선언해야한다
    @JvmStatic
    fun isNull(file: File?): Boolean = file == null
}

위의 코틀린 언어처럼 선언을 하면 JavaKotlin에서는 각각 다음과 같이 호출이 가능하다.

// Java
void checkFile(File file) {
    FileUtils.isNull(file);
}

// Kotlin
fun checkFile(file: File) {
    FileUtils.isNull(file)
}

코틀린 쪽이 조금 복잡해지긴 했지만, 더 이상 문서 분리에 대한 문제도 생기지 않고 다른 파일에 있는 메소드를 호출할 때 생기던 문제도 발생하지 않는다.


정리하며

유틸 라이브러리를 만들면서 JavaKotlin의 차이 때문에 이런저런 트러블이 상당히 많았다. 이번에 글을 정리하면서 Kotlin으로 호환되는 라이브러리를 만드는데 도움이 되길 바라면서 이 글을 마치도록 하겠다.


참고한 문헌 및 글

[Java/Kotlin] Kotlin은 static이 없다.
Kotlin에는 static 키워드가 없다.🧐 그럼 코틀린에선 static을 대체하기 위해 무엇을 써야 할까?우선, 안드로이드에선 정적(static) 변수/메서드를 사용하는 경우가 보통은 아래와 같다.Activity, Fragment의 인텐트 extra로 사용하는 키
What is the equivalent of Java static methods in Kotlin?
There is no static keyword in Kotlin. What is the best way to represent a static Java method in Kotlin?