
Diktat是Kotlin的嚴格編碼標準,由Kotlin代碼樣式規則的集合組成,該規則以抽象語法樹(AST)的訪問者為基礎,該規則構成了Ktlint頂部的訪問者。它的目的是在連續集成/連續部署(CI/CD)過程中檢測並自動固定代碼氣味。您可以在此處找到受支持的規則和檢查的全面列表。
Diktat已獲得認可,並已添加到靜態分析工具,Kotlin-Awsome和Kompar的列表中。我們對社區的支持表示感謝!
| CodeStyle | 檢查 | 例子 | 演示 | 白皮書 | 一組檢查 |
儘管還有其他工具,例如detekt和ktlint執行靜態分析,但您可能會想知道為什麼需要Diktat。這是關鍵原因:
更多檢查: Diktat擁有100多次檢查,並與其codeistyle緊密相結合。
唯一的檢查: Diktat引入了其他襯里中找不到的獨特檢查。
高度可配置:每次檢查都是高度可配置的,允許自定義和抑制。檢查配置選項和抑制。
嚴格的CodeStyle: Diktat強制執行可以在您的項目中採用和應用的詳細座談。
手動下載diktat:在這裡
或使用curl :
curl -sSLO https://github.com/saveourtool/diktat/releases/download/v2.0.0/diktat && chmod a+x diktat僅適用於Windows 。手動下載diktat.cmd:此處
最後,運行ktlint(注射diktat)以檢查“ dir/your/your/dir”中的“*.kt”文件:
$ ./diktat " dir/your/dir/**/*.kt "在窗戶上
diktat.bat "dir/your/dir/**/*.kt"
為了自動框架所有違反代碼樣式,請使用--mode fix選項。
您可以在我們的示例中查看如何配置它:
< plugin >
< groupId >com.saveourtool.diktat</ groupId >
< artifactId >diktat-maven-plugin</ artifactId >
< version >${diktat.version}</ version >
< executions >
< execution >
< id >diktat</ id >
< phase >none</ phase >
< goals >
< goal >check</ goal >
< goal >fix</ goal >
</ goals >
< configuration >
< inputs >
< input >${project.basedir}/src/main/kotlin</ input >
< input >${project.basedir}/src/test/kotlin</ input >
</ inputs >
< diktatConfigFile >diktat-analysis.yml</ diktatConfigFile >
< excludes >
< exclude >${project.basedir}/src/test/kotlin/excluded</ exclude >
</ excludes >
</ configuration >
</ execution >
</ executions >
</ plugin >要以僅檢查模式運行diktat,請使用命令$ mvn diktat:check@diktat 。要在自動更正模式下運行Diktat,請使用命令$ mvn diktat:fix@diktat 。
在這些情況下,請求在命令行上要求特定的maven executionId (上述示例中的尾巴diktat )至關重要:
在您的pom.xml中,您有多個具有不同配置的執行(例如:多個規則集):
< executions >
< execution >
< id >diktat-basic</ id >
< configuration >
< diktatConfigFile >diktat-analysis.yml</ diktatConfigFile >
</ configuration >
</ execution >
< execution >
< id >diktat-advanced</ id >
< configuration >
< diktatConfigFile >diktat-analysis-advanced.yml</ diktatConfigFile >
</ configuration >
</ execution >
</ executions >帶有DIKTAT規則的YAML文件具有非默認名稱和/或居住在非默認位置:
< executions >
< execution >
< id >diktat</ id >
< configuration >
< diktatConfigFile >/non/default/rule-set-file.yml</ diktatConfigFile >
</ configuration >
</ execution >
</ executions >diktatConfigFile ,或者如果指向不存在的文件,則Diktat以默認配置運行。如果您省略了executionId :
$ mvn diktat:check - 插件將使用默認配置並在項目目錄中搜索diktat-analysis.yml文件(您仍然可以通過編輯YAML文件來自定義規則集)。
需要一個不低於7.0的Gradle版本
您可以在我們的示例中查看插件的配置方式:
plugins {
id( " com.saveourtool.diktat " ) version " 2.0.0 "
}注意如果要將插件應用於多模塊項目”
import com.saveourtool.diktat.plugin.gradle.DiktatGradlePlugin plugins { id( " com.saveourtool.diktat " ) version " 2.0.0 " apply false } allprojects { apply< DiktatGradlePlugin >() }
然後,您可以使用diktat擴展名配置Diktat:
diktat {
inputs {
include( " src/**/*.kt " ) // path matching this pattern (per PatternFilterable) that will be checked by diktat
exclude( " src/test/kotlin/excluded/** " ) // path matching this pattern will not be checked by diktat
}
debug = true // turn on debug logging
}另外,在diktat擴展程序中,您可以配置不同的記者及其輸出。您可以指定json , html , sarif , plain (默認)。如果設置output ,則應該是文件路徑。如果未設置,結果將打印到Stdout。您可以指定多個記者。如果未指定記者,則將與stdout一起使用plain作為輸出。
diktat {
reporters {
plain()
json()
html {
output = file( " someFile.html " )
}
// checkstyle()
// sarif()
// gitHubActions()
}
}您可以使用任務./gradlew diktatCheck運行diktat檢查,並自動使用任務./gradlew diktatFix來修復錯誤。
一塵不染是襯裡聚合器。
自版5.10.0以來,Diktat可以通過一塵不染的Gradle-Plugin運行
plugins {
id( " com.diffplug.spotless " ) version " 5.10.0 "
}
spotless {
kotlin {
diktat()
}
kotlinGradle {
diktat()
}
}spotless {
kotlin {
diktat( " 2.0.0 " ).configFile( " full/path/to/diktat-analysis.yml " )
}
}自版2.8.0以來,Diktat可以通過一塵不染的武器運行
< plugin >
< groupId >com.diffplug.spotless</ groupId >
< artifactId >spotless-maven-plugin</ artifactId >
< version >${spotless.version}</ version >
< configuration >
< kotlin >
< diktat />
</ kotlin >
</ configuration >
</ plugin >< diktat >
< version >2.0.0</ version > <!-- optional -->
< configFile >full/path/to/diktat-analysis.yml</ configFile > <!-- optional, configuration file path -->
</ diktat >我們建議每個人都使用常見的“ sarif”格式作為CI/CD中的reporter 。 GitHub與SARIF格式有一個集成,並為您提供了拉動請求中Diktat問題的本機報告。

Gradle插件:
githubActions = true
Maven插件(pom.xml):
< githubActions >true</ githubActions >Maven插件(CLI選項):
mvn -B diktat:check@diktat -Ddiktat.githubActions=true
- name : Upload SARIF to Github using the upload-sarif action
uses : github/codeql-action/upload-sarif@v1
if : ${{ always() }}
with :
sarif_file : ${{ github.workspace }}注意: codeql-action/upload-sarif將上傳文件的數量限制為15。如果您的項目具有超過15個子標記,則將超過限制,並且步驟將失敗。為了解決此問題,可以合併SARIF報告。
diktat-gradle-plugin通過mergeDiktatReports任務提供了此功能。此任務匯總了所有Gradle項目的所有DIKTAT任務的報告,該項目產生了SARIF報告,並將合併報告輸出到Root Project的Build Directory中。然後,這個單個文件可以用作GitHub操作的輸入:
with :
sarif_file : build/reports/diktat/diktat-merged.sarif diktat-analysis.yml進行自定義在Diktat中,我們支持了diktat-analysis.yml ,可以輕鬆更改並有助於自定義您自己的規則集。它具有簡單的字段: name - 規則的名稱, enabled (true/false) - 啟用或禁用該規則(默認規則啟用所有規則), configuration - 為此特定規則的一些額外獨特配置的簡單映射。例如:
- name : HEADER_MISSING_OR_WRONG_COPYRIGHT
# all rules are enabled by the default. To disable add 'enabled: false' to the config.
enabled : true
configuration :
isCopyrightMandatory : true
copyrightText : Copyright (c) Jeff Lebowski, 2012-2020. All rights reserved.請注意,您可以指定並放置diktat-analysis.yml ,其中包含diktat在項目的父目錄中的配置,以存儲build.gradle/pom.xml同一級別。
請參閱Diktat-Analysis.yml中的默認配置
另請參閱Diktat支持的所有規則的列表。
例如:
@Suppress( " FUNCTION_NAME_INCORRECT_CASE " )
class SomeClass {
fun methODTREE (): String {
}
}例如:
@Suppress( " diktat " )
class SomeClass {
fun methODTREE (): String {
}
}- name : HEADER_NOT_BEFORE_PACKAGE
enabled : true
ignoreAnnotated : [MyAnnotation, Compose, Controller]這些群體與CodeStyle章節有關。
要禁用章節,您需要將以下配置添加到常見配置( - name: DIKTAT_COMMON ):
disabledChapters : " 1, 2, 3 "將檢查映射到章節可以在一組檢查中找到。
在一個大型現有項目上設置代碼樣式分析時,通常沒有能力一次修復所有發現。為了逐步採用,Diktat和Ktlint支持基線模式。首次使用Active基線運行KTLINT時,將生成基線文件。這是一個XML文件,該文件具有該工具的完整髮現列表。在以後的調用中,僅報告基線文件中未的發現。基線可以用CLI標誌激活:
./diktat --baseline=diktat-baseline.xml ** / * .kt或帶有Maven或Gradle插件中的相應配置選項。基線報告旨在將其添加到VC中,但可以在需要時將其刪除並重新生成。
查看我們的貢獻政策和行為準則
我序言
1。命名
2。評論
3。一般格式(排版)
4。變量和類型
5。功能
6。類,接口和擴展功能
本文檔的目的是提供一個規範,軟件開發人員可以參考以增強其編寫一致,易於閱讀和高質量代碼的能力。這樣的規範最終將提高軟件開發效率和產品競爭力。為了將代碼視為高質量,它必須具有以下特徵:
像其他現代編程語言一樣,Kotlin是一種高級編程語言,符合以下一般原則:
另外,我們需要在Kotlin編程時考慮以下因素:
編寫乾淨,簡單的Kotlin代碼
Kotlin結合了兩個主要編程範式:功能和麵向對象。這兩個範式都是值得信賴和著名的軟件工程實踐。作為一種年輕的編程語言,Kotlin建立在諸如Java,C ++,C#和Scala之類的知名語言上。這使Kotlin能夠引入許多功能,以幫助開發人員編寫清潔程序,更可讀的代碼,同時還減少複雜的代碼結構的數量。例如,類型和無效的安全性,擴展功能,infix語法,不變性,val/var分化,面向表達式的特徵,“何時”語句,收集,類型自動轉換和其他句法糖的工作更加容易。
遵循科特林成語
科特林(Kotlin)的作者安德烈·布雷斯拉夫(Andrey Breslav)提到,科特林既務實又實用,但不是學術。它的務實功能使想法可以輕鬆地轉換為真正的工作軟件。 Kotlin比其前輩更接近自然語言,並且實現了以下設計原則:可讀性,可重複性,互操作性,安全性和工具友好性(https://blog.jetbrains.com/kotlin.com/kotlin/2018/2018/10/kotlinconf-2018-anncements/)。
有效地使用Kotlin
一些Kotlin功能可以幫助您編寫更高的績效代碼:包括豐富的Coroutine庫,序列,內聯功能/類,基本類型的數組,TailRec和Conterin的呼叫。
規則- 編程時應遵循的約定。
建議- 編程時應考慮的慣例。
說明- 規則和建議的必要解釋。
有效的例子- 建議的規則和建議示例。
無效的例子- 不推薦的規則和建議示例。
除非另有說明,否則此規範適用於Kotlin的1.3版和後期。
即使可能存在例外,必須了解為什麼需要規則和建議。根據項目情況或個人習慣,您可以違反一些規則。但是,請記住,一個例外可能會導致許多例外,並最終可以破壞代碼一致性。因此,應該很少有例外。修改開源代碼或第三方代碼時,您可以選擇使用此開源項目(而不是使用現有規格)中的代碼樣式來維持一致性。直接基於Android本機操作系統接口(例如Android框架)的軟件與Android樣式保持一致。
在編程中,有意義和適當地命名變量,功能,類等並不總是那麼容易。使用有意義的名稱有助於清楚地表達您的代碼的主要想法和功能,並避免誤解,不必要的編碼和解碼,“魔術”數字以及不合適的縮寫。
注意:源文件編碼格式(包括註釋)必須僅為UTF-8。 ASCII水平空間特徵(即0x20,空間)是唯一允許的空格字符。標籤不應用於凹痕。
本節介紹了命名標識符的一般規則。
對於標識符,請使用以下命名約定:
所有標識符都應僅使用ASCII字母或數字,並且名稱應匹配正則表達式w{2,64} 。說明:每個有效的標識符名稱應與正則表達式w{2,64}匹配。 {2,64}表示名稱長度為2至64個字符,並且可變名稱的長度應與其壽命範圍,功能和責任成正比。通常建議使用小於31個字符的名稱長度。但是,這取決於項目。否則,與超類的仿製藥或繼承的類聲明可能會導致線路破裂。名稱不得使用特殊的前綴或後綴。以下示例是不適當的名稱:name_,mname,s_name和name。
選擇可以描述內容的文件名。使用駱駝盒(pascalcase)和.kt擴展。
命名的典型例子:
| 意義 | 正確的 | 不正確 |
|---|---|---|
| “ XML HTTP請求” | xmlhttprequest | xmlhttprequest |
| “新客戶ID” | newcustomerid | newcustomerid |
| “內部秒錶” | Innerstopwatch | Innerstopwatch |
| “支持iOS上的IPv6” | 支持SIPV6ONIOS | 支持SIPV6ONIOS |
| “ YouTube進口商” | YouTubeImporter | YouTubeImporter |
val `my dummy name - with - minus` = " value "唯一的例外是Unit tests.
@Test fun `my test` () { /* ... */ }| 預期的 | 令人困惑的名稱 | 建議的名字 |
|---|---|---|
| 0(零) | o,d | OBJ,DGT |
| 1(一個) | 我,l | 它,ln,線 |
| 2(兩個) | z | N1,N2 |
| 5(五) | s | XS,str |
| 6(六) | e | 前,榆樹 |
| 8(八) | b | BT,NXT |
| n,h | H,n | NR,頭,高度 |
| RN,m | M,RN | MBR,項目 |
例外:
e變量可用於在捕獲塊中捕獲異常: catch (e: Exception) {}| 類型 | 命名風格 |
|---|---|
| 接口,類,註釋,枚舉類型和對像類型名稱 | 駱駝盒,從大寫字母開始。測試類具有測試後綴。文件名是“ TopClassName'.kt。 |
| 類字段,本地變量,方法和方法參數 | 駱駝盒以低案例字母開頭。可以用“ _”強調測試方法;唯一的例外是備份屬性。 |
| 靜態常數和枚舉值 | 只有大寫用“ _”強調 |
| 通用類型變量 | 單個大寫字母,可以隨後是一個數字,例如: E, T, U, X, T2 |
| 例外 | 與類名稱相同,但有後綴異常,例如: AccessException和NullPointerException |
軟件包名稱在較低的情況下,並由點分開。您公司內開發的代碼應從your.company.domain.允許在包名稱中允許數字。每個文件都應具有package指令。軟件包名稱全部以小寫字母編寫,並且連續的單詞被串聯在一起(沒有下劃線)。軟件包名稱應包含產品或模塊名稱和部門(或團隊)名稱,以防止與其他團隊發生衝突。不允許數字。例如: org.apache.commons.lang3 , xxx.yyy.v2 。
例外:
your.company.domain.com.example._123name 。org.example.hyphenated_name , int_.example ,有時會允許下劃線。有效示例:
package your.company.domain.mobilecontrol.views本節介紹了命名類,枚舉和接口的一般規則。
類,枚舉和接口名稱使用UpperCamelCase命名。遵循以下描述的命名規則:
類名稱通常是使用駱駝盒命名法(例如UpperCamelcase)表示的名詞(或名詞短語)。例如: Character或ImmutableList 。接口名稱也可以是名詞或名詞短語(例如List )或形容詞或形容詞短語(例如Readable )。請注意,動詞不用於命名類。但是,可以使用名詞(例如Customer , WikiPage和Account )。盡量避免使用模糊的單詞,例如Manager和Process 。
測試課程從他們正在測試的類的名稱開始,並以“測試”結尾。例如, HashTest或HashIntegrationTest 。
無效示例:
class marcoPolo {}
class XMLService {}
interface TAPromotion {}
class info {}有效示例:
class MarcoPolo {}
class XmlService {}
interface TaPromotion {}
class Order {}本節介紹了命名功能的一般規則。
功能名稱應使用lowerCamelCase命名法。遵循以下描述的命名規則:
lowerCamelCase )表示的動詞或動詞短語。例如: sendMessage , stopProcess或calculateValue 。要命名函數,請使用以下格式規則:a)獲取,修改或計算一個特定值:get + non-non-non-non-boolean field()。請注意,Kotlin編譯器會自動為某些類生成getters,並為“ get”字段使用特殊語法:Kotlin Private Val字段:String get(){}。 kotlin私有val字段:字符串get(){}。
private val field : String
get() {
}注意:呼叫屬性訪問語法是直接調用getter的首選。在這種情況下,Kotlin編譯器會自動調用相應的Getter。
b) is + boolean變量名稱()
c) set +字段/屬性名稱()。但是,請注意,Kotlin的語法和代碼生成與A點A中描述的語法和代碼生成完全相同。
d) has +名詞 /形容詞()
e)動詞()注意:注意:動詞主要用於操作對象,例如document.print ()
f)動詞 + noun()
g)回調函數允許使用介詞 +動詞格式的名稱,例如: onCreate() , onDestroy() , toString() 。
無效示例:
fun type (): String
fun Finished (): Boolean
fun visible (boolean)
fun DRAW ()
fun KeyListener ( Listener )有效示例:
fun getType (): String
fun isFinished (): Boolean
fun setVisible (boolean)
fun draw ()
fun addKeyListener ( Listener )_ )可以包含在JUNIT測試功能名稱中,應用作分離器。每個邏輯零件都在lowerCamelCase中表示,例如使用下劃線的典型模式: pop_emptyStack 。本節介紹了命名約束的一般規則。
恆定名稱應在上案例中,單詞通過下劃線分開。一般不變的命名約定如下:
const關鍵字或頂級/ val本地變量創建的屬性。在大多數情況下,可以從object / companion object /文件頂級識別常數為const val屬性。這些變量包含固定的常數值,程序員通常永遠不應該更改。這包括基本類型,字符串,不變類型以及不變的類型的不變收藏。該值不是可以更改的對象的恆定值。val變量都是常數。Logger和Lock )可以在大寫作為常數中,也可以作為常規變量的駱駝情況。magic numbers 。 SQL或記錄字符串不應被視為魔術數字,也不應將其定義為字符串常數。魔術常數(例如NUM_FIVE = 5或NUM_5 = 5不應被視為常數。這是因為如果錯誤將其更改為NUM_5 = 50或55。這些常數通常代表業務邏輯值,例如算法中的測量,容量,範圍,位置,位置,稅率,稅率,促銷折扣和電源基礎倍數。您可以避免使用以下方法使用魔術數字:isEmpty()函數,而不是檢查該size == 0 。要隨著time的推移,請使用java.time API的內置插件。無效示例:
var int MAXUSERNUM = 200 ;
val String sL = " Launcher " ;有效示例:
const val int MAX_USER_NUM = 200 ;
const val String APPLICATION_NAME = " Launcher " ;本節介紹了命名變量的一般規則。
非恆定字段的名稱應使用駱駝盒,然後從小寫字母開始。即使局部變量是最終且不可變的,也不能將其視為恆定。因此,它不應使用前面的規則。集合類型變量的名稱(集合,列表等)應包含複數名詞。例如: var namesList: List<String>
非恆定變量的名稱應使用lowerCamelCase 。用於存儲單例對象的最終不變字段的名稱可以使用相同的駱駝盒符號。
無效示例:
customername : String
user : List < String > = listof()有效示例:
var customerName : String
val users : List < String > = listOf ();
val mutableCollection : MutableSet < String > = HashSet ()避免使用具有負含義的布爾變量名稱。當使用邏輯運算符和具有負含義的名稱時,代碼可能難以理解,這被稱為“雙負”。例如,不容易理解! Javabeans規範會自動生成布爾類屬性的ISXXX()getters。但是,並非所有返回布爾類型的方法都有此符號。對於布爾局部變量或方法,強烈建議您添加非敏化前綴,包括IS(Javabeans通常使用),具有,可以,應該和必須。當您在Java生成Getters時,現代集成的開發環境(IDE)已經能夠為您做到這一點。對於Kotlin而言,此過程更加簡單,因為所有內容都在引擎蓋下的字節代碼級別上。
無效示例:
val isNoError : Boolean
val isNotFound : Boolean
fun empty ()
fun next ();有效示例:
val isError : Boolean
val isFound : Boolean
val hasLicense : Boolean
val canEvaluate : Boolean
val shouldAbort : Boolean
fun isEmpty ()
fun hasNext ()最好的做法是通過摘要開始您的代碼,這可以是一個句子。嘗試在完全沒有評論和每條代碼行的明顯評論語句之間取得平衡。評論應準確,清晰地表達,而不重複類,接口或方法的名稱。評論不是解決錯誤代碼的解決方案。取而代之的是,您應該在註意到問題或計劃修復該代碼後立即修復代碼(通過輸入todo評論,包括jira號碼)。評論應準確反映該代碼的設計思想和邏輯,並進一步描述其業務邏輯。結果,其他程序員在嘗試理解代碼時可以節省時間。想像一下,您正在寫評論,以幫助自己了解將來代碼背後的原始想法。
KDOC是Javadoc的塊標記語法(擴展以支持Kotlin的特定構造)和Markdown的Inline標記的組合。以下示例顯示了KDOC的基本格式:
/* *
* There are multiple lines of KDoc text,
* Other ...
*/
fun method ( arg : String ) {
// ...
}它也以以下單線形式顯示:
/* * Short form of KDoc. */當您將整個KDOC塊存儲在一行中時,請使用單行形式(並且沒有KDOC Mark @xxx)。有關如何使用KDOC的詳細說明,請參閱官方文件。
至少應將KDOC用於每個公眾,受保護或內部裝飾類,界面,枚舉,方法和成員字段(屬性)。如果需要,其他代碼塊也可以具有KDOC。而不是在類的主要構造函數中使用屬性之前使用註釋或kDoc,而是在類的kDoc中使用@property標籤。還應在具有@property標籤的KDOC中記錄主構造函數的所有屬性。
不正確的示例:
/* *
* Class description
*/
class Example (
/* *
* property description
*/
val foo : Foo ,
// another property description
val bar : Bar
)正確的示例:
/* *
* Class description
* @property foo property description
* @property bar another property description
*/
class Example (
val foo : Foo ,
val bar : Bar
)不正確的示例:
class Example {
fun doGood () {
/* *
* wrong place for kdoc
*/
1 + 2
}
}正確的示例:
class Example {
fun doGood () {
/*
* right place for block comment
*/
1 + 2
}
}例外:
對於屬性的設置/獲取器,明顯的註釋(例如this getter returns field )是可選的。請注意,Kotlin在引擎蓋下生成簡單的get/set方法。
為簡單的單行方法添加註釋是可選的,如下所示:
val isEmpty : Boolean
get() = this .size == 0或者
fun isEmptyList ( list : List < String >) = list.size == 0注意:如果方法與超類方法幾乎相同,則可以跳過KDOC進行替代。
當該方法具有諸如參數,返回值或可以投擲異常之類的細節時,必須在KDOC塊中進行描述(帶有@param, @return,@throws等)。
有效示例:
/* *
* This is the short overview comment for the example interface.
* / * Add a blank line between the comment text and each KDoc tag underneath * /
* @since 1.6
*/
protected abstract class Sample {
/* *
* This is a long comment with whitespace that should be split in
* comments on multiple lines if the line comment formatting is enabled.
* / * Add a blank line between the comment text and each KDoc tag underneath * /
* @param fox A quick brown fox jumps over the lazy dog
* @return battle between fox and dog
*/
protected abstract fun foo ( Fox fox)
/* *
* These possibilities include: Formatting of header comments
* / * Add a blank line between the comment text and each KDoc tag underneath * /
* @return battle between fox and dog
* @throws ProblemException if lazy dog wins
*/
protected fun bar () throws ProblemException {
// Some comments / * No need to add a blank line here * /
var aVar = .. .
// Some comments / * Add a blank line before the comment * /
fun doSome ()
}
}KDOC標籤和內容之間應只有一個空間。標籤按以下順序排列:@param, @return和@throws。
因此,KDOC應包含以下內容:
implSpec , apiNote和implNote )在它們之後需要一個空線。@implSpec :與API實施相關的規範,它應該讓實施者決定是否覆蓋它。@apiNote :解釋API預防措施,包括是否允許NULL以及該方法是否為線程安全,以及算法複雜性,輸入和輸出範圍,異常等。@implNote :與API實現有關的註釋,該註釋應牢記實現者。@param , @return , @throws和其他評論。@param , @return , @throws 。 KDOC不應包含:*/符號)之間不應有空的線條。@author標籤。當您可以使用git blame或您選擇的VC來瀏覽“更改”歷史記錄時,誰最初創建了一個班級都沒關係。重要說明:@deprecated標籤。而是使用@Deprecated註釋。@since標籤僅用於版本。請勿在@since標籤中使用日期,這是令人困惑且準確的。如果無法用一行描述標籤塊,則從@位置的四個空格縮進新線的內容以實現對齊( @計數為一個 +三個空格)。
例外:
當標籤塊中的描述性文本太長而無法包裝時,您可以在最後一行中與描述性文本對齊。多個標籤的描述性文本不需要對齊。請參閱3.8水平空間。
在Kotlin,與Java相比,您可以將幾個類放入一個文件中,因此每個類應具有KDOC格式的註釋(如規則2.1中所述)。此評論應包含@since標籤。正確的樣式是在發布功能時編寫應用程序版本。它應該在@since標籤之後輸入。
示例:
/* *
* Description of functionality
*
* @since 1.6
*/可以添加其他KDOC標籤(例如@param類型參數和@See。)如下:
/* *
* Description of functionality
*
* @apiNote: Important information about API
*
* @since 1.6
*/本節介紹在文件標頭上添加註釋的一般規則。
在包裝名稱和導入之前,應將對文件標頭的評論放置。如果您需要在註釋中添加更多內容,請隨後以相同的格式添加它。
對文件標頭的評論必須包括版權信息,而無需創建日期和作者的姓名(使用VCS進行歷史管理)。另外,描述文件中包含多個或沒有類的內容。
華為的以下示例描述了版權許可的格式:
中文版本:版权所有 (c) 华为技术有限公司 2012-2020
英文版本: Copyright (c) Huawei Technologies Co., Ltd. 2012-2020. All rights reserved. 2012和2020是該文件首次創建的年份和本年度。
請勿將發行筆記放在標題中,請使用VC跟踪文件的更改。可以使用版本的@since標籤在單個KDOC中標記出顯著的更改。
無效示例:
/* *
* Release notes:
* 2019-10-11: added class Foo
*/
class Foo有效示例:
/* *
* @since 2.4.0
*/
class Foo版權聲明可以使用您公司的子公司,如下所示:
中文版本:版权所有 (c) 海思半导体 2012-2020
英文版本: Copyright (c) Hisilicon Technologies Co., Ltd. 2012-2020. All rights reserved.
版權信息不應以KDOC樣式編寫或使用單行註釋。它必須從文件的開頭開始。以下示例是華為的版權語句,沒有其他功能評論:
/*
* Copyright (c) Huawei Technologies Co., Ltd. 2012-2020. All rights reserved.
*/編寫文件標頭或高級類評論時應考慮以下因素:
*/符號之後應該有一個空白行。如果這是頂級課程的評論,則類聲明應立即開始而不使用newline。@apiNote之後沒有內容,則應刪除整個標籤塊。對功能標頭的評論放置在上面的函數聲明或定義上。在函數聲明及其KDOC之間不應該存在新線。使用前面的<< c2.1,kDoc >>樣式規則。
如第1章所述,該函數名稱應盡可能反映其功能。因此,在KDOC中,嘗試描述函數名稱中未提及的功能。避免對虛擬編碼進行不必要的評論。
功能標題註釋的內容是可選的,但不限於功能描述,返回值,性能約束,使用情況,內存約定,算法實現,重點要求等。
本節介紹添加代碼註釋的一般規則。
最好在評論的主體和KDOC標籤塊之間添加空白線。另外,請考慮以下規則:
有效示例:
/* *
* This is the short overview comment for the example interface.
*
* @since 1.6
*/
public interface Example {
// Some comments /* Since it is the first member definition in this code block, there is no need to add a blank line here */
val aField : String = .. .
/* Add a blank line above the comment */
// Some comments
val bField : String = .. .
/* Add a blank line above the comment */
/* *
* This is a long comment with whitespace that should be split in
* multiple line comments in case the line comment formatting is enabled.
* /* blank line between description and Kdoc tag */
* @param fox A quick brown fox jumps over the lazy dog
* @return the rounds of battle of fox and dog
*/
fun foo ( Fox fox)
/* Add a blank line above the comment */
/* *
* These possibilities include: Formatting of header comments
*
* @return the rounds of battle of fox and dog
* @throws ProblemException if lazy dog wins
*/
fun bar () throws ProblemException {
// Some comments /* Since it is the first member definition in this range, there is no need to add a blank line here */
var aVar = .. .
// Some comments /* Add a blank line above the comment */
fun doSome ()
}
}if-else-if方案中使用條件註釋,請將註釋放入else-if分支或條件塊中,而不是在else-if之前。這使得代碼更容易理解。當IF塊與捲曲括號一起使用時,打開捲曲括號後應將註釋放在下一行上。與Java相比,Kotlin語句中的if語句返回值。因此,評論塊可以描述整個if-statement 。有效示例:
val foo = 100 // right-side comment
val bar = 200 /* right-side comment */
// general comment for the value and whole if-else condition
val someVal = if (nr % 15 == 0 ) {
// when nr is a multiple of both 3 and 5
println ( " fizzbuzz " )
} else if (nr % 3 == 0 ) {
// when nr is a multiple of 3, but not 5
// We print "fizz", only.
println ( " fizz " )
} else if (nr % 5 == 0 ) {
// when nr is a multiple of 5, but not 3
// we print "buzz" only.
println ( " buzz " )
} else {
// otherwise, we print the number.
println (x)
}// , /* , /** and * )之後開始所有註釋(包括KDOC),並帶有空間有效示例:
val x = 0 // this is a comment 不要對未使用的代碼塊(包括導入)發表評論。立即刪除這些代碼塊。代碼不用於存儲歷史記錄。 GIT,SVN或其他VCS工具應用於此目的。未使用的進口增加了代碼的耦合,不利於維護。評論的代碼無法適當維護。為了重複使用代碼,您可能會引入很容易錯過的缺陷。正確的方法是在不再使用時直接刪除不必要的代碼。如果您再次需要代碼,請考慮自行評論以來,可能會發生更改或重寫它。
正式交付給客戶的代碼通常不包含todo/fixme註釋。 TODO註釋通常用於描述需要改進和添加的修改點。例如,重構FixMe註釋通常用於描述隨後將修復並且對應用程序並不關鍵的已知缺陷和錯誤。他們都應該具有統一的樣式,以促進統一的文本搜索處理。
例子:
// TODO(<author-name>): Jira-XXX - support new json format
// FIXME: Jira-XXX - fix NPE in this code block在版本開發階段,可以使用這些註釋來突出代碼中的問題,但是在發布新產品版本之前,所有這些註釋都應修復。
本節介紹了與使用代碼中使用文件有關的規則。
如果文件太長且複雜,則應將其分為較小的文件,功能或模塊。文件不應超過2000行(非空和不被調音線)。建議根據其零件的職責或層次結構水平或垂直劃分文件。此規則的唯一例外是代碼生成 - 未手動修改的自動生成的文件可能更長。
源文件按以下順序包含代碼塊:版權,軟件包名稱,導入和頂級類。他們應該用一條空白行分開。
a)代碼塊應按以下順序:
@file註釋b)每個代碼塊應由空白行分開。
c)導入語句是按字母順序排列的,而無需使用斷路和通配符(通配符進口 - * )。
d)建議:一個.kt源文件應僅包含一個類聲明,其名稱應與文件名匹配
e)避免空的不包含代碼或僅包含導入/註釋/軟件包名稱的空文件
f)應刪除未使用的進口
從上到下,順序是以下內容:
每個類別都應按字母順序排列。每個組應以空白線分離。此樣式與Android進口順序兼容。
有效示例:
import android.* // android
import androidx.* // android
import com.android.* // android
import com.your.company.* // your company's libs
import your.company.* // your company's libs
import com.fasterxml.jackson.databind.ObjectMapper // other third-party dependencies
import org.junit.jupiter.api.Assertions
import java.io.IOException // java core packages
import java.net.URL
import kotlin.system.exitProcess // kotlin standard library
import kotlinx.coroutines.* // official kotlin extension library 類似類的代碼結構(類,接口等)的聲明部分應按以下順序:編譯時間常數(用於對象),類屬性,晚期屬性類屬性,初始化類,構造,構造函數,公共方法,內部方法,受保護的方法,私人方法,私人方法和伴侶對象。空白線應分開他們的聲明。筆記:
const val )應按字母順序排列。班級或接口的聲明部分應按以下順序:
例外: private val記錄器的所有變體都應放在類的開頭( private val log , LOG , logger等)。
Kotlin允許多種頂級聲明類型:類,對象,接口,屬性和功能。當聲明多個類或零類(例如功能)時,根據規則2.2.1,您應在標題KDOC中記錄整個文件。聲明頂級結構時,請保留以下順序:
const val , val , lateinit var , var )注意:擴展功能不應根據規則6.2.3在同一文件中聲明接收器。
有效示例:
package com.saveourtool.diktat.example
const val CONSTANT = 42
val topLevelProperty = " String constant "
internal typealias ExamplesHandler = ( IExample ) -> Unit
interface IExample
class Example : IExample
private class Internal
fun Other. asExample (): Example { /* ... */ }
private fun Other. asInternal (): Internal { /* ... */ }
fun doStuff () { /* ... */ }注意:Kotlin腳本(.kts)允許將任意代碼放在頂級。編寫Kotlin腳本時,您應該首先聲明所有屬性,類和功能。只有這樣,您才應在頂級執行功能。仍然建議包裝邏輯在功能內部包裝邏輯,並避免使用函數調用的頂級語句或在諸如run類的頂級範圍函數中使用代碼的塊。
例子:
/* class declarations */
/* function declarations */
run {
// call functions here
}本節介紹了代碼中使用括號的一般規則。
如果程序主體為空或僅包含一個語句,則應始終使用括號, if , for , do and while和nate else語句。在語句when ,在特殊的Kotlin中,您不需要將牙套用於單線語句。
有效示例:
when (node.elementType) {
FILE -> {
checkTopLevelDoc(node)
checkSomething()
}
CLASS -> checkClassElements(node)
}異常:唯一的例外是Kotlin中的三元運算符(單行if () <> else <> )
無效示例:
val value = if (string.isEmpty()) // WRONG!
0
else
1有效示例:
val value = if (string.isEmpty()) 0 else 1 // Okay if (condition) {
println ( " test " )
} else {
println ( 0 )
}對於非空塊和塊結構,將開放支架放在線的末端。按照K&R樣式(1TB或OTB)進行帶括號的非空代碼塊:
else , finally (從do-while語句while )或catch關鍵字。這些關鍵字不應由Newline字符從閉合支架中分開。例外情況:
-> )之後出現新線字符(請參閱規則3.6.2的第5點)。 arg.map { value ->
foo(value)
}else / catch / finally / while (從do-while語句中)關鍵字關閉支架應保持在同一條線上: do {
if ( true ) {
x ++
} else {
x --
}
} while (x > 0 )有效示例:
return arg.map { value ->
while (condition()) {
method()
}
value
}
return MyClass () {
@Override
fun method () {
if (condition()) {
try {
something()
} catch (e : ProblemException ) {
recover()
}
} else if (otherCondition()) {
somethingElse()
} else {
lastThing()
}
}
}僅允許空間進行凹痕,每個縮進應等於four spaces (不允許使用選項卡)。如果您喜歡使用選項卡,只需將它們配置為自動更改為IDE中的空格即可。如果將這些代碼塊放置在新線路上,應縮進它們,並滿足以下條件:
+ / - / && / = / etc)。someObject
.map()
.filter()arg.map { value ->
foo(value)
}例外:
參數列表:
a)八個空間用於縮進參數列表(無論是在聲明還是在呼叫站點中)。
b)如果參數列表中的參數在不同的行上可以對齊。
如果在任何二進制操作員之後有新線,則使用八個空間。
當將新線放置在點之前時,將八個空間用於類似功能的樣式。
Supertype列表:
a)如果在新線上列表之前的結腸,則使用四個空間。
b)在每種超構型之前使用四個空間,如果結腸在新線上,則使用八個空間。
注意:在所有語句之後,例如, if ,等等for但是,根據此代碼樣式,此類語句需要支撐。
if (condition)
foo()例外:
8 spaces對齊。移至新行的參數可以與以前的參數相同: fun visit (
node : ASTNode ,
autoCorrect : Boolean ,
params : KtLint . ExperimentalParams ,
emit : (offset: Int , errorMessage: String , canBeAutoCorrected: Boolean ) -> Unit
) {
}+ / - / *這樣的運算符可以用8 spaces縮進: val abcdef = " my splitted " +
" string "lintMethod(
"""
|val q = 1
|
""" .trimMargin()
)4 spaces或8 spaces縮進的超級型清單。 class A :
B ()
class A
:
B ()避免空塊,並確保括號從新線開始。空代碼塊可以立即在同一行和下一行上關閉。但是,建議在打開和關閉括號之間{} (請參見下面的示例)進行新線。
通常,禁止空代碼塊;使用它們被認為是一種不良練習(尤其是對於捕獲塊)。它們適用於覆蓋功能,當基類的功能在類套件中不需要時,用於用作函數的lambdas,用於實現功能接口的空功能。
override fun foo () {
}有效的示例(再次注意通常禁止空塊):
fun doNothing () {}
fun doNothingElse () {
}
fun foo ( bar : () -> Unit = {})無效的例子:
try {
doSomething()
} catch (e : Some ) {}改用以下有效代碼:
try {
doSomething()
} catch (e : Some ) {
}線長應小於120個符號。否則,應該將其分開。
如果complex property初始化太長,則應將其分為優先級:
無效示例:
val complexProperty = 1 + 2 + 3 + 4有效示例:
val complexProperty = 1 + 2 +
3 + 4無效示例:
val complexProperty = ( 1 + 2 + 3 > 0 ) && ( 23 * 4 > 10 * 6 )有效示例:
val complexProperty = ( 1 + 2 + 3 > 0 ) &&
( 23 * 4 > 10 * 6 )如果在Elvis Operator (?:)中應分開長線,那就這樣做了
無效示例:
val value = first ? : second有效示例:
val value = first
? : second如果Dot Qualified Expression或Safe Access Expression中的長線,則這樣做了:
無效示例:
val value = This . Is . Very . Long . Dot . Qualified . Expression有效示例:
val value = This . Is . Very . Long
. Dot . Qualified . Expression無效示例:
val value = This . Is ?. Very ?. Long? . Safe ?. Access ?. Expression有效示例:
val value = This . Is ?. Very ?. Long
?. Safe ?. Access ?. Expression如果value arguments list太長,則應將其分開:
無效示例:
val result1 = ManyParamInFunction (firstArgument, secondArgument, thirdArgument, fourthArgument, fifthArguments)有效示例:
val result1 = ManyParamInFunction (firstArgument,
secondArgument, thirdArgument, fourthArgument,
fifthArguments)如果annotation太長,也應該分開:
無效示例:
@Query(value = " select * from table where age = 10 " , nativeQuery = true )
fun foo () {}有效示例:
@Query(
value = " select * from table where age = 10 " ,
nativeQuery = true )
fun foo () {}長長的一行function應分開:
無效示例:
fun foo () = goo().write( " TooLong " )有效示例:
fun foo () =
goo().write( " TooLong " )長binary expression應分為優先級:
無效示例:
if (( x > 100 ) || y < 100 && ! isFoo()) {}有效示例:
if (( x > 100 ) ||
y < 100 && ! isFoo()) {} String template也可以在字符串文本中的空白空間中拆分
無效示例:
val nameString = " This is very long string template "有效示例:
val nameString = " This is very long " +
" string template "長Lambda argument應分解:
無效示例:
val variable = a?.filter { it.elementType == true } ? : null有效示例:
val variable = a?.filter {
it.elementType == true
} ? : null長時間應分配When Entry一行:
無效示例:
when (elem) {
true -> long.argument.whenEntry
}有效示例:
when (elem) {
true -> {
long.argument.whenEntry
}
}如果上面的示例不合適,但是需要分開該行,並且在property中,則可以像以下那樣固定。
無效示例:
val element = veryLongNameFunction(firstParam)有效示例:
val element =
varyLongNameFunction(firstParam) Eol comment也可以分開,但這取決於評論位置。如果此評論與代碼在同一行上,則應在線之前:
無效示例:
fun foo () {
val name = " Nick " // this comment is too long
}有效示例:
fun foo () {
// this comment is too long
val name = " Nick "
}但是,如果此評論在新行上 - 應該分為幾行:
無效示例:
// This comment is too long. It should be on two lines.
fun foo () {}有效示例:
// This comment is too long.
// It should be on two lines.
fun foo () {}國際代碼樣式禁止non-Latin ( non-ASCII )符號。 (請參閱標識符)但是,如果您仍然打算使用它們,請遵循以下約定:
一個廣泛的角色佔據了兩個狹窄字符的寬度。角色的“寬”和“狹窄”部分由其東亞寬度Unicode屬性定義。通常,狹窄的字符也稱為“半寬”字符。 ASCII字符集中的所有字符都包括字母(例如a, A ),數字(例如0, 3 )和標點空間(例如, { ),所有這些都是狹窄的字符。寬字符也稱為“全寬”字符。中文字符(例如中, 文),中文標點符號( , ; ),全寬字母和數字(例如A、3 )是“全寬”字符。這些字符中的每個都代表兩個狹窄的字符。
如“ Newline”部分中所述,應包裝超過此限制的任何線( 120 narrow symbols )。
例外:
package和import語句。本節包含有關使用線路斷路的規則和建議。
每行最多可以具有一個代碼語句。該建議禁止使用代碼;因為它降低了代碼可見性。
無效示例:
val a = " " ; val b = " "有效示例:
val a = " "
val b = " " ; )。線結束時不應有冗餘的分號。當需要一個newline字符來拆分行時,應將其放置在此類運營商&& / ||之後。 / + / etc。和所有infix函數(例如xor )。但是,Newline字符應放在運營商之前. , ?. , ?: , 和:: 。
請注意,所有比較運算符,例如== , > , < ,不應分開。
無效示例:
if (node !=
null && test != null ) {}有效示例:
if (node != null &&
test != null ) {
}注意:您需要遵循功能樣式,這意味著每個功能都會在鏈條中調用.如果功能鏈包含多個呼叫,則應以新線路開始:
val value = otherValue !!
.map { x -> x }
.filter {
val a = true
true
}
.size注意:解析器禁止分離!!從其檢查的值中的操作員。
例外:如果在三元運算符的分支內使用了功能鏈,則無需用新線拆分。
有效示例:
if (condition) list.map { foo(it) }.filter { bar(it) } else list.drop( 1 )注意:如果DOT合格的表達式在條件內或作為參數傳遞 - 應該用新變量替換。
無效示例:
if (node.treeParent.treeParent?.treeParent.findChildByType( IDENTIFIER ) != null ) {}有效示例:
val grandIdentifier = node
.treeParent
.treeParent
?.treeParent
.findChildByType( IDENTIFIER )
if (grandIdentifier != null ) {}第二個有效示例:
val grandIdentifier = node.treeParent
.treeParent
?.treeParent
.findChildByType( IDENTIFIER )
if (grandIdentifier != null ) {}= )之後放置。( 。應立即在名稱之後放置一個在聲明或呼叫站點中沒有任何空格的插件。, )。it ),則應在開放式撐桿之後放置NewLine字符( { )。以下示例說明了此規則:無效示例:
value.map { name -> foo()
bar()
}有效示例:
value.map { name ->
foo()
bar()
}
val someValue = { node : String -> node }而不是:
override fun toString (): String { return " hi " }使用:
override fun toString () = " hi "有效示例:
class Foo ( val a : String ,
b : String ,
val c : String ) {
}
fun foo (
a : String ,
b : String ,
c : String
) {
}當且僅當第一個參數與開放括號的線上時,所有參數都可以通過第一個參數水平對齊。否則,開放括號後應該會有一個斷路。
Kotlin 1.4引入了尾隨逗號作為可選功能,因此通常建議將所有參數放在單獨的線上並附加尾隨逗號。這使得合併衝突的解決方案更加容易。
有效示例:
fun foo (
a : String ,
b : String ,
) {
}對於函數調用/構造函數參數/等,也應該做同樣的工作
Kotlin在以下情況下支持尾逗號:
枚舉價值參數類屬性和參數函數值參數參數具有可選類型(包括設置器)索引後綴lambda參數時,當輸入集合文字(註釋)類型參數類型參數參數破壞聲明
有效示例:
class MyFavouriteVeryLongClassHolder :
MyLongHolder < MyFavouriteVeryLongClass >(),
SomeOtherInterface ,
AndAnotherOne { }減少不必要的空白線並保持緊湊的代碼大小。通過減少不必要的空白行,您可以在一個屏幕上顯示更多代碼,從而提高代碼可讀性。
init塊和對象之間(請參見3.1.2)。有效示例:
fun baz () {
doSomething() // No need to add blank lines at the beginning and end of the code block
// ...
}本節介紹了使用代碼中使用空格的一般規則和建議。
請按照以下建議使用空間分開關鍵字:
注意:這些建議是針對符號位於同一行上的情況。但是,在某些情況下,可以使用斷路代替空間。
單獨的關鍵字(例如, if , when , for )與單個空格的開頭括號。唯一的例外是constructor關鍵字,不應將其與開放括號分開。
單獨的關鍵字類似於else ,或者用單個空格從開放式支架( { ) try 。如果在沒有括號的三元式語句中使用else ,則else之間應有一個空間,然後在以下語句之間有一個空間: if (condition) foo() else bar()
在所有打開括號( { )之前,請使用單個空格。唯一的例外是將lambda作為括號內的參數傳遞:
private fun foo ( a : ( Int ) -> Int , b : Int ) {}
foo({x : Int -> x}, 5 ) // no space before '{'where的通用結構中的結腸: where T : Type(str: String) -> str.length()例外:
:: :)沒有空間寫:Object::toString. ):object.toString()?.和!!與對象名稱保持在同一條線上:object?.toString()..用於創建範圍:1..100在( , ), : :)和( ; )之後使用空格,除非符號在行末端。但是,請注意,此代碼樣式禁止在行中間使用( ; )(請參閱3.3.2)。一條線的末端不應有空格。唯一的場景是在結腸後沒有空間的唯一場景是在註釋中使用結腸指定使用點目標(例如, @param:JsonProperty )。 ;應該沒有空間, : 。
空間和結腸的例外:
:用於分離類型和supertype,包括匿名對象(在對象關鍵字之後)有效示例:
abstract class Foo < out T : Any > : IFoo { }
class FooImpl : Foo () {
constructor (x : String ) : this (x) { /* ... */ }
val x = object : IFoo { /* ... */ }
}標識符及其類型之間只有一個空間: list: List<String>如果類型無效,以前應該沒有空間? 。
使用[]運算符( get/set )時,標識符和[ : someList[0]之間的空間不應。
方法或構造函數名稱(在聲明和呼叫站點上)和括號: foo() {}之間都不應有空間。請注意,此子規則僅與空間有關;在3.6.2中描述了空格的規則。例如,該規則不禁止以下代碼:
fun foo
(
a : String
)切勿在之後放置空間( , [ , < (當在模板中用作括號時)或之前) , ] , > (當模板中用作括號時)。
前綴/後綴操作員(例如!!或++ )及其操作數之間不應有空格。
水平對齊是指通過向代碼添加空間來對齊代碼塊。水平對齊不應使用,因為:
建議:對齊僅看起來適合enum class ,可以在表格格式中使用它來提高代碼可讀性:
enum class Warnings ( private val id : Int , private val canBeAutoCorrected : Boolean , private val warn : String ) : Rule {
PACKAGE_NAME_MISSING ( 1 , true , " no package name declared in a file " ),
PACKAGE_NAME_INCORRECT_CASE ( 2 , true , " package name should be completely in a lower case " ),
PACKAGE_NAME_INCORRECT_PREFIX ( 3 , false , " package name should start from the company's domain " )
;
}有效示例:
private val nr : Int // no alignment, but looks fine
private var color : Color // no alignment無效示例:
private val nr : Int // aligned comment with extra spaces
private val color : Color // alignment for a comment and alignment for identifier name枚舉值通過逗號和線路斷開,並以';;放在新線上。
;在新線路上: enum class Warnings {
A ,
B ,
C ,
;
}這將有助於解決衝突並減少在合併拉的請求期間的衝突數量。另外,使用尾隨逗號。
enum class Suit { CLUBS , HEARTS , SPADES , DIAMONDS } val isCelsius = true
val isFahrenheit = false使用枚舉類:
enum class TemperatureScale { CELSIUS , FAHRENHEIT }-1, 0, and 1的魔術數量進行比較;改用枚舉。 enum class ComparisonResult {
ORDERED_ASCENDING ,
ORDERED_SAME ,
ORDERED_DESCENDING ,
;
}本節介紹了變量聲明的規則。
每個屬性或變量必須在單獨的行上聲明。
無效示例:
val n1 : Int ; val n2 : Int 聲明局部變量,靠近它們首先被用來最小化其範圍的點。這還將提高代碼的可讀性。局部變量通常在聲明期間或之後立即初始化。該類的成員字段應集體聲明(有關班級結構的詳細信息,請參見規則3.1.2)。
除非列舉條件變量或密封類型,否則when語句必須具有“其他”分支。每個when都應包含else語句組,即使它不包含任何代碼。
異常:如果enum or sealed類型的“'何時”表示所有枚舉值,則無需擁有“ else”分支。編譯器丟失時可以發出警告。
每個應用於類,方法或構造函數的註釋都應放在其自己的線上。考慮以下示例:
有效示例:
@MustBeDocumented
@CustomAnnotation
fun getNameIfPresent () { /* ... */ }有效示例:
@CustomAnnotation class Foo {}有效示例:
@MustBeDocumented @CustomAnnotation val loader : DataLoader塊註釋應與周圍代碼相同的縮進級別。請參見下面的示例。
有效示例:
class SomeClass {
/*
* This is
* okay
*/
fun foo () {}
}注意:使用/*...*/ block註釋以通過IDE啟用自動格式。
本節包含有關修飾符和恆定值的建議。
如果聲明具有多個修飾符,請始終遵循正確的順序。有效序列:
public / internal / protected / private
expect / actual
final / open / abstract / sealed / const
external
override
lateinit
tailrec
crossinline
vararg
suspend
inner
out
enum / annotation
companion
inline / noinline
reified
infix
operator
data下劃線的字符應分離長數值。注意:使用下劃線可以簡化閱讀,並有助於在數字常數中找到錯誤。
val oneMillion = 1_000_000
val creditCardNumber = 1234_5678_9012_3456L
val socialSecurityNumber = 999_99_9999L
val hexBytes = 0xFF_EC_DE_5E
val bytes = 0b11010010_01101001_10010100_10010010 更喜歡用清晰的名稱定義常數,描述魔術數的含義。有效示例:
class Person () {
fun isAdult ( age : Int ): Boolean = age >= majority
companion object {
private const val majority = 18
}
}無效示例:
class Person () {
fun isAdult ( age : Int ): Boolean = age >= 18
}本節介紹了使用字符串的一般規則。
如果字符串可以放在一條線上,則禁止字符串串聯。改用原始字符串和字符串模板。 Kotlin顯著改善了字符串的使用:弦模板,原始字符串。因此,與使用顯式串聯相比,當適當的kotlin字符串用於短行時,代碼看起來更好,並且您無需用newline字符將它們拆分。
無效示例:
val myStr = " Super string "
val value = myStr + " concatenated "有效示例:
val myStr = " Super string "
val value = " $myStr concatenated " 字符串模板中的冗餘捲髮括號
如果字符串模板中只有一個變量,則無需使用這樣的模板。直接使用此變量。無效示例:
val someString = " ${myArgument} ${myArgument.foo()} "有效示例:
val someString = " $myArgument ${myArgument.foo()} "冗餘字符串模板
如果字符串模板僅包含一個變量 - 無需使用字符串模板。直接使用此變量。
無效示例:
val someString = " $myArgument "有效示例:
val someString = myArgument本節介紹了與條件語句有關的一般規則。
如果可能的話,應通過將其條件與Infix Operator &&串聯到單個嵌套中。
這通過減少嵌套語言構造的數量來提高可讀性。
無效示例:
if (cond1) {
if (cond2) {
doSomething()
}
}有效示例:
if (cond1 && cond2) {
doSomething()
}無效示例:
if (cond1) {
if (cond2 || cond3) {
doSomething()
}
}有效示例:
if (cond1 && (cond2 || cond3)) {
doSomething()
}如果可能的話,應根據布爾代數規則簡化太複雜的條件。簡化表達式時,請考慮以下規則:
foo() || false > foo() )!(!a) - > a )a && b && a > a && b )a || (a && b) - > a ),請從析取刪除表達式。a && (a || b) - > a )!(a || b) - > !a && !b )有效的示例
if (condition1 && condition2) {
foo()
}無效示例
if (condition1 && condition2 && condition1) {
foo()
}本節專用於使用代碼中的變量和類型的規則和建議。
使用變量的規則在以下主題中說明。
浮點數在廣泛的值中提供了良好的近似值,但是在某些情況下它們無法產生準確的結果。二進制浮點數不適合精確計算,因為在具有有限長度的binary representation中不可能表示0.1或任何其他10的負功率。
以下代碼示例似乎很明顯:
val myValue = 2.0 - 1.1
println (myValue)但是,它將打印以下值: 0.8999999999999999
因此,對於精確的計算(例如,在金融或精確的科學中),建議使用諸如Int , Long , BigDecimal類的類型。 BigDecimal類型應該是一個不錯的選擇。
無效的示例:將圍繞包含六個或七個小數數以上的浮點數。
val eFloat = 2.7182818284f // Float, will be rounded to 2.7182817有效示例:(需要精確的計算時):
val income = BigDecimal ( " 2.0 " )
val expense = BigDecimal ( " 1.1 " )
println (income.subtract(expense)) // you will obtain 0.9 here 數字浮點類型值不應直接與等位運算符(==)或其他方法(例如compareTo()和equals()進行比較。由於浮點數涉及計算機表示中的精確問題,因此最好按照規則4.1.1中建議使用BigDecimal來進行準確的計算和比較。以下代碼描述了這些問題。
無效示例:
val f1 = 1.0f - 0.9f
val f2 = 0.9f - 0.8f
if (f1 == f2) {
println ( " Expected to enter here " )
} else {
println ( " But this block will be reached " )
}
val flt1 = f1;
val flt2 = f2;
if (flt1.equals(flt2)) {
println ( " Expected to enter here " )
} else {
println ( " But this block will be reached " )
}有效示例:
val foo = 1.03f
val bar = 0.42f
if (abs(foo - bar) > 1e - 6f ) {
println ( " Ok " )
} else {
println ( " Not " )
}帶val修飾符的變量是不變的(僅讀取)。使用val變量而不是var變量,可以提高代碼的魯棒性和可讀性。這是因為在業務邏輯中可以重新分配var變量。但是,在某些情況下,有循環或累加器,僅允許使用var 。
本節提供了使用類型的建議。
Kotlin編譯器推出了智能演員,以幫助減少代碼的大小。
無效示例:
if (x is String ) {
print ((x as String ).length) // x was already automatically cast to String - no need to use 'as' keyword here
}有效示例:
if (x is String ) {
print (x.length) // x was already automatically cast to String - no need to use 'as' keyword here
}此外,Kotlin 1.3引入了合同,為智能鑄造提供了增強的邏輯。 Contracts are used and are very stable in stdlib , for example:
fun bar ( x : String? ) {
if ( ! x.isNullOrEmpty()) {
println ( " length of ' $x ' is ${x.length} " ) // smartcasted to not-null
}
}Smart cast and contracts are a better choice because they reduce boilerplate code and features forced type conversion.
無效示例:
fun String?. isNotNull (): Boolean = this != null
fun foo ( s : String? ) {
if (s.isNotNull()) s !! .length // No smartcast here and !! operator is used
}有效示例:
fun foo ( s : String? ) {
if (s.isNotNull()) s.length // We have used a method with contract from stdlib that helped compiler to execute smart cast
} Type aliases provide alternative names for existing types. If the type name is too long, you can replace it with a shorter name, which helps to shorten long generic types. For example, code looks much more readable if you introduce a typealias instead of a long chain of nested generic types. We recommend using a typealias if the type contains more than two nested generic types and is longer than 25 chars .
無效示例:
val b : MutableMap < String , MutableList < String >>有效示例:
typealias FileTable = MutableMap < String , MutableList < String >>
val b : FileTableYou can also provide additional aliases for function (lambda-like) types:
typealias MyHandler = ( Int , String , Any ) -> Unit
typealias Predicate < T > = ( T ) -> BooleanKotlin is declared as a null-safe programming language. However, to achieve compatibility with Java, it still supports nullable types.
To avoid NullPointerException and help the compiler prevent Null Pointer Exceptions, avoid using nullable types (with ? symbol).
無效示例:
val a : Int? = 0有效示例:
val a : Int = 0 Nevertheless, when using Java libraries extensively, you have to use nullable types and enrich the code with !!和? symbols. Avoid using nullable types for Kotlin stdlib (declared in official documentation). Try to use initializers for empty collections. For example, if you want to initialize a list instead of null , use emptyList() .
無效示例:
val a : List < Int > ? = null有效示例:
val a : List < Int > = emptyList()Like in Java, classes in Kotlin may have type parameters. To create an instance of such a class, we typically need to provide type arguments:
val myVariable : Map < Int , String > = emptyMap< Int , String >()However, the compiler can inherit type parameters from the r-value (value assigned to a variable). Therefore, it will not force users to declare the type explicitly. These declarations are not recommended because programmers would need to find the return value and understand the variable type by looking at the method.
無效示例:
val myVariable = emptyMap< Int , String >()有效示例:
val myVariable : Map < Int , String > = emptyMap()Try to avoid explicit null checks (explicit comparison with null ) Kotlin is declared as Null-safe language. However, Kotlin architects wanted Kotlin to be fully compatible with Java; that's why the null keyword was also introduced in Kotlin.
There are several code-structures that can be used in Kotlin to avoid null-checks. For example: ?: , .let {} , .also {} , etc
Invalid example:
// example 1
var myVar : Int? = null
if (myVar == null ) {
println ( " null " )
return
}
// example 2
if (myVar != null ) {
println ( " not null " )
return
}
// example 3
val anotherVal = if (myVar != null ) {
println ( " not null " )
1
} else {
2
}
// example 4
if (myVar == null ) {
println ( " null " )
} else {
println ( " not null " )
}Valid example:
// example 1
var myVar : Int? = null
myVar ? : run {
println ( " null " )
return
}
// example 2
myVar?. let {
println ( " not null " )
return
}
// example 3
val anotherVal = myVar?. also {
println ( " not null " )
1
} ? : 2
// example 4
myVar?. let {
println ( " not null " )
} ? : run { println ( " null " ) }例外:
In the case of complex expressions, such as multiple else-if structures or long conditional statements, there is common sense to use explicit comparison with null .
有效示例:
if (myVar != null ) {
println ( " not null " )
} else if (anotherCondition) {
println ( " Other condition " )
} if (myVar == null || otherValue == 5 && isValid) {} Please also note, that instead of using require(a != null) with a not null check - you should use a special Kotlin function called requireNotNull(a) .
This section describes the rules of using functions in your code.
Developers can write clean code by gaining knowledge of how to build design patterns and avoid code smells. You should utilize this approach, along with functional style, when writing Kotlin code. The concepts behind functional style are as follows: Functions are the smallest unit of combinable and reusable code. They should have clean logic, high cohesion , and low coupling to organize the code effectively. The code in functions should be simple and not conceal the author's original intentions.
Additionally, it should have a clean abstraction, and control statements should be used straightforwardly. The side effects (code that does not affect a function's return value but affects global/object instance variables) should not be used for state changes of an object. The only exceptions to this are state machines.
Kotlin is designed to support and encourage functional programming, featuring the corresponding built-in mechanisms. Also, it supports standard collections and sequences feature methods that enable functional programming (for example, apply , with , let , and run ), Kotlin Higher-Order functions, function types, lambdas, and default function arguments. As previously discussed, Kotlin supports and encourages the use of immutable types, which in turn motivates programmers to write pure functions that avoid side effects and have a corresponding output for specific input. The pipeline data flow for the pure function comprises a functional paradigm. It is easy to implement concurrent programming when you have chains of function calls, where each step features the following characteristics:
There can be only one side effect in this data stream, which can be placed only at the end of the execution queue.
The function should be displayable on one screen and only implement one certain logic. If a function is too long, it often means complex and could be split or simplified. Functions should consist of 30 lines (non-empty and non-comment) in total.
Exception: Some functions that implement complex algorithms may exceed 30 lines due to aggregation and comprehensiveness. Linter warnings for such functions can be suppressed .
Even if a long function works well, new problems or bugs may appear due to the function's complex logic once it is modified by someone else. Therefore, it is recommended to split such functions into several separate and shorter functions that are easier to manage. This approach will enable other programmers to read and modify the code properly.
The nesting depth of a function's code block is the depth of mutual inclusion between the code control blocks in the function (for example: if, for, while, and when). Each nesting level will increase the amount of effort needed to read the code because you need to remember the current "stack" (for example, entering conditional statements and loops). Exception: The nesting levels of the lambda expressions, local classes, and anonymous classes in functions are calculated based on the innermost function. The nesting levels of enclosing methods are not accumulated. Functional decomposition should be implemented to avoid confusion for the developer who reads the code. This will help the reader switch between contexts.
Nested functions create a more complex function context, thereby confusing readers. With nested functions, the visibility context may not be evident to the code reader.
無效示例:
fun foo () {
fun nested (): String {
return " String from nested function "
}
println ( " Nested Output: ${nested()} " )
}Don't use negated function calls if it can be replaced with negated version of this function
無效示例:
fun foo () {
val list = listOf ( 1 , 2 , 3 )
if ( ! list.isEmpty()) {
// Some cool logic
}
}有效示例:
fun foo () {
val list = listOf ( 1 , 2 , 3 )
if (list.isNotEmpty()) {
// Some cool logic
}
}The rules for using function arguments are described in the below topics.
With such notation, it is easier to use curly brackets, leading to better code readability.
有效示例:
// declaration
fun myFoo ( someArg : Int , myLambda : () -> Unit ) {
// ...
}
// usage
myFoo( 1 ) {
println ( " hey " )
}A long argument list is a code smell that leads to less reliable code. It is recommended to reduce the number of parameters. Having more than five parameters leads to difficulties in maintenance and conflicts merging. If parameter groups appear in different functions multiple times, these parameters are closely related and can be encapsulated into a single Data Class. It is recommended that you use Data Classes and Maps to unify these function arguments.
In Java, default values for function arguments are prohibited. That is why the function should be overloaded when you need to create a function with fewer arguments. In Kotlin, you can use default arguments instead.
無效示例:
private fun foo ( arg : Int ) {
// ...
}
private fun foo () {
// ...
}有效示例:
private fun foo ( arg : Int = 0) {
// ...
}Try to avoid using runBlocking in asynchronous code
無效示例:
GlobalScope .async {
runBlocking {
count ++
}
}The lambda without parameters shouldn't be too long. If a lambda is too long, it can confuse the user. Lambda without parameters should consist of 10 lines (non-empty and non-comment) in total.
Expressions with unnecessary, custom labels generally increase complexity and worsen the maintainability of the code.
無效示例:
run lab@ {
list.forEach {
return @lab
}
}有效示例:
list.forEachIndexed { index, i ->
return @forEachIndexed
}
lab@ for (i : Int in q) {
for (j : Int in q) {
println (i)
break @lab
}
}This section describes the rules of denoting classes in your code.
When a class has a single constructor, it should be defined as a primary constructor in the declaration of the class. If the class contains only one explicit constructor, it should be converted to a primary constructor.
無效示例:
class Test {
var a : Int
constructor (a : Int ) {
this .a = a
}
}有效示例:
class Test ( var a : Int ) {
// ...
}
// in case of any annotations or modifiers used on a constructor:
class Test private constructor( var a : Int ) {
// ...
}Some people say that the data class is a code smell. However, if you need to use it (which makes your code more simple), you can utilize the Kotlin data class . The main purpose of this class is to hold data, but also data class will automatically generate several useful methods:
Therefore, instead of using normal classes:
class Test {
var a : Int = 0
get() = field
set(value : Int ) { field = value}
}
class Test {
var a : Int = 0
var b : Int = 0
constructor (a : Int , b : Int ) {
this .a = a
this .b = b
}
}
// or
class Test ( var a : Int = 0 , var b : Int = 0 )
// or
class Test () {
var a : Int = 0
var b : Int = 0
}prefer data classes:
data class Test1 ( var a : Int = 0 , var b : Int = 0 )Exception 1 : Note that data classes cannot be abstract, open, sealed, or inner; that is why these types of classes cannot be changed to a data class.
Exception 2 : No need to convert a class to a data class if this class extends some other class or implements an interface.
The primary constructor is a part of the class header; it is placed after the class name and type parameters (optional) but can be omitted if it is not used.
無效示例:
// simple case that does not need a primary constructor
class Test () {
var a : Int = 0
var b : Int = 0
}
// empty primary constructor is not needed here
// it can be replaced with a primary contructor with one argument or removed
class Test () {
var a = " Property "
init {
println ( " some init " )
}
constructor (a : String ) : this () {
this .a = a
}
}有效示例:
// the good example here is a data class; this example also shows that you should get rid of braces for the primary constructor
class Test {
var a : Int = 0
var b : Int = 0
}Several init blocks are redundant and generally should not be used in your class. The primary constructor cannot contain any code. That is why Kotlin has introduced init blocks. These blocks store the code to be run during the class initialization. Kotlin allows writing multiple initialization blocks executed in the same order as they appear in the class body. Even when you follow (rule 3.2)[#r3.2], this makes your code less readable as the programmer needs to keep in mind all init blocks and trace the execution of the code. Therefore, you should try to use a single init block to reduce the code's complexity. If you need to do some logging or make some calculations before the class property assignment, you can use powerful functional programming. This will reduce the possibility of the error if your init blocks' order is accidentally changed and make the code logic more coupled. It is always enough to use one init block to implement your idea in Kotlin.
無效示例:
class YourClass ( var name : String ) {
init {
println ( " First initializer block that prints ${name} " )
}
val property = " Property: ${name.length} " . also (::println)
init {
println ( " Second initializer block that prints ${name.length} " )
}
}有效示例:
class YourClass ( var name : String ) {
init {
println ( " First initializer block that prints ${name} " )
}
val property = " Property: ${name.length} " . also { prop ->
println (prop)
println ( " Second initializer block that prints ${name.length} " )
}
} The init block was not added to Kotlin to help you initialize your properties; it is needed for more complex tasks. Therefore if the init block contains only assignments of variables - move it directly to properties to be correctly initialized near the declaration. In some cases, this rule can be in clash with 6.1.1, but that should not stop you.
無效示例:
class A ( baseUrl : String ) {
private val customUrl : String
init {
customUrl = " $baseUrl /myUrl "
}
}有效示例:
class A ( baseUrl : String ) {
private val customUrl = " $baseUrl /myUrl "
}The explicit supertype qualification should not be used if there is no clash between called methods. This rule is applicable to both interfaces and classes.
無效示例:
open class Rectangle {
open fun draw () { /* ... */ }
}
class Square () : Rectangle() {
override fun draw () {
super < Rectangle >.draw() // no need in super<Rectangle> here
}
}Abstract classes are used to force a developer to implement some of its parts in their inheritors. When the abstract class has no abstract methods, it was set abstract incorrectly and can be converted to a regular class.
無效示例:
abstract class NotAbstract {
fun foo () {}
fun test () {}
}有效示例:
abstract class NotAbstract {
abstract fun foo ()
fun test () {}
}
// OR
class NotAbstract {
fun foo () {}
fun test () {}
}Kotlin has a mechanism of backing properties. In some cases, implicit backing is not enough and it should be done explicitly:
private var _table : Map < String , Int > ? = null
val table : Map < String , Int >
get() {
if ( _table == null ) {
_table = HashMap () // Type parameters are inferred
}
return _table ? : throw AssertionError ( " Set to null by another thread " )
} In this case, the name of the backing property ( _table ) should be the same as the name of the real property ( table ) but should have an underscore ( _ ) prefix. It is one of the exceptions from the identifier names rule
Kotlin has a perfect mechanism of properties. Kotlin compiler automatically generates get and set methods for properties and can override them.
Invalid example:
class A {
var size : Int = 0
set(value) {
println ( " Side effect " )
field = value
}
// user of this class does not expect calling A.size receive size * 2
get() = field * 2
} From the callee code, these methods look like access to this property: A().isEmpty = true for setter and A().isEmpty for getter.
However, when get and set are overridden, it isn't very clear for a developer who uses this particular class. The developer expects to get the property value but receives some unknown value and some extra side-effect hidden by the custom getter/setter. Use extra functions instead to avoid confusion.
有效示例:
class A {
var size : Int = 0
fun initSize ( value : Int ) {
// some custom logic
}
// this will not confuse developer and he will get exactly what he expects
fun goodNameThatDescribesThisGetter () = this .size * 2
} Exception: Private setters are only exceptions that are not prohibited by this rule.
If you ignored recommendation 6.1.8, be careful with using the name of the property in your custom getter/setter as it can accidentally cause a recursive call and a StackOverflow Error . Use the field keyword instead.
Invalid example (very bad) :
var isEmpty : Boolean
set(value) {
println ( " Side effect " )
isEmpty = value
}
get() = isEmptyIn Java, trivial getters - are the getters that are just returning the field value. Trivial setters - are merely setting the field with a value without any transformation. However, in Kotlin, trivial getters/setters are generated by default. There is no need to use it explicitly for all types of data structures in Kotlin.
無效示例:
class A {
var a : Int = 0
get() = field
set(value : Int ) { field = value }
//
}有效示例:
class A {
var a : Int = 0
get() = field
set(value : Int ) { field = value }
//
}In Java, before functional programming became popular, many classes from common libraries used the configuration paradigm. To use these classes, you had to create an object with the constructor with 0-2 arguments and set the fields needed to run the object. In Kotlin, to reduce the number of dummy code line and to group objects apply extension was added:
無效示例:
class HttpClient ( var name : String ) {
var url : String = " "
var port : String = " "
var timeout = 0
fun doRequest () {}
}
fun main () {
val httpClient = HttpClient ( " myConnection " )
httpClient.url = " http://example.com "
httpClient.port = " 8080 "
httpClient.timeout = 100
httpCLient.doRequest()
}
有效示例:
class HttpClient ( var name : String ) {
var url : String = " "
var port : String = " "
var timeout = 0
fun doRequest () {}
}
fun main () {
val httpClient = HttpClient ( " myConnection " )
. apply {
url = " http://example.com "
port = " 8080 "
timeout = 100
}
httpClient.doRequest()
}If a class has only one immutable property, then it can be converted to the inline class.
Sometimes it is necessary for business logic to create a wrapper around some type. However, it introduces runtime overhead due to additional heap allocations. Moreover, if the wrapped type is primitive, the performance hit is terrible, because primitive types are usually heavily optimized by the runtime, while their wrappers don't get any special treatment.
無效示例:
class Password {
val value : String
}有效示例:
inline class Password ( val value : String )This section describes the rules of using extension functions in your code.
Extension functions is a killer-feature in Kotlin. It gives you a chance to extend classes that were already implemented in external libraries and helps you to make classes less heavy. Extension functions are resolved statically.
It is recommended that for classes, the non-tightly coupled functions, which are rarely used in the class, should be implemented as extension functions where possible. They should be implemented in the same class/file where they are used. This is a non-deterministic rule, so the code cannot be checked or fixed automatically by a static analyzer.
You should avoid declaring extension functions with the same name and signature if their receivers are base and inheritor classes (possible_bug), as extension functions are resolved statically. There could be a situation when a developer implements two extension functions: one is for the base class and another for the inheritor. This can lead to an issue when an incorrect method is used.
無效示例:
open class A
class B : A ()
// two extension functions with the same signature
fun A. foo () = " A "
fun B. foo () = " B "
fun printClassName ( s : A ) { println (s.foo()) }
// this call will run foo() method from the base class A, but
// programmer can expect to run foo() from the class inheritor B
fun main () { printClassName( B ()) }You should not use extension functions for the class in the same file, where it is defined.
無效示例:
class SomeClass {
}
fun SomeClass. deleteAllSpaces () {
}You should not use property length with operation - 1, you can change this to lastIndex
無效示例:
val A = " name "
val B = A .length - 1
val C = A [ A .length - 1 ]有效示例:
val A = " name "
val B = A .lastIndex
val C = A [ A .lastIndex]An Interface in Kotlin can contain declarations of abstract methods, as well as method implementations. What makes them different from abstract classes is that interfaces cannot store state. They can have properties, but these need to be abstract or to provide accessor implementations.
Kotlin's interfaces can define attributes and functions. In Kotlin and Java, the interface is the main presentation means of application programming interface (API) design and should take precedence over the use of (abstract) classes.
This section describes the rules of using objects in code.
Avoid using utility classes/objects; use extensions instead. As described in 6.2 Extension functions, using extension functions is a powerful method. This enables you to avoid unnecessary complexity and class/object wrapping and use top-level functions instead.
無效示例:
object StringUtil {
fun stringInfo ( myString : String ): Int {
return myString.count{ " something " .contains(it) }
}
}
StringUtil .stringInfo( " myStr " )有效示例:
fun String. stringInfo (): Int {
return this .count{ " something " .contains(it) }
}
" myStr " .stringInfo()Kotlin's objects are extremely useful when you need to implement some interface from an external library that does not have any state. There is no need to use classes for such structures.
有效示例:
interface I {
fun foo()
}
object O: I {
override fun foo() {}
}
This section describes general rules for .kts files
It is still recommended wrapping logic inside functions and avoid using top-level statements for function calls or wrapping blocks of code in top-level scope functions like run .
有效示例:
run {
// some code
}
fun foo() {
}