こんにちは、Habr! JetBrainsのKotlin Coding Conventionsドキュメンテーションページの著者の翻訳に注目してください。
内容:
Intellij Ideaのスタイルガイドの使用
現在のマニュアルに従ってIntellij Ideaでフォーマットを適用するには、Kotlinプラグインバージョン1.2.20以降をインストールする必要があります。[設定] | [ エディター| コードスタイル| Kotlinの右上隅にある[…から設定]リンクをクリックし、ドロップダウンメニューから[定義済みスタイル] / Kotlinスタイルガイドを選択します。
コードが推奨スタイルに従ってフォーマットされていることを確認するには、検査設定に移動して、「Kotlin |スタイルの問題|ファイルがプロジェクト設定に従ってフォーマットされていません」チェックを有効にします。 命名規則などの他の検証ルールは、デフォルトで有効になっています。
プロジェクト構造
フォルダー構造
異なる言語を使用するプロジェクトでは、Kotlinコードを含むファイルは他の言語のコードと同じフォルダーにあり、メイン言語で受け入れられている同じファイル構造を使用する必要があります。 たとえば、Javaの場合、ファイルはパッケージ名に応じたフォルダー構造にある必要があります。
Kotlinのみを使用するプロジェクトでは、推奨されるフォルダー構造は次のとおりです。フォルダーを使用して、ルートディレクトリをスキップしてパッケージを整理します。 プロジェクト内のすべてのコードがパッケージ「org.example.kotlin」およびそのパッケージにある場合、パッケージ「org.example.kotlin」に属するソースファイルはプロジェクトのルートディレクトリにあり、パッケージのソースコードが含まれるファイルは「org」である必要があります。 example.kotlin.foo.bar "サブディレクトリにあるべきです" foo / bar "プロジェクトのルートに対して相対的です。
ソースファイルの名前
Kotlinファイルに含まれるクラスが1つだけの場合(トップレベルの宣言に関連している可能性があります)、拡張子.kt
クラスと同様に名前を付ける必要があります。 ファイルに複数のクラスが含まれている場合、またはトップレベルの宣言のみが含まれている場合は、ファイルの内容を説明する名前を選択し、それに応じてファイルに名前を付けます。 ファイルに名前を付けるには、大文字のキャメルハンプを使用します(例: ProcessDeclarations.kt
)。
ファイル名には、ファイル内のコードが実行する内容を記述する必要があります。 つまり、ファイルの命名に「Util」のような無意味な単語を使用しないでください。
ソースファイルの整理
複数の宣言(クラス、関数、または最上位のプロパティ)を同じKotlinソースファイルに配置することは、これらの宣言が意味的に相互に密接に関連しており、ファイルサイズが合理的(数百行以内)である場合に歓迎されます。
特に、このクラスの適用のすべての側面に適用されるクラスの拡張関数を定義する場合、クラス自体が定義されているのと同じファイルにそれらを配置します。 このクラスを使用する特定のコンテキストでのみ意味のある拡張機能を定義する場合は、この拡張機能を使用するコードの隣に配置します。 「すべてのFoo拡張機能」を保存するためだけにファイルを作成しないでください。
クラス構造
通常、クラスのコンテンツは次の順序でソートされます。
- プロパティ宣言と初期化子ブロック
- 二次コンストラクタ
- メソッド宣言
- コンパニオンオブジェクト
メソッド宣言をアルファベット順または視覚的に並べ替えたり、通常のメソッドを拡張メソッドから分離したりしないでください。 代わりに、論理的に接続されたコードをまとめて、クラスを上から下に読む人が何が起こるかのロジックに従うことができるようにします。 並べ替え順序を1つ選択し(高レベルのコードを最初に[高レベルのものを最初に]、詳細を後で、またはその逆に)、それに従ってください。
これらのクラスを使用するコードの横にネストされたクラスを配置します。 クラスが外部での使用を目的としており、クラス内で参照されていない場合は、クラスをコンパニオンオブジェクトの後に配置します。
インターフェイス実装フレームワーク
インターフェイスを実装するとき、実装されるインターフェイスと同じ構造を維持します(必要に応じて、実装に使用される追加のプライベートメソッドと交互に切り替えます)
構造をオーバーライドします
再定義は常に次々にまとめられます。
命名規則
Kotlinは、Javaと同じ命名規則に従います。 特に:
パッケージ名は小文字であり、アンダースコア(org.example.myproject)は使用しません。 通常、複数の単語の名前を使用することはお勧めしませんが、複数の単語を使用する必要がある場合は、それらを単純に組み合わせるか、ラクダのこぶ(org.examle.myProject)を使用できます。
クラスとオブジェクトの名前は大文字で始まり、ラクダのこぶを使用します。
open class DeclarationProcessor { ... } object EmptyDeclarationProcessor : DeclarationProcessor() { ... }
機能名
関数、プロパティ、およびローカル変数の名前は小文字で始まり、アンダースコアは含まれません。
fun processDeclarations() { ... } var declarationCount = ...
例外:クラスのインスタンス化に使用されるファクトリー関数は、作成されるクラスと同じ名前を持つ場合があります。
abstract class Foo { ... } class FooImpl : Foo { ... } fun Foo(): Foo { return FooImpl(...) }
試験方法の名前
テスト(およびテストのみ)では、スペースを逆コンマで囲んだメソッド名を使用できます。 (このようなメソッド名は現在Androidランタイムでサポートされていないことに注意してください。)メソッド名にアンダースコアを使用することもテストコードで許可されています。
class MyTestCase { @Test fun `ensure everything works`() { ... } @Test fun ensureEverythingWorks_onAndroid() { ... } }
プロパティの命名
定数名( const
ラベルが付いたプロパティ、トップレベルプロパティ、または不変データを含むカスタムget
関数を持たないval
オブジェクト)は、アンダースコアで区切って大文字にする必要があります。
const val MAX_COUNT = 8 val USER_NAME_FIELD = "UserName"
動作または変更可能なデータを持つオブジェクトを含むトップレベルの名前またはオブジェクトプロパティは、ラクダハンプで共通名を使用する必要があります。
val mutableCollection: MutableSet<String> = HashSet()
シングルトンオブジェクトを参照するプロパティ名は、クラス宣言と同じ命名スタイルを使用できます。
val PersonComparator: Comparator<Person> = ...
列挙型では、使用状況に応じて、アンダースコアで区切られた大文字またはキャメルハンプスタイルで書かれた名前を使用できます。
enum class Color { RED, GREEN }
enum class Color { RedColor, GreenColor }
翻訳者注:異なるスタイルを混ぜないでください。 いずれかのスタイルを選択して、デザインでそれに固執します。
非表示プロパティの命名
クラスに概念的に同じ2つのプロパティがあり、一方がパブリックAPIの一部であり、もう一方が実装の一部である場合、アンダースコアを非表示プロパティの名前のプレフィックスとして使用します。
class C { private val _elementList = mutableListOf<Element>() val elementList: List<Element> get() = _elementList }
適切な名前の選択
クラス名は通常、クラスが何であるかを説明する名詞またはフレーズです。
List, PersonReader
メソッドの名前は通常、メソッドの機能を説明する動詞またはフレーズアクションです。
close, readPersons
また、名前は、メソッドがオブジェクトを変更するか、新しいオブジェクトを返すかを示す必要があります。 たとえば、 sort
はコレクションを変更するsort
あり、sortはコレクションの新しいソートされたコピーの戻り値です。
名前はエンティティの目的を明確に示す必要があります。したがって、名前に意味のない単語( Manager
、 Wrapper
など)を使用しないようにすることをお勧めします。
広告名の一部として頭字語を使用する場合、2文字( IOStream
)で構成される場合は大文字を使用します。 または、長い場合は最初の文字のみを大文字にします( XmlFormatter
、 HttpInputStream
)。
書式設定
ほとんどの場合、KotlinはJavaのフォーマット規則に従います。
インデントするには4つのスペースを使用します。 タブを使用しないでください。
ブレースの場合、構造を開始する行の末尾に開きブレースを配置し、開口構造と水平方向に整列した別の行に閉じブレースを配置します。
if (elements != null) { for (element in elements) { // ... } }
(注:Kotlinでは、セミコロンはオプションであるため、行の折り返しが重要です。言語設計にはJavaスタイルの波括弧が含まれます。別のフォーマットスタイルを使用しようとすると、予期しないコード実行動作が発生する場合があります。)
水平方向のスペース
二項演算子(a + b)
周りにスペースを置きます。 例外:演算子「範囲」 (0..i)
周りにスペースを入れないでください
単項演算子の周りにスペースを入れないでください(a++)
キー制御語( if
、 when
、 for
while
)と対応する開き括弧の間にスペースを入れます。
コンストラクター、メソッド、またはメソッドのプライマリ宣言で、開き括弧の前にスペースを入れないでください。
class A(val x: Int) fun foo(x: Int) { ... } fun bar() { foo(1) }
(
、 [
または前]
、 )
後にスペースを入れないでください。
ポイントの周りにスペースを置かないでください.
または演算子?.
:
foo.bar().filter { it > 2 }.joinToString() foo?.()
コメント//
二重スラッシュの後にスペースを入れます。
//
型パラメーターを示すために使用される山括弧の周りにスペースを入れないでください。
Class Map<K, V> { ... }
::
クラスメソッドへの参照を示すために、二重コロンの周りにスペースを入れないでください。
Foo::class String::length
前にスペースを入れないでください?
null
をマークするために使用:
String?
一般的に、あらゆる種類の水平方向の配置は避けてください。 識別子の名前を別の長さの名前に変更しても、コードのフォーマットには影響しません。
コロン
次の場合、コロンの前にスペースを入れます。
- タイプをスーパータイプから分離するために使用される場合;
abstract class Foo<out T : Any>
- スーパークラスコンストラクターまたは同じクラスの別のコンストラクターに委任する場合。
constructor(x: String) : super(x) { ... } constructor(x: String) : this(x) { ... }
- キーワードオブジェクトの後。
val x = object : IFoo { ... }
広告とそのタイプを区切るときに:
前にスペースを入れないでください。
abstract fun foo(a: Int): T
:
後には必ずスペースを入れて:
abstract class Foo<out T : Any> : IFoo { abstract fun foo(a: Int): T } class FooImpl : Foo() { constructor(x: String) : this(x) { ... } val x = object : IFoo { ... } }
クラス宣言のフォーマット
いくつかの基本的なコンストラクターパラメーターと短い名前を持つクラスは、1行で記述できます。
class Person(id: Int, name: String)
より長い名前またはパラメータの数を持つクラスは、コンストラクタの各メインパラメータがインデント付きの個別の行になるようにフォーマットする必要があります。 また、閉じ括弧は新しい行になければなりません。 継承を使用する場合、スーパークラスのコンストラクターまたは実装されたインターフェイスのリストの呼び出しは、ブラケットと同じ行に配置する必要があります。
class Person( id: Int, name: String, surname: String ) : Human(id, name) { ... }
インターフェイスを指定してスーパークラスのコンストラクターを呼び出す場合、最初にスーパークラスのコンストラクターを見つけてから、新しい行のインターフェイスの名前を左揃えにします。
class Person( id: Int, name: String, surname: String ) : Human(id, name), KotlinMaker { ... }
スーパータイプの長いリストを持つクラスの場合、コロンの後に改行を入れて、すべてのスーパータイプ名を水平に左に揃える必要があります。
class MyFavouriteVeryLongClassHolder : MyLongHolder<MyFavouriteVeryLongClass>(), SomeOtherInterface, AndAnotherOne { fun foo() { ... } }
クラスの見出しが長いときにクラスの見出しとその本文を明確に分離するには、クラスの見出しの後に空の行を配置するか(上記の例のように)、開始ブレースを別の行に配置します。
class MyFavouriteVeryLongClassHolder : MyLongHolder<MyFavouriteVeryLongClass>(), SomeOtherInterface, AndAnotherOne { fun foo() { ... } }
コンストラクターのパラメーターには、通常のインデント(4スペース)を使用します。
理由:これにより、メインコンストラクターで宣言されたプロパティが、クラス本体で宣言されたプロパティと同じインデントを持つようになります。
修飾子
広告に複数の修飾子が含まれる場合は、常に次の順序で配置します。
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
引数なしの注釈は1行に配置できます。
@JsonExclude @JvmField var x: String
引数なしの1つの注釈は、対応する宣言と同じ行に配置できます。
@Test fun foo() { ... }
ファイル注釈
ファイルへの注釈は、ファイルのコメント(存在する場合)の後、パッケージステートメントの前に配置され、空行でパッケージから分離されます(パッケージではなくファイルを対象にしているという事実を強調するため)。
/** License, copyright and whatever */ @file:JvmName("FooBar") package foo.bar
関数のフォーマット
メソッドシグネチャが1行に収まらない場合は、次の構文を使用します。
fun longMethodName( argument: ArgumentType = defaultValue, argument2: AnotherArgumentType ): ReturnType { // body }
関数パラメーターには通常のインデント(4つのスペース)を使用します。
根拠:コンストラクターパラメーターとの一貫性
1行で構成される関数には、中括弧なしの式を使用することをお勧めします。
fun foo(): Int { // bad return 1 } fun foo() = 1 // good
単一行式の書式設定
単一行関数の本体が宣言と同じ行に収まらない場合は、最初の行に=記号を入れます。 式の本文を4スペース分インデントします。
fun f(x: String) = x.length
プロパティのフォーマット
単純な読み取り専用プロパティの場合、単一行フォーマットを使用することをお勧めします。
val isEmpty: Boolean get() = size == 0
より複雑なプロパティの場合は、常に別の行でget
およびset
を使用しget
。
val foo: String get() { ... }
初期化のあるプロパティの場合、初期化子が長すぎる場合、等号の後に改行を追加し、初期化文字列に4つのスペースのインデントを追加します。
private val defaultCharset: Charset? = EncodingRegistry.getInstance().getDefaultCharsetForPropertiesFiles(file)
制御命令のフォーマット
if
またはwhen
制御ステートメントの条件が複数行の場合、ステートメントの本体の周りに中括弧を常に使用します。 条件の後続の各行を、文の先頭から4スペース分インデントします。 条件の閉じ括弧と開き中括弧を別の行に配置します。
if (!component.isSyncing && !hasAnyKotlinRuntimeInScope(module) ) { return createKotlinNotConfiguredPanel(module) }
根拠:条件本体と条件本体のきちんとした整列と明確な分離
else
、 catch
、 finally
キーワード、およびdo / whileループのwhile
キーワードを前の閉じ中括弧と同じ行に配置します。
if (condition) { // body } else { // else part } try { // body } finally { // cleanup }
命令のwhen
条件が複数のブロックで構成されている場合、それらを空行で互いに分離することをお勧めします。
private fun parsePropertyValue(propName: String, token: Token) { when (token) { is Token.ValueToken -> callback.visitValue(propName, token.value) Token.LBRACE -> { // ... } } }
when
ステートメントの短いブロックを中括弧なしで同じ行に配置します。
when (foo) { true -> bar() // good false -> { baz() } // bad }
メソッド呼び出しのフォーマット
パラメータの長いリストを使用する場合は、括弧の後に改行を入れてください。 4つのスペースをインデントし、1行に論理的に接続された引数をグループ化します。
drawSquare( x = 10, y = 10, width = 100, height = 100, fill = true )
パラメーター名とその値の間の等号の前後にスペースを使用します。
チェーン関数呼び出しのフォーマット
連鎖呼び出しを使用する場合は、を入れ.
または?.
4つのスペースに1つのインデントがある新しい行の演算子:
val anchor = owner ?.firstChild!! .siblings(forward = true) .dropWhile { it is PsiComment || it is PsiWhiteSpace }
チェーンの最初の呼び出しでは、通常、その前に改行が必要ですが、コードが適切に読み取られ、意味がある場合は、それを行わないのが普通です。
ラムダ式のフォーマット
ラムダ式では、中括弧とパラメーターを本文から分離する矢印の周りにスペースを使用する必要があります。 呼び出しが単一のラムダ文字を受け入れる場合、可能な限り括弧の外側で使用する必要があります。
list.filter { it > 10 }
ラベルをラムダ式に割り当てる場合、ラベルと左中括弧の間にスペースを入れないでください。
fun foo() { ints.forEach lit@{ // ... } }
複数行のラムダ式でパラメータ名を宣言する場合、名前を最初の行に、次に矢印に、新しい行に関数本体の先頭に配置します。
appendCommaSeparated(properties) { prop -> val propertyValue = prop.get(obj) // ... }
パラメータリストが1行に収まらない場合は、矢印を別の行に配置します。
foo { context: Context, environment: Env -> context.configureEnv(environment) }
書類
複数行のドキュメントを使用する場合は、 /**
を別の行に置き、後続の各行をアスタリスクで開始します。
/** * This is a documentation comment * on multiple lines. */
短いドキュメントを1行に配置できます。
/** This is a short documentation comment. */
一般に、 paramおよびreturnタグの使用は避けてください。 代わりに、パラメーターの説明と戻り値をドキュメントコメントに直接含め、記載されている場所にパラメーター参照を追加します。 paramを使用して、メインテキストの意味に合わない長い説明が必要な場合にのみ戻ります。
// Avoid doing this: /** * 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) = ... // Do this instead: /** * Returns the absolute value of the given [number]. */ fun abs(number: Int) = ...
不要な構造の回避
Kotlinの多くの構文構造はオプションであり、開発環境では不要であると強調されています;コードを「明確」にするためだけにコードで使用しないでください。
単位キーワードの使用
関数では、Unitキーワードの使用は使用しないでください。
fun foo() { // ": Unit" is omitted here }
セミコロン
あらゆる機会にセミコロンを使用しないでください。
文字列パターン
単純な変数をテンプレート文字列に貼り付けるときに、中括弧を使用しないでください。 長い表現に対してのみ中括弧を使用します。
println("$name has ${children.size} children")
言語機能の慣用的な使用
不変性
可変データの前に不変データを使用することをお勧めします。 ローカル変数とプロパティは、実際に変更されない限り、常にvar
ではなくval
として宣言してください。
変更しないコレクションを宣言するには、常に不変のコレクションインターフェイス( Collection
、 List
、 Set
、 Map
)を使用します。 可能な限り、コレクションの作成にファクトリメソッドを使用する場合は、不変のコレクションを返す実装を使用します。
// Bad: use of mutable collection type for value which will not be mutated fun validateValue(actualValue: String, allowedValues: HashSet<String>) { ... } // Good: immutable collection type used instead fun validateValue(actualValue: String, allowedValues: Set<String>) { ... } // Bad: arrayListOf() returns ArrayList<T>, which is a mutable collection type val allowedValues = arrayListOf("a", "b", "c") // Good: listOf() returns List<T> val allowedValues = listOf("a", "b", "c")
: .
.
// Bad fun foo() = foo("a") fun foo(a: String) { ... } // Good fun foo(a: String = "a") { ... }
[Type alias]
, , :
typealias MouseClickHandler = (Any, MouseEvent) -> Unit typealias PersonIndex = Map<String, Person>
-
-, , it
. - .
-
. - , . , - .
( @
) .
, , boolean
, .
drawSquare(x = 10, y = 10, width = 100, height = 100, fill = true)
try
, if
when
, return
:
return if (x) foo() else bar() // , if (x) return foo() else return bar() // return when(x) { 0 -> "zero" else -> "nonzero" } // , when(x) { 0 -> return "zero" else -> return "nonzero" }
if
when
if
when
when (x) { null -> ... else -> ... } if (x == null) ... else ... //
, when
.
Boolean?
Boolean?
, if (value == true)
if (value == false)
, if (value ?: false)
if (value != null && value)
.
filtet
, map
.. . : forEach
( for
null forEach
)
, , , .
until
( ):
for (i in 0..n - 1) { ... } // bad for (i in 0 until n) { ... } // good
.
\n
escape- .
, trimIndent
, , trimMargin
, :
assertEquals( """ Foo Bar """.trimIndent(), value ) val a = """if(a > 1) { | return a |}""".trimMargin()
. , , .
:
- ( )
- ,
. , , , , . API, , . , .
infix
, , . : and
, to
, zip
. : add
.
infix
, .
, , . , , . , , .
class Point(val x: Double, val y: Double) { companion object { fun fromPolar(angle: Double, radius: Double) = Point(...) } }
, , .
: , , Kotlinnull
,null
public
/, , Kotlin:
fun apiCall(): String = MyJavaApi.getProperty("name")
(package-level class-level) Kotlin:
class Person { val name: String = MyJavaApi.getProperty("name") }
, , Kotlin :
fun main() { val name = MyJavaApi.getProperty("name") println(name) }
apply
/ with
/ run
/ also
/ let
Kotlin . , :
- ? , ,
it
,this
(also
let
).also
, .
// Context object is 'it' class Baz { var currentBar: Bar? val observable: Observable val foo = createBar().also { currentBar = it // Accessing property of Baz observable.registerCallback(it) // Passing context object as argument } } // Receiver not used in the block val foo = createBar().also { LOG.info("Bar created") } // Context object is 'this' class Baz { val foo: Bar = createBar().apply { color = RED // Accessing only properties of Bar text = "Foo" } }
- ? ,
apply
also
. ,with
,let
run
.
// Return value is context object class Baz { val foo: Bar = createBar().apply { color = RED // Accessing only properties of Bar text = "Foo" } } // Return value is block result class Baz { val foo: Bar = createNetworkConnection().let { loadBar() } }
- null ? ,
apply
,let
run
. ,with
also
.
// Context object is nullable person.email?.let { sendEmail(it) } // Context object is non-null and accessible directly with(person) { println("First name: $firstName, last name: $lastName") }
API:
- ( API)
- ( )
- KDoc public api, , /