Kotlin MultiplatformSeniorExperience

Какие 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 и безопасность
  • Умеет ранжировать риски по вероятности и влиянию

Related topics