差距赢了。 从JetBrains翻译Kotlin编码约定文档

哈Ha! 我提请您注意JetBrains提供的Kotlin编码约定文档页面的翻译。


原始文件


内容:



使用Intellij Idea上的样式指南


要按照当前手册在Intellij Idea中应用格式设置,您需要安装Kotlin插件版本1.2.20或更高版本,请转至“设置” |“设置”。 编辑器 代码样式| Kotlin,单击右上角的“从...设置”链接,然后从下拉菜单中选择“预定义样式” / Kotlin样式指南。


要验证您的代码是否按照建议的样式进行了格式化,请转到检查设置并启用“ Kotlin |样式问题|文件未根据项目设置进行格式化”检查。 默认情况下会启用其他验证规则,例如命名约定。


项目结构


资料夹结构


在使用不同语言的项目中,带有Kotlin代码的文件必须与其他语言的代码位于同一文件夹中,并使用主要语言接受的相同文件结构。 例如,对于Java,文件必须根据包名称位于文件夹结构中。


在仅使用Kotlin的项目中,建议的文件夹结构为:使用文件夹来组织软件包,而跳过根目录,即 如果项目中的所有代码都在软件包“ org.example.kotlin”及其软件包中,则属于软件包“ org.example.kotlin”的源文件应位于项目的根目录中,而带有软件包“ org”的源文件应位于项目的根目录中。 example.kotlin.foo.bar“应该位于子目录” foo / bar“中,相对于项目的根目录。


源文件名


如果Kotlin文件仅包含一个类(可能与顶级声明相关),则应命名该文件以及扩展名为.kt的类。 如果文件包含多个类或仅具有顶级声明,请选择一个描述文件包含内容的名称并相应地命名该文件。 使用驼峰驼峰和大写首字母命名文件(例如ProcessDeclarations.kt )。


文件名应描述文件中代码的作用。 也就是说,您应避免使用诸如“ Util”之类的毫无意义的单词来命名文件。


组织源文件


如果这些声明在语义上紧密相关并且文件大小保持合理(不超过几百行),则欢迎在同一Kotlin源文件中放置多个声明(类,函数或顶级属性)。


特别是,在为适用于应用该类各个方面的类定义扩展功能时,请将它们放在定义了类本身的同一文件中。 当定义仅对使用此类的特定上下文有意义的扩展功能时,请将其放置在使用扩展功能的代码旁边。 不要创建仅用于存储“所有Foo扩展名”的文件。


类结构


通常,类的内容按以下顺序排序:


  • 属性声明和初始化程序块
  • 二级建设者
  • 方法声明
  • 伴侣对象

不要按字母或视觉顺序对方法声明进行排序,也不要将普通方法与扩展方法分开。 而是将逻辑连接的代码放在一起,以便从上到下阅读该类的人可以遵循发生的逻辑。 选择一个排序顺序(首先是高层代码,然后是“细节”,反之亦然),然后坚持下去。


将嵌套的类放在使用这些类的代码旁边。 如果类是供外部使用的,并且未在类内引用,则将它们放在伴随对象的末尾。


接口实施框架


在实现接口时,请保持与要实现的接口相同的结构(如有必要,将其与用于实现的其他专用方法交替使用)


覆盖结构


重新定义总是一个接一个地放在一起。


命名规则


Kotlin遵循与Java相同的命名约定。 特别是:


小写的程序包名称,不要使用下划线(org.example.myproject)。 通常不建议使用多个单词中的名称,但是如果您需要使用多个单词,则可以将它们简单地组合在一起,也可以使用驼峰(org.examle.myProject)。


大写和对象名称以大写字母开头,并使用驼峰驼峰:


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

功能名称


函数,属性和局部变量的名称以小写字母开头,并且不包含下划线:


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

例外:用于实例化类的工厂函数可能与正在创建的类具有相同的名称:


 abstract class Foo { ... } class FooImpl : Foo { ... } fun Foo(): Foo { return FooImpl(...) } 

测试方法名称


在测试中(并且仅在测试中),允许使用方法名称,并在名称之间使用逗号分隔。 (请注意,Android运行时当前不支持此类方法名称。)测试代码中还允许在方法名称中使用下划线。


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

属性命名


常量名称(标有const属性,顶级属性或没有包含不可变数据的自定义get函数的val对象)必须大写,并用下划线分隔:


 const val MAX_COUNT = 8 val USER_NAME_FIELD = "UserName" 

包含具有行为或可变数据的对象的顶级名称或对象属性应在驼峰中使用通用名称:


 val mutableCollection: MutableSet<String> = HashSet() 

引用Singleton对象的属性名称可以使用与类声明相同的命名方式:


 val PersonComparator: Comparator<Person> = ... 

对于枚举,您可以使用以大写字母写的名称,并以下划线或驼峰驼峰式开头,视使用情况而定,以大写字母开头。


 enum class Color { RED, GREEN } 

 enum class Color { RedColor, GreenColor } 

译者注:只是不要混合使用不同的样式。 选择一种样式并在您的设计中坚持使用。


命名隐藏属性


如果一个类具有两个在概念上相同的属性,但一个属性是公共API的一部分,另一个属性是实现的一部分,请使用下划线作为隐藏属性名称的前缀:


 class C { private val _elementList = mutableListOf<Element>() val elementList: List<Element> get() = _elementList } 

选择正确的名字


类名称通常是解释类是什么的名词或短语:


 List, PersonReader 

该方法的名称通常是动词或短​​语操作,用于说明该方法的作用:


 close, readPersons 

该名称还应指示该方法是更改​​对象还是返回新对象。 例如, sort是一种更改集合的排序, sorted是该集合的新排序副本的返回。


名称应该清楚地表明实体的用途,因此最好避免在名称中使用无意义的词( ManagerWrapper等)。


在广告名称中使用首字母缩写词时,如果首字母缩写词由两个字母( IOStream )组成,请使用大写字母;否则,请使用大写字母。 如果首字母较长,则仅首字母大写( XmlFormatterHttpInputStream )。


格式化


在大多数情况下,Kotlin遵循Java格式约定。


使用4个空格缩进。 不要使用标签。


对于撑杆,将开口撑杆放置在结构开始的线的末端,将闭合撑杆放置在与开口结构水平对齐的单独线上。


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

(注意:在Kotlin中,分号是可选的,因此换行很重要。语言设计涉及Java样式的花括号,如果尝试使用其他格式样式,则可能会遇到意外的代码执行行为。)


水平空间


在二进制运算符(a + b)周围放置空格。 例外:请勿在“范围至”运算符(0..i)周围放置空格


不要在一元运算符(a++)周围放置空格


在关键控制词( ifwhenforwhile )和相应的方括号之间放置空格。


在构造函数,方法或方法的主声明中,请勿在左括号之前放置空格。


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

切勿在([或之前])后面放置空格。


切勿在点周围放置空间. 还是运算符?.


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

在双斜杠后加一个空格//


 //   

请勿在用于指示类型参数的尖括号周围放置空格:


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

不要在双冒号周围放置空格以指示对::类方法的引用:


 Foo::class String::length 

不要在前面放一个空格? 用于标记null


 String? 

通常,避免使用任何类型的水平对齐方式。 将标识符重命名为其他长度的名称不应影响代码的格式。


冒号


在冒号前面放置一个空格:在以下情况下:


  • 用于将类型与超级类型分开时;

 abstract class Foo<out T : Any> 

  • 当委托给超类构造函数或同一个类的另一个构造函数时;

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

  • 在关键字对象之后。

 val x = object : IFoo { ... } 

:分隔广告及其类型时,请不要在之前放置空格。


 abstract fun foo(a: Int): T 

始终在:之后放置一个空格:


 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 { ... } } 

类声明格式


具有几个基本构造函数参数和短名称的类可以写在一行上:


 class Person(id: Int, name: String) 

具有较长名称或参数数量的类必须进行格式化,以使构造函数的每个主要参数都位于带有缩进的单独行中。 另外,右括号必须在新行上。 如果我们使用继承,则对超类的构造函数的调用或已实现接口的列表应与括号位于同一行:


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

在指定接口并调用超类的构造函数时,必须首先找到超类的构造函数,然后在新行上将接口名称左对齐:


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

对于具有一长串超级类型的类,您需要在冒号后放置一个换行符,并将所有超级类型名称水平对齐到左侧:


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

要在班级标题较长时清楚地将班级标题及其主体分开,请在班级标题后放置一个空行(如上例所示),或将开括号放在单独的行上:


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

构造函数参数使用常规缩进(4个空格)。


基本原理:这可以确保在主构造函数中声明的属性具有与在类主体中声明的属性相同的缩进。


修饰符


如果广告包含多个修饰符,请始终按以下顺序排列它们:


 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 

将所有注释放在修饰符之前:


 @Named("Foo") private val foo: Foo 

如果您不在使用库,请省略多余的修饰符(例如public)。


格式注释


批注通常放在附加声明的声明之前的不同行上,并具有相同的缩进:


 @Target(AnnotationTarget.PROPERTY) annotation class JsonExclude 

不带参数的注释可以位于一行:


 @JsonExclude @JvmField var x: String 

一个没有参数的注释可以和相应的声明放在同一行:


 @Test fun foo() { ... } 

文件注释


文件注释放置在文件注释(如果有)之后,在package语句之前,并以空行与软件包分开(以强调它们是针对文件而不是软件包的事实)。


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

功能格式化


如果方法签名不适合一行,请使用以下语法:


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

对函数参数使用常规缩进(4个空格)。


理由:与构造函数参数一致

对于由一行组成的函数,最好使用不带大括号的表达式。


 fun foo(): Int { // bad return 1 } fun foo() = 1 // good 

单行表达式格式


如果单行函数的主体与声明不在同一行,请将=符号放在第一行。 将表达式的主体缩进4个空格。


 fun f(x: String) = x.length 

属性格式


对于简单的只读属性,最好使用单行格式:


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

对于更复杂的属性,请始终在单独的行上使用getset


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

对于具有初始化的属性,如果初始化程序太长,请在等号后添加一个换行符,并为初始化字符串添加四个空格的缩进:


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

格式化控制指令


如果ifwhen控制语句中的条件为多行,请始终在语句主体周围使用花括号。 相对于语句的开头,条件的每一行都缩进4个空格。 将条件的右括号和左花括号放在单独的一行上:


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

理由:条件主体与条件主体的整齐对齐和清晰分离

elsecatchfinally关键字以及do / while循环的while关键字与上一个结束大括号放在同一行:


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

如果指令的when条件由几个块组成,建议使用空行将它们彼此分开:


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

将简短的when语句块放在没有花括号的同一行上。


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

格式化方法调用


当使用一长串参数时,请将换行符放在括号后面。 缩进4个空格并将逻辑上相关的参数分组在一行上。


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

在参数名称及其值之间的等号周围使用空格。


格式化链函数调用


使用链接的呼叫时,请放入. 还是?. 换行符,在四个空格中有一个缩进:


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

链中的第一个调用通常应该在其前面有一个换行符,但是如果代码可读性更好并且很有意义,通常不这样做。


格式化Lambda表达式


在lambda表达式中,应在花括号和将参数与主体分开的箭头周围使用空格。 如果呼叫接受单个Lambda字符,则应尽可能在括号外使用它。


 list.filter { it > 10 } 

将标签分配给lambda表达式时,请勿在标签和左括号之间留空格:


 fun foo() { ints.forEach lit@{ // ... } } 

在多行lambda表达式中声明参数名称时,请将名称放在第一行,然后是箭头,然后在新行中将函数主体的开头:


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

如果参数列表不在一行上,请将箭头放在另一行上:


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

文书工作


使用多行文档时,将/**放在单独的行上,然后在每行之后加星号:


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

简短的文档可以放在一行上:


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

通常,避免使用paramreturn标签。 相反,应在文档注释中直接包含参数说明和返回值,并在提及参数的地方添加参数引用。 使用param并仅在需要较长的描述而不符合正文含义时返回


 // 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) = ... 

避免不必要的构造


Kotlin中的许多语法构造都是可选的,并且在开发环境中突出显示为不必要的;您不应在代码中使用它们,只是使代码“清晰”。


使用关键字单位


在函数中,不应使用Unit关键字:


 fun foo() { // ": Unit" is omitted here } 

分号


避免在任何机会都使用分号。


字符串模式


将简单变量插入模板字符串时,请勿使用花括号。 花括号仅用于长表达式。


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

惯用语言功能


不变性


最好在可变数据之前使用不可变数据。 始终将局部变量和属性声明为val ,而不是var ,除非它们确实发生了变化。


始终使用不可变的集合接口( CollectionListSetMap )声明不变的集合。 每次利用工厂方法创建集合时,请使用返回不可变集合的实现:


 // 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 . , :


  • ? , , it , this ( also let ). also , .

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

  • ? , apply also . , with , let run .

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

  • null ? , apply , let run . , with 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") } 


API:


  • ( API)
  • ( )
  • KDoc public api, , /

Source: https://habr.com/ru/post/zh-CN431990/


All Articles