Kotlin 编码规约

作者: 光剑书架上的书 | 来源:发表于2019-07-25 15:44 被阅读26次

    Kotlin 编码规约


    编码规范

    本页包含当前 Kotlin 语言的编码风格。

    应用风格指南

    如需根据本风格指南配置 IntelliJ 格式化程序,请安装 Kotlin 插件1.2.20 或更高版本,转到“Settings | Editor | Code Style | Kotlin”,点击右上角的“Set from...”链接,并从菜单中选择“Predefined style / Kotlin style guide”。

    如需验证代码已按风格指南格式化,请转到探查设置(Inspections)并启用“Kotlin | Style issues | File is not formatted according to project settings”探查项。验证风格指南中描述的其他问题(如命名约定)的附加探查项默认已启用。

    源代码组织

    目录结构

    在混合语言项目中,Kotlin 源文件应当与 Java 源文件位于同一源文件根目录下,并遵循相同的目录结构(每个文件应存储在与其 package 语句对应的目录中
    )。

    在纯 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.example.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()
    

    保存单例对象引用的属性的名称可以使用与 object 声明相同的命名风格:

    val PersonComparator: Comparator<Person> = ...
    

    对于枚举常量,可以使用大写、下划线分隔的名称(enum class Color { RED, GREEN })也可使用以大写字母开头的常规驼峰名称,具体取决于用途。

    幕后属性的名称

    如果一个类有两个概念上相同的属性,一个是公共 API 的一部分,另一个是实现细节,那么使用下划线作为私有属性名称的前缀:

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

    选择好名称

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

    方法的名称通常是动词或动词短语,说明该方法什么:closereadPersons。修改对象或者返回一个新对象的名称也应遵循建议。例如 sort 是对一个集合就地排序,而 sorted 是返回一个排序后的集合副本。

    名称应该表明实体的目的是什么,所以最好避免在名称中使用无意义的单词(ManagerWrapper 等)。

    当使用首字母缩写作为名称的一部分时,如果缩写由两个字母组成,就将其大写(IOStream);而如果缩写更长一些,就只大写其首字母(XmlFormatterHttpInputStream)。

    格式化

    在大多数情况下,Kotlin 遵循 Java 编码规范。

    使用 4 个空格缩进。不要使用 tab。

    对于花括号,将左花括号放在结构起始处的行尾,而将右花括号放在与左括结构横向对齐的单独一行。

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

    (注意:在 Kotlin 中,分号是可选的,因此换行很重要。语言设计采用Java 风格的花括号格式,如果尝试使用不同的格式化风格,那么可能会遇到意外的行为。)

    横向空白

    在二元操作符左右留空格(a + b)。例外:不要在“range to”操作符(0..i)左右留空格。

    不要在一元运算符左右留空格(a++

    在控制流关键字(ifwhenfor 以及 while)与相应的左括号之间留空格。

    不要在主构造函数声明、方法声明或者方法调用的左括号之前留空格。

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

    绝不在 ([ 之后或者 ]) 之前留空格。

    绝不在. 或者 ?. 左右留空格:foo.bar().filter { it > 2 }.joinToString(), foo?.bar()

    // 之后留一个空格:// 这是一条注释

    不要在用于指定类型参数的尖括号前后留空格:class Map<K, V> { …… }

    不要在 :: 前后留空格:Foo::classString::length

    不要在用于标记可空类型的 ? 前留空格:String?

    作为一般规则,避免任何类型的水平对齐。将标识符重命名为不同长度的名称不应该影响声明或者任何用法的格式。

    冒号

    在以下场景中的 : 之前留一个空格:

    • 当它用于分隔类型与超类型时;
    • 当委托给一个超类的构造函数或者同一类的另一个构造函数时;
    • object 关键字之后。

    而当分隔声明与其类型时,不要在 : 之前留空格。

    : 之后总要留一个空格。

    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 语句之前,并且用一个空白行与 package 分开(为了强调其针对文件而不是包)。

    /** 授权许可、版权以及任何其他内容 */
    @file:JvmName("FooBar")
    
    package foo.bar
    

    函数格式化

    如果函数签名不适合单行,请使用以下语法:

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

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

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

    对于由单个表达式构成的函数体,优先使用表达式形式。

    fun foo(): Int {     // 不良
        return 1 
    }
    
    fun foo() = 1        // 良好
    

    表达式函数体格式化

    如果函数的表达式函数体与函数声明不适合放在同一行,那么将 = 留在第一行。将表达式函数体缩进 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) {
        // 主体
    } else {
        // else 部分
    }
    
    try {
        // 主体
    } finally {
        // 清理
    }
    

    when 语句中,如果一个分支不止一行,可以考虑用空行将其与相邻的分支块分开:

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

    将短分支放在与条件相同的行上,无需花括号。

    when (foo) {
        true -> bar() // 良好
        false -> { baz() } // 不良
    }
    

    方法调用格式化

    在较长参数列表的左括号后添加一个换行符。按 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)
    }
    

    文档注释

    对于较长的文档注释,将开头 /** 放在一个独立行中,并且后续每行都以星号开头:

    /**
     * 这是一条多行
     * 文档注释。
     */
    

    简短注释可以放在一行内:

    /** 这是一条简短文档注释。 */
    

    通常,避免使用 @param@return 标记。而是将参数与返回值的描述直接合并到文档注释中,并在提到参数的任何地方加上参数链接。只有当需要不适合放进主文本流程的冗长描述时才应使用 @param@return

    // 避免这样:
    
    /**
     * 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) = ……
    
    // 而要这样:
    
    /**
     * Returns the absolute value of the given [number].
     */
    fun abs(number: Int) = ……
    

    避免重复结构

    一般来说,如果 Kotlin 中的某种语法结构是可选的并且被 IDE高亮为冗余的,那么应该在代码中省略之。为了清楚起见,不要在代码中保留不必要的语法元素。

    Unit

    如果函数返回 Unit,那么应该省略返回类型:

    fun foo() { // 这里省略了“: Unit”
    
    }
    

    分号

    尽可能省略分号。

    字符串模版

    将简单变量传入到字符串模版中时不要使用花括号。只有用到更长表达式时才使用花括号。

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

    语言特性的惯用法

    不可变性

    优先使用不可变(而不是可变)数据。初始化后未修改的局部变量与属性,总是将其声明为 val 而不是 var

    总是使用不可变集合接口(Collection, List, Set, Map)来声明无需改变的集合。使用工厂函数创建集合实例时,尽可能选用返回不可变集合类型的函数:

    // 不良:使用可变集合类型作为无需改变的值
    fun validateValue(actualValue: String, allowedValues: HashSet<String>) { …… }
    
    // 良好:使用不可变集合类型
    fun validateValue(actualValue: String, allowedValues: Set<String>) { …… }
    
    // 不良:arrayListOf() 返回 ArrayList<T>,这是一个可变集合类型
    val allowedValues = arrayListOf("a", "b", "c")
    
    // 良好:listOf() 返回 List<T>
    val allowedValues = listOf("a", "b", "c")
    

    默认参数值

    优先声明带有默认参数的函数而不是声明重载函数。

    // 不良
    fun foo() = foo("a")
    fun foo(a: String) { …… }
    
    // 良好
    fun foo(a: String = "a") { …… }
    

    类型别名

    如果有一个在代码库中多次用到的函数类型或者带有类型参数的类型,那么最好为它定义一个类型别名:

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

    Lambda 表达式参数

    在简短、非嵌套的 lambda 表达式中建议使用 it 用法而不是显式声明参数。而在有参数的嵌套 lambda 表达式中,始终应该显式声明参数。

    在 lambda 表达式中返回

    避免在 lambda 表达式中使用多个返回到标签。请考虑重新组织这样的 lambda 表达式使其只有单一退出点。如果这无法做到或者不够清晰,请考虑将 lambda 表达式转换为匿名函数。

    不要在 lambda 表达式的最后一条语句中使用返回到标签。

    命名参数

    当一个方法接受多个相同的原生类型参数或者多个 Boolean 类型参数时,请使用命名参数语法,除非在上下文中的所有参数的含义都已绝对清楚。

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

    使用条件语句

    优先使用 tryifwhen 的表达形式。例如:

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

    优先选用上述代码而不是:

    if (x)
        return foo()
    else
        return bar()
        
    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) 检测。

    使用循环

    优先使用高阶函数(filtermap 等)而不是循环。例外:forEach(优先使用常规的 for 循环,除非 forEach 的接收者是可空的或者 forEach 用做长调用链的一部分。)

    当在使用多个高阶函数的复杂表达式与循环之间进行选择时,请了解每种情况下所执行操作的开销并且记得考虑性能因素。

    区间上循环

    使用 until 函数在一个区间上循环:

    for (i in 0..n - 1) { …… }  // 不良
    for (i in 0 until n) { …… }  // 良好
    

    使用字符串

    优先使用字符串模板而不是字符串拼接。

    优先使用多行字符串而不是将 \n 转义序列嵌入到常规字符串字面值中。

    如需在多行字符串中维护缩进,当生成的字符串不需要任何内部缩进时使用 trimIndent,而需要内部缩进时使用 trimMargin

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

    函数还是属性

    在某些情况下,不带参数的函数可与只读属性互换。虽然语义相似,但是在某种程度上有一些风格上的约定。

    底层算法优先使用属性而不是函数:

    • 不会抛异常
    • 计算开销小(或者在首次运行时缓存)
    • 如果对象状态没有改变,那么多次调用都会返回相同结果

    使用扩展函数

    放手去用扩展函数。每当你有一个主要用于某个对象的函数时,可以考虑使其成为一个以该对象为接收者的扩展函数。为了尽量减少 API 污染,尽可能地限制扩展函数的可见性。根据需要,使用局部扩展函数、成员扩展函数或者具有私有可视性的顶层扩展函数。

    使用中缀函数

    一个函数只有用于两个角色类似的对象时才将其声明为中缀函数。良好示例如:andtozip。不良示例如:add

    如果一个方法会改动其接收者,那么不要声明为中缀形式。

    工厂函数

    如果为一个类声明一个工厂函数,那么不要让它与类自身同名。优先使用独特的名称,该名称能表明为何该工厂函数的行为与众不同。只有当确实没有特殊的语义时,才可以使用与该类相同的名称。

    例如:

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

    如果一个对象有多个重载的构造函数,它们并非调用不同的超类构造函数,并且不能简化为具有默认参数值的单个构造函数,那么优先用工厂函数取代这些重载的构造函数。

    平台类型

    返回平台类型表达式的公有函数/方法必须显式声明其 Kotlin 类型:

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

    任何使用平台类型表达式初始化的属性(包级别或类级别)必须显式声明其 Kotlin 类型:

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

    使用平台类型表达式初始化的局部值可以有也可以没有类型声明:

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

    使用作用域函数 apply/with/run/also/let

    Kotlin 提供了一系列用来在给定对象上下文中执行代码块的函数:letrunwithapply 以及 also。关于不同情况下选择正确作用域函数的准则,请参考作用域函数

    库的编码规范

    在编写库时,建议遵循一组额外的规则以确保 API 的稳定性:

    • 总是显式指定成员的可见性(以避免将声明意外暴露为公有 API )
    • 总是显式指定函数返回类型以及属性类型(以避免当实现改变时意外更改返回类型)
    • 为所有公有成员提供 KDoc 注释,不需要任何新文档的覆盖成员除外
      (以支持为该库生成文档)


    Kotlin 开发者社区

    国内第一Kotlin 开发者社区公众号,主要分享、交流 Kotlin 编程语言、Spring Boot、Android、React.js/Node.js、函数式编程、编程思想等相关主题。

    Kotlin 开发者社区

    Kotlin Coding Conventions

    This page contains the current coding style for the Kotlin language.

    Applying the style guide

    To configure the IntelliJ formatter according to this style guide, please install Kotlin plugin version1.2.20 or newer, go to Settings | Editor | Code Style | Kotlin, click on "Set from..." link in the upperright corner, and select "Predefined style / Kotlin style guide" from the menu.

    To verify that your code is formatted according to the style guide, go to the inspection settings and enablethe "Kotlin | Style issues | File is not formatted according to project settings" inspection. Additionalinspections that verify other issues described in the style guide (such as naming conventions) are enabled by default.

    Source code organization

    Directory structure

    In mixed-language projects, Kotlin source files should reside in the same source root as the Java source files,and follow the same directory structure (each file should be stored in the directory corresponding to each packagestatement).

    In pure Kotlin projects, the recommended directory structure is to follow the package structure withthe common root package omitted (e.g. if all the code in the project is in the "org.example.kotlin" package and itssubpackages, files with the "org.example.kotlin" package should be placed directly under the source root, andfiles in "org.example.kotlin.foo.bar" should be in the "foo/bar" subdirectory of the source root).

    Source file names

    If a Kotlin file contains a single class (potentially with related top-level declarations), its name should be the sameas 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(e.g. ProcessDeclarations.kt).

    The name of the file should describe what the code in the file does. Therefore, you should avoid using meaninglesswords such as "Util" in file names.

    Source file organization

    Placing multiple declarations (classes, top-level functions or properties) in the same Kotlin source file is encouragedas long as these declarations are closely related to each other semantically and the file size remains reasonable(not exceeding a few hundred lines).

    In particular, when defining extension functions for a class which are relevant for all clients of this class,put them in the same file where the class itself is defined. When defining extension functions that make senseonly for a specific client, put them next to the code of that client. Do not create files just to hold"all extensions of Foo".

    Class layout

    Generally, the contents of a class is sorted in the following order:

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

    Do not sort the method declarations alphabetically or by visibility, and do not separate regular methodsfrom extension methods. Instead, put related stuff together, so that someone reading the class from top to bottom wouldbe able to follow the logic of what's happening. Choose an order (either higher-level stuff first, or vice versa)and stick to it.

    Put nested classes next to the code that uses those classes. If the classes are intended to be used externally and aren'treferenced inside the class, put them in the end, after the companion object.

    Interface implementation layout

    When implementing an interface, keep the implementing members in the same order as members of the interface (if necessary,interspersed with additional private methods used for the implementation)

    Overload layout

    Always put overloads next to each other in a class.

    Naming rules

    Kotlin follows the Java naming conventions. In particular:

    Names of packages are always lower case and do not use underscores (org.example.myproject). Using multi-wordnames is generally discouraged, but if you do need to use multiple words, you can either simply concatenate them togetheror use camel humps (org.example.myProject).

    Names of classes and objects start with an upper case letter and use camel humps:

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

    Function names

    Names of functions, properties and local variables start with a lower case letter and use camel humps and no underscores:

    fun processDeclarations() { ... }
    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(...) }
    

    Names for test methods

    In tests (and only in tests), it's acceptable to use method names with spaces enclosed in backticks.(Note that such method names are currently not supported by the Android runtime.) Underscores in method names arealso allowed in test code.

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

    Property names

    Names of constants (properties marked with const, or top-level or object val properties with no custom get functionthat hold deeply immutable data) should use uppercase underscore-separated names:

    const val MAX_COUNT = 8
    val USER_NAME_FIELD = "UserName"
    

    Names of top-level or object properties which hold objects with behavior or mutable data should use regular camel-hump names:

    val mutableCollection: MutableSet<String> = HashSet()
    

    Names of properties holding references to singleton objects can use the same naming style as object declarations:

    val PersonComparator: Comparator<Person> = ...
    

    For enum constants, it's OK to use either uppercase underscore-separated names(enum class Color { RED, GREEN }) or regular camel-humps names starting with an uppercase letter, depending on the usage.

    Names for backing properties

    If a class has two properties which are conceptually the same but one is part of a public API and another is an implementationdetail, use an underscore as the prefix for the name of the private property:

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

    Choosing good names

    The name of a class is usually a noun or a noun phrase explaining what the class is: List, PersonReader.

    The name of a method is usually a verb or a verb phrase saying what the method does: close, readPersons.The name should also suggest if the method is mutating the object or returning a new one. For instance sort issorting a collection in place, while sorted is returning a sorted copy of the collection.

    The names should make it clear what the purpose of the entity is, so it's best to avoid using meaningless words(Manager, Wrapper etc.) in names.

    When using an acronym as part of a declaration name, capitalize it if it consists of two letters (IOStream);capitalize only the first letter if it is longer (XmlFormatter, HttpInputStream).

    Formatting

    In most cases, Kotlin follows the Java coding conventions.

    Use 4 spaces for indentation. Do not use tabs.

    For curly braces, put the opening brace in the end of the line where the construct begins, and the closing braceon 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. The language design assumesJava-style braces, and you may encounter surprising behavior if you try to use a different formatting style.)

    Horizontal whitespace

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

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

    Put spaces between control flow keywords (if, when, for and while) and the corresponding opening parenthesis.

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

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

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

    Never put a space around . or ?.: foo.bar().filter { it > 2 }.joinToString(), foo?.bar()

    Put a space after //: // This is a comment

    Do not put spaces around angle brackets used to specify type parameters: class Map<K, V> { ... }

    Do not put spaces around ::: Foo::class, String::length

    Do not put a space before ? used to mark a nullable type: String?

    As a general rule, avoid horizontal alignment of any kind. Renaming an identifier to a name with a different lengthshould not affect the formatting of either the declaration or any of the usages.

    Colon

    Put a space before : in the following cases:

    • when it's used to separate a type and a supertype;
    • when delegating to a superclass constructor or a different constructor of the same class;
    • after the object keyword.

    Don't put a space before : when it separates a declaration and its type.

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

    Class header formatting

    Classes with a few primary constructor parameters can be written in a single line:

    class Person(id: Int, name: String)
    

    Classes with longer headers should be formatted so that each primary constructor parameter is in a separate line with indentation.Also, the closing parenthesis should be on a new line. If we use inheritance, then the superclass constructor call or list of implemented interfacesshould be located on the same line as the parenthesis:

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

    For multiple interfaces, the superclass constructor call should be located first and then each interface should be located in a different line:

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

    For classes with a long supertype list, put a line break after the colon and align all supertype names horizontally:

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

    To clearly separate the class header and body when the class header is long, either put a blank linefollowing the class header (as in the example above), or put the opening curly 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 primary constructor have the same indentation as properties
    declared in the body of a class.

    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
    

    Place all annotations before modifiers:

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

    Unless you're working on a library, omit redundant modifiers (e.g. public).

    Annotation formatting

    Annotations are typically 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 may be placed on the same line:

    @JsonExclude @JvmField
    var x: String
    

    A single annotation without arguments may be placed on the same line as the corresponding declaration:

    @Test fun foo() { ... }
    

    File annotations

    File annotations are placed after the file comment (if any), before the package statement, and are separated from package with a blank line (to emphasize the fact that they target the file and not the package).

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

    Function formatting

    If the function signature doesn't fit on a single 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 constructor parameters

    Prefer using an expression body for functions with the body consisting of a single expression.

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

    Expression body formatting

    If the function has an expression body that doesn't fit in the same line as the declaration, put the = sign on the first line.Indent the expression body by 4 spaces.

    fun f(x: String) =
        x.length
    

    Property formatting

    For very simple read-only properties, consider one-line formatting:

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

    For more complex properties, always put get and set keywords on separate lines:

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

    For properties with an initializer, if the initializer is long, add a line break after the equals signand indent the initializer by four spaces:

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

    Formatting control flow statements

    If the condition of an if or when statement is multiline, always use curly braces around the body of the statement.Indent each subsequent line of the condition by 4 spaces relative to statement begin.Put the closing parentheses of the condition together with the opening curly brace on a separate line:

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

    Rationale: Tidy alignment and clear separation of condition and statement body

    Put the else, catch, finally keywords, as well as the while keyword of a do/while loop, on the same line as thepreceding curly brace:

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

    In a when statement, if a branch is more than a single line, consider separating it from adjacent case blocks with a blank line:

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

    Put short branches on the same line as the condition, without braces.

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

    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
    )
    

    Put spaces around the = sign separating the argument name and value.

    Chained call wrapping

    When wrapping chained calls, put the . character or the ?. operator on the next line, with a single indent:

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

    The first call in the chain usually should have a line break before it, but it's OK to omit it if the code makes more sense that way.

    Lambda formatting

    In lambda expressions, spaces should be used around the curly braces, as well as around the arrow which separates the parametersfrom the body. If a call takes a single lambda, it should be passed outside of parentheses whenever possible.

    list.filter { it > 10 }
    

    If assigning a label for a lambda, do not put a space between the label and the opening curly brace:

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

    When declaring parameter names in a multiline lambda, put the names on the first line, followed by the arrow and the newline:

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

    If the parameter list is too long to fit on a line, put the arrow on a separate line:

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

    Documentation comments

    For longer documentation comments, place the opening /** on a separate line and begin each subsequent linewith an asterisk:

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

    Short comments can be placed on a single line:

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

    Generally, avoid using @param and @return tags. Instead, incorporate the description of parameters and return valuesdirectly 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) = ...
    

    Avoiding redundant constructs

    In general, if a certain syntactic construction in Kotlin is optional and highlighted by the IDEas redundant, you should omit it in your code. Do not leave unnecessary syntactic elements in codejust "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 ifthey are not modified after initialization.

    Always use immutable collection interfaces (Collection, List, Set, Map) to declare collections which are notmutated. When using factory functions to create collection instances, always use functions that return immutablecollection 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")
    

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

    Type aliases

    If you have a functional type or a type with type parameters which is used multiple times in a codebase, prefer defininga type alias for it:

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

    Lambda parameters

    In lambdas which are short and not nested, it's recommended to use the it convention instead of declaring the parameterexplicitly. In nested lambdas with parameters, parameters should be always declared explicitly.

    Returns in a lambda

    Avoid using multiple labeled returns in a lambda. Consider restructuring the lambda so that it will have a single exit point.If that's not possible or not clear enough, consider converting the lambda into an anonymous function.

    Do not use a labeled return for the last statement in a lambda.

    Named arguments

    Use the named argument syntax when a method takes multiple parameters of the same primitive type, or for parameters of Boolean type,unless the meaning of all parameters is absolutely clear from context.

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

    Using conditional statements

    Prefer using the expression form of try, if and when. Examples:

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

    The above is preferable to:

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

    if versus when

    Prefer using if for binary conditions instead of when. Instead of

    when (x) {
        null -> ...
        else -> ...
    }
    

    use if (x == null) ... else ...

    Prefer using when if there are three or more options.

    Using nullable Boolean values in conditions

    If you need to use a nullable Boolean in a conditional statement, use if (value == true) or if (value == false) checks.

    Using loops

    Prefer using higher-order functions (filter, map etc.) to loops. Exception: forEach (prefer using a regular for loop instead,unless the receiver of forEach is nullable or forEach is used as part of a longer call chain).

    When making a choice between a complex expression using multiple higher-order functions and a loop, understand the costof the operations being performed in each case and keep performance considerations in mind.

    Loops on ranges

    Use the until function to loop over an open range:

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

    Using strings

    Prefer using string templates to string concatenation.

    Prefer to use multiline strings instead of embedding \n escape sequences into regular string literals.

    To maintain indentation in multiline strings, use trimIndent when the resulting string does not require any internalindentation, or trimMargin when internal indentation 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 might be interchangeable with read-only properties.Although the semantics are similar, there are some stylistic conventions on when to prefer one to another.

    Prefer a property over a function when the underlying algorithm:

    • does not throw
    • is cheap to calculate (or caсhed on the first run)
    • returns the same result over invocations if the object state hasn't changed

    Using extension functions

    Use extension functions liberally. Every time you have a function that works primarily on an object, consider making itan extension function accepting that object as a receiver. To minimize API pollution, restrict the visibility ofextension functions as much as it makes sense. As necessary, use local extension functions, member extension functions,or top-level extension functions with private visibility.

    Using infix functions

    Declare a function as infix only when it works on two objects which play a similar role. Good examples: and, to, zip.Bad example: add.

    Don't declare a method as infix if it mutates the receiver object.

    Factory functions

    If you declare a factory function for a class, avoid giving it the same name as the class itself. Prefer using a distinct namemaking it clear why the behavior of the factory function is special. Only if there is really no special semantics,you can use the same name as the class.

    Example:

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

    If you have an object with multiple overloaded constructors that don't call different superclass constructors andcan't be reduced to a single constructor with default argument values, prefer to replace the overloaded constructors withfactory functions.

    Platform types

    A public function/method returning an expression of a platform type must declare its Kotlin type explicitly:

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

    Any property (package-level or class-level) initialised with an expression of a platform type must declare its Kotlin type explicitly:

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

    A local value initialised with an expression of a platform type may or may not have a type declaration:

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

    Using scope functions apply/with/run/also/let

    Kotlin provides a variety of functions to execute a block of code in the context of a given object: let, run, with, apply, and also.For the guidance on choosing the right scope function for your case, refer to Scope Functions.

    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)

    参考文档

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

    相关文章

      网友评论

        本文标题:Kotlin 编码规约

        本文链接:https://www.haomeiwen.com/subject/stshrctx.html