⬆️ ⬇️

Spaces have won. Translation of Kotlin Coding Conventions from JetBrains

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 newer, 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 drop down 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 the 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 the 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:





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:



 open class DeclarationProcessor { ... } object EmptyDeclarationProcessor : DeclarationProcessor() { ... } 


Name of functions



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



 fun processDeclarations() { ... } var declarationCount = ... 


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



 abstract class Foo { ... } class FooImpl : Foo { ... } fun Foo(): 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.



 class MyTestCase { @Test fun `ensure everything works`() { ... } @Test fun ensureEverythingWorks_onAndroid() { ... } } 


Property naming



Names of constants (properties marked const , or properties of a top-level or val object with no user-defined get function that contain immutable data) should be referred to as capital letters separated by underscores:



 const val MAX_COUNT = 8 val 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.



 enum class Color { RED, GREEN } 


 enum class Color { 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:



 class C { private val _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, sort is sorting changing the collection, and sorted is returning 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 , Wrapper , etc.) in the 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 spaces between the key control words ( if , when , for and while ) and the corresponding opening brackets.



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



 class A(val x: Int) fun foo(x: Int) { ... } fun bar() { foo(1) } 


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



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



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


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 the double colon to indicate a reference to the class method ::



 Foo::class String::length 


Do not put a space in front of ? used to mark a type that is null capable:



 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:





 abstract class Foo<out T : Any> 




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




 val x = object : IFoo { ... } 


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



 abstract fun foo(a: Int): T 


Always put a space after :



 abstract class Foo<out T : Any> : IFoo { abstract fun foo(a: Int): T } class FooImpl : 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:



 class Person(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:



 class Person( 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:



 class Person( 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:



 class MyFavouriteVeryLongClassHolder : MyLongHolder<MyFavouriteVeryLongClass>(), SomeOtherInterface, AndAnotherOne { fun foo() { ... } } 


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:



 class MyFavouriteVeryLongClassHolder : MyLongHolder<MyFavouriteVeryLongClass>(), SomeOtherInterface, AndAnotherOne { fun foo() { ... } } 


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 / internal expect / actual final / open / abstract / sealed / const external override lateinit tailrec vararg suspend inner enum / annotation companion inline infix operator data 


Place all annotations before modifiers:



 @Named("Foo") private val 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) annotation class JsonExclude 


Annotations without arguments can be placed on one line:



 @JsonExclude @JvmField var x: String 


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



 @Test fun foo() { ... } 


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, and not at 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:



 fun longMethodName( 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.



 fun foo(): Int { // bad return 1 } fun foo() = 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.



 fun f(x: String) = x.length 


Property formatting



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



 val isEmpty: Boolean get() = size == 0 


For more complex properties, always use get and set on 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:



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


Formatting instruction instructions



If the condition in the if or when control statement is multi-line, always use curly braces around the operator 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


Place the keywords else , catch , finally , and the while keyword of a do / while loop on the same line as the previous closing brace:



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


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



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


Put short when blocks of instructions on a single line without curly brackets.



 when (foo) { true -> bar() // good false -> { 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 the 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:



 fun foo() { 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 multiline 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. */ fun abs(number: Int) = ... // Do this instead: /** * Returns the absolute value of the given [number]. */ fun abs(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:



 fun foo() { // ": 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 , not var , unless they really change.



Always use immutable collection interfaces ( Collection , List , Set , Map ) to declare collections 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 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") 


: .






.



 // Bad fun foo() = foo("a") fun foo(a: String) { ... } // Good fun foo(a: String = "a") { ... } 


[Type alias]



, , :



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


-



-, , it . - .



-



. - , . , - .



( @ ) .





, , boolean , .



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




try , if when , return :



 return if (x) foo() else bar() //   ,    if (x) return foo() else return bar() // return when(x) { 0 -> "zero" else -> "nonzero" } //   ,    when(x) { 0 -> return "zero" else -> return "nonzero" } 


if when



if when



 when (x) { null -> ... else -> ... } if (x == null) ... else ... //      


, when .



Boolean?



Boolean? , if (value == true) if (value == false) , if (value ?: false) if (value != null && value) .





filtet , map .. . : forEach ( for null forEach )



, , , .





until ( ):



 for (i in 0..n - 1) { ... } // bad for (i in 0 until n) { ... } // good 




.



\n escape- .



, trimIndent , , trimMargin , :



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




. , , .



:







. , , , , . API, , . , .





infix , , . : and , to , zip . : add .



infix , .





, , . , , . , , .



 class Point(val x: Double, val y: Double) { companion object { fun fromPolar(angle: Double, radius: Double) = Point(...) } } 


, , .





: , , Kotlin null , null


public /, , Kotlin:



 fun apiCall(): String = MyJavaApi.getProperty("name") 


(package-level class-level) Kotlin:



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


, , Kotlin :



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


apply / with / run / also / let



Kotlin . , :





 // Context object is 'it' class Baz { 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 block val foo = createBar().also { LOG.info("Bar created") } // Context object is 'this' class Baz { val foo: Bar = createBar().apply { color = RED // Accessing only properties of Bar text = "Foo" } } 




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




 // 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") } 




API:





')

Source: https://habr.com/ru/post/431990/



All Articles