A style guide for developers at Gridstone who write in Kotlin.
- Variable names are
camelCase
- Type names are
CaptialCamelCase
- Function names are
camelCase
- Constants are
CAPITAL_SNAKE_CASE
- Enum entries are
CAPITAL_SNAKE_CASE
const val GREETING = "Hello, world!"
class Foo {
private val secret = "shhh"
fun greetTheWorld() {
println(GREETING)
}
}
enum Bar { FIRST_BAR, SECOND_BAR }
Do not use Hungarian notation.
When naming things, treat acroyms as full words.
// OK
val request = XmlHttpRequest()
// Not OK
val request = XMLHTTPRequest()
Lines may be no more than 100 characters in length.
- Preserves readability for individual lines
- Ensures that two files can be displayed side-by-side on most displays
Use 2 space characters for indentation. Use 4 spaces for continuation indents.
fun foo() {
val bar = Observable.just(1)
.map { it.toString() }
}
- Using spaces instead of tabs means code must look good regardless of tab-length settings
- Using 2 spaces helps maximise the use of our limited horizontal real estate
When parameters of functions or classes extends into multiple lines those parameters must be aligned.
data class Foo(val bar1: Int,
val bar2: String,
val bar3: Float)
fun bar(baz1: Int,
baz2: String,
baz3: Float) {
// ...
}
val foo = bar(baz1,
baz2,
baz3)
It's also acceptable to align parameters as
fun foo(bar1, bar2
bar3, bar4)
- Alignment is especially useful for data classes which may have many properties
When methods are invoked in a chain they must be aligned.
// OK
val foo: List<String> = list
.filter {
it > 10
}
.map {
it.toString()
}
// Not OK
val bar: List<String> = list.filter {
it > 10
}.map {
it.toString()
}
Short methods and classes may be declared on a single line.
data class Foo(val foo1: Int, val foo2: String)
fun bar(): String = "What would you like to drink?"
Use a space either side of a colon when declaring inheritance. Use only a space after a colon when declaring a variable/method type.
interface Foo<out T : Any> : Bar {
fun foo(a: Int): T
}
Lines that are intentionally blank for formatting purposes should contain no characters at all.
Always use explicit imports. Never use wildcard imports.
// OK
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
// Not OK
import android.view.*
Type inference is powerful but can be easily abused. As a general rule, type inference should only be used when it's explicitly clear what the resulting type will be.
// OK
fun foo() = "Hello."
val bar = Bar()
// Not OK
val baz = generateBaz()
Under any other the circumstances the type must be explicitly declared, even if it's not necessary for the compiler.
Whenever possible lambdas should be placed outside of parentheses.
// OK
list.filter { it > 10 }
// Not OK
list.filter({ it > 10 })
The implicit it
parameter given to lambdas should be used judiciously. Unless a lambda is very
short, consider overriding it
with a more meaningful name.
// Specifying the lambda parameter of `user` rather than `it` helps this code.
webServices.getUsers()
.flatMapIterable { it }
.map { user ->
when {
user.isAdmin() -> Admin(user)
user.isManager() -> Manager(user)
}
}
Unused lambda parameters should be replaced with an underscore.
foo { _, _, bar -> println(bar) }
Return values inside of lambdas have the potential to be confusing to readers. If a lambda returns a value and has more than one line then a return tag must be explicitly provided.
// OK
list.filter { it > 3 }
.map {
val x = it * 4
return@map x + 7
}
// Not OK
list.filter { it > 3 }
.map {
val x = it * 4
x + 7
}
Whenever possible return from a method rather than creating additional levels of indentation with conditions.
// OK
fun foo(bar: Int) {
if (bar < 3) return
// Do other stuff.
}
// Not OK
fun foo(bar :Int) {
if (bar >= 3) {
// Do other stuff.
}
}
Short if
statements may be declared on a single line.
fun foo(bar: Bar) {
if (bar.qualifiesForThing()) bar.doThing()
else bar.doSomethingElse()
}
However if either the if
or the else
section take up mutiple lines then both must make use of
curly braces.
// OK
fun foo(bar: Bar): Int {
if (bar.qualifiesForThing()) {
val baz = bar.getThing()
return baz.calcSomeInt()
} else {
return 3
}
}
// Not OK
fun foo(bar: Bar): Int {
if (bar.qualifiesForThing()) {
val baz = bar.getThing()
return baz.calcSomeInt()
} else return 3
}
If a variable is set as the result of an if
statement then prefer the formatting:
val foo: String = if (something()) bar else baz
If the condition is long then prefer:
val foo: String =
if (someCalculationThatIsLong() > someOtherLongCalculation()) bar
else baz
It is not manditory to comment every section of code written. People often forget that when writing comments, they commit to maintaining not only their code but also their documentation. Comments that are out-of-date with their corresponding code are potentially dangerous.
Methods such as size()
on a collection don't need comments, as their purpose is immediately apparent
to the user. Adding a commment only adds noise.
When documenting a class, function, or property to be used by others KDoc style should be used.
/**
* Calculates bounding box that contains both provided [Rect]s
*/
fun getEnclosingBounds(first: Rect, second: Rect): Rect
It is not necessary to provide @param
,@return
tags, as they often result in needless duplication.
Comments intended to aid someone who is reading code should use C++ style // comments
. For formatting
purposes there should be a space between the slashes and the content.
// This is a helpful one line comment.
TODO
Comments should either be in the format of
// TODO Some task that needs to be done.
or make use of Kotlin's TODO() function
TODO("Some task that needs to be done.")
Many of the assertions below are based on John Carmarck's thoughts.
If a function has explicit inputs, outputs, and doesn't modify external state then don't be afraid of its length. These are the easiest functions to test and often a long section of linear processing is neater than declaring many "helper" functions that are only used once.
If a function is only ever called by one other function, that function should most likely become a local function.
If a function is only ever called in one place consider inlining it. This doesn't mean appending
Kotlin's inline
keyword but rather refers to taking the contents of the method and replacing them
where the function was originally invoked. If that section of code could later be used as a funcion
in many places then consider pulling it out again, but only when necessary.