Android 之 kotlin 语言学习笔记二(编码样式)
参考官方文档:https://developer.android.google.cn/kotlin/style-guide?hl=zh-cn#whitespace
1、源文件命名
- 所有源文件都必须编码为 UTF-8。
- 如果源文件只包含一个顶级类,则文件名应为该类的名称(区分大小写)加上 .kt 扩展名。
- 如果源文件包含多个顶级声明,则应选择一个可描述文件内容的名称(采用 PascalCase 大小写形式;如果文件名为复数,亦可采用驼峰命名法)并加上 .kt 扩展名。
// MyClass.ktclass MyClass { }// Bar.ktclass Bar { }fun Runnable.toBar(): Bar = // …// Map.ktfun <T, O> Set<T>.map(func: (T) -> O): List<O> = // …fun <T, O> List<T>.map(func: (T) -> O): List<O> = // …// extensions.ktfun MyClass.process() = // …fun MyResult.print() = // …
2、特殊字符
(1) 空白字符
除了行终止符序列之外,ASCII 水平空格字符 (0x20) 是唯一一种可以出现在源文件中任意位置的空白字符。这意味着:
- 字符串和字符字面量中的其他所有空白字符都会进行转义。
- 制表符不用于缩进。
(2) 特殊转义序列
对于任何具有特殊转义序列(\b、\n、\r、\t、'、"、\ 和 $)的字符,将使用该序列,而不是相应的 Unicode 转义字符(例如 \u000a)。
(3)非 ASCII 字符
对于其余非 ASCII 字符,要么使用实际的 Unicode 字符(例如 ∞),要么使用等效的 Unicode 转义字符(例如 \u221e)。具体选择仅取决于哪种字符可使代码更容易阅读和理解。建议不要对任何位置的可输出字符使用 Unicode 转义字符,强烈建议不要在字符串字面量和注释之外使用 Unicode 转义字符。
// 最好:即使没有注释,也非常清楚。val unitAbbrev = "μs" // 差:没有理由对可打印字符使用转义。 val unitAbbrev = "\u03bcs" // μs// 差:读者不知道这是什么。val unitAbbrev = "\u03bcs" // 好:对不可打印字符使用转义,并在必要时添加注释。return "\ufeff" + content
3、结构
- .kt 文件由下面几部分组成(按顺序列出),各部分用一个空白行隔开:
- 版权和/或许可标头(可选)
- 文件级注解
- package 语句
- import 语句
- 顶级声明
(1)版权/许可
- 如果版权或许可标头需要放在文件中,应将其放在多行注释的上方,并让其紧挨着多行注释。
/** Copyright 2017 Google, Inc.** ...*/
- 请勿使用 KDoc 样式或单行样式的注释。
KDoc 样式/*** Copyright 2017 Google, Inc.** ...*/单行样式// Copyright 2017 Google, Inc.//// ...
(2)文件级注解
- 应将具有 “file” 使用处目标的注解放在任何标头注释和软件包声明之间。
@file:JvmName("Foo")package org.jetbrains.demo
(3)package 语句
- package 语句不受任何列限制且从不换行。
(4)import 语句
- 应将类、函数和属性的 import 语句归在单个列表中并按 ASCII 进行排序。
- 不允许(任何类型的)通配符导入。
- 与 package 语句类似,import 语句也不受列限制且从不换行。
(5)顶级声明
- .kt 文件可以在顶级声明一个或多个类型、函数、属性或类型别名。
- 对文件的内容量和内容顺序没有做出明确的限制。通常按从上到下的顺序读取源文件,顺序通常应反映出位置比较靠上的声明将有助于理解位置比较靠下的声明。
- 每个类都采用某种逻辑顺序,类的维护人员在被问及时应可以解释清楚相应逻辑顺序。例如,新函数不应直接习惯性地添加到类的末尾,因为这样会产生“按添加日期先后顺序”排序,而这不是逻辑排序。
4、大括号
- when 分支以及具有不超过一个 else 分支且仅占一行的 if 表达式不需要大括号。
if (string.isEmpty()) returnval result =if (string.isEmpty()) DEFAULT_VALUE else stringwhen (value) {0 -> return// …}
- 任何 if、for、when 分支、do 和 while 语句及表达式都需要大括号,即使主体为空或仅包含一个语句也是如此。
if (string.isEmpty())return // WRONG!if (string.isEmpty()) {return // Okay}if (string.isEmpty()) return // WRONGelse doLotsOfProcessingOn(string, otherParametersHere)if (string.isEmpty()) {return // Okay} else {doLotsOfProcessingOn(string, otherParametersHere)}
(1)非空块
对于非空块和类似块的构造,大括号遵循 Kernighan 和 Ritchie (K&R) 样式(“埃及括号”):
- 左大括号前面没有换行符。
- 左大括号后面有换行符。
- 右大括号前面有换行符。
- 仅当右大括号终止语句或者终止函数、构造函数或命名类的主体时,它后面才有换行符。例如,如果大括号后跟 else 或一个英文逗号,就不应在它后面换行。
object : MyClass() {override fun foo() {if (condition()) {try {something()} catch (e: ProblemException) {recover()}} else if (otherCondition()) {somethingElse()} else {lastThing()}}}
(2)空块
- 空块或类似块的构造必须采用 K&R 样式。
try {doSomething()} catch (e: Exception) {} // WRONG!try {doSomething()} catch (e: Exception) {} // Okay
(3)表达式
- 仅当整个表达式适合放在一行时,用作表达式的 if/else 条件语句才能省略大括号。
// Okayval value = if (string.isEmpty()) 0 else 1 // WRONG!val value = if (string.isEmpty()) 0else1// Okay val value = if (string.isEmpty()) { 0} else {1}
5、换行
- 代码的列限制为最多 100 个字符。除非是下面说明的情况,否则任何超过此限制的行都必须换行:
- 无法遵循列限制的行(例如,KDoc 中的长网址)
- package 和 import 语句
- 注释中可以剪切并粘贴到 shell 中的命令行
(1)在何处换行
换行的首要原则是:更倾向于在较高的句法级别换行。此外:
-
某行在运算符或 infix 函数名称处换行时,换行符将在该运算符或 infix 函数名称后面。
-
某行在以下“类似运算符”的符号处换行时,换行符将在该符号前面:
1)点分隔符(.、?.)。
2)成员引用的两个冒号 ( :: )。
3)方法或构造函数名称始终贴在它后面的左圆括号 (() 上。
4)逗号 (,) 始终贴在它前面的标记上。
5)lambda 箭头 (->) 始终贴在它前面的参数列表上。 -
换行的主要目标是让代码清晰,而不一定是让代码适合放在最少数量的行中。
(2)函数
- 当函数参数不适合放在一行上时,应让每个参数声明独占一行。以这种格式定义的参数应使用单缩进 (+4)。右圆括号 ()) 和返回类型独占一行,没有额外的缩进。
fun <T> Iterable<T>.joinToString(separator: CharSequence = ", ",prefix: CharSequence = "",postfix: CharSequence = ""): String {// …}
(3)表达式函数
- 当函数只包含一个表达式时,它可以表示为表达式函数。
override fun toString(): String {return "Hey"}// 可以简写为override fun toString(): String = "Hey"
(4)属性
- 当属性初始化不适合放在一行时,应在等号 (=) 后面换行,并进行缩进。
private val defaultCharset: Charset? =EncodingRegistry.getInstance().getDefaultCharsetForPropertiesFiles(file)
- 声明 get 和/或 set 函数的属性应让每个函数独占一行,并使用正常的缩进 (+4)。对它们进行格式设置时,使用的规则与函数相同。
var directory: File? = nullset(value) {// …}
- 只读属性可以使用适合放在一行的较短语法。
val defaultExtension: String get() = "kt"
6、空白
(1)空白行
- 一个空白行会出现在:
- 出现在类的连续成员(属性、构造函数、函数、嵌套类等)之间。
- 根据需要出现在语句之间,用于将代码划分为一些逻辑子部分。
- (可选)出现在函数中的第一个语句前面、类的第一个成员前面,或类的最后一个成员后面(既不鼓励也不反对)。
- 根据本文档中其他部分(如结构部分)的要求出现。
- 允许出现多个连续的空白行,但不鼓励也从不要求必须采用这种样式。
(2) ASCII 空格
- 将任何保留字(例如 if、 for 或 catch )与它后面的左括号 (() 隔开。
// WRONG!for(i in 0..1) {}// Okayfor (i in 0..1) {}
- 将任何保留字(如 else 或 catch)与该行中在它前面的右大括号 (}) 隔开。
// WRONG!}else {}// Okay} else {}
- 在任何左大括号 ({) 前面。
// WRONG!if (list.isEmpty()){}// Okayif (list.isEmpty()) {}
- 在任何二元运算符的两边。
// WRONG!val two = 1+1// Okayval two = 1 + 1
- lambda 表达式中的箭头 (->)。
// WRONG!ints.map { value->value.toString() }// Okayints.map { value -> value.toString() }
- 仅当在用于指定基类或接口的类声明中使用,或在泛型约束的 where 子句中使用时,才会出现在冒号 ( : ) 前面。
// WRONG!class Foo: Runnable// Okayclass Foo : Runnable// WRONGfun <T: Comparable> max(a: T, b: T)// Okayfun <T : Comparable> max(a: T, b: T)// WRONGfun <T> max(a: T, b: T) where T: Comparable<T>// Okayfun <T> max(a: T, b: T) where T : Comparable<T>
- 在逗号 (,) 或冒号 ( : ) 后面。
// WRONG!val oneAndTwo = listOf(1,2)// Okayval oneAndTwo = listOf(1, 2)// WRONG!class Foo :Runnable// Okayclass Foo : Runnable
- 在开始行尾注释的双正斜杠 (//) 的两边。在此处,允许出现多个空格,但不要求必须采用这种样式。
// WRONG!var debugging = false//disabled by default// Okayvar debugging = false // disabled by default
(3)不适用空格情况
- 成员引用的两个冒号 ( :: )。
// WRONG!val toString = Any :: toString// Okayval toString = Any::toString
- 点分隔符 (.)。
// WRONGit . toString()// Okayit.toString()
- 范围运算符 ( … )。
// WRONGfor (i in 1 .. 4) {print(i)}// Okayfor (i in 1..4) {print(i)}
7、枚举类
- 对于没有函数且没有关于其常量的文档的枚举,可以选择性地将其格式设为单行。
enum class Answer { YES, NO, MAYBE }
- 将枚举中的常量放在单独的行上时,它们之间不需要空白行,但它们定义主体时除外。
enum class Answer {YES,NO,MAYBE {override fun toString() = """¯\_(ツ)_/¯"""}}
- 由于枚举类是类,因此用于类格式设置的其他所有规则都适用。
8、注释
- 应将成员或类型注释放在单独的行上,让其紧接在标注的构造前面。
@Retention(SOURCE)@Target(FUNCTION, PROPERTY_SETTER, FIELD)annotation class Global
- 可以将不带参数的注释放在一行上。
@JvmField @Volatilevar disposable: Disposable? = null
- 如果只存在一个不带参数的注解,可以将其与声明放在同一行上。
@Volatile var disposable: Disposable? = null@Test fun selectAll() {// …}
- @[…] 语法只能用于明确的使用处目标,并且只能用于将没有参数的两个或更多注释组合在一行中。
@field:[JvmStatic Volatile]var disposable: Disposable? = null
9、隐式返回/属性类型
- 如果表达式函数主体或属性初始化器是标量值,或者可以根据主体明确推断出返回类型,则可以将其省略。
override fun toString(): String = "Hey"// becomesoverride fun toString() = "Hey"private val ICON: Icon = IconLoader.getIcon("/icons/kotlin.png")// becomesprivate val ICON = IconLoader.getIcon("/icons/kotlin.png")
- 在编写库时,如果显式类型声明是公共 API 的一部分,则应将其保留。
10、命名
- 标识符仅使用 ASCII 字母和数字,除少数情况下,还会使用下划线。因此,每个有效的标识符名称都可匹配正则表达式 \w+。
- 不使用特殊前缀或后缀(例如 name_、mName、s_name 和 kName 示例中的前缀或后缀),但后备属性除外。
(1)软件包名称
- 软件包名称全部为小写字母,连续的单词直接连接在一起(没有下划线)。
// Okaypackage com.example.deepspace// WRONG!package com.example.deepSpace// WRONG!package com.example.deep_space
(2)类型名称
- 类名采用 PascalCase 大小写形式编写,通常是名词或名词短语。例如,Character 或 ImmutableList。
- 接口名称也可以是名词或名词短语(例如 List),但有时还可以是形容词或形容词短语(例如 Readable)。
- 测试类的命名方式是以测试的类的名称开头且以 Test 结尾。例如,HashTest 或 HashIntegrationTest。
(3)函数名称
- 函数名称采用 camelCase 大小写形式编写,通常是动词或动词短语。例如,sendMessage 或 stop。
- 允许在测试函数名称中出现下划线,用于分隔名称的逻辑组成部分。
@Test fun pop_emptyStack() {// …}
- 函数名称不应包含空格,因为并非所有平台都支持函数名称包含空格(值得注意的是,Android 不支持函数名称包含空格)。
// WRONG!fun `test every possible case`() {}// OKfun testEveryPossibleCase() {}
(4)常量名称
- 常量名称使用 UPPER_SNAKE_CASE 大小写形式:全部为大写字母,单词用下划线分隔。