Spaces have won. Translation of Kotlin Coding Conventions from JetBrains

https://kotlinlang.org/docs/reference/coding-conventions.html
  • Transfer

Hi, Habr! I bring to your attention the author’s translation of the Kotlin Coding Conventions page from JetBrains.


Original documentation


Content:



Application of Style Guide to Intellij Idea


To apply formatting in Intellij Idea in accordance with the current manual, you need to install Kotlin plugin version 1.2.20 or later, go to Settings | Editor | Code Style | Kotlin, click on the "Set from ..." link in the upper right corner and select the "Predefined style" / Kotlin style guide "from the dropdown menu.


To verify that your code is formatted according to the recommended style, go to the inspection settings and enable the check "Kotlin | Style issues | File is not formatted according to project settings". Other validation rules, such as naming convention checks, are enabled by default.


Project structure


Folder structure


In projects using different languages, files with code on Kotlin should be in the same folder as the code in other languages ​​and use the same file structure that is adopted for the main language. For example, for Java files should be in the folder structure according to the package name.


In projects using only Kotlin, the recommended folder structure is: use folders to organize packages with the root directory skipped, i.e. if all the code in the project is in the package "org.example.kotlin" and its packages, then the source code files belonging to the package "org.example.kotlin" should be in the root directory of the project, and the source code files of the package "org. example.kotlin.foo.bar "should be located in the" foo / bar "subdirectory relative to the project root.


Source File Names


If a Kotlin file contains only one class (possibly related to a high-level declaration [top-level declaration]), then it must be named, as well as a class with an extension .kt. If the file contains several classes or has only top level declarations, choose a name that describes what the file contains and name the file appropriately. Use camel hump with a capital first letter to name files (for example ProcessDeclarations.kt).


The name of the files should describe what the code in the file does. That is, you should avoid meaningless words like "Util" for naming files.


Source Code Organization


Placing multiple declarations (classes, functions, or top-level properties) in the same Kotlin source file is welcome if these declarations are closely related to each other semantically and the file size remains reasonable (no more than a few hundred lines).


In particular, when defining extension functions for a class that relate to all aspects of applying this class, put them in the same file where the class itself is defined. When defining extension functions that make sense only for the specific context of use of this class, put them next to the code using this extension function. Do not create files only to store "all Foo extensions".


Class structure


Typically, the class content is sorted in the following order:


  • Property declarations and initializer blocks
  • Secondary constructors
  • Method declarations
  • Companion objects

Do not sort the declarations of methods alphabetically or visually and do not separate the usual methods from the extension methods. Instead, put the logically related code together so that someone who reads the class from top to bottom can follow the logic of what is happening. Choose one sort order (high-level code at the beginning [higher-level stuff first], and details later or vice versa) and stick to it.


Place nested classes next to the code that uses these classes. If the classes are intended for external use and are not referenced within the class, put them at the end after the companion object.


Interface implementation structure


When implementing an interface, keep the same structure as the interface being implemented (alternating it if necessary with additional private methods used for implementation)


Override structure


Overrides always put together, one after the other.


Naming rules


Kotlin follows the same naming conventions as Java. In particular:


Package names are in lower case and do not use underscores (org.example.myproject). Using multi-word names is usually not recommended, but if you need to use multiple words, you can either just merge them together, or use camel hump (org.examle.myProject).


The names of classes and objects begin with a capital letter and use camel hump:


openclassDeclarationProcessor{ ... }
object EmptyDeclarationProcessor : DeclarationProcessor() { ... }

Name of functions


The names of functions, properties, and local variables begin with a lowercase letter and do not contain underscores:


funprocessDeclarations() { ... }
var declarationCount = ...

Exception: factory functions used to create instances of classes may have the same name as the class being created:


abstractclassFoo{ ... }
classFooImpl : Foo { ... }funFoo(): Foo { return FooImpl(...) }

The name of the test methods


In tests (and only in tests), it is permissible to use method names with spaces enclosed in backquotes. (Note that these method names are not currently supported by the Android runtime.) Underscores in method names are also allowed in the test code.


classMyTestCase{
     @Testfun `ensure everything works`() { ... }
     @TestfunensureEverythingWorks_onAndroid() { ... }
}

Property naming


The names of the constants (properties marked const, or properties of a top level or an object valwithout a user function getthat contain immutable data) should be called capital letters separated by underscores:


constval MAX_COUNT = 8val USER_NAME_FIELD = "UserName"

Names of the top level or object properties that contain objects with behavior or variable data should use the usual names in camel hump:


val mutableCollection: MutableSet<String> = HashSet()

Names of properties containing references to Singleton objects can use the same naming style as class declarations:


val PersonComparator: Comparator<Person> = ...

For enumerations, you can use names written in capital letters separated by underscores or camel hump style, starting with a capital letter, depending on use.


enumclassColor{ RED, GREEN }

enumclassColor{ RedColor, GreenColor }

Translator's note: Just do not mix different styles. Choose one style and stick to it in your project.


Naming Hidden Properties


If a class has two properties that are conceptually the same, but one is part of the open API, and the other is part of the implementation, use the underscore as a prefix for the name of the hidden property:


classC{
    privateval _elementList = mutableListOf<Element>()
    val elementList: List<Element>
         get() = _elementList
}

Choosing the right titles


A class name is usually a noun or phrase explaining what a class is:


List, PersonReader

The name of the method is usually a verb or phrase action explaining what the method does:


close, readPersons

The name should also suggest whether the method changes an object or returns a new one. For example, sortthis is a sorting changing the collection, and sortedreturning a new sorted copy of the collection.


Names should clearly indicate the purpose of the entity, so it is best to avoid using meaningless words ( Manager, Wrapperetc.) in names.


When using the acronym as part of the name of the ad, use capital letters if it consists of two letters ( IOStream); or capitalize only the first letter if it is longer ( XmlFormatter, HttpInputStream).


Formatting


In most cases, Kotlin follows Java formatting conventions.


Use 4 spaces to indent. Do not use tabs.


For braces, place the opening brace at the end of the line where the structure begins, and the closing brace on a separate line aligned horizontally with the opening structure.


if (elements != null) {
    for (element in elements) {
        // ...
    }
}

(Note: semicolons are optional in Kotlin, so line wrapping is important. The language design assumes Java-style braces, and you may encounter unexpected behavior when executing code if you try to use a different formatting style.)


Horizontal spaces


Put spaces around binary operators (a + b). Exception: do not put spaces around the "range to" operator(0..i)


Do not put spaces around unary operators. (a++)


Place the gaps between the key control words ( if, when, forand while) and the respective opening bracket.


Do not put a space before the opening parenthesis in the primary declaration of a constructor, method, or method.


classA(val x: Int)
funfoo(x: Int) { ... }
funbar() {
    foo(1)
}

Never put a space after (, [or before ], ).


Never put space around a point .or operator ?.:


foo.bar().filter { it > 2 }.joinToString()
foo?.бар()

Put a space after the double slash for the comment //:


// это комментарий

Do not put spaces around the angle brackets used to specify type parameters:


Class Map<K, V> { ... }

Do not put spaces around a double colon to indicate a reference to a class method :::


Foo::class
String::length

Do not put a space before it is ?used to mark the type that allows the value null:


String?

As a rule, avoid any type of horizontal alignment. Renaming an identifier to a name of a different length should not affect the formatting of the code.


Colon


Put a space before the colon :in the following cases:


  • when it is used to separate type and super type;

abstractclassFoo<out T : Any>

  • when delegating to the constructor of a superclass or another constructor of the same class;

constructor(x: String) : super(x) { ... }
constructor(x: String) : this(x) { ... }

  • after the keyword object.

val x = object : IFoo { ... }

Do not put a space in front :when it separates the ad and its type.


abstractfunfoo(a: Int): T

Always put a space after :.


abstractclassFoo<out T : Any> : IFoo {abstractfunfoo(a: Int): T
}
classFooImpl : Foo() {
    constructor(x: String) : this(x) { ... }
    val x = object : IFoo { ... }
}

Formatting Class Declarations


Classes with several basic constructor parameters and short names can be written in one line:


classPerson(id: Int, name: String)

Classes with longer names or a number of parameters must be formatted so that each major constructor parameter is on a separate indented line. Also, the closing parenthesis must be on a new line. If we use inheritance, then the call to the superclass constructor or the list of implemented interfaces should be located on the same line as the bracket:


classPerson(
    id: Int,
    name: String,
    surname: String
) : Human(id, name) { ... }

When specifying the interface and invoking the superclass constructor, first the superclass constructor must be located, then the interface name on the new line will be left-aligned:


classPerson(
    id: Int,
    name: String,
    surname: String
) : Human(id, name),
    KotlinMaker { ... }

For classes with a long list of super types, you need to put a line break after the colon and align all the names of the super types horizontally left-handedly:


classMyFavouriteVeryLongClassHolder :
    MyLongHolder<MyFavouriteVeryLongClass>(),
    SomeOtherInterface,
    AndAnotherOne {
    funfoo() { ... }
}

To clearly separate the class heading and its body when the class heading is long, either put an empty line after the class heading (as in the example above), or put the opening brace on a separate line:


classMyFavouriteVeryLongClassHolder :
    MyLongHolder<MyFavouriteVeryLongClass>(),
    SomeOtherInterface,
    AndAnotherOne
{
    funfoo() { ... }
}

Use regular indent (4 spaces) for constructor parameters.


Rationale: This ensures that properties declared in the main constructor are indented as properties declared in the body of the class.


Modifiers


If your ad contains several modifiers, always put them in the following order:


public / protected / private / internalexpect / actualfinal / open / abstract / sealed / constexternaloverridelateinittailrecvarargsuspendinnerenum / annotationcompanioninlineinfixoperatordata

Place all annotations before modifiers:


@Named("Foo")privateval foo: Foo

If you are not working on the library, omit redundant modifiers (for example, public).


Annotation Formatting


Annotations are usually placed on separate lines before the declaration to which they are attached, and with the same indentation:


@Target(AnnotationTarget.PROPERTY)annotationclassJsonExclude

Annotations without arguments can be placed on one line:


@JsonExclude@JvmFieldvar x: String

One annotation without arguments can be placed on the same line as the corresponding declaration:


@Testfunfoo() { ... }

File annotations


Annotations to files are placed after the comment to the file (if any), before the package instruction and separated from the package with an empty line (to emphasize the fact that they are aimed at the file, not the package).


/** License, copyright and whatever */@file:JvmName("FooBar")
package foo.bar

Formatting Functions


If the method signature does not fit on one line, use the following syntax:


funlongMethodName(
    argument: ArgumentType = defaultValue,
    argument2: AnotherArgumentType
): ReturnType {
    // body
}

Use regular indent (4 spaces) for function parameters.


Rationale: Consistency with Designer Parameters

It is preferable to use an expression without braces for functions consisting of one line.


funfoo(): Int {     // badreturn1
}
funfoo() = 1// good

Formatting a single line expression


If the body of a single-line function does not fit in the same line as the declaration, put the = sign in the first line. Indent the body of the expression by 4 spaces.


funf(x: String) =
    x.length

Property formatting


For simple read-only properties, it is preferable to use single-line formatting:


val isEmpty: Booleanget() = size == 0

For more complex properties, always use getand seton separate lines:


val foo: String
    get() { ... }

For properties with initialization, if the initializer is too long, add a line break after the equal sign and an indent of four spaces for the initialization string:


privateval defaultCharset: Charset? =
    EncodingRegistry.getInstance().getDefaultCharsetForPropertiesFiles(file)

Formatting instruction instructions


If the condition is in a control instruction ifor whenmulti-line, always use curly braces around the operator's body. Indent each subsequent line of the condition by 4 spaces relative to the beginning of the operator. Put the closing condition brackets together with the opening brace on a separate line:


if (!component.isSyncing &&
    !hasAnyKotlinRuntimeInScope(module)
) {
    return createKotlinNotConfiguredPanel(module)
}

Rationale: neat alignment and a clear separation of body conditions and body conditions

Put keywords else, catch, finallyand a keyword whiledo / while loop on the same line as the previous closing brace:


if (condition) {
    // body
} else {
    // else part
}
try {
    // body
} finally {
    // cleanup
}

If the conditions of the wheninstruction consist of several blocks, it is recommended to separate each other with an empty line:


privatefunparsePropertyValue(propName: String, token: Token) {
    when (token) {
        is Token.ValueToken ->
            callback.visitValue(propName, token.value)
        Token.LBRACE -> { // ...
        }
    }
}

Place the short wheninstruction blocks on a single line without curly brackets.


when (foo) {
    true -> bar() // goodfalse -> { baz() } // bad
}

Formatting Method Calls


When using a long list of parameters, place the line break after the parenthesis. Use an indent in 4 spaces and group the arguments related logically into one line.


drawSquare(
    x = 10, y = 10,
    width = 100, height = 100,
    fill = true
)

Use spaces around the equal sign between the parameter name and its value.


Formatting chained function calls


When using chain calls, place .or ?.operators on a new line with one indentation in 4 spaces:


val anchor = owner
    ?.firstChild!!
    .siblings(forward = true)
    .dropWhile { it is PsiComment || it is PsiWhiteSpace }

The first call in the chain should usually have a line break before it, but this is normal, not to do it if the code is better read and it makes sense.


Formatting lambda expressions


In lambda expressions, spaces should be used around braces and around the arrow separating parameters from the body. If the call accepts one lambda symbol, it should be used, if possible, outside of parentheses.


list.filter { it > 10 }

When assigning a label to a lambda expression, do not put a space between the label and the opening brace:


funfoo() {
    ints.forEach lit@{
        // ...
    }
}

When declaring parameter names in a multi-line lambda expression, place the names on the first line, then the arrow, and on the new line, the beginning of the function body:


appendCommaSeparated(properties) { prop ->
    val propertyValue = prop.get(obj)  // ...
}

If the parameter list does not fit on one line, put the arrow on a separate line:


foo {
   context: Context,
   environment: Env
   ->
   context.configureEnv(environment)
}

Paperwork


When using multi-line documentation, put /**on a separate line, and begin each subsequent line with an asterisk:


/**
 * This is a documentation comment
 * on multiple lines.
 */

Short documentation can be placed on one line:


/** This is a short documentation comment. */

As a rule, avoid using param and return tags . Instead, include a description of the parameters and return values ​​directly in the comment to the documentation and add references to the parameters wherever they are mentioned. Use param and return only when you need a long description that does not fit into the meaning 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.
 */funabs(number: Int) = ...
// Do this instead:/**
 * Returns the absolute value of the given [number].
 */funabs(number: Int) = ...

Avoiding unnecessary constructs


Many syntactic constructions in Kotlin are optional and are highlighted by the development environment as unnecessary; you should not use them in your code just to give "clarity" to your code.


Use of the keyword Unit


In functions, the use of the keyword Unit should not be used:


funfoo() { // ": Unit" is omitted here
}

Semicolon


Avoid using a semicolon at every opportunity.


String patterns


Do not use curly braces when inserting a simple variable into a template string. Use curly braces only for long expressions.


println("$name has ${children.size} children")

Idiomatic use of language features


Immutability


It is preferable to use immutable data before changing. Always declare local variables and properties as val, rather than varif they do not really change.


Always use immutable collections interfaces ( Collection, List, Set, Map) to collections of ads that do not change. Whenever possible, when using factory methods to create a collection, use an implementation that returns immutable collections:


// Bad: use of mutable collection type for value which will not be mutatedfunvalidateValue(actualValue: String, allowedValues: HashSet<String>) { ... }
// Good: immutable collection type used insteadfunvalidateValue(actualValue: String, allowedValues: Set<String>) { ... }
// Bad: arrayListOf() returns ArrayList<T>, which is a mutable collection typeval allowedValues = arrayListOf("a", "b", "c")
// Good: listOf() returns List<T>val allowedValues = listOf("a", "b", "c")

Translator's note: there must be extremely compelling reasons for using mutable collections.


Default Parameter Value


It is always preferable to declare functions with default parameter values ​​instead of declaring overloaded functions.


// Badfunfoo() = foo("a")
funfoo(a: String) { ... }
// Goodfunfoo(a: String = "a") { ... }

Creating aliases [Type alias]


If you have a functional type or a type with parameters of a type that is used several times in the code, it is better to define an alias for it:


typealias MouseClickHandler = (Any, MouseEvent) -> Unittypealias PersonIndex = Map<String, Person>

Parameter naming in lambda expressions


In lambda expressions that are short and not nested, it is recommended to use the itnaming convention instead of explicitly declaring a parameter. In nested lambda expressions with parameters, parameters should always be explicitly named.


Return values ​​from lambda expressions


Avoid using multiple marked return points in lambda. Consider restructuring lambda expressions so that they have a single exit point. If this is impossible or not sufficiently clear, consider converting a lambda expression into an anonymous function.


Do not use the flagged return ( @) for the last operator in the lambda.


Calling methods with naming arguments


Use the syntax of named arguments when a method takes several parameters of the same primitive type, or for type parameters boolean, if the meaning of all parameters is not completely clear from the context.


drawSquare(x = 10, y = 10, width = 100, height = 100, fill = true)

Rules for control structures with conditions


Prefer to use design returns try, ifand when, before explicit use with the keyword return:


returnif (x) foo() else bar() // Такая конструкция предпочтительнее, чем конструкция нижеif (x)
    return foo()
elsereturn bar()
//returnwhen(x) {
    0 -> "zero"else -> "nonzero"
} // Такая конструкция предпочтительнее, чем конструкция нижеwhen(x) {
    0 -> return"zero"else -> return"nonzero"
}

if vs when


It is better to use ifwhen using two possible conditions instead ofwhen


when (x) {
    null -> ...
    else -> ...
}
if (x == null) ... else ... // Такой синтаксис понятнее и короче

If there are more than two possible conditions, it is better to use the whenconstruction.


Use Boolean values? in conditional statements


If you need to use a Boolean?value in a conditional operator, use comparisons with the keyword if (value == true)or if (value == false), instead of type checks if (value ?: false)or , to use comparisons if (value != null && value).


Use cycles


It is preferable to use high-level functions such as filtet, mapetc. instead of cycles. Exception: forEach(it is preferable to use a normal loop forif the iteration source is not null orforEach not part of a large chain of transformations)


When choosing between a complex expression that uses several high-level functions and a cycle, it is necessary to understand and take into account the cost of each operation, and to make a choice from considerations of performance and readability.


Range loops


Use untilfor cycles excluding borders (open intervals):


for (i in0..n - 1) { ... }  // badfor (i in0 until n) { ... }  // good

Formatting strings


It is preferable to use string patterns to connect strings.


Instead of embedding \nescape sequences in regular string literals, use a multiline record type.


To preserve indenting in multi-line lines, use trimIndentwhen the resulting line does not require internal indent, or trimMarginwhen internal indenting is required:


assertEquals(
    """
    Foo
    Bar
    """.trimIndent(),
    value
)
val a = """if(a > 1) {
          |    return a
          |}""".trimMargin()

Functions vs. properties


In some cases, functions with no arguments can be interchanged with read-only properties. Although their semantics is similar, there are some stylistic agreements about when to prefer one to another.


Prefer the property of the function with the basic algorithm:


  • throws no exceptions
  • easy to calculate (or cached when first running)
  • returns the same result for each call if the state of the object has not changed

Using Extension Functions


Use the extension functions easily and freely. Every time you have a function that works in the main object, consider making it an extension function that accepts this object as a receiver. To minimize API contamination, limit the visibility of the extension functions as much as possible. If necessary, use local extension functions, member expansion functions, or top-level expansion functions with closed visibility.


Using infix functions


Declare a function as infixonly when it works with two objects that play a similar role. Good examples: and, to, zip. Bad example add.


Do not declare a method as infixif it changes the recipient object.


Factory Functions


If a factory function is declared for a class, you should not give it the same name as the class itself. Prefer to use a separate name, making it clear why the behavior of the factory function is special. Only if there is really no special semantics, can you use the same name as the class.


classPoint(val x: Double, val y: Double) {
    companionobject {
        funfromPolar(angle: Double, radius: Double) = Point(...)
    }
}

If there is an object with several overloaded constructors that do not call various superclass constructors and cannot be reduced to a single constructor with default argument values, it is preferable to replace the overloaded constructors with functions of the factory.


Platform types


Translator's note: platform types are the receipt of an object of type from any code written not on Kotlin and which can return both a nullvalue and notnull

public A function / method that returns a value of the platform type must explicitly declare the Kotlin type:


funapiCall(): String = MyJavaApi.getProperty("name")

Any property (package-level or class-level) initialized by a call to the platform type must be explicitly indicated by the Kotlin type:


classPerson{
    val name: String = MyJavaApi.getProperty("name")
}

Local variables that are initialized by platform types can be declared without explicitly specifying Kotlin type:


funmain() {
    val name = MyJavaApi.getProperty("name")
    println(name)
}

Using functions apply/ with/ run/ also/let


Kotlin provides many functions for executing a block of code in the context of a given object. To select the correct function, consider the following:


  • Do you call methods of multiple objects in a block or pass an instance of a context object as an argument? If so, use one of the functions that allows you to access the context object as a it, rather than to this( alsoor let). Use alsoif the receiver is not used at all in the unit.

// Context object is 'it'classBaz{
    var currentBar: Bar?
    val observable: Observable
    val foo = createBar().also {
        currentBar = it                    // Accessing property of Baz
        observable.registerCallback(it)    // Passing context object as argument
    }
}
// Receiver not used in the blockval foo = createBar().also {
    LOG.info("Bar created")
}
// Context object is 'this'classBaz{
    val foo: Bar = createBar().apply {
        color = RED    // Accessing only properties of Bar
        text = "Foo"
    }
}

  • What should be the result of the call? If the result should be a context object, use applyor also. If you need to return a value from a block, use with, letor run.

// Return value is context objectclassBaz{
    val foo: Bar = createBar().apply {
        color = RED    // Accessing only properties of Bar
        text = "Foo"
    }
}
// Return value is block resultclassBaz{
    val foo: Bar = createNetworkConnection().let {
        loadBar()
    }
}

  • Is the context object null or computed as a result of a call chain? If so, use apply, letor run. Otherwise, use withor also.

// Context object is nullable
person.email?.let { sendEmail(it) }
// Context object is non-null and accessible directly
with(person) {
    println("First name: $firstName, last name: $lastName")
}

Rules when creating libraries


When writing libraries, it is recommended to follow an additional set of rules to ensure the stability of the API:


  • Always explicitly indicate the visibility of the member (to avoid accidentally disclosing the ad as a public API)
  • Always explicitly specify return types from functions and property types (to avoid accidental changes in the type of data returned when the implementation changes)
  • Providing KDoc documentation for all public api, with the exception of overrides, which have their own documentation and do not require updating / refining

Also popular now: