Изучение разработки под 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 в рамках общего рефакторинга кода, вы можете прочитать в статье ниже.