Общий и платформенный код в одном модуле
Граница между общим и платформенным кодом — и как решить, по какую сторону положить класс.
Платформенно-нейтральный код — код, который зависит только от стандартной библиотеки Kotlin и мультиплатформенных зависимостей, без обращений к API конкретной ОС.
Где проходит граница
Решая, делить класс или нет, спросите: «Зависит ли он от платформы?» Если класс считает скидку, валидирует email, парсит JSON — он нейтрален, ему место в commonMain. Если он открывает файл, читает SharedPreferences, показывает уведомление — он платформенный.
Серая зона — вещи вроде текущего времени, генерации UUID, логирования. Они концептуально общие, но реализуются через платформенный API. Их выносят в общий код как интерфейс или expect-объявление, а реализацию дают на каждой платформе.
Пример разделения
Бизнес-логика — общая:
// commonMain
class PriceCalculator {
fun finalPrice(base: Double, discountPercent: Int): Double {
require(discountPercent in 0..100)
return base * (100 - discountPercent) / 100.0
}
}А вот доступ к хранилищу — платформенный, и в общий код он попадает через абстракцию:
// commonMain
interface KeyValueStore {
fun putString(key: String, value: String)
fun getString(key: String): String?
}На Android реализация обернёт SharedPreferences, на iOS — NSUserDefaults. Общий код работает только с интерфейсом и не знает, что под ним.
Как работает под капотом
Этот приём — обычная инверсия зависимостей. Общий код объявляет контракт (interface) и принимает его реализацию извне (через конструктор или DI). Компилятор общего набора видит только интерфейс и спокойно собирается под все цели. Конкретные классы живут в платформенных наборах и «доезжают» до общего кода во время выполнения. Это масштабируемая альтернатива expect/actual: для сложных зависимостей интерфейс гибче, для одиночных функций и констант — лаконичнее expect/actual.
Частые ошибки
Соблазн — добавить в общий интерфейс метод, который имеет смысл только на одной платформе («показать Android-тост»). Так протекает платформенность. Интерфейсы в общем коде должны описывать возможности, а не платформенные детали: не «showAndroidToast», а «showMessage». Вторая ошибка — выносить в общий код слишком мало, оставляя дублирование; начните с самого ценного — сетевого и доменного слоя.
Итоги
- Нейтральный код — в
commonMain; платформенный — в платформенных наборах. - Серую зону выносят через интерфейс или
expect/actual. - Общий код зависит от абстракций, реализации приходят с платформ.
- Интерфейсы описывают возможности, а не платформенные детали.