🐕

Kotlin公式コーディング規約/日本語メモ

2023/05/07に公開

Introduction

Kotlin公式コーディング規約の日本語訳.
直訳ではなく意訳です.
Kotlin初心者のためおかしなところもあるかもしれません.
随時アップデートしていきます.
https://kotlinlang.org/docs/coding-conventions.html#avoid-redundant-constructs

IDEでスタイルを設定する

IntelliJとAndroid Studioでは指定されたコーディングスタイルに合わせてコードを自動的にフォーマットするよう設定できる.

スタイルガイドを設定する

  1. Settings/Preferences|Editor|Inspections|General. に移動.
  2. Set from.... をクリック.
  3. Kotlin style guide を選択.

Verify that your code follows the style guide

コードスタイルに従っていることを確認する

  1. Settings/Preferences | Editor | Inspections | General に移動
  2. Incorrect formatting inspectionをオンにする. スタイルガイドに記載されている他の問題(命名規則など)を確認する追加の検証はデフォルトで有効になっている.

ソースコードの編成

ディレクトリ構造

純粋なKotlinプロジェクトでは共通のルートパッケージを省略したパッケージ構造に従うディレクトリ構造を推奨する.例えば,もしプルジェクト内のすべてのコードがパッケージorg.example.kotlinとそのサブパッケージ内にある場合,'org.example.kotlin'パッケージを含むファイルはソースルートの直下に配置し,org.example.kotlin.network.socket内のファイルはソースルートのサブディレクトリnetwork/socketに配置する.

ソースファイル名

パスカルケースを使用ProcessDeclarations.kt
ファイル名はクラスやインターフェイス名と同じにし拡張子.ktを追加する.
ファイルに複数のクラスが含まれている,またはトップレベルの宣言のみが含まれている場合はファイルの内容を説明する名前を付ける.
Utilのような意味のないネーミングは避ける.

ソースファイル編成

複数の宣言(クラス,トップレベルの関数,プロパティ)を同じKotlinファイルに入す置することは,これらの宣言の意味が密接に関連しており,ファイルサイズが妥当な範囲(数百行を超えない)限り推奨.クラスのクライアントに関連する拡張関数を定義するときは,そのクラスと同じファイルに記述する.特定のクライアントに対してのみ意味を持つ拡張関数を定義するときは,そのクライアントのコードの横に配置する.一部のクラスのすべての拡張関数を保持するためだけにファイルを作成することは避ける。

クラスレイアウト

クラスの内容は次の順序で行う.

  1. プロパティの宣言と初期化ブロック
  2. セカンダリコンストラクタ
  3. メソッド宣言
  4. コンパニオンオブジェクト

メソッドをアルファベット順や可視性によって並べたり,通常メソッドと拡張メソッドを分けない.
代わりに,クラスを上から下まで読んで何が起こっているかを終えるようにする.
ネストされたクラスは、それらのクラスを使用するコードのすぐ隣に配置する.クラス内で参照されずに外部から使用されることが意図されている場合は、コンパニオンオブジェクトの後に配置する.

インターフェイスの実装レイアウト

インターフェイスを実装するときは、実装メンバーをインターフェイスのメンバーと同じ順序に保つ (必要に応じて実装に使用される追加のプライベート メソッドを散在させる).

オーバーロードレイアウト

オーバーロードはクラス内で常に並べて配置する.

命名規則

パッケージ

小文字で_は使用しないorg.example.project
複数の単語は使用しないようにするべき.使用する場合は単語を連結するかキャメルケースorg.example.myProject

クラスとオブジェクト

パスカルケース

open class DeclarationProcessor { /*...*/ }

object EmptyDeclarationProcessor : DeclarationProcessor() { /*...*/ }

関数名

キャメルケース

fun processDeclarations() { /*...*/ }
var declarationCount = 1

例外: クラスのインスタンスを作成するために使用されるファクトリ関数は、抽象戻り型と同じ名前を持つことができる.

interface Foo { /*...*/ }

class FooImpl : Foo { /*...*/ }

fun Foo(): Foo { return FooImpl() }

テストメソッドの名前

バッククォートで囲まれたスペースを含むメソッド名を使用できる.
_も使用可能.

class MyTestCase {
     @Test fun `ensure everything works`() { /*...*/ }

     @Test fun ensureEverythingWorks_onAndroid() { /*...*/ }
}

プロパティ名

定数は大文字のスネークケース

const val MAX_COUNT = 8
val USER_NAME_FIELD = "UserName"

動作や可変データを保持するトップレベルまたはオブジェクトのプロパティ名はキャメルケース

val mutableCollection: MutableSet<String> = HashSet()

単一インスタンスオブジェクトを保持するトップレベルまたはオブジェクトプロパティの名前は、オブジェクト宣言と同じ命名スタイルを使用

val PersonComparator: Comparator<Person> = /*...*/

列挙型の定数については、使用に応じて大文字のアンダースコア区切りの名前(スクリーミングスネークケース)(enum class Color { RED、GREEN })またはアッパーキャメルケースの名前を使用しても問題ない。

バッキングプロパティの名前

1つがパブリックAPI の一部であり、もう1つが実装の詳細である2つのプロパティがある場合は、プライベートプロパティの名前のプレフィックスとしてアンダースコアを使用可能.

class C {
    private val _elementList = mutableListOf<Element>()

    val elementList: List<Element>
         get() = _elementList
}

良い名前を選ぶ

クラスの名前は通常名刺または名詞句.List, PersonReader

メソッドの名前は通常動詞または動詞句.close, readPersons
メソッドがオブジェクトを変更するか,新しいオブジェクトを返すかを示唆する必要がある.
例:sortはその場でソート,sortedはソートされたコピーを返す

意味のない単語(Manager, Wrapper)を使用しない.

宣言の一部として頭字語を使用する場合,頭字語が2文字の場合は大文字IOStream,最初の文字が長い場合は最初の文字のみを大文字にする(XmlFormatter, HttpInputStream)

書式設定

インデント

4つのスペースを使用しタブは使用しない.

中括弧は構造が始まる行の末尾と,別の行で構造の先頭と同じラインに配置する.

if (elements != null) {
    for (element in elements) {
        // ...
    }
}

Kotlinではセミコロンはオプションであるため,改行が重要.

水平方向のスペース

  • 二項演算子の前後にスペース(a + b)例外:"range to"演算子にはスペースを入れない(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?.bar()
  • '//'の後にスペースを入れる.// This is a comment
  • 型パラメーターの指定に使用される山括弧の周りにスペースを入れない.class Map<K, V> { ... }
  • ::の前後にスペースを入れない.Foo::class, String::length
  • null許容のための?の前にスペースを入れないString?

原則として水平方向の配置は避ける.

コロン

次のケースでは:の前にスペースを入れる

  • タイプとスーパータイプを分離するために使用される場合
  • スーパークラス コンストラクターまたは同じクラスの別のコンストラクターに委譲する場合
  • objectキーワードの後
    宣言とその型を区切るときは:の前にスペースを入れない.
    常に:の後にはスペースを入れる.
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 / fun // as a modifier in `fun interface`
companion
inline / value
infix
operator
data

修飾子の前にすべてのアノテーションを配置

@Named("Foo")
private val foo: Foo

ライブラリで作業している場合を除き、冗長な修飾子 (たとえば、public) を省略する.

アノテーション

宣言の前の別々の行に配置し,同じインデントを付ける.

@Target(AnnotationTarget.PROPERTY)
annotation class JsonExclude

引数の無いアノテーションは小名木上に置くことが可能.

@JsonExclude @JvmField
var x: String

引数の無い単一のアノテーションは対応する宣言と同じ行に配置

@Test fun foo() { /*...*/ }

ファイルアノテーション

ファイルコメントの後,packageステートメントの前に配置し空白行で区切る.

/** License, copyright and whatever */
@file:JvmName("FooBar")

package foo.bar

関数

関数シグネチャが 1 行に収まらない場合は、次の構文を使用する.

fun longMethodName(
    argument: ArgumentType = defaultValue,
    argument2: AnotherArgumentType,
): ReturnType {
    // body
}

本体が単一の式で構成される関数には、式本体を使用することを推奨.

fun foo(): Int {     // bad
    return 1
}

fun foo() = 1        // good

表現体

最初の行が宣言と同じ行に収まらない場合,最初の行に=を付け改行し4つのスペースでインデント.

fun f(x: String, y: String, z: String) =
    veryLongFunctionCallWithManyWords(andLongParametersToo(), x, y, z)

プロパティ

単純な読み取り専用プロパティは1行での記述を検討.

val isEmpty: Boolean get() = size == 0

複雑なプロパティの場合は常にgetsetを別の行に入れる.

val foo: String
    get() { /*...*/ }

初期化子を持つプロパティの場合,初期化子が長い場合は=の後に改行を追加し4つのスペースでインデント.

private val defaultCharset: Charset? =
    EncodingRegistry.getInstance().getDefaultCharsetForPropertiesFiles(file)

制御フローステートメント

if,whenの本体が複数行の場合は常に中括弧で囲む.

if (!component.isSyncing &&
    !hasAnyKotlinRuntimeInScope(module)
) {
    return createKotlinNotConfiguredPanel(module)
}

else, catch, finally ,do-whilewhileは中括弧と同じ行に置く.

if (condition) {
    // body
} else {
    // else part
}

try {
    // body
} finally {
    // cleanup
}

whenで分岐が1行を超える場合は,空白行で区切る.

private fun parsePropertyValue(propName: String, token: Token) {
    when (token) {
        is Token.ValueToken ->
            callback.visitValue(propName, token.value)

        Token.LBRACE -> { // ...
        }
    }
}

中括弧なしで、条件と同じ行に短い分岐を配置する.

when (foo) {
    true -> bar() // good
    false -> { baz() } // bad
}

メソッド呼び出し

長い引数リストは括弧の後に改行.引数を4つのスペースでインデント.関連の強い複数の引数は同じ行に配置.=の前後にスペース.

drawSquare(
    x = 10, y = 10,
    width = 100, height = 100,
    fill = true
)

連鎖呼び出しをラップ

連鎖呼び出しをラップするときは次の行に.または?.を配置.

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)
}

末尾のコンマ

末尾のコンマはオプションだが,使用することを推奨.

class Person(
    val firstName: String,
    val lastName: String,
    val age: Int, // trailing comma
)

ドキュメントのコメント

長いコメントは/**で始め改行の後後続を*で始める.

/**
 * This is a documentation comment
 * on multiple lines.
 */

短いコメントは 1 行で記述

/** This is a short documentation comment. */

@paramおよび@returnタグの使用は避ける.代わりに、パラメーターと戻り値の説明をドキュメントコメントに直接組み込み,言及されているパラメーターへのリンクを追加する.@paramand, @returnは本文の流れに収まらない長い説明が必要な場合にのみ使用する.

// 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): Int { /*...*/ }

// Do this instead:

/**
 * Returns the absolute value of the given [number].
 */
fun abs(number: Int): Int { /*...*/ }

冗長な構造を避ける

Unitリターン

関数が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") { /*...*/ }

型エイリアス

コードベースで複数回使用される関数型または型パラメーターを持つ型がある場合は型エイリアスを定義することを推奨.

typealias MouseClickHandler = (Any, MouseEvent) -> Unit
typealias PersonIndex = Map<String, Person>

名前の衝突を避けるためにプライベートまたは内部型エイリアスを使用する場合はパッケージとインポートで言及されるimport ... as ...に記載されているものを優先する.

ラムダパラメータ

短くネストされていないラムダではitパラメーターを明示的に宣言する代わりに、規則を使用することを推奨.パラメーターを持つネストされたラムダでは、常にパラメーターを明示的に宣言する.

ラムダのreturn

ラムダで複数のラベル付きreturnを使用することは避ける.
ラムダの最後のステートメントにラベル付きのreturnを使用しない.

名前付き引数

メソッドが同じプリミティブ型の複数のパラメーターを受け取る場合,またはBoolean型のパラメーターの場合は,すべてのパラメーターの意味がコンテキストから完全に明確でない限り名前付き引数構文を使用する.

drawSquare(x = 10, y = 10, width = 100, height = 100, fill = true)

条件文

try, if, whenの表現形式の使用を推奨.

// better
return if (x) foo() else bar()

// not better
if (x)
    return foo()
else
    return bar()
// better
return when(x) {
    0 -> "zero"
    else -> "nonzero"
}

// not better
when(x) {
    0 -> return "zero"
    else -> return "nonzero"
}

if vs when

バイナリ条件にはwhenよりもifの使用を推奨.

// better
if (x == null) ... else ...

// not better
when (x) {
    null -> // ...
    else -> // ...
}

択肢が3つ以上ある場合はwhenを推奨.

条件内のnull許容値

条件ステートメントでnull許容のBooleanを使用する際はif (value == true)またはif (value == false)のチェックを行う.

ループ

ループよりも高階関数(filter, map)の使用を推奨.
例外: forEachのレシーバーがnull許容であるかforEachがより長い呼び出しチェーンの一部として使用されている場合を除き,代わりに通常のforループを使用することを推奨する.

範囲のループ

untilを使用して開いている範囲をループする

for (i in 0..n - 1) { /*...*/ }  // bad
for (i in 0 until n) { /*...*/ }  // good

文字列

文字列連結よりも文字列テンプレートを優先する.
\nエスケープシーケンスを通常の文字列リテラルに埋め込むよりも複数行の文字列を優先する.
複数行の文字列でインデントを維持するには,結果の文字列が内部インデントを必要としない場合は trimIndentを使用し,内部インデントが必要な場合はtrimMarginを使用する.

println("""
    Not
    trimmed
    text
    """
       )

println("""
    Trimmed
    text
    """.trimIndent()
       )

println()

val a = """Trimmed to margin text:
          |if(a > 1) {
          |    return a
          |}""".trimMargin()

println(a)

関数とプロパティ

場合によっては引数のない関数が読み取り専用プロパティと交換可能になることがある.セマンティクスは似ているが,どちらを優先するかについていくつかの文体上の規則がある.
基礎となるアルゴリズムが次の場合,関数よりもプロパティを優先する.

  • throwしない
  • 計算が安価(または最初の実行時にキャッシュされる)
  • オブジェクトの状態が変更されていない場合,呼び出しに対して同じ結果を返す

拡張関数

拡張関数を自由に使用してよい.主にオブジェクトに対して機能する関数がある場合は常にそのオブジェクトをレシーバーとして受け入れる拡張関数にすることを検討する.APIの汚染を最小限に抑えるには拡張関数の可視性を理にかなった範囲に限定する。必要に応じてプライベートな可視性を持つローカル拡張関数,メンバー拡張関数,またはトップレベル拡張関数を使用する.

ファクトリ関数

クラスのファクトリ関数を宣言する場合はクラス自体と同じ名前を付けないようにする.
ファクトリ関数の動作が特別な理由を明確にするために個別の名前を使用することを推奨する.
特別なセマンティクスがない場合にのみクラスと同じ名前を使用できる.

class Point(val x: Double, val y: Double) {
    companion object {
        fun fromPolar(angle: Double, radius: Double) = Point(...)
    }
}

異なるスーパークラスコンストラクターを呼び出さず既定の引数値を持つ単一のコンストラクターに縮小できない複数のオーバーロードされたコンストラクターを持つオブジェクトがある場合は,オーバーロードされたコンストラクターをファクトリ関数に置き換えることを推奨する.

プラットフォームの種類

プラットフォーム型の式を返すパブリック関数/メソッドはその型を明示的に宣言する必要がある.

fun apiCall(): String = MyJavaApi.getProperty("name")

プラットフォーム型の式で初期化されたプロパティ(パッケージ レベルまたはクラスレベル)はその型を明示的に宣言する必要がある.

class Person {
    val name: String = MyJavaApi.getProperty("name")
}

プラットフォーム型の式で初期化されたローカル値には型宣言がある場合とない場合がある.

fun main() {
    val name = MyJavaApi.getProperty("name")
    println(name)
}

スコープ関数 apply/with/run/also/let

Kotlinは特定のオブジェクトのコンテキストでコードのブロックを実行する一連の関数を提供する: let, run, with, apply, also

ライブラリのコーディング規約

ライブラリを作成するときはAPI の安定性を確保するために追加の一連のルールに従うことを推奨する.

  • メンバーの可視性を常に明示的に指定する (誤って宣言をパブリック API として公開しないようにするため).
  • 関数の戻り値の型とプロパティの型を常に明示的に指定する(実装が変更されたときに戻り値の型を誤って変更しないようにするため).
  • 新しいドキュメントを必要としないオーバーライドを除いてすべてのパブリックメンバーにKDocコメントを提供する(ライブラリのドキュメントの生成をサポートするため).

Discussion