本文件完整定義了對於以 Kotlin 程式設計語言編寫的原始碼,Google 所採用的 Android 程式設計標準。只有在遵循本文所列規則的情況下,Kotlin 來源檔案才能稱為符合 Google Android 樣式。
如同其他程式設計樣式指南一樣,本指南不僅涵蓋了格式美學的問題,也涉及其他類型的慣例或程式設計標準。不過,本文主要說明我們普遍遵守的固定規則,並避免提供無法清楚透過人工/工具實行的建議。
來源檔案
所有來源檔案都必須以 UTF-8 格式編碼。
命名
如果來源檔案只包含一個頂層類別,則檔案名稱必須區分大小寫,並加上 .kt
副檔名。在其他情況下,如果來源檔案包含多個頂層宣告,請選擇能說明檔案內容的名稱、套用首字母大寫拼法 (若是複數檔案名稱則可使用駝峰式大小寫),並加上 .kt
副檔名。
// MyClass.kt class MyClass { }
// 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> = // …
// extensions.kt fun MyClass.process() = // … fun MyResult.print() = // …
特殊字元
空白字元
除了行結束字元序列以外,ASCII 水平空格字元 (0x20) 是出現在來源檔案中任何位置的唯一空白字元。這表明:
- 字串和字元常值中的所有其他空白字元都已逸出。
- 分頁字元「不會」用於縮排。
特殊逸出序列
對於任何含有特殊逸出序列的字元 (\b
、\n
、\r
、\t
、\'
、\"
、\\
和 \$
),系統會使用該序列,而不是對應的萬國碼 (Unicode) (例如 \u000a
) 逸出。
非 ASCII 字元
針對其餘非 ASCII 字元,系統會使用實際的萬國碼 (Unicode) 字元 (例如 ∞
) 或對等的萬國碼 (Unicode) 逸出 (例如 \u221e
)。如何選擇,僅取決於哪一項使程式碼更容易讀取及理解。我們不建議對任何位置的可顯示字元使用萬國碼 (Unicode) 逸出,而且強烈建議不要在字串常值和註解以外使用這類逸出。
範例 | 討論 |
---|---|
val unitAbbrev = "μs" |
最佳:即使沒有註解,也十分清楚。 |
val unitAbbrev = "\u03bcs" // μs |
差:無故將逸出與可顯示字元搭配使用。 |
val unitAbbrev = "\u03bcs" |
差:讀者不明白這是什麼。 |
return "\ufeff" + content |
好:對不可列印字元使用逸出,並視需要加註。 |
結構
.kt
檔案依序包含下列各項:
- 版權和/或授權標頭 (選填)
- 檔案層級註解
- 套件陳述式
- 匯入陳述式
- 頂層宣告
每個部分用一個空白行分隔符。
版權/授權
如果檔案中含有版權或授權標頭,則應放在多行註解的最頂端。
/* * Copyright 2017 Google, Inc. * * ... */
請勿使用 KDoc 樣式或單行樣式註解。
/** * Copyright 2017 Google, Inc. * * ... */
// Copyright 2017 Google, Inc. // // ...
檔案層級註解
含有「file」使用場目標的註解會放在任何標頭註解和套件宣告之間。
套件陳述式
套件陳述式不受任何欄限制,也不會自動換行。
匯入陳述式
類別、函式和屬性的匯入陳述式會彙整成一份清單,並以 ASCII 排序。
不得匯入任何類型的萬用字元。
與套件陳述式類似,匯入陳述式不受欄限制,而且一律不會自動換行。
頂層宣告
.kt
檔案可在頂層宣告一或多個類型、函式、屬性或類型別名。
檔案內容應聚焦於單一主題。例如,主題可以是單一公開類型,或對多個接收器類型執行相同作業的一組擴充功能函式。無關聯的宣告應以各自的檔案分隔,且單一檔案中的公開宣告應盡可能最小化。
沒有對檔案內容的數量和順序設立明確的限制。
來源檔案通常從上到下讀取,意味著順序一般會反映出,離頂層較近的宣告有助於理解離頂層較遠的宣告。不同的檔案可能會選擇以不同順序排列內容。同樣,一份檔案可能會包含 100 個屬性、另外 10 個函式和另一個類別。
重要的是,每份檔案都使用某種邏輯順序,其維護者可藉由此順序來解釋檔案。例如,新函式並非只是習慣地在檔案末端加入,因為這會導致「依新增日期排序」,而非依邏輯排序。
類別成員排序
類別中成員的順序採用與頂層宣告相同的規則。
格式設定
大括號
when
分支版本,以及具有不超過一個 else
分支版本和僅占一行的 if
運算式,都不需要大括號。
if (string.isEmpty()) return val result = if (string.isEmpty()) DEFAULT_VALUE else string when (value) { 0 -> return // … }
所有 if
、for
、when
分支版本、do
、while
陳述式和運算式都需要大括號,即使主體為空白或只包含單一陳述式也一樣。
if (string.isEmpty()) return // WRONG! if (string.isEmpty()) { return // Okay } if (string.isEmpty()) return // WRONG else doLotsOfProcessingOn(string, otherParametersHere) if (string.isEmpty()) { return // Okay } else { doLotsOfProcessingOn(string, otherParametersHere) }
非空白區塊
在非空白區塊和區塊式建構中,大括號遵循 Kernighan 和 Ritchie 樣式 (「埃及大括號」):
- 左大括號前面不換行。
- 左大括號後面換行。
- 右大括號前面換行。
- 「僅在」右大括號終止陳述式,或終止函式、建構函式或「已命名」類別的主體時,才在右大括號後面換行。例如,如果右大括號後面有
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() } } }
以下為列舉類別的幾個例外狀況。
空白區塊
空白區塊或區塊式結構必須採用 K&R 樣式。
try { doSomething() } catch (e: Exception) {} // WRONG!
try { doSomething() } catch (e: Exception) { } // Okay
運算式
「只有」在整個運算式占一行時,用做運算式的 if/else
條件才可省略大括號。
val value = if (string.isEmpty()) 0 else 1 // Okay
val value = if (string.isEmpty()) // WRONG! 0 else 1
val value = if (string.isEmpty()) { // Okay 0 } else { 1 }
縮排
每當新區塊或區塊式結構開啟時,縮排才會增加四個空格。區塊結束後,縮排會回到先前的縮排層級。縮排層級會套用至整個區塊內的程式碼和註解。
每行採用一個陳述式
每個陳述式後面都要換行。請勿使用分號。
換行
程式碼的欄限制為 100 個字元。如下所述,除下文另有說明外,任何超過這個限制的行都必須換行。
例外狀況:
- 無法遵守欄限制的行,例如 KDoc 中的長網址
package
和import
陳述式- 註解中的指令列 (可剪下及貼到殼層內)
換行位置
換行的最佳指令是:傾向於在較高的語法層級中換行。另外:
- 在運算子或中置函式名稱中換行時,應在運算子或中置函式名稱後面換行。
- 在以下「類似運算子的」符號中換行時,應在符號前面換行:
- 點分隔符 (
.
、?.
)。 - 成員參考資料的兩個冒號 (
::
)。
- 點分隔符 (
- 方法或建構函式名稱會附在後面的左括號 (
(
) 上。 - 半形逗號 (
,
) 會附在其前面的權杖上。 - lambda 箭頭 (
->
) 會附在其前面的引數清單上。
函式
如果函式簽名無法只占一行,各項參數宣告應各占一行。採用這種格式的參數必須使用一個縮排 (+4)。右括號 ()
) 與傳回類型獨占一行,且不加入其他縮排。
fun <T> Iterable<T>.joinToString( separator: CharSequence = ", ", prefix: CharSequence = "", postfix: CharSequence = "" ): String { // … }
運算式函式
函式如果只包含單一運算式,就能以運算式函式表示。
override fun toString(): String { return "Hey" }
override fun toString(): String = "Hey"
屬性
當屬性初始設定程式無法只占一行時,請在等號 (=
) 後面換行,並使用縮排。
private val defaultCharset: Charset? = EncodingRegistry.getInstance().getDefaultCharsetForPropertiesFiles(file)
宣告 get
和/或 set
函式的屬性應各占一行,並加上正常縮排 (+4)。請使用與函式相同的規則設定格式。
var directory: File? = null set(value) { // … }唯讀屬性可使用只占一行的較短語法。
val defaultExtension: String get() = "kt"
空白字元
垂直
系統會在以下位置顯示單一空白行:
- 在類別的連續成員「之間」:屬性、建構函式、函式、巢狀類別等。
- 例外狀況:可選擇在兩個連續屬性 (中間沒有其他程式碼) 之間加入空白行。您可以視需要使用這類空白行來建立屬性邏輯分組,並將屬性與支援屬性 (如果有) 建立關聯。
- 例外狀況:下文介紹了列舉常數之間的空白行。
- 「可視需要」在陳述式之間顯示,用於程式碼整理成邏輯的子區段。
- 「可選擇」在函式中第一個陳述式之前、類別的第一個成員之前或類別的最後一個成員之後加入空白行 (並非建議做法,也不是應避免的做法)。
- 依照本文其他各節 (例如「結構」一節) 的要求加入空白行。
系統允許使用多個連續的空白行,但我們不鼓勵或要求這麼做。
水平
除了語言或其他樣式規則有所要求,以及除了常值、註解和 KDoc 外,只有下列位置會顯示單個 ASCII 空格:
- 用於分隔任何保留字 (例如
if
、for
或catch
) 與同一行上在它後面的左括號 ((
)。// WRONG! for(i in 0..1) { }
// Okay for (i in 0..1) { }
- 用於分隔任何保留字 (例如
else
或catch
) 與同一行上在它前面的右大括號 (}
)。// WRONG! }else { }
// Okay } else { }
-
顯示在左大括號 (
{
) 前方。// WRONG! if (list.isEmpty()){ }
// Okay if (list.isEmpty()) { }
-
顯示在任何二元運算子的兩側。
// WRONG! val two = 1+1
// Okay val two = 1 + 1
這也適用於下列「類似運算子」的符號:- lambda 運算式中的箭頭 (
->
)。// WRONG! ints.map { value->value.toString() }
// Okay ints.map { value -> value.toString() }
-
成員參照的兩個冒號 (
::
)。// WRONG! val toString = Any :: toString
// Okay val toString = Any::toString
-
點分隔符 (
.
)。// WRONG it . toString()
// Okay it.toString()
-
範圍運算子 (
..
)。// WRONG for (i in 1 .. 4) { print(i) }
// Okay for (i in 1..4) { print(i) }
- lambda 運算式中的箭頭 (
-
只有用於指定基礎類別或介面的類別宣告,或用於泛型條件約束的
where
子句中時,才會顯示在冒號 (:
) 前方。// WRONG! class Foo: Runnable
// Okay class Foo : Runnable
// WRONG fun <T: Comparable> max(a: T, b: T)
// Okay fun <T : Comparable> max(a: T, b: T)
// WRONG fun <T> max(a: T, b: T) where T: Comparable<T>
// Okay fun <T> max(a: T, b: T) where T : Comparable<T>
-
顯示在半形逗號 (
,
) 或冒號 (:
) 後方。// WRONG! val oneAndTwo = listOf(1,2)
// Okay val oneAndTwo = listOf(1, 2)
// WRONG! class Foo :Runnable
// Okay class Foo : Runnable
-
顯示在行末註解開頭雙斜線 (
//
) 的兩側。此處可以使用多個空格,但並非必要。// WRONG! var debugging = false//disabled by default
// Okay var debugging = false // disabled by default
這條規則絕不可解釋為要求或禁止在行的開頭或結尾插入其他空格;它只用於規定內部空格。
特定建構
列舉類別
列舉如果沒有函式和關於其常數的說明文件,則可選擇採用單行的格式。
enum class Answer { YES, NO, MAYBE }
如果列舉中的常數是放置在單獨的行中,除非用於定義主體,否則不需要在這些常數之間插入空白行。
enum class Answer { YES, NO, MAYBE { override fun toString() = """¯\_(ツ)_/¯""" } }
由於列舉類別是類別,因此同樣適用其他用於設定類別格式的規則。
註解
成員或類型註解應獨立成行,放在加註的建構前面。
@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 個或以上不含引數的註解。
@field:[JvmStatic Volatile] var disposable: Disposable? = null
隱式傳回/屬性類型
如果運算式函式主體或屬性初始設定程式是純量值,或可從主體中明確推導傳回類型,則可予省略。
override fun toString(): String = "Hey" // becomes override fun toString() = "Hey"
private val ICON: Icon = IconLoader.getIcon("/icons/kotlin.png") // becomes private val ICON = IconLoader.getIcon("/icons/kotlin.png")
編寫程式庫時,如果明確的類型宣告為公用 API 的一部分,則請保留該宣告。
命名
ID 只能使用 ASCII 字母和數字,而且在下文所述的少數情況下會加上底線。因此,每個有效 ID 名稱都會以規則運算式 \w+
比對。
除了幕後屬性 (請參閱「幕後屬性」) 外,系統不會使用特殊前置字元或後置字元,比如範例 name_
、mName
、s_name
和 kName
中的相關字元。
套件名稱
套件名稱全為小寫,連續字詞會串連在一起 (沒有底線)。
// Okay package com.example.deepspace // WRONG! package com.example.deepSpace // WRONG! package com.example.deep_space
類型名稱
類別名稱以 PascalCase 首字母大寫拼法編寫,通常是名詞或名詞片語。例如,Character
或 ImmutableList
。介面名稱也可能是名詞或名詞片語 (例如 List
),但有時也可以是形容詞或形容詞片語 (例如 Readable
)。
測試類別的名稱會以所測試的類別的名稱開頭,並以 Test
結尾。例如,HashTest
或 HashIntegrationTest
。
函式名稱
函式名稱會以 camelCase 駝峰式大小寫拼法編寫,通常為動詞或動詞片語。例如,sendMessage
或 stop
。
測試函數名稱中可加上底線,以分隔名稱中的邏輯元件。
@Test fun pop_emptyStack() { // … }
加註 @Composable
且傳回 Unit
的函式皆採用大駝峰式命名法,並以名詞形式命名,就像這些函式是類型一樣。
@Composable fun NameTag(name: String) { // … }
函式名稱不應包含空格,因為每個平台都不支援空格 (特別是 Android 不完全支援空格)。
// WRONG! fun `test every possible case`() {} // OK fun testEveryPossibleCase() {}
常數名稱
常數名稱採用 UPPER_SNAKE_CASE:所有字母皆為大寫英文字母,並以底線分隔字詞。不過,到底什麼「是」常數?
常數是不含自訂 get
函式的 val
屬性,其內容基本上不可變更,其函式沒有可偵測的副作用。這包括不可變的類型、不可變類型的集合,以及標示為 const
的純量和字串。如果執行個體的任何可觀測狀態變更,它就不是常數。只是刻意不改變物件並不足夠。
const val NUMBER = 5 val NAMES = listOf("Alice", "Bob") val AGES = mapOf("Alice" to 35, "Bob" to 32) val COMMA_JOINER = Joiner.on(',') // Joiner is immutable val EMPTY_ARRAY = arrayOf()
這些名稱通常是名詞或名詞片語。
常數值只能在 object
內定義,或用做頂層宣告。值如果符合常數的要求,但在 class
內定義,則必須使用非常數名稱。
做為純量值的常數必須使用 const
修飾符。
非常數名稱
非常數名稱以 camelCase 駝峰式大小寫拼法編寫。這些名稱適用於執行個體屬性、本機屬性和參數名稱。
val variable = "var" val nonConstScalar = "non-const" val mutableCollection: MutableSet= HashSet() val mutableElements = listOf(mutableInstance) val mutableValues = mapOf("Alice" to mutableInstance, "Bob" to mutableInstance2) val logger = Logger.getLogger(MyClass::class.java.name) val nonEmptyArray = arrayOf("these", "can", "change")
這些名稱通常是名詞或名詞片語。
支援屬性
需要支援屬性時,其名稱必須和實際屬性的名稱完全比對,但前面加上底線的實際屬性名稱除外。
private var _table: Map? = null val table: Map get() { if (_table == null) { _table = HashMap() } return _table ?: throw AssertionError() }
類型變數名稱
每個類型變數都會以下列任一樣式命名:
- 單個大寫字母,後面可加上一個數字 (例如
E
、T
、X
、T2
) - 類別所用形式中的名稱,後面加上大寫字母
T
(例如RequestT
、FooBarT
)
駝峰式大小寫
有時候,將英文片語轉換為駝峰式大小寫的合理方法不止一種,例如縮寫「IPv6」或「iOS」等縮寫或特殊的建構。請使用以下配置來提升可預測性。
從名稱的散式著手:
- 將片語轉換為純 ASCII,並移除任何所有格號。 例如,「Müller 的演算法」可能變成「Muellers 演算法」。
- 將這個結果分成多個字詞,並在空格和其餘任何標點符號 (通常是連字號) 處分割。「建議」:如果任何字詞在常見用法中已有慣用的駝峰式大小寫樣式,請將字詞分成各個組成部分 (例如「AdWords」會變成「ad words」)。請注意,「iOS」等字詞本身並非實際採用駝峰式大小寫;它並未遵守任何慣例,因此這項建議不適用。
- 現在,請全部改為小寫字母 (包括縮寫),然後採取下列任一做法:
- 以大寫表示每個字詞的第一個字元,呈現大駝峰式命名法的樣式。
- 除了第一個字詞外,以大寫表示每個字詞的第一個字元,呈現小駝峰式命名法的樣式。
- 最後,將所有字詞加入同一個 ID 中。
請注意,原字詞的大小寫幾乎完全忽略了。
散式 | 正確 | 錯誤 |
---|---|---|
「XML Http Request」 | XmlHttpRequest |
XMLHTTPRequest |
「new customer ID」 | newCustomerId |
newCustomerID |
「inner stopwatch」 | innerStopwatch |
innerStopWatch |
「supports IPv6 on iOS」 | supportsIpv6OnIos |
supportsIPv6OnIOS |
「YouTube importer」 | YouTubeImporter |
YoutubeImporter * |
(* 可接受,但不建議使用。)
說明文件
格式設定
KDoc 區塊的基本格式設定見此範例:
/** * Multiple lines of KDoc text are written here, * wrapped normally… */ fun method(arg: String) { // … }
...或見此單行範例:
/** An especially short bit of KDoc. */
系統始終可接受這種基本格式。如果整個 KDoc 區塊 (包括註解標記) 可在一行內顯示,就可以替換單行格式。請注意,這個選項僅適用於沒有區塊標記 (例如 @return
) 的情況。
段落
段落之間和區塊標記群組 (如果有) 之前會顯示一條空白行,也就是僅含對齊的前導星號 (*
) 的行。
區塊標記
使用的任何標準「區塊標記」都會以 @constructor
、@receiver
、@param
、@property
、@return
、@throws
、@see
的順序顯示,且這些標記絕對不會與空白說明一起顯示。如果區塊標記不能只占一行,連續行會從 @
的位置縮排 4 個空格。
匯總片段
每個 KDoc 區塊都以簡短的匯總片段開頭。這個片段非常重要:它是出現在特定結構定義 (例如類別和方法索引) 中文字的唯一部分。
這是一個片段,也就是名詞片語或動詞片語,而不是完整的句子。它不以「A `Foo` is a...
」或「This method returns...
」開頭,也未構成完整的祈使句,例如「Save the record.
」。不過,這個片段使用大寫字母和標點符號,就像是完整的句子一樣。
使用方式
至少每種 public
類型以及該類型的每個 public
或 protected
成員都有 KDoc,但下文列出了幾個例外狀況。
例外狀況:一目了然的函式
對於 getFoo
等「簡單、明確」的函式和 foo
等屬性,「KDoc」是可選的。在這些情況中,除了「傳回 foo」外,真的沒有任何要說的內容。
引用例外狀況,證明省略一般讀者可能需要知道的相關資訊的合理性,這並不適當。例如,對於名為 getCanonicalName
的函式或名為 canonicalName
的屬性,如果一般讀者可能不知道「標準化名稱」這個字詞的意涵,則不要忽略其說明文件 (理由是它只寫著 /** Returns the canonical name. */
)!
例外狀況:覆寫
KDoc 不一定會出現在覆寫超級類型方法的方法中。