Какие production-риски есть у Kotlin Multiplatform: memory, lifecycle bugs, accessibility, offline mode, app size или platform-specific behavior?
Production-риски KMP: различия в поведении Kotlin/Native vs JVM (freeze, memory model), разный размер бинарников на платформах, ограниченный stdlib на JS/Native, сложность отладки expect/actual и проблемы с зависимостями, не имеющими KMP-артефактов.
Production-риски Kotlin Multiplatform
Kotlin Multiplatform (KMP) позволяет разделять бизнес-логику между JVM, iOS (Kotlin/Native), Web (Kotlin/JS) и другими платформами. Переход в production выявляет ряд нетривиальных рисков.
Memory Model: Kotlin/Native vs JVM
До Kotlin 1.7.20 Kotlin/Native использовал строгую модель памяти (strict memory model): объекты, доступные из нескольких потоков, должны были быть «frozen». С 1.7.20 введена новая модель памяти (совместимая с JVM), но legacy код с freeze() ещё встречается:
// Старый код с freeze (Kotlin/Native, до 1.7.20)
@SharedImmutable
val config = AppConfig(apiUrl = "https://api.example.com").freeze()
// Новая модель (1.7.20+) — freeze не нужен
// gradle.properties:
// kotlin.native.binary.memoryModel=experimental (было)
// Теперь это default
// Современный общий код — просто data class
data class AppConfig(val apiUrl: String)
expect/actual и platform-specific баги
expect/actual механизм — основа KMP. Ошибки в actual-реализациях сложно отловить в общих тестах:
// commonMain
expect class PlatformLogger() {
fun log(message: String)
}
// androidMain
actual class PlatformLogger actual constructor() {
actual fun log(message: String) {
android.util.Log.d("App", message)
}
}
// iosMain
actual class PlatformLogger actual constructor() {
actual fun log(message: String) {
println(message) // NSLog в production лучше
}
}
Риск: поведение может отличаться между платформами. Тесты в commonTest проверяют только общую логику, не actual-реализации.
Зависимости без KMP-артефактов
Популярные JVM-библиотеки часто не имеют KMP-версий. Необходимо использовать expect/actual или искать KMP-альтернативы:
// build.gradle.kts — KMP-совместимые библиотеки
kotlin {
sourceSets {
val commonMain by getting {
dependencies {
// KMP-совместимые альтернативы
implementation("io.ktor:ktor-client-core:2.3.10") // вместо OkHttp
implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.6.3") // вместо Gson
implementation("org.jetbrains.kotlinx:kotlinx-datetime:0.5.0") // вместо java.time
implementation("io.insert-koin:koin-core:3.5.3") // DI без Hilt
}
}
val androidMain by getting {
dependencies {
implementation("io.ktor:ktor-client-okhttp:2.3.10")
}
}
val iosMain by getting {
dependencies {
implementation("io.ktor:ktor-client-darwin:2.3.10")
}
}
}
}
Размер iOS-бинарника
Kotlin/Native компилирует в значительно больший бинарник, чем Swift/ObjC. Типичное KMP-приложение добавляет 10–20 МБ к iOS IPA. Оптимизация через Xcode bitcode и strip symbols:
// build.gradle.kts — оптимизация Native бинарника
kotlin {
targets.withType<KotlinNativeTarget> {
binaries.all {
freeCompilerArgs += "-Xdisable-phases=EscapeAnalysis"
}
binaries.framework("shared") {
isStatic = true // статический фреймворк меньше динамического
optimized = true // включает оптимизации для release
}
}
}
Отладка и stack traces
Kotlin/Native crash reports в iOS Crashlytics показывают mangled символы. Нужен dSYM-файл и символизация:
// Включить дебаг-символы для release
binaries.framework("shared") {
debuggable = true // для staging
// В production — false, но сохраняйте dSYM отдельно
}
Подводные камни
- Legacy Kotlin/Native freeze model: код, написанный до 1.7.20, может падать с InvalidMutabilityException на новой модели памяти при неправильном использовании.
- Kotlinx.coroutines на iOS требует явного управления Main dispatcher — без импорта kotlinx-coroutines-core-iosarm64 не работает.
- expect/actual без unit-тестов для каждой actual-реализации — платформенные баги проходят незамеченными.
- Kotlin/JS генерирует крупные JS-бинарники без агрессивного DCE (Dead Code Elimination) — нужен явный включение.
- SQLDelight/Room: Room не поддерживает KMP — только SQLDelight имеет commonMain API.
- Время компиляции iOS-таргета значительно больше JVM — CI pipeline нужно оптимизировать (кэш Kotlin/Native toolchain).
- Kotlin/Native не поддерживает reflection — любой код, зависящий от java.lang.reflect, не работает на iOS.
- Совместимость версий компилятора и klib-артефактов: обновление kotlin_version может сломать транзитивные зависимости.
What hurts your answer
- Говорить только о запуске Kotlin Multiplatform, но не об эксплуатации
- Не упоминать observability, обновления, безопасность и rollback
- Описывать риски абстрактно, без способов их снижать
What they're listening for
- Видит production-риски Kotlin Multiplatform
- Говорит про monitoring, rollout, rollback и безопасность
- Умеет ранжировать риски по вероятности и влиянию