應用風格指南
如需根據(jù)本風格指南配置 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 文件包含單個類(以及可能相關的頂層聲明),那么文件名應該與該類的名稱相同,并追加 .kt 擴展名。如果文件包含多個類或只包含頂層聲明, 那么選擇一個描述該文件所包含內容的名稱,并以此命名該文件。使用首字母大寫的駝峰風格(例如ProcessDeclarations.kt )。
文件的名稱應該描述文件中代碼的作用。因此,應避免在文件名中使用諸如“Util”之類的無意義詞語。
源文件組織
鼓勵多個聲明(類、頂級函數(shù)或者屬性)放在同一個 Kotlin 源文件中, 只要這些聲明在語義上彼此緊密關聯(lián)并且文件保持合理大小 (不超過幾百行)。
特別是在為類定義與類的所有客戶都相關的擴展函數(shù)時, 請將它們放在與類自身定義相同的地方。而在定義僅對指定客戶有意義的擴展函數(shù)時,請將它們放在緊挨該客戶代碼之后。不要只是為了保存 “Foo 的所有擴展函數(shù)”而創(chuàng)建文件。
類布局
通常,一個類的內容按以下順序排列:
1.屬性聲明與初始化塊
2.次構造函數(shù)
3.方法聲明
4.伴生對象
不要按字母順序或者可見性對方法聲明排序,也不要將常規(guī)方法與擴展方法分開。而是要把相關的東西放在一起,這樣從上到下閱讀類的人就能夠跟進所發(fā)生事情的邏輯。選擇一個順序(高級別優(yōu)先,或者相反)并堅持下去。
將嵌套類放在緊挨使用這些類的代碼之后。如果打算在外部使用嵌套類,而且類中并沒有引用這些類,那么把它們放到末尾,在伴生對象之后。
接口實現(xiàn)布局
在實現(xiàn)一個接口時,實現(xiàn)成員的順序應該與該接口的成員順序相同(如果需要, 還要插入用于實現(xiàn)的額外的私有方法)。
重載布局
在類中總是將重載放在一起。
命名規(guī)則
在 Kotlin 中,包名與類名的命名規(guī)則非常簡單:
-包的名稱總是小寫且不使用下劃線( org.example.project )。 通常不鼓勵使用多個詞的名稱,但是如果確實需要使用多個詞,可以將它們連接在一起或使用駝峰風格( org.example.myProject )。
-類與對象的名稱以大寫字母開頭并使用駝峰風格:
open class DeclarationProcessor { /*……*/ }
object EmptyDeclarationProcessor : DeclarationProcessor() { /*……*/ }
函數(shù)名
函數(shù)、屬性與局部變量的名稱以小寫字母開頭、使用駝峰風格而不使用下劃線:
fun processDeclarations() { /*……*/ }
var declarationCount = 1
例外:用于創(chuàng)建類實例的工廠函數(shù)可以與抽象返回類型具有相同的名稱:
interface Foo { /*……*/ }
class FooImpl : Foo { /*……*/ }
fun Foo(): Foo { return FooImpl() }
測試方法的名稱
當且僅當在測試中,可以使用反引號括起來的帶空格的方法名。 (請注意,Android 運行時目前不支持這樣的方法名。)測試代碼中也允許方法名使用下劃線。
class MyTestCase {
@Test fun `ensure everything works`() { /*...*/ }
@Test fun ensureEverythingWorks_onAndroid() { /*...*/ }
}
屬性名
常量名稱(標有 const 的屬性,或者保存不可變數(shù)據(jù)的沒有自定義 get函數(shù)的頂層/對象 val屬性)應該使用大寫、下劃線分隔的名稱:
const val MAX_COUNT = 8
val USER_NAME_FIELD = "UserName"
保存帶有行為的對象或者可變數(shù)據(jù)的頂層/對象屬性的名稱應該使用駝峰風格名稱:
val mutableCollection: MutableSet = HashSet()
保存單例對象引用的屬性的名稱可以使用與 object聲明相同的命名風格:
val PersonComparator: Comparator = /*...*/
對于枚舉常量,可以使用大寫、下劃線分隔的名稱 ( enum class Color { RED, GREEN })也可使用首字母大寫的常規(guī)駝峰名稱,具體取決于用途。
幕后屬性的名稱
如果一個類有兩個概念上相同的屬性,一個是公共 API 的一部分,另一個是實現(xiàn)細節(jié),那么使用下劃線作為私有屬性名稱的前綴:
class C {
private val _elementList = mutableListOf()
val elementList: List
get() = _elementList
}
選擇好名稱
類的名稱通常是用來解釋類是什么的名詞或者名詞短語: List 、 PersonReader 。
方法的名稱通常是動詞或動詞短語,說明該方法做什么: close 、 readPersons 。 修改對象或者返回一個新對象的名稱也應遵循建議。例如 sort 是對一個集合就地排序,而 sorted是返回一個排序后的集合副本。
名稱應該表明實體的目的是什么,所以最好避免在名稱中使用無意義的單詞 ( Manager 、Wrapper 等)。
當使用首字母縮寫作為名稱的一部分時,如果縮寫由兩個字母組成,就將其大寫( IOStream ); 而如果縮寫更長一些,就只大寫其首字母( XmlFormatter 、HttpInputStream )。
格式化
使用 4 個空格縮進。不要使用 tab。
對于花括號,將左花括號放在結構起始處的行尾,而將右花括號放在與左括結構橫向對齊的單獨一行。
if (elements != null) {
for (element in elements) {
// ……
}
}
(注意:在 Kotlin 中,分號是可選的,因此換行很重要。語言設計采用 Java 風格的花括號格式,如果嘗試使用不同的格式化風格,那么可能會遇到意外的行為。)
橫向空白
在二元操作符左右留空格( a + b )。例外:不要在“range to”操作符( 0..i )左右留空格。
不要在一元運算符左右留空格( a++ )
在控制流關鍵字(if、when、 for 以及while )與相應的左括號之間留空格。
不要在主構造函數(shù)聲明、方法聲明或者方法調用的左括號之前留空格。
class A(val x: Int)
fun foo(x: Int) { …… }
fun bar() { …… }
foo(1)
}
絕不在 ( 、 [ 之后或者 ] 、 )之前留空格。
絕不在.或者?.左右留空格: foo.bar().filter { it > 2 }.joinToString() , foo?.bar()
在 //之后留一個空格://這是一條注釋
不要在用于指定類型參數(shù)的尖括號前后留空格: class Map { …… }
不要在 :: 前后留空格:Foo::class 、 String::length
不要在用于標記可空類型的?前留空格: String?
作為一般規(guī)則,避免任何類型的水平對齊。將標識符重命名為不同長度的名稱不應該影響聲明或者任何用法的格式。
冒號
在以下場景中的 : 之前留一個空格:
-當它用于分隔類型與超類型時;
-當委托給一個超類的構造函數(shù)或者同一類的另一個構造函數(shù)時;
-在 object 關鍵字之后。
而當分隔聲明與其類型時,不要在:之前留空格。在 :之后總要留一個空格。
abstract class Foo : IFoo {
abstract fun foo(a: Int): T
}
class FooImpl : Foo() {
constructor(x: String) : this(x) { /*……*/ }
val x = object : IFoo { /*……*/ }
}
類頭格式化
具有少數(shù)主構造函數(shù)參數(shù)的類可以寫成一行:
class Person(id: Int, name: String)
具有較長類頭的類應該格式化,以使每個主構造函數(shù)參數(shù)都在帶有縮進的獨立的行中。 另外,右括號應該位于一個新行上。如果使用了繼承,那么超類的構造函數(shù)調用或者所實現(xiàn)接口的列表應該與右括號位于同一行:
class Person(
id: Int,
name: String,
surname: String
) : Human(id, name) { /*……*/ }
對于多個接口,應該將超類構造函數(shù)調用放在首位,然后將每個接口應放在不同的行中:
class Person(
id: Int,
name: String,
surname: String
) : Human(id, name),
KotlinMaker { /*……*/ }
對于具有很長超類型列表的類,在冒號后面換行,并橫向對齊所有超類型名:
class MyFavouriteVeryLongClassHolder :
MyLongHolder(),
SomeOtherInterface,
AndAnotherOne {
fun foo() { /*...*/ }
}
為了將類頭與類體分隔清楚,當類頭很長時,可以在類頭后放一空行 (如上例所示)或者將左花括號放在獨立行上:
class MyFavouriteVeryLongClassHolder :
MyLongHolder(),
SomeOtherInterface,
AndAnotherOne
{
fun foo() { /*...*/ }
}
理由:這確保了在主構造函數(shù)中聲明的屬性與 在類體中聲明的屬性具有相同的縮進。
修飾符
如果一個聲明有多個修飾符,請始終按照以下順序安放:
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
無參數(shù)的注解可以放在同一行:
@JsonExclude @JvmField
var x: String
無參數(shù)的單個注解可以與相應的聲明放在同一行:
@Test fun foo() { /*……*/ }
文件注解
文件注解位于文件注釋(如果有的話)之后、 package 語句之前,并且用一個空白行與package 分開(為了強調其針對文件而不是包)。
/** 授權許可、版權以及任何其他內容 */
@file:JvmName("FooBar")
package foo.bar
函數(shù)格式化
如果函數(shù)簽名不適合單行,請使用以下語法:
fun longMethodName(
argument: ArgumentType = defaultValue,
argument2: AnotherArgumentType
): ReturnType {
// 函數(shù)體
}
函數(shù)參數(shù)使用常規(guī)縮進(4 個空格)。
*理由:與構造函數(shù)參數(shù)一致*
對于由單個表達式構成的函數(shù)體,優(yōu)先使用表達式形式。
fun foo(): Int { // 不良
return 1
}
fun foo() = 1 // 良好
表達式函數(shù)體格式化
如果函數(shù)的表達式函數(shù)體與函數(shù)聲明不適合放在同一行,那么將=留在第一行。 將表達式函數(shù)體縮進 4 個空格。
fun f(x: String) =
x.length
屬性格式化
對于非常簡單的只讀屬性,請考慮單行格式:
val isEmpty: Boolean get() = size == 0
對于更復雜的屬性,總是將 get 與 set 關鍵字放在不同的行上:
val foo: String
get() { /*……*/ }
對于具有初始化器的屬性,如果初始化器很長,那么在等號后增加一個換行并將初始化器縮進四個空格:
private val defaultCharset: Charset? =
EncodingRegistry.getInstance().getDefaultCharsetForPropertiesFiles(file)
格式化控制流語句
如果 if或when 語句的條件有多行,那么在語句體外邊總是使用大括號。 將該條件的每個后續(xù)行相對于條件語句起始處縮進 4 個空格。 將該條件的右圓括號與左花括號放在單獨一行:
if (!component.isSyncing &&
!hasAnyKotlinRuntimeInScope(module)
) {
return createKotlinNotConfiguredPanel(module)
}
*理由:對齊整齊并且將條件與語句體分隔清楚*
將 else 、 catch 、 finally關鍵字以及do/while 循環(huán)的 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() } // 不良
}
方法調用格式化
在較長參數(shù)列表的左括號后添加一個換行符。按 4 個空格縮進參數(shù)。 將密切相關的多個參數(shù)分在同一行。
drawSquare(
x = 10, y = 10,
width = 100, height = 100,
fill = true
)
在分隔參數(shù)名與值的 =左右留空格。
鏈式調用換行
當對鏈式調用換行時,將.字符或者 ?. 操作符放在下一行,并帶有單倍縮進:
val anchor = owner
?.firstChild!!
.siblings(forward = true)
.dropWhile { it is PsiComment || it is PsiWhiteSpace }
調用鏈的第一個調用通常在換行之前,當然如果能讓代碼更有意義也可以忽略這點。
Lambda 表達式格式化
在 lambda 表達式中,應該在花括號左右以及分隔參數(shù)與代碼體的箭頭左右留空格。 如果一個調用接受單個lambda 表達式,應該盡可能將其放在圓括號外邊傳入。
list.filter { it > 10 }
如果為 lambda 表達式分配一個標簽,那么不要在該標簽與左花括號之間留空格:
fun foo() {
ints.forEach lit@{
// ……
}
}
在多行的 lambda 表達式中聲明參數(shù)名時,將參數(shù)名放在第一行,后跟箭頭與換行符:
appendCommaSeparated(properties) { prop ->
val propertyValue = prop.get(obj) // ……
}
如果參數(shù)列表太長而無法放在一行上,請將箭頭放在單獨一行:
foo {
context: Context,
environment: Env
->
context.configureEnv(environment)
}
文檔注釋
對于較長的文檔注釋,將開頭 /** 放在一個獨立行中,并且后續(xù)每行都以星號開頭:
/**
* 這是一條多行
* 文檔注釋。
*/
簡短注釋可以放在一行內:
/** 這是一條簡短文檔注釋。 */
通常,避免使用 @param與@return 標記。而是將參數(shù)與返回值的描述直接合并到文檔注釋中,并在提到參數(shù)的任何地方加上參數(shù)鏈接。 只有當需要不適合放進主文本流程的冗長描述時才應使用 @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
如果函數(shù)返回 Unit,那么應該省略返回類型:
fun foo() { // 這里省略了“: Unit”
}
分號
盡可能省略分號。
字符串模版
將簡單變量傳入到字符串模版中時不要使用花括號。只有用到更長表達式時才使用花括號。
println("$name has ${children.size} children")
語言特性的慣用法
不可變性
優(yōu)先使用不可變(而不是可變)數(shù)據(jù)。初始化后未修改的局部變量與屬性,總是將其聲明為val而不是 var 。
總是使用不可變集合接口( Collection ,List , Set , Map )來聲明無需改變的集合。使用工廠函數(shù)創(chuàng)建集合實例時,盡可能選用返回不可變集合類型的函數(shù):
// 不良:使用可變集合類型作為無需改變的值
fun validateValue(actualValue: String, allowedValues: HashSet) { …… }
// 良好:使用不可變集合類型
fun validateValue(actualValue: String, allowedValues: Set) { …… }
// 不良:arrayListOf() 返回 ArrayList,這是一個可變集合類型
val allowedValues = arrayListOf("a", "b", "c")
// 良好:listOf() 返回 List
val allowedValues = listOf("a", "b", "c")
默認參數(shù)值
優(yōu)先聲明帶有默認參數(shù)的函數(shù)而不是聲明重載函數(shù)。
// 不良
fun foo() = foo("a")
fun foo(a: String) { /*……*/ }
// 良好
fun foo(a: String = "a") { /*……*/ }
類型別名
如果有一個在代碼庫中多次用到的函數(shù)類型或者帶有類型參數(shù)的類型,那么最好為它定義一個類型別名:
typealias MouseClickHandler = (Any, MouseEvent) -> Unit
typealias PersonIndex = Map
Lambda 表達式參數(shù)
在簡短、非嵌套的** lambda **表達式中建議使用it 用法而不是顯式聲明參數(shù)。而在有參數(shù)的嵌套 lambda 表達式中,始終應該顯式聲明參數(shù)。
在 lambda 表達式中返回
避免在lambda 表達式中使用多個返回到標簽。請考慮重新組織這樣的 lambda 表達式使其只有單一退出點。 如果這無法做到或者不夠清晰,請考慮將 lambda 表達式轉換為匿名函數(shù)。
不要在 lambda 表達式的最后一條語句中使用返回到標簽。
具名參數(shù)
當一個方法接受多個相同的原生類型參數(shù)或者多個 Boolean 類型參數(shù)時,請使用具名參數(shù)語法, 除非在上下文中的所有參數(shù)的含義都已絕對清楚。
drawSquare(x = 10, y = 10, width = 100, height = 100, fill = true)
使用條件語句
優(yōu)先使用 try 、if 與 when 的表達形式。例如:
return if (x) foo() else bar()
return when(x) {
0 -> "zero"
else -> "nonzero"
}
優(yōu)先選用上述代碼而不是:
if (x)
return foo()
else
return bar()
when(x) {
0 -> return "zero"
else -> return "nonzero"
}
if 還是 when
二元條件優(yōu)先使用 if而不是 when 。不要使用
when (x) {
null -> // ……
else -> // ……
}
而應使用 if (x == null) …… else ……
如果有三個或多個選項時優(yōu)先使用when。
在條件中使用可空的 Boolean 值
如果需要在條件語句中用到可空的`` Boolean, 使用if (value == true)或if (value ==false)``` 檢測。
使用循環(huán)
優(yōu)先使用高階函數(shù)(filter、 map等)而不是循環(huán)。例外: forEach(優(yōu)先使用常規(guī)的for 循環(huán), 除非 forEach的接收者是可空的或者 forEach 用做長調用鏈的一部分。)
當在使用多個高階函數(shù)的復雜表達式與循環(huán)之間進行選擇時,請了解每種情況下所執(zhí)行操作的開銷并且記得考慮性能因素。
區(qū)間上循環(huán)
使用 until 函數(shù)在一個開區(qū)間上循環(huán):
for (i in 0..n - 1) { /*……*/ } // 不良
for (i in 0 until n) { /*……*/ } // 良好
使用字符串
優(yōu)先使用字符串模板而不是字符串拼接。
優(yōu)先使用多行字符串而不是將 \n轉義序列嵌入到常規(guī)字符串字面值中。
如需在多行字符串中維護縮進,當生成的字符串不需要任何內部縮進時使用 trimIndent ,而需要內部縮進時使用 trimMargin :
assertEquals(
"""
Foo
Bar
""".trimIndent(),
value
)
val a = """if(a > 1) {
| return a
|}""".trimMargin()
函數(shù)還是屬性
在某些情況下,不帶參數(shù)的函數(shù)可與只讀屬性互換。 雖然語義相似,但是在某種程度上有一些風格上的約定。
底層算法優(yōu)先使用屬性而不是函數(shù):
1.不會拋異常
2.計算開銷?。ɑ蛘咴谑状芜\行時緩存)
3.如果對象狀態(tài)沒有改變,那么多次調用都會返回相同結果
使用擴展函數(shù)
放手去用擴展函數(shù)。每當你有一個主要用于某個對象的函數(shù)時,可以考慮使其成為一個以該對象為接收者的擴展函數(shù)。為了盡量減少 API 污染,盡可能地限制擴展函數(shù)的可見性。根據(jù)需要,使用局部擴展函數(shù)、成員擴展函數(shù)或者具有私有可視性的頂層擴展函數(shù)。
使用中綴函數(shù)
一個函數(shù)只有用于兩個角色類似的對象時才將其聲明為中綴函數(shù)。良好示例如: and 、to 、 zip 。 不良示例如: add 。
如果一個方法會改動其接收者,那么不要聲明為中綴形式。
工廠函數(shù)
如果為一個類聲明一個工廠函數(shù),那么不要讓它與類自身同名。優(yōu)先使用獨特的名稱, 該名稱能表明為何該工廠函數(shù)的行為與眾不同。只有當確實沒有特殊的語義時, 才可以使用與該類相同的名稱。
例如:
class Point(val x: Double, val y: Double) {
companion object {
fun fromPolar(angle: Double, radius: Double) = Point(...)
}
}
如果一個對象有多個重載的構造函數(shù),它們并非調用不同的超類構造函數(shù),并且不能簡化為具有默認參數(shù)值的單個構造函數(shù),那么優(yōu)先用工廠函數(shù)取代這些重載的構造函數(shù)。
平臺類型
返回平臺類型表達式的公有函數(shù)/方法必須顯式聲明其 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)
}
使用作用域函數(shù) apply/with/run/also/let
Kotlin 提供了一系列用來在給定對象上下文中執(zhí)行代碼塊的函數(shù): let 、run 、with 、apply 以及 also 。 關于不同情況下選擇正確作用域函數(shù)的準則。
庫的編碼規(guī)范
在編寫庫時,建議遵循一組額外的規(guī)則以確保 API 的穩(wěn)定性:
1. 總是顯式指定成員的可見性(以避免將聲明意外暴露為公有 API )
2.總是顯式指定函數(shù)返回類型以及屬性類型(以避免當實現(xiàn)改變時意外更改返回類型)
3.為所有公有成員提供 KDoc 注釋,不需要任何新文檔的覆蓋成員除外 (以支持為該庫生成文檔)