Opinions on a flexible language.
Files should be named according to their majority content. For example:
class User {}
should be named User.kt
.
interface IUser {...}
should be named IUser.kt
When the content of the file is not contained within a single enclosure, consider naming the file after the general concept that the code acts upon. For example:
fun<T> List.of(t: T) {}
fun<T> List.from(fn: () => T) {}
could be named ListExtensions.kt
Variables should be named in regards to their data or state that they hold. Prefer lengthy (but descriptive) variable names over short (but confusing) variables.
var isUserActive = true // good
var isActive = true // okay
var foo = true // bad
When storing boolean values in variables, begin the variable name with is
.
var isActive = true // good
var active = true // bad
There are no special prefixes or suffixes to differentiate between classes and other major types. For example, this guide recommends using I
as a prefix to interfaces, classes have no such expectation.
Classes should be named according to their responsibility. For example, a class that holds data for a user could simply be handled like so:
class User() {} // good
class Foo() {} // bad
It is common to suffix a class with a design pattern. If this helps create readability, it is recommended. Attempt to stick with existing (or team agreed upon) design patterns.
class UserRepository() {} // good
Classes are final in Kotlin and for a good reason. Only open a class if it is intended to be extended.
Enums should be named according to the value they represent. If we needed an enum to determine if the user's calendar format was days, weeks, months, or years we would build one like so:
enum class CalendarFormat {
DAYS, WEEKS, MONTHS, YEARS
}
Enum constants should be uppercase.
enum class CalendarFormat {
DAYS, WEEKS, MONTHS, YEARS // good
}
enum class CalendarFormat {
days, weeks, months, years // bad
}
Prefer Sealed classes over enums when working with categorical values, especially if some of the enumerations require additional data.
Good:
sealed class CalendarFormat {
class Days: CalendarFormat()
class Weeks: CalendarFormat()
data class Months (val isLeapYear: Boolean): CalendarFormat()
class Years: CalendarFormat()
}
Bad:
enum class CalendarFormat(val isLeapYear: Boolean) {
DAYS(false),
WEEKS(false),
MONTHS(true),
YEARS(false)
}
Use data class when creating simple data structures. Prefer to keep data classes simple and without business logic.
data class User(val name: String, val password: String) // good
class User(val name: String, val password: String) {} // bad
Omit the constructor keyword unless you need to provide an annotation or visibility modifier to the constructor.
Good:
class User(val name: String) {} // no keyword
class UserInfo @Inject constructor(val dob: Date) {} // annotated
class UserPhoto private constructor(val url: String) {} // private
Bad:
class User constructor(val name: String) {} // unnecessary
Use an init block for any logic that needs to take place upon construction.
Good:
class User(val name: String) {
private val upperName: String
init {
upperName = name.toUpperCase()
}
}
Use only ONE init block to help reduce complexity.
Good:
class User(val name: String) {
private val upperName: String
private val lowerName: String
init {
upperName = name.toUpperCase()
lowerName = name.toLowerCase()
}
}
Bad:
class User(val name: String) {
private val upperName: String
init {
upperName = name.toUpperCase()
}
private val lowerName: String
init {
lowerName = name.toLowerCase()
}
}
Consider using a primary constructor with nullable parameters instead of a secondary constructor.
Good:
class User(val first: String, val last: String = "Johnson") {}
User(first = "Ted")
Bad:
class User(val first: String, val last: String) {
constructor(first: String): this(first, "Johnson") {}
}
Call constructors using named parameters if the constructor accepts 2 or more parameters.
UserEmail("Brad.Cypert@thatReallyPopularEmailService.com") // good
User(first = "Brad", last = "Cypert", age = 25) // good
Car("Volkswagen", "GTI", 2018, 4, 4) // bad
If a public property must be backed by a private property, prefix _
to the public property name to create the private property name. Example:
Good:
private val _loginChanges: MutableLiveData<Boolean> = MutableLiveData<Boolean>()
val loginChanges: LiveData<Boolean> = _loginChanges
Bad:
private val someLoginChanges: MutableLiveData<Boolean> = MutableLiveData<Boolean>()
val loginChanges: LiveData<Boolean> = someLoginChanges
Better yet, override the getter for the public property to return the expected property (this is particularly useful when holding a mutable type privately, but exposing an immutable type).
private val _loginChanges: MutableLiveData<Boolean> = MutableLiveData<Boolean>()
val loginChanges: LiveData<Boolean> get() = _loginChanges
- Avoid use of the
public
modifier. - Default to
private
. - Consider using
protected
when it needs to be visibile in subclasses, too. - Avoid use of
internal
unless necessary.
Objects should be named similarly to classes (see below). The are no special prefixes or suffixes to differentiate between objects and classes. Do not include Singleton
, Object
, or Instance
in the object name.
object MyObject {} // good
object IMyObject {} // bad
object MyObjectImpl {} // bad
object MyObjectSingleton {} // bad
Objects should be named according to their responsibility. For example, assume we have an object that we use to format a username.
object UsernameFormatter {} // good
object UserFormatter {} // bad, this does not format a user
object UsernameFormatterInstance {} // bad, instance is implied
It is common to use suffix an object with a design pattern. If this helps create readability, it is recommended. Attempt to stick with existing (or team agreed upon) design patterns.
object UserRepository {} // good
- Interface names should begin with an
I
. For exampleIDataFacade
.
Good:
interface IDataFacade {}
class HttpApi(): IDataFacade {}
Good:
interface IDataFacade {}
class DataFacade(): IDataFacade{}
Bad:
interface DataFacade {} // needs an "I"
class HttpApi(): DataFacade {}
Direct implementations of the interface should not begin with an I
and not end with an Impl
.
Bad:
interface IDataFacade {} // this isnt bad
class DataFacadeImpl(): IDataFacade {} // this is
Functions are intended to achieve something and should be named as with, preferably starting with a verb.
fun user() { ... } // bad
fun getUser() { ... } // good
A function that returns a boolean can start with get
or, more preferably, is
(or similar). Using is
to pose a simple yes/no question clearly indicates that a boolean is expected.
fun getIsActive(): Boolean { ... } // okay
fun isActive(): Boolean { ... } // good
fun hasEmail(): Boolean { ... } // good
fun email(): Boolean { ... } // bad
Always use a function expression when a function would otherwise only contain one expression unless that expression would span multiple lines
fun isActive(user: User) = user.isActive && user.isNotBanned // good
fun isActive(user: User): Boolean {
return user.isActive && user.isNotBanned
} // bad
fun isActive(user: User) = user.isActive &&
user.isNotBanned &&
user.isPaid &&
user.isNotLegacy // bad
Prefer using public properties instead of functions that begin with get
.
var user = User() // good
fun getUser() { ... } // bad
Override getters and/or setters instead of providing a custom get/set function.
Good:
var name = ""
get() = field.toUpperCase()
set(v: String) { field = v.toLowerCase()}
Bad:
private var _name = ""
fun getName(): String = _name.toUpperCase()
fun setName(v: String) { _name = v.toLowerCase }
When simply modifing the visibility of the accessor method, do not provide a method body.
Good:
var name: String = "Brad"
private set
Bad:
var name: String = "Brad"
private set(n: String) { field = n }
Yes
Prefer lambda expressions over anonymous functions.
listOf<Int>(1,2,3).find { it % 2 == 0 } // good
listOf<Int>(1,2,3).find(fun (i: Int): Boolean { return i % 2 == 0 }) // bad
Use clear parameter names when writing lamdba expressions if the intentions of the lambda is not obvious.
listOf<Int>(1,2,3).map { value -> value + 1 } // good
listOf<Int>(1,2,3).map { x -> x + 1 } // okay, obvious lambda body
listOf<Int>(1,2,3).map { it + 1 } // okay, obvious lambda body
listOf<Int>(1,2,3).map { user -> user + 1 } // bad, unclear function body
listOf<Int>(1,2,3).map {
val isDivisibleByTwo = it % 2 == 0
if (isDivisibleByTwo) {
arbitratyMap[it]
} else {
0
}
} // bad, not clear function body or implications if `it` parameter
When defining lambdas that are nested, provide a name for each parameter to the lambda so as to avoid unclear nesting of it
s.
val l = listOf(
listOf(1,2,3),
listOf(4,5,6),
listOf(7,8,9)
)
l.map { sublist ->
sublist.map { nestedValue -> nestedValue * 2 }
} // good
l.map {
it.map { it * 2 }
} // bad
If the lambda body is not simple (IE: if it has any nested lambdas), insert a line break between the parameter declaration and the lambda body.
l.map { sublist ->
sublist.map { nestedValue -> nestedValue * 2 }
} // good
l.map { sublist ->
sublist.map { nestedValue ->
nestedValue * 2
}
} // good
l.map { sublist -> sublist.map { nestedValue -> nestedValue * 2 } } // bad
When defining anonymous functions as parameters, indicate the the param is a function in the name. This helps readability when calling the function with multiple function parameters.
fun<T> find(l: List<T>, searchFn: (T) => Boolean, defaultFn: () => T) // good
fun<T> find(l: List<T>, search: (T) => Boolean, default: () => T) // bad