Удобство отладки
Пользователи вашей библиотеки будут использовать её функциональность для создания собственных решений, в которых могут возникать ошибки, требующие выявления и устранения. Этот процесс может проходить в отладчике во время разработки или с помощью средств журналирования и наблюдаемости в рабочей среде. Следуя приведённым рекомендациям, вы можете упростить отладку своей библиотеки.
Реализуйте метод toString для типов с состоянием
Для каждого типа, содержащего состояние, предоставьте содержательную реализацию toString. Эта реализация должна возвращать понятное представление текущего содержимого экземпляра, в том числе для внутренних типов.
Поскольку представления типов в формате toString часто записываются в журналы, при реализации этого метода учитывайте безопасность и не возвращайте конфиденциальные данные пользователей.
Следите за тем, чтобы формат описания состояния был максимально единообразным для разных типов вашей библиотеки. Если этот формат является частью контракта, реализуемого вашим API, его следует явно описать и тщательно задокументировать. Вывод методов toString может использоваться для синтаксического анализа, например в автоматизированных наборах тестов.
Например, рассмотрим следующие типы из библиотеки для работы с подписками на сервисы:
enum class SubscriptionResultReason {
Success, InsufficientFunds, IncompatibleAccount
}
class SubscriptionResult(
val result: Boolean,
val reason: SubscriptionResultReason,
val description: String
)
Без метода toString вывод экземпляра SubscriptionResult мало полезен:
fun main() {
val result = SubscriptionResult(
false,
IncompatibleAccount,
"Users account does not support this type of subscription"
)
//prints 'org.example.SubscriptionResult@13221655'
println(result)
}
В отладчике информация тоже отображается не слишком наглядно:
Простая реализация toString значительно улучшает вывод в обоих случаях:
//prints 'Subscription failed (reason=IncompatibleAccount, description="Users
// account does not support this type of subscription")'
override fun toString(): String {
val resultText = if(result) "succeeded" else "failed"
return "Subscription $resultText (reason=$reason, description=\"$description\")"
}
Может возникнуть соблазн использовать классы данных, чтобы автоматически получить метод toString, однако по причинам, связанным с обратной совместимостью, этого делать не рекомендуется. Подробнее о классах данных рассказывается в разделе Не используйте классы данных в API.
Обратите внимание: состояние, описанное в методе toString, не обязательно должно содержать информацию из предметной области. Оно может относиться к состоянию текущих запросов (как в примере выше), работоспособности подключений к внешним сервисам или промежуточному состоянию выполняемой операции.
Например, рассмотрим следующий тип построителя:
class Person(
val name: String?,
val age: Int?,
val children: List<Person>
) {
override fun toString(): String =
"Person(name=$name, age=$age, children=$children)"
}
class PersonBuilder {
var name: String? = null
var age: Int? = null
val children = arrayListOf<Person>()
fun child(personBuilder: PersonBuilder.() -> Unit = {}) {
children.add(person(personBuilder))
}
fun build(): Person = Person(name, age, children)
}
fun person(personBuilder: PersonBuilder.() -> Unit = {}): Person =
PersonBuilder().apply(personBuilder).build()
Вот как можно использовать этот тип:
Если приостановить выполнение кода на точке останова, показанной на изображении выше, отображаемая информация будет бесполезна:
Простая реализация toString значительно улучшает вывод:
override fun toString(): String =
"PersonBuilder(name=$name, age=$age, children=$children)"
После этого отладчик показывает:
Так вы сразу увидите, какие поля заданы, а какие — нет.
Выберите и задокументируйте политику обработки исключений
Как обсуждалось в разделе Выбирайте подходящий механизм обработки ошибок, иногда библиотеке уместно выбрасывать исключение, чтобы сообщить об ошибке. Для этого можно создать собственные типы исключений.
Библиотекам, которые абстрагируют низкоуровневые API и упрощают работу с ними, также потребуется обрабатывать исключения, выбрасываемые используемыми ими зависимостями. Библиотека может подавить исключение, передать его без изменений, преобразовать в исключение другого типа или сообщить об ошибке пользователям иным способом.
Любой из этих вариантов может быть подходящим — всё зависит от контекста. Например:
Если пользователь выбрал библиотеку A исключительно ради удобства, которое она обеспечивает при работе с библиотекой B, библиотеке A может быть уместно без изменений повторно выбрасывать любые исключения, возникшие в библиотеке B.
Если библиотека A использует библиотеку B исключительно как внутреннюю деталь реализации, исключения, специфичные для библиотеки B, никогда не должны быть доступны пользователям библиотеки A.
Выберите и задокументируйте единый подход к обработке исключений, чтобы пользователи могли эффективно работать с вашей библиотекой. Это особенно важно для отладки. Пользователи должны иметь возможность определить в отладчике и журналах, что исключение возникло в вашей библиотеке.
Тип исключения должен указывать на тип ошибки, а содержащиеся в нём данные — помогать пользователю найти первопричину проблемы. Распространённый подход — обернуть низкоуровневое исключение в исключение, специфичное для библиотеки, и сделать исходное исключение доступным как cause.
Поддерживайте восстановление трассировки стека корутин для пользовательских исключений
Чтобы упростить отладку, добавьте в пользовательские типы исключений своей библиотеки поддержку восстановления трассировки стека корутин. Это улучшит поддержку библиотекой kotlinx.coroutines и других сред асинхронного выполнения в Kotlin.
Когда корутина получает исключение от другой корутины через приостанавливающую функцию, восстановление трассировки стека создаёт копию исключения, дополняя её кадрами стека, ведущими к вызову этой функции.
Библиотека kotlinx.coroutines автоматически восстанавливает трассировку стека для исключений с конструкторами, принимающими только сообщение об исключении, только причину, и то и другое или не принимающими аргументов. Если тип исключения в вашей библиотеке требует дополнительных аргументов конструктора, например номера строки или кода ошибки, реализуйте интерфейс StackTraceRecoverable.
Чтобы реализовать этот интерфейс, переопределите функцию copyForStackTraceRecovery(). В переопределённой функции верните новый экземпляр исключения для восстановления трассировки стека или null, если не хотите, чтобы библиотека kotlinx.coroutines копировала исключение.
Интерфейс StackTraceRecoverable входит в стандартную библиотеку Kotlin, поэтому его реализация не добавляет зависимость от библиотеки kotlinx.coroutines.
Вот пример пользовательского исключения, которое сохраняет свойство line при создании нового экземпляра для восстановления трассировки стека:
import kotlin.coroutines.ExperimentalStdlibCoroutineSupportApi
import kotlin.coroutines.debug.StackTraceRecoverable
@OptIn(ExperimentalStdlibCoroutineSupportApi::class)
class FileEditException
// The implementation requires a private constructor
// to pass the cause to the IllegalStateException constructor
private constructor(
val line: Int,
private val detail: String,
cause: Throwable?,
) : IllegalStateException("When editing line $line: $detail", cause),
// Implements StackTraceRecoverable for stack trace recovery
StackTraceRecoverable<FileEditException> {
constructor(line: Int, detail: String) : this(line, detail, null)
// Copies the line number and message details
override fun copyForStackTraceRecovery(): FileEditException =
FileEditException(line, detail, this)
}
fun main() {
val original = FileEditException(15, "Unexpected token")
// Normally, you don't need to call this function directly unless you're testing its behavior
// The kotlinx.coroutines library invokes it automatically during stack trace recovery
val copy = original.copyForStackTraceRecovery()
println(copy.message)
// When editing line 15: Unexpected token
println(copy.cause == original)
// true
}
Следующий шаг
В следующей части руководства вы узнаете о тестируемости.
© 2010–2026 JetBrains s.r.o. and Kotlin Programming Language contributors
Licensed under the Apache License, Version 2.0.
https://kotlinlang.org/docs/api-guidelines-debuggability.html