
소개
Kotlin은 정적 타입 언어로서 JVM 중심의 높은 상호운용성과 간결한 문법, 그리고 코루틴 기반 비동기 모델을 제공합니다. 이 글은 언어의 변하지 않는(또는 오랫동안 유지되어 온) 핵심 개념을 정리하고, 실무에서 고려해야 할 트레이드오프를 설명한 뒤 간단한 Kotlin Multiplatform 예제를 통해 실전 적용 방식을 보여줍니다.
핵심 개념 요약
아래 개념들은 Kotlin을 사용할 때 핵심적으로 이해해야 하는 안정적인 특징들입니다.
- 널 안전성(null-safety): Nullable 타입과 안전 호출(?.), 엘비스(?:) 연산자, non-null 단언(!!) 등을 통해 런타임 NPE를 줄이는 것을 목표로 합니다.
- 표현성 있는 타입 시스템: 데이터 클래스(data class), sealed 클래스/인터페이스, 타입 추론, 확장 함수(extension functions)로 도메인 모델을 깔끔하게 표현할 수 있습니다.
- 코루틴(Coroutines): 경량 스레드 스타일의 비동기 처리 모델을 제공합니다. structured concurrency(구조화된 동시성)를 지원하며 suspend 함수, Dispatcher, Flow 등을 사용합니다.
- 자바 상호운용성: 기존 Java 라이브러리와의 상호운용이 우수하여 JVM 생태계 자원을 활용할 수 있습니다.
- Kotlin Multiplatform (KMP): 공통 비즈니스 로직을 공유하고 플랫폼별 구현(expect/actual)을 분리하여 iOS, Android, JS, JVM 등에서 재사용 가능합니다.
주요 트레이드오프
언어 기능은 생산성을 올려주지만 항상 비용(성능, 복잡성, 빌드 시간 등)이 따릅니다. 대표적인 트레이드오프를 정리합니다.
- 간결성 vs 명시성 — Kotlin의 짧은 문법(데이터 클래스, 확장 함수)은 코드량을 줄여주지만 지나친 추상화는 가독성을 해칠 수 있습니다. 라이브러리 경계에서는 명시적인 계약을 선호하세요.
- 코루틴의 편의성 vs 실행 컨텍스트 관리 — 코루틴은 가볍지만 Dispatcher 선택, 스코프(CoroutineScope) 관리, 취소(Cancellation) 설계가 필요합니다. 잘못된 Dispatcher 사용은 블로킹 I/O로 인해 성능 문제를 일으킬 수 있습니다.
- 멀티플랫폼 공유 코드의 장점 vs 플랫폼별 복잡성 — 공통 코드를 늘리면 중복이 줄지만, I/O, 네이티브 API, UI 등 플랫폼별 차이를 관리하기 위한 expect/actual, 플랫폼별 테스트 및 빌드 설정이 필요합니다.
- 런타임·배포 측면 — JVM에서는 GC와 런타임 성능을 고려해야 하고, Native/JS 타겟은 바이너리 크기와 스타트업 특성, 상호운용성(특히 iOS/Objective-C/Swift) 관련 추가 작업이 필요합니다.
실전 예제: Kotlin Multiplatform으로 공통 비즈니스 로직 만들기
간단한 키-값 저장소 인터페이스를 공통 모듈에 두고 플랫폼별 구현을 제공하는 예제입니다. 공통 로직에서 코루틴을 사용해 비동기적으로 동작하도록 설계합니다.
공통 코드(commonMain)의 핵심 파일들:
// commonMain/src/commonMain/kotlin/PlatformStorage.kt
expect interface PlatformStorage {
suspend fun read(key: String): String?
suspend fun write(key: String, value: String)
}
// commonMain/src/commonMain/kotlin/Outcome.kt
sealed class Outcome<out T> {
data class Success<T>(val value: T) : Outcome<T>()
data class Error(val throwable: Throwable) : Outcome<Nothing>()
}
// commonMain/src/commonMain/kotlin/KeyValueRepository.kt
class KeyValueRepository(private val storage: PlatformStorage) {
suspend fun get(key: String): Outcome<String> = try {
val value = storage.read(key)
if (value != null) Outcome.Success(value) else Outcome.Error(NoSuchElementException("No value for $key"))
} catch (e: Throwable) {
Outcome.Error(e)
}
suspend fun put(key: String, value: String): Outcome<Unit> = try {
storage.write(key, value)
Outcome.Success(Unit)
} catch (e: Throwable) {
Outcome.Error(e)
}
}
JVM 플랫폼(actual 구현):
// jvmMain/src/jvmMain/kotlin/PlatformStorageJvm.kt
import java.util.concurrent.ConcurrentHashMap
actual class PlatformStorageJvm : PlatformStorage {
private val map = ConcurrentHashMap<String, String>()
actual override suspend fun read(key: String): String? = map[key]
actual override suspend fun write(key: String, value: String) { map[key] = value }
}
JS 플랫폼(actual 구현, 간단한 예):
// jsMain/src/jsMain/kotlin/PlatformStorageJs.kt
actual class PlatformStorageJs : PlatformStorage {
private val map = mutableMapOf<String, String>()
actual override suspend fun read(key: String): String? = map[key]
actual override suspend fun write(key: String, value: String) { map[key] = value }
}
JVM에서 동작 확인 (예: 콘솔 테스트):
// jvmMain/src/jvmMain/kotlin/Main.kt
import kotlinx.coroutines.runBlocking
fun main() = runBlocking {
val storage = PlatformStorageJvm()
val repo = KeyValueRepository(storage)
println(repo.put("hello", "world"))
println(repo.get("hello"))
}
간단한 설명:
- expect/actual 패턴으로 플랫폼별 구현을 분리해 공통 비즈니스 로직을 재사용합니다.
- Outcome 같은 sealed 클래스로 성공/실패를 표현하면 플랫폼 간 에러 처리 정책을 일관되게 유지할 수 있습니다.
- 코루틴(suspend)을 사용하면 호출자는 I/O 모델을 신경 쓰지 않고 비동기 처리를 할 수 있습니다. 실제 I/O가 블로킹이라면 플랫폼 구현에서 적절한 Dispatcher(예: Dispatchers.IO)로 위임해야 합니다.
실무 팁 요약
간단한 실무 조언들입니다.
- 공통 로직은 비즈니스 규칙과 도메인 모델에 집중시키고, I/O나 플랫폼 API는 expect/actual로 분리하세요.
- 코루틴 스코프와 취소 전략을 설계(예: ViewModelScope, lifecycleScope)해 메모리 누수를 방지하세요.
- 테스트는 공통 모듈과 플랫폼별 모듈을 별도로 구성해 단위 테스트와 통합 테스트를 나누어 작성하세요.
- 성능 민감한 부분은 프로파일링해서 JVM, Native, JS 각 플랫폼의 특성에 맞게 최적화하세요.
마무리
Kotlin은 생산성과 안전성을 모두 제공하는 언어로, 올바른 아키텍처와 플랫폼 경계를 설계하면 멀티플랫폼으로 큰 이점을 얻을 수 있습니다. 반면 플랫폼별 차이와 빌드/배포 비용은 반드시 고려해야 합니다. 위 예제는 간단한 시작점으로, 실무에서는 에러 처리, 로깅, 동시성 정책 등을 더 세밀하게 설계하시기 바랍니다.