In this post, I will discuss the Kotlin coding conventions which are a lot similar to other programming languages. So without further delay lets move to our today's discussion.
Source code organization
Source code organization is one of the first things we need to discuss in Kotlin coding conventions. Just like in other programming languages, an organization of source code is important in Kotlin for a consistent look across all the projects.
Directory structure
The source files should reside in the same source root as the Java source files, and follow the same directory structure. In pure Kotlin projects, the recommended directory structure is to follow the package structure with the common root package omitted.
Example: if all the code in the project is in the "org.example.kotlin" package and its sub-packages, files with the "org.example.kotlin" package should be placed directly under the source root, and files in "org.example.kotlin.foo.bar" should be in the "foo/bar" subdirectory of the source root.
Source file names
If a file contains a single class, its name should be the same as the name of the class, with the .kt extension appended. If a file contains multiple classes or only top-level declarations, choose a name describing what the file contains, and name the file accordingly. Use camel humps with an uppercase first letter.
Example: FileName.kt
Class layout
Class contents are sorted in the following order:
- Property declarations and initializer blocks.
- Secondary constructors.
- Method declarations.
- Companion object.
Interface implementation & Overload layout
Keep the implementing members in the same order as members of the interface and always put overloads next to each other in a class.
Naming rules
If you are familiar with java naming convention, then it will be easy for you to get around it.
In particular: name of packages are always lower case and do not use underscores. Using multi-word names is generally discouraged, but if you do need to use multiple words, you can either simply concatenate them together or use camel humps (i.e. org.example.myProject).
Name of classes and objects start with an upper case letter and use camel humps:
open class DeclarationProcessor { ... }
object EmptyDeclarationProcessor : DeclarationProcessor() { ... }
object EmptyDeclarationProcessor : DeclarationProcessor() { ... }
Name of functions, properties and local variables start with a lower case letter and use camel humps and no underscores:
fun processDeclarations() { ... }
var declarationCount = ...}
var declarationCount = ...}
Exception: factory functions used to create instances of classes can have the same name as the class being created:
abstract class Foo { ... }
class FooImpl : Foo { ... }
fun Foo(): Foo { return FooImpl(...) }
class FooImpl : Foo { ... }
fun Foo(): Foo { return FooImpl(...) }
Formatting
Use 4 spaces for indentation. Do not use tabs. For curly braces, put the opening brace at the end of the line where the construct begins, and the closing brace on a separate line aligned horizontally with the opening construct.
if (elements != null) {
for (element in elements) {
// ...
}
}
Note: In Kotlin, semicolons are optional, and therefore line breaks are significant.
Horizontal whitespace
Put spaces around binary operators.
Example:
a + b
Exception: don't put spaces around the "range to" operator.
Example:
0..i
Do not put spaces around unary operators.
Example:
a++
Put spaces between control flow keywords (
if
, when
, for
and while
) and the corresponding opening parenthesis.Modifiers
If a declaration has multiple modifiers, always put them in the following order:
public / protected / private / internal
expect / actual
final / open / abstract / sealed / const
external
override
lateinit
tailrec
vararg
suspend
inner
enum / annotation
companion
inline
infix
operator
data
Method call formatting
In long argument lists, put a line break after the opening parenthesis. Indent arguments by 4 spaces. Group multiple closely related arguments on the same line.
drawSquare(
x = 10, y = 10,
width = 100, height = 100,
fill = true
)
x = 10, y = 10,
width = 100, height = 100,
fill = true
)
Put spaces around the
=
sign separating the argument name and value.Documentation comments
For multiple line comments, place the opening
/**
on a separate line and begin each subsequent line with an asterisk:
/**
* This is a documentation comment
* on multiple lines.
*/
* This is a documentation comment
* on multiple lines.
*/
Short comments can be placed on a single line:
/** This is a short documentation comment. */
Avoid using
@param
and @return
tags. Instead, incorporate the description of parameters and return values directly into the documentation comment, and add links to parameters wherever they are mentioned. Use @param
and @return
only when a lengthy description is required which doesn't fit into the flow of the main text.
// Avoid doing this:
/**
* Returns the absolute value of the given number.
* @param number The number to return the absolute value for.
* @return The absolute value.
*/
fun abs(number: Int) = ...
// Do this instead:
/**
* Returns the absolute value of the given [number].
*/
fun abs(number: Int) = ...
/**
* Returns the absolute value of the given number.
* @param number The number to return the absolute value for.
* @return The absolute value.
*/
fun abs(number: Int) = ...
// Do this instead:
/**
* Returns the absolute value of the given [number].
*/
fun abs(number: Int) = ...
Avoiding redundant constructs
If a certain syntactic construction in Kotlin is optional and highlighted by the IDE as redundant, you should omit it in your code. Do not leave unnecessary syntactic elements in code just "for clarity".
Unit
If a function returns Unit, the return type should be omitted:
fun foo() { // ": Unit" is omitted here
}
}
Semicolons
Omit semicolons whenever possible.
String templates
Don't use curly braces when inserting a simple variable into a string template. Use curly braces only for longer expressions.
println("$name has ${children.size} children")
Idiomatic use of language features
Immutability
Prefer using immutable data to mutable. Always declare local variables and properties as
val
rather than var
if they are not modified after initialization. Always use immutable collection interfaces (Collection
, List
, Set
, Map
) to declare collections which are not mutated. When using factory functions to create collection instances, always use functions that return immutable collection types when possible:
// Bad: use of mutable collection type for value which will not be mutated
fun validateValue(actualValue: String, allowedValues: HashSet<String>) { ... }
// Good: immutable collection type used instead
fun validateValue(actualValue: String, allowedValues: Set<String>) { ... }
// Bad: arrayListOf() returns ArrayList<T>, which is a mutable collection type
val allowedValues = arrayListOf("a", "b", "c")
// Good: listOf() returns List<T>
val allowedValues = listOf("a", "b", "c")
fun validateValue(actualValue: String, allowedValues: HashSet<String>) { ... }
// Good: immutable collection type used instead
fun validateValue(actualValue: String, allowedValues: Set<String>) { ... }
// Bad: arrayListOf() returns ArrayList<T>, which is a mutable collection type
val allowedValues = arrayListOf("a", "b", "c")
// Good: listOf() returns List<T>
val allowedValues = listOf("a", "b", "c")
Default parameter values
Prefer declaring functions with default parameter values to declaring overloaded functions.
// Bad
fun foo() = foo("a")
fun foo(a: String) { ... }
// Good
fun foo(a: String = "a") { ... }
fun foo() = foo("a")
fun foo(a: String) { ... }
// Good
fun foo(a: String = "a") { ... }
Coding conventions for libraries
When writing libraries, it's recommended to follow an additional set of rules to ensure API stability:
- Always explicitly specify member visibility (to avoid accidentally exposing declarations as public API)
- Always explicitly specify function return types and property types (to avoid accidentally changing the return type when the implementation changes)
- Provide KDoc comments for all public members, with the exception of overrides that do not require any new documentation (to support generating documentation for the library)
Comments
Post a Comment