Изучение разработки под Android

Избегайте Котлина !! с функцией общего изящного отчета об ошибках

Когда возникает соблазн просто использовать !! в Котлине, чтобы гарантировать ненулевое значение

В своем путешествии по переносу некоторого кода из приложения с Android API 29 на Android API 30 я обнаружил, что некоторые API были улучшены, чтобы указать допустимость значений NULL. например

public String getStringExtra(String name) {
        return mExtras == null ? null : mExtras.getString(name);    }

изменился на

public @Nullable String getStringExtra(String name) {
    return mExtras == null ? null : mExtras.getString(name);
}

Следовательно, наш код Kotlin, который использует getStringExtra, как показано ниже, будет жаловаться на ошибку, поскольку API-интерфейс String, допускающий значение NULL, может быть назначен для String, не допускающего значения NULL.

private val someID: String by lazy {
    intent.getStringExtra(ID_KEY)
}

Быстрые способы решения

Самый быстрый способ решить эту проблему - использовать !!, поскольку мы почти уверены, что он никогда не будет равен нулю.

private val someID: String by lazy {
    intent.getStringExtra(ID_KEY)!!
}

Но это не так приятно, так как это приведет к сбою приложения, как только это произойдет. Не изящно по отношению к покупателю.

В моем случае я не против установить для него пустую строку, если она пуста. Так что ниже все в порядке, хотя это не идеально, по крайней мере, это не приведет к сбою в приложении клиента.

private val someID: String by lazy {
    intent.getStringExtra(ID_KEY) ?: ""
}

Но я хочу знать, когда это произойдет

Используя нижеприведенный

private val someID: String by lazy {
    intent.getStringExtra(ID_KEY) ?: ""
}

Я никогда не узнаю, когда это произойдет. Я хочу, чтобы он сообщал мне в моем отчете о сбоях как нефатальную ошибку. Я могу использовать !!, чтобы вывести его из строя, чтобы он отправил отчет в мою консоль отчетов о сбоях (например, Firebase Crashlytic или New Relic), но пользователь не обрадуется, поскольку увидит сбой.

Просто воспользуемся этим

private val someID: String by lazy {
    try {
        intent.getStringExtra(ID_KEY)!!
    } catch (e: Exception) {
        reportToCrashlyic(e)
        ""
    }
}

Это работает.

Но представьте, если их много, у меня везде try-catch 🧐

Превратите это в функцию

Мы можем превратить его в общую функцию, как показано ниже, используя универсальный

fun <T : Any> T?.reportNull(
    default: T
): T {
    try {
        this!!
    } catch (e: Exception) {
        reportToCrashlyic(e)
        default
    }
}

Теперь моя функция будет такой, как показано ниже. Милая аккуратная. Это изящно, и все же сообщить о проблеме моему репортеру о сбоях.

private val someID: String by lazy {
    intent.getStringExtra(ID_KEY)
        .reportNull("")
}

Функция еще не идеальна.

Мне не нравится общая функция из-за нижеприведенного.

  • Мне не нравится !!`
  • Я также хочу отправить несколько сообщений своему репортеру о сбоях, когда такое случается.

Поэтому я переписал его, как показано ниже.

fun <T : Any> T?.reportNull(
    default: T,
    message: String = ""
): T {
    if (this == null) {
        reportToCrashlyic(NullPointerException(message))
        return default
    }
    return this
}

Выглядит хорошо, правда? Но…

Одна большая проблема!

Если мы проверим отчет о сбоях на этом

Вы увидите, что сбой зарегистрирован на высоком уровне, как показано ниже.

YourUtilFile.kt, строка XX
your.package.YourUtilFile.reportNull

Это означает, что все такие ошибки будут объединяться и отображаться как сработавшие в этой общей функции, независимо от того, где они возникли.

Вы по-прежнему можете найти их именно через трассировку стека, но теперь гораздо труднее узнать, какой из них более или менее.

Итак, как мы можем с этим справиться? Отказаться от общей функции, а весь код отправки обрабатывать собственные сообщения об ошибках?

Решение

К счастью, есть одно простое решение. inline.

inline fun <T : Any> T?.reportNull(
    default: T,
    message: String = ""
): T {
    if (this == null) {
        reportToCrashlyic(NullPointerException(message))
        return default
    }
    return this
}

Создание функции inline объединит общую функцию с вызывающей функцией. И любой сгенерированный отчет о сбое будет как от вызывающей функции.

Предупреждение: использование inline увеличит размер вашего apk, поскольку общий код будет реплицирован для каждой функции.

Это от меня. Я уверен, что есть более элегантные способы справиться с таким сценарием. Не стесняйтесь поделиться, если у вас есть хороший способ справиться с такими !!.

Подробнее о том, как обрабатывать !! Kotlin в рамках общего рефакторинга кода, вы можете прочитать в статье ниже.