可调试性
This chapter contains considerations about debuggability.
Always provide a toString() method
To make debugging easier, add a toString()
implementation to every class you introduce, even to internal ones.
If toString()
is part of a contract, document it explicitly.
The following code is a simplified example from a graphical modeling area:
class Vector2D(val x: Int, val y: Int)
fun main() {
val result = (1..20).map { Vector2D(it, it) }
println(result)
}
The output of this code is not very useful:
[Vector2D@27bc2616, Vector2D@3941a79c, Vector2D@506e1b77,...]
Neither is the information provided in the debug tool window:
To make both logging and debugging much more readable, add a simple toString()
implementation like this:
override fun toString(): String =
"Vector2D(x=$x, y=$y)"
This results in improved output:
[Vector2D(x=1, y=1), Vector2D(x=2, y=2), Vector2D(x=3, y=3), ...
It might seem like a good idea to use data classes because they have a
toString()
method automatically. In the Backward compatibility section of this guide, you'll learn why it's better not to do this.
Consider implementing toString()
even if you don't think the class is going to be printed anywhere, as it can help in
unexpected ways. For example, inside builders,
it may be important to see the current state of the builder.
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 person(personBuilder: PersonBuilder.() -> Unit = {}): Person {
val builder = PersonBuilder()
builder.personBuilder()
return Person(builder.name, builder.age, builder.children)
}
The intended use of the code above is the following:
If you set a breakpoint on the line after the closing brace of the first child
(as on the picture above), you see
a non-descriptive string in debug output:
If you add a simple toString()
implementation like this:
override fun toString(): String =
"PersonBuilder(name=$name, age=$age, children=$children)"
The debug data becomes much clearer:
You can also see immediately which fields are set and which are not.
Be careful with exposing fields in
toString()
because it might be easy to get aStackOverflowException
. For example, ifchildren
has a reference to a parent, that would create a circular reference. Also, be careful about exposing lists and maps, astoString()
can expand a deeply nested hierarchy.{type = "warning"}
What's next?
Learn about APIs' backward compatibility.