转载请注明出处.This Translation is under CC BY-NC-SA 3.0 CN License.

Android Kotlin Style guide

Android Kotlin 代码规范

目录


1. 源文件

所以的源文件都是 UTF-8 编码。

1.1 命名

如果一个文件只有一个顶层的类,那么文件名应该是大小写敏感的,并且以 .kt 结尾。不然,如果一个文件有很多顶层类,选择一个符合内容的名字,用以驼峰命名法,并以 .kt 结尾。

// Foo.kt
class Foo { }

// Bar.kt
class Bar { }
fun Runnable.toBar(): Bar = // …

// Map.kt
fun <T, O> Set<T>.map(func: (T) -> O): List<O> = // …
fun <T, O> List<T>.map(func: (T) -> O): List<O> = // …

1.1.1 特殊字符

1.1.1.1 空白字符

除了终止序列外,ASCII horizontal space character (0x20) 是源文件里唯一可以出现的空白字符。也就是说:

  1. 所有其他的空白字符都会被转义。
  2. Tab 键不能用来缩进。

1.1.1.2 特殊转义字符

对于所有的转义字符(\b, \n, \r, \t, ', ", \, 和 $),使用上面这种表达方式,而尽量不使用Unicode(比如: \u000a)。

1.1.1.3 非-ASCII 字符

对于非-ASCII 字符,Unicode (比如:)和相同的转义字符(比如:\u221e)都可以使用。这取决于那种方式更容易理解。Unicode的转义字符对于可打印的字符不推荐使用,字符串和注释之外的地方更不推荐。

例子 评价
return "\ufeff" + content 好:对于不可打印的字符是可行的
val unitAbbrev = "\u03bcs" // μs 差:多此一举
val unitAbbrev = "\u03bcs" 差:不知所云
val unitAbbrev = "μs" 棒:没有注释也十分清晰

1.2 结构

一个 .kt 文件依次包含了下列的内容:

  1. Copyright 和 License(可选的)

  2. 文件级别的注解

  3. 包语句

  4. import 语句

  5. 顶层的声明

每个结构可以用一个空白行隔开。

如果有 Copyright 和 License 的话,它们应该在文件的最上方,并且以多行注释的方式。

/*
 * Copyright 2017 Google, Inc.
 *
 * ...
 */

不要使用 KDoc-style 或者单行注释的方式:

/**
 * Copyright 2017 Google, Inc.
 *
 * ...
 */
// Copyright 2017 Google, Inc.
//
// ...

1.2.2 文件级别的注解

文件的注解使用 use-site target应该写在 Copyright 的注释和包语句之间。

1.2.3 包语句

包声明不受任何限制,也不要换行(指line-wrapping)。

1.2.4 import 语句

一切的导入语句应该放在一起。

使用通配符导入是不允许的。

和包语句类似,import 语句也不受任何限制,也不要换行(指line-wrapping)。

1.2.5 顶层声明

一个 .kt 文件一个或多个的类型、函数,属性或者类型别名在顶层(类级别)。

一个文件的内容应该保持一个主题。也就是说,这个文件可以是一个单独的公有类或者是一系列功能耦合的扩展函数。不相关的声明应该分离出来写在另一个文件,并且一个文件里公有的声明应该尽量得少。

但对文件内容的数量和顺序没有明确的限制。

源文件一般都是从顶到底读取的(对读者而言),所以一般的,写在前面的声明可以帮助理解写在后面的声明。但是不同的文件可以选择不同的内容顺序。

重要的是每个类都使用一些逻辑顺序,如果被问到,维护者可以解释清楚.比如说,新的方法不应该仅仅被添加到类的末尾,因为这是按时间排序而不是按逻辑排序.

1.3 类成员的顺序

一个类里的成员顺序和顶层声明的顺序一样。


2. 格式

2.1 大括号

大括号({})在 if 语句和 when 语句,没有 elese ifelse 时,是可以省略的,并且写成一行。

if (string.isEmpty()) return

when (value) {
    0 -> return
    // …
}

除此之外,对于任何的 ifforwhendowhile 大括号都是必须的,即使是空语句。

if (string.isEmpty())
    return  // 错误

if (string.isEmpty()) {
    return  // 正确
}

2.1.1非空的代码块

对于非空的代码块和类似块结构的代码,大括号遵循 K&R 风格。

  • 左括号之前不换行

  • 左括号后换行

  • 右括号前换行

  • 右括号后换行,只有在这个括号结束了一个语句、函数、构造函数 或者 类,才换行。比如,if 的右大括号之后是不换行的,如果有 else 的话。

return Runnable {
    while (condition()) {
        foo()
    }
}

return object : MyClass() {
    override fun foo() {
        if (condition()) {
            try {
                something()
            } catch (e: ProblemException) {
                recover()
            }
        } else if (otherCondition()) {
            somethingElse()
        } else {
            lastThing()
        }
    }
}

几个例外见枚举类

2.1.2 空代码块

一个空代码块必须是 K&R 风格的。

try {
    doSomething()
} catch (e: Exception) {} // 错误

try {
    doSomething()
} catch (e: Exception) {
} // 正确

2.1.3 表达式

一个 if、else 判断在作为表达式的时候,如果一行之内写的下的话,可以省略大括号。

val value = if (string.isEmpty()) 0 else 1  // 正确
val value = if (string.isEmpty())  // 错误!
                0
            else
                1
val value = if (string.isEmpty()) { // 正确
    0
} else {
    1
}

2.2 块缩进

每当创建一个新的代码块或者类似代码块的结构,代码块内的缩进增加4(JakeWharton大神也吐槽这一点?所以我也选择+2了,和Google Java Style 一样。)。当代码块结束的时候,缩进变回原来的缩进级别。缩进级别同时适用于代码和注释。

2.3 一行一语句

一行一个语句。不要使用分号。

2.4 换行

译者注:2.4节的换行都是指 Line wrapping

一行的代码有100列的限制。任何超过这个限制的都需要换行。下面是几个例外:

  • 一些不可能遵从列限制的行(比如:一些很长的URL)。
  • packageimport 语句。
  • 注释里的需要复制粘贴到命令行的命令语句。

2.4.1 何处换行

自动换行的首要准则是:更倾向于在更高的语法级别处断开。

  1. 如果在一个非赋值运算符处断开,应该在该符号之前断开。(比如 +,他应该在下一行。)

    • 这也适用于以下“类运算符”符号
      • 点分隔符 (.)
      • 双冒号方法引用 (::)
  2. 如果在一个赋值运算符处断开,应该在该符号之后断开。(比如 =,它与前面的内容留在同一行。译者注)但是两种方案都是可以的。

  3. 方法名或构造方法名与左括号(()留在同一行。

1。 逗号(,)于前面的内容在同一行。

  1. Lambda表达式的箭头->处一般不会换行,除非lambda表达式只包含一个单独的无大括号的语句。例子:

2.4.2 换行后的缩进

换行时,每个延续的行都要至少+8空格的缩进.

当有多个连续的行时,缩进可能不止8个空格.通常,当且仅当它们是语法上的并行元素时,才采用相同的缩进级别.

2.4.3 函数

当一个函数的 signature 不能很好的写在一行时,可以把每个参数分成一行写。每行的参数缩进++8;。右括号)return 语句都放在一行里,并且不加缩进。

fun <T> Iterable<T>.joinToString(
        separator: CharSequence = ", ",
        prefix: CharSequence = "",
        postfix: CharSequence = ""
): String {
    // …
}

2.4.3.1 表达式函数

当一个函数只有一个表达式的时候,它可以写成表达式函数

override fun toString(): String {
    return "Hey"
}
override fun toString(): String = "Hey"

表达式函数不应该换行,如果一个表达式函数写不下一行的话,使用一般的函数体,缩进也按照一般的函数。

2.4.4 属性

当一个属性(译者的理解:相当于 Java 里的字段/变量/对象)初始化的时候,不能在一行里写下,可以在 = 后面换行,使用块缩进(+2)。

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

属性声明 getset 时,采用块缩进,像函数一样。

var directory: File? = null
    set(value) {
        // …
    }

只读的属性可以在一行内使用更简明的语法:

val defaultExtension: String get() = "kt"

2.5 空白

2.5.1 垂直空白(空行)

一个空行出现在:

  1. 在类的连续成员或初始化之间:属性,构造方法,函数,嵌套类等.

    • 例外:两个连续的属性(它们之间没有其他代码)之间的空白行是可选的.这种情况下,空白行用来对属性进行逻辑分组的。

    • 例外:枚举常数之间的空白行。

  2. 语句之间,根据需要把语句按逻辑分成组.

  3. 可选地,在一个函数的第一个语句之前、在类的第一个成员以前或者在类的最后一个成员之后。(既不鼓励也不反对)。

  4. 根据本文件其他部分的要求(不如1.2结构)。

允许多个连续的空白行,但不鼓励.

2.5.2 水平空白

除了语言和其他风格规则的要求和文字,注释,KDoc之外,单个的ASCII空格只出现在下面这几个地方。

  1. 分离保留字(比如 if, for ,catch)和紧随其后的左括号(().
  • // 错误
    for(i in 0..1) {
    }
    
  • // 正确
    for (i in 0..1) {
    }
    
  1. 分隔保留字与其前面的右大括号(})(如else, catch)。
  • // 错误
    }else {
    }
    
  • // 正确
    } else {
    }
    
  1. 在任意的左括号前({
  •   // 错误!
      if (list.isEmpty()){
      }
    
    
    
  •   // 正确!
      if (list.isEmpty()) {
      }
    
  1. 在任意的二元操作符之间
  • // 错误!
    val two = 1+1
    
  • // 正确
    val two = 1 + 1
    

这也适用于”类操作符”:

  • Lambda表达式的箭头(->)。

    • // 错误!
      ints.map { value->value.toString() }
      
    • // 正确
      ints.map { value -> value.toString() }
      

但不适用:

  • 双冒号(::)的方法引用:

    • // 错误!
      val toString = Any :: toString
      
    • // 正确!
      val toString = Any::toString
      
  • 句点操作符(.):

    • // 错误
      it . toString()
      
    • // 正确
      it.toString()
      
  1. 在冒号(:)之前,仅仅在类/接口声明或者使用 where约束上下限的时候使用。
  • // 错误!
    class Foo: Runnable
    
  • // 正确
    class Foo : Runnable
    
  • // 错误
    fun <T: Comparable> max(a: T, b: T)
    
  • // 正确
    fun <T : Comparable> max(a: T, b: T)
    
  • // 错误
    fun <T> max(a: T, b: T) where T: Comparable<T>
    
  • // 正确
    fun <T> max(a: T, b: T) where T : Comparable<T>
    
  1. 在逗号(,)和冒号(:)之后。
  • // 错误!
    val oneAndTwo = listOf(1,2)
    
  • // 正确
    val oneAndTwo = listOf(1, 2)
    
  • // 错误!
    class Foo :Runnable
    
  • // 正确
    class Foo : Runnable
    
  1. 在注释的两个斜杠(//)两侧,这里允许多个空格,但不是必须的.
  • // 错误!
    var debugging = false//disabled by default
    
  • // 正确
    var debugging = false // disabled by default
    

2.6 特殊构造

2.6.1 枚举类

一个没有函数和文档的枚举类可以格式化为一行。

enum class Answer { YES, NO, MAYBE }

而当一个枚举常量被分成多行时,枚举常量之间也是不需要空行的,除非它定义了函数体。

enum class Answer {
    YES,
    NO,

    MAYBE {
        override fun toString() = """¯\_(ツ)_/¯"""
    }
}

枚举类也是类,所以累的其他格式化规范也适用。

2.6.2 注解

成员和类型注释放在被注解的结构的前面,并分行。

@Retention(SOURCE)
@Target(FUNCTION, PROPERTY_SETTER, FIELD)
annotation class Global

没有参数的注解可以放在同一行。

@JvmField @Volatile
var disposable: Disposable? = null

当只有一个没有参数的注解的时候,可以和声明放在同一行。

@Volatile var disposable: Disposable? = null

@Test fun selectAll() {
    // …
}

2.6.3 隐式的 return 和 参数类型

如果一个表达式函数或者是属性的初始化是一个标量(译者注:明确的)值或者它的返回类型可以清楚的观察到,那么也可以省略返回类型。

override fun toString(): String = "Hey"
// 变成
override fun toString() = "Hey"
private val ICON: Icon = IconLoader.getIcon("/icons/kotlin.png")
// 变成
private val ICON = IconLoader.getIcon("/icons/kotlin.png")

但是如果你在写一个库的话,请保留明确的类型,如果是公共的API的话。


3. 命名

不是偷懒2333,没有必要重复翻译嘛。

与Google Java Style 一致,见Google Java 代码风格规范指南


4. 文档

KDoc

与Google Java Style 一致,见Google Java 代码风格规范指南