본문 바로가기

프로그래밍/Android

[Kotlin] Coding conventions

일반적으로 알려지고 따르기 쉬운 코딩 규칙은 모든 프로그래밍 언어에 필수이다. 여기에서는 Kotlin을 사용하는 프로젝트의 코드 스타일 및 코드 구성에 대한 지침을 제공한다.

IDE에서 스타일 설정

Kotlin용으로 가장 많이 사용되는 두 가지 IDE인 IntelliJ IDEA 및 Android Studio는 코드 스타일을 위한 강력한 지원을 제공한다. 주어진 코드 스타일에 따라 코드 형식을 자동으로 지정하도록 구성할 수 있다.

스타일 적용 가이드

  1. Settings | Editor | Code Style | Kotlin 으로 이동한다.
  2. Set from.... 을 클릭한다.
  3. Kotlin style guide 를 선택한다.

코드가 스타일 가이드를 따르는지 확인

  1. Settings | Editor | Inspections | Kotlin 으로 이동한다.
  2. Kotlin | Style issues 를 연다.
  3. File is not formatted according to project settings 를 체크한다. 스타일 가이드에 설명된 다른 문제(예: 명명 규칙)을 확인하는 추가 검사는 기본적으로 활성화 되어 있다.

소스 코드 구성

디렉토리 구조

순수한 Kotlin 프로젝트에서 권장되는 디렉토리 구조는 공통 루트 패키지가 생략된 패키지 구조를 따른다. 예를 들어 프로젝트의 모든 코드가 org.example.kotlin 패키지와 그 하위 패키지에 있는 경우 org.example.kotlin 패키지가 있는 파일은 소스 루트 바로 아래에 배치되어야 하고 org.example.kotlin.network.socket의 파일은 소스 루트의 network/socket 하위 디렉토리에 있어야 한다.

JVM에서: Kotlin이 Java와 함께 사용되는 프로젝트에서 Kotlin 소스 파일은 Java 소스 파일과 동일한 소스 루트에 있어야 하며 동일한 디렉토리 구조를 따라야 한다. 각 파일은 각 패키지 문에 해당하는 디렉토리에 저장되어야 한다.

소스 파일 이름

Kotlin 파일에 단일 클래스(관련 최상위 선언이 있을 수 있음)가 포함된 경우 해당 이름은 .kt 확장자가 추가된 클래스 이름과 동일해야 한다. 파일에 여러 클래스가 포함되어 있거나 최상위 선언만 포함된 경우 파일에 포함된 내용을 설명하는 이름을 선택하고 그에 따라 파일 이름을 지정한다. 대문자 첫 번째 문자(Pascal 케이스라고도 함)와 함께 대문자 camel case를 사용하시오(예: ProcessDeclarations.kt).

파일 이름은 파일의 코드가 수행하는 작업을 설명해야 한다. 따라서 파일 이름에 Util과 같은 의미 없는 단어를 사용하지 않아야 한다.

소스 파일 구성

동일한 Kotlin 소스 파일에 여러 선언(클래스, 최상위 함수 또는 속성)을 배치하는 것은 이러한 선언이 의미적으로 서로 밀접하게 관련되어 있고 파일 크기가 합리적으로 유지되는 한(수백 줄을 초과하지 않는 한) 권장된다.

특히, 이 클래스의 모든 클라이언트와 관련된 클래스에 대한 확장 함수를 정의할 때 클래스 자체와 동일한 파일에 넣는다. 특정 클라이언트에 대해서만 의미가 있는 확장 기능을 정의할 때 해당 클라이언트의 코드와 함께 두어야 한다. 일부 클래스의 모든 확장을 보유하기 위해 파일을 생성하지 마시오.

클래스 레이아웃

클래스의 내용은 다음 순서로 진행되어야 한다.

  1. 프로퍼티 선언 및 초기화 블록
  2. 보조 생성자
  3. 메소드 선언
  4. Companion object

메소드 선언을 알파벳순 또는 가시성을 기준으로 정렬하지 말고 일반 메소드를 확장 메소드와 분리하지 마시오. 대신 관련 내용을 함께 모아서 클래스를 위에서 아래로 읽는 사람이 무슨 일이 일어나고 있는지 논리를 따를 수 있도록 하시오. 순서를 선택하고(높은 수준의 항목을 먼저 선택하거나 그 반대의 경우도 마찬가지) 해당 명령을 따르시오.

중첩 클래스는 해당 클래스를 사용하는 코드 뒤에 배치한다. 클래스가 외부에서 사용되도록 의도되고 클래스 내부에서 참조되지 않는 경우 Companion Object 뒤에 마지막에 넣는다.

인터페이스 구현 레이아웃

인터페이스를 구현할 때 구현하는 구성원을 인터페이스의 구성원과 같은 순서로 유지하시오(필요한 경우 구현에 사용되는 추가 개인 메서드가 산재되어 있음).

오버로드 레이아웃

항상 오버로드는 클래스 뒤에 넣는다.

네이밍 규칙

Kotlin의 패키지 및 클래스 명명 규칙은 매우 간단하다.

  • 패키지 이름은 항상 소문자이며 밑줄을 사용하지 않는다(org.example.project). 여러 단어로 된 이름을 사용하는 것은 일반적으로 권장되지 않지만 여러 단어를 사용해야 하는 경우에는 camel case(org.example.myProject)를 사용할 수 있다.
  • 클래스 및 객체의 이름은 대문자로 시작하고 camel case를 사용한다.
open class DeclarationProcessor { /*...*/ }

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

함수 이름

함수, 프로퍼티 및 지역 변수의 이름은 소문자로 시작하고 밑줄 없이 카멜 케이스를 사용한다.

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

예외: 클래스의 인스턴스를 생성하는 데 사용되는 팩토리 함수는 추상 반환 타입과 동일한 이름을 가질 수 있다.

interface Foo { /*...*/ }

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

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

테스트 메서드 이름

테스트에서(테스트에서만) 백틱으로 묶인 공백이 있는 메서드 이름을 사용할 수 있다. 이러한 메서드 이름은 현재 Android 런타임에서 지원되지 않는다. 메서드 이름의 밑줄은 테스트 코드에서는 허용된다.

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

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

프로퍼티 이름

상수의 이름(const로 표시된 프로퍼티 또는 완전히 변경할 수 없는 데이터를 보유한 커스텀 get 함수가 없는 최상위 또는 오브젝트의 val 프로퍼티)의 이름은 밑줄로 구분된 대문자 이름(screaming snake case) 이름을 사용해야 한다.

const val MAX_COUNT = 8
val USER_NAME_FIELD = "UserName"

동작 또는 변경 가능한 데이터가 있는 오브젝트를 보유하는 최상위 또는 오브젝트 프로퍼티의 이름은 camel case를 사용해야 한다.

val mutableCollection: MutableSet<String> = HashSet()

싱글톤 오브젝트에 대한 참조를 보유하는 프로퍼티의 이름은 오브젝트 선언과 동일한 명명 스타일을 사용할 수 있다.

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

열거형 상수의 경우 사용법에 따라 대문자로 밑줄로 구분된 이름(screaming snake case)(enum class Color { RED, GREEN }) 또는 대문자 카멜 케이스 이름을 사용하는 것이 좋다.

backing 프로퍼티 이름

클래스에 개념적으로 동일한 두 개의 프로퍼티가 있지만 하나는 공개 API의 일부이고 다른 하나는 구현 세부 정보인 경우 비공개 속성 이름은 접두사로 밑줄을 사용한다.

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

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

좋은 이름을 선택해라

클래스 이름은 일반적으로 명사 또는 클래스가 무엇인지 설명하는 명사구이다: List, PersonReader.

메서드의 이름은 일반적으로 메서드가 수행하는 작업을 나타내는 동사 또는 동사 구문이다: close, readPersons.
또한 이름은 메서드가 오브젝트를 변경하거나 새 오브젝트를 반환하는지 여부를 암시해야 한다. 예를 들어 sort는 컬렉션을 제자리에 정렬하는 것이고 sorted는 컬렉션의 정렬된 복사본을 반환한다.

이름은 엔티티의 목적이 무엇인지 명확해야 하므로 이름에 의미 없는 단어(Manager, Wrapper)를 사용하지 않는 것이 가장 좋다.

이름을 선언할 때 일부로 두문자어를 사용할 때 두 글자(IOStream)로 구성된 경우 대문자로 사용하시오. 긴 경우 첫 번째 문자만 대문자로 표시한다(XmlFormatter, HttpInputStream).

Formatting

들여쓰기

들여쓰기에는 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?.bar()
  • // 뒤에는 공백을 넣으시오 : // 이것은 주석이다.
  • 유형 매개변수를 지정하는 데 사용되는 꺾쇠 괄호 주위에 공백을 두지 마시오. class Map<K, V> { ... }
  • :: 주위에 공백을 두지 마시오. : Foo::class, String::length
  • nullable 타입을 나타내는데 사용하는 ? 앞에 공백을 두지 마시오. : String?
  • 일반적으로 어떤 종류의 수평 정렬도 피하시오. 식별자의 이름을 다른 길이의 이름으로 바꾸는 것은 선언 형식이나 사용법에 영향을 미치지 않아야 한다.

콜론 :

다음과 같은 경우 : 앞에 공백을 넣으시오.

  • 타입과 슈퍼 타입을 구분하는데 사용되는 경우
  • 슈퍼클래스 생성자나 같은 클래스의 다른 생성자에게 위임할 때
  • 오브젝트 키워드 뒤에

선언과 타입을 구분할 때 : 앞에 공백을 두지 마시오.

: 뒤에는 항상 공백을 둔다.

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

Class headers

몇 가지 기본 생성자 매개변수가 있는 클래스는 한 줄로 작성될 수 있다.

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개)를 사용한다. 기본 생성자에 선언된 프로퍼티가 클래스 본문에 선언된 프로퍼티와 동일한 들여쓰기를 갖도록 한다.

Modifiers 순서

선언에 modifier가 여러 개인 경우 항상 다음 순서로 정렬된다.

public / protected / private / internal
expect / actual
final / open / abstract / sealed / const
external
override
lateinit
tailrec
vararg
suspend
inner
enum / annotation / fun // `fun interface`의 modifier
companion
inline / value
infix
operator
data

modifier 앞에 모든 annotation들을 배치한다.

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

라이브러리에서 작업하지 않는 한 중복되는 modifier를 생략하시오(예: public).

Annotations

annotation이 첨부된 선언 앞에 동일한 들여쓰기를 사용하여 별도의 줄에 annotation을 배치한다.

@Target(AnnotationTarget.PROPERTY)
annotation class JsonExclude

인수가 없는 annotation은 같은 줄에 배치할 수 있다.

@JsonExclude @JvmField
var x: String

인수가 없는 단일 annotation은 해당 선언과 동일한 행에 배치할 수 있다.

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

File annotations

File annotations은 File comment(있는 경우) 뒤, 패키지 문 앞에 배치되며 빈 줄로 패키지와 구분된다.(패키지가 아닌 파일을 대상으로 한다는 사실을 강조하기 위해)

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

package foo.bar

Functions

function signature가 한 줄에 맞지 않으면 아래처럼 사용한다.

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

함수 파라미터에 일반 들여쓰기(4개의 공백)를 사용한다. 생성자 파라미터와의 일관성을 보장하는 데 도움이 된다.

단일 표현식으로 구성된 본문이 있는 함수에 대해 표현식 본문을 사용하는 것을 선호한다.

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

fun foo() = 1        // good

Expression bodies

함수에 첫 번째 줄이 선언과 같은 줄에 맞지 않는 표현식 본문이 있는 경우 첫 번째 줄에 = 기호를 넣고 표현식 본문을 4칸 들여쓴다.

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

Properties

매우 단순한 읽기 전용 프로퍼티의 경우 한 줄 형식을 고려하라

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

더 복잡한 프로퍼티의 경우 항상 get 및 set 키워드를 별도의 줄에 배치하라

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 문에서 분기가 한 줄 이상인 경우 빈 줄로 인접한 case 블록과 구분하는 것을 고려하시오.

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
)

인수 이름과 값을 구분하는 = 기호 주위에 공백을 넣는다.

Wrap chained 호출

chained 호출을 wrapping 할 때 . 문자 또는 ?. 는 들여쓰기를 하여 다음 줄에 순차적으로 배치한다.

val anchor = owner
    ?.firstChild!!
    .siblings(forward = true)
    .dropWhile { it is PsiComment || it is PsiWhiteSpace }

체인의 첫 번째 호출은 일반적으로 그 앞에 줄 바꿈이 있어야 하지만 코드가 그런 식으로 더 합리적이면 생략해도 된다.

Lambdas

람다 식에서 중괄호 주위와 본문에서 매개변수를 구분하는 화살표 주위에 공백을 사용해야 한다. 호출이 단일 람다를 사용하는 경우 가능하면 괄호 외부로 반환한다.

list.filter { it > 10 }

람다에 레이블을 할당하는 경우 레이블과 여는 중괄호 사이에 공백을 두지 마시오.

fun foo() {
    ints.forEach lit@{
        // ...
    }
}

여러 줄 람다에서 파라미터 이름을 선언할 때 첫 번째 줄에 이름을 입력하고 그 뒤에 화살표를 입력하고 줄을 바꾼다.

appendCommaSeparated(properties) { prop ->
    val propertyValue = prop.get(obj)  // ...
}

파라미터 리스트가 너무 길어 한 줄에 맞지 않으면 화살표를 별도의 줄에 넣으시오.

foo {
   context: Context,
   environment: Env
   ->
   context.configureEnv(environment)
}

후행 쉼표(trailing comma)

후행 쉼표는 일련의 엘리먼트 중 마지막 항목 뒤에 오는 쉼표 기호이다.

class Person(
    val firstName: String,
    val lastName: String,
    val age: Int, // 후행 쉼표(trailing comma)
)

후행 쉼표를 사용하면 다음과 같은 몇 가지 이점이 있다.

  • 모든 포커스가 변경된 값에 있기 때문에 버전 제어 차이점이 더 명확해진다.
  • 엘리먼트를 쉽게 추가하고 재정렬할 수 있다. 엘리먼트를 조작하는 경우 쉼표를 추가하거나 삭제할 필요가 없다.
  • 예를 들어 오브젝트 이니셜라이저의 경우 코드 생성을 단순화한다. 마지막 엘리먼트에도 쉼표가 있을 수 있다.

후행 쉼표는 완전히 선택 사항이다. 코드가 없어도 계속 작동한다. Kotlin 스타일 가이드는 선언 사이트에서 후행 쉼표를 사용하도록 권장하고 호출 사이트에 대한 귀하의 재량에 따라 남겨둔다.

IntelliJ IDEA 포맷터에서 후행 쉼표를 활성화하려면 Settings | Editor | Code Style | Kotlin에서 Other 탭을 열고 Use trailing comma 옵션을 선택하면 된다.

Documentation comments

더 긴 문서 주석의 경우 여는 /**를 별도의 줄에 배치하고 각 후속 줄을 *로 시작한다.

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

짧은 주석은 한 줄에 넣을 수 있다.

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

일반적으로 @param 및 @return 태그를 사용하지 마시오. 대신 파라미터에 대한 설명과 리턴 값을 문서 주석에 직접 통합하고 파라미터가 언급되는 곳마다 파라미터에 대한 링크를 추가하시오. @param, @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) { /*...*/ }

// Do this instead:

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

Avoid redundant constructs

일반적으로 Kotlin의 특정 구문 구성이 선택 사항이고 IDE에서 중복으로 강조 표시되는 경우 코드에서 이를 생략해야 한다. "명확성을 위해" 코드에 불필요한 구문 요소를 남겨두지 마시오.

Unit return type

함수가 Unit을 반환하면 반환 타입을 생략해야 한다.

fun foo() { // ": Unit" is omitted here

}

Semicolons

가능하면 세미콜론을 생략하시오.

String templates

단순 변수를 문자열 템플릿에 삽입할 때 중괄호를 사용하지 마시오. 더 긴 표현식에만 중괄호를 사용하시오.

println("$name has ${children.size} children")

언어 기능의 관용적 사용(Idiomatic use of language features)

불변(Immutability)

변경 가능한 데이터보다 변경 불가능한 데이터를 사용하는 것을 선호한다. 초기화 후 수정되지 않은 경우 지역 변수와 프로퍼티를 항상 var가 아닌 val로 선언하시오.

항상 변경할 수 없는 컬렉션 인터페이스(Collection, List, Set, Map)를 사용하여 변경되지 않은 컬렉션을 선언한다. 팩토리 함수를 사용하여 컬렉션 인스턴스를 생성할 때 가능하면 항상 변경할 수 없는 컬렉션 유형을 반환하는 함수를 사용해라

// Bad: 변경되지 않을 값에 대해 변경 가능한 컬렉션 유형 사용
fun validateValue(actualValue: String, allowedValues: HashSet<String>) { ... }

// Good: 대신 사용할 수 있는 변경 불가능한 컬렉션 타입
fun validateValue(actualValue: String, allowedValues: Set<String>) { ... }

// Bad: arrayListOf()는 변경 가능한 컬렉션 타입인 ArrayList<T>를 반환한다.
val allowedValues = arrayListOf("a", "b", "c")

// Good: listOf()는 List<T>를 반환한다.
val allowedValues = listOf("a", "b", "c")

기본 파라미터 값

오버로드된 함수를 선언하는 것보다 기본 매개변수 값을 사용하여 함수를 선언하는 것을 선호한다.

// Bad
fun foo() = foo("a")
fun foo(a: String) { /*...*/ }

// Good
fun foo(a: String = "a") { /*...*/ }

Type aliases

코드베이스에서 여러 번 사용되는 함수형 또는 타입 파라미터가 있는 타입이 있는 경우 이에 대한 Type aliase을 정의하는 것이 좋다.

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

이름 충돌을 피하기 위해 private 또는 internal type alias를 사용하는 경우 Packgaes and Imports에서 언급한 import … as … 를 선호한다.

Lambda parameters

짧고 중첩되지 않은 람다에서는 파라미터를 명시적으로 선언하는 대신 it 규칙을 사용하는 것이 좋다. 파라미터가 있는 중첩 람다에서는 항상 파라미터를 명시적으로 선언해야 한다.

Returns in a lambda

람다에서 레이블이 지정된 여러개의 반환을 사용하지 마시오. 단일 출구 지점을 갖도록 람다를 재구성하는 것을 고려하시오. 이것이 가능하지 않거나 충분히 명확하지 않은 경우 람다를 익명 함수로 변환하는 것을 고려하시오.

람다의 마지막 문에 레이블이 지정된 반환을 사용하지 마시오.

Named arguments

메소드가 동일한 기본 타입의 여러 파라미터를 사용하거나 Boolean 유형의 파라미터에 대해 모든 파라미터의 의미가 컨텍스트에서 절대적으로 명확하지 않은 경우 명명된 인수 구문을 사용하시오.

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

Conditional statements

try, if 및 when의 표현 형식을 사용하는 것이 좋다.

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

위의 사항은 다음과 같은 경우에 바람직합니다.

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

if VS when

이진 조건에는 when 대신 if를 사용하는 것이 선호된다. 예를 들어 다음과 같은 경우 이 구문을 사용한다.

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

When을 사용할 경우

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

세 가지 이상의 옵션이 있는 경우 when을 사용하는 것이 좋다.

조건의 Nullable Boolean 값

조건문에서 nullable Boolean을 사용해야 하는 경우 if(value == true) 또는 if(value == false) 으로 확인한다.

Loops

Loop보다 고차 함수(filter, map 등)를 사용하는 것을 선호한다. 예외: forEach(forEach의 수신자가 nullable이거나 forEach가 더 긴 호출 체인의 일부로 사용되지 않는 한 대신 일반 for 루프를 사용하는 것이 좋다).

여러 고차 함수를 사용하는 복잡한 표현식과 루프 중에서 선택할 때 각 경우에 수행되는 작업의 비용을 이해하고 성능 고려 사항을 염두에 두시오.

Loops on ranges

열린 범위에 대해 반복하려면 until 함수를 사용하시오.

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

Strings

문자열 연결보다 문자열 템플릿을 선호한다.

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

Functions vs properties

어떤 경우에는 인수가 없는 함수가 읽기 전용 프로퍼티로 교체될 수 있다. 의미론은 유사하지만 서로를 선호하는 경우에 대한 몇 가지 문체 규칙이 있다.

기본 알고리즘이 다음과 같은 경우 함수보다 프로퍼티를 선호한다.

  • throw가 없을 경우
  • 계산 비용이 저렴한 경우(혹은 첫 번째 실행 시 캐시가 될 경우)
  • 오브젝트 상태가 변경되지 않은 경우 호출에 대해 동일한 결과를 반환할 때

Extension functions

확장 기능을 자유롭게 사용하시오. 주로 오브젝트에 대해 작동하는 함수가 있을 때마다 해당 오브젝트를 수신기로 받아들이는 확장 함수로 만드는 것을 고려하시오. API 오염을 최소화하려면 확장 기능의 가시성을 합리적으로 제한하시오. 필요에 따라 로컬 확장 함수, 멤버 확장 함수 또는 private 가시성이 있는 최상위 확장 함수를 사용한다.

Infix functions

비슷한 역할을 하는 두 오브젝트에 대해 작동할 때만 함수를 infix로 선언하시오.
종은 예 : and, to, zip 나쁜 예 : add

리시버 오브젝트를 변경하는 경우 메서드를 infix로 선언하지 마시오.

Factory functions

클래스에 대한 팩토리 함수를 선언하는 경우 클래스 자체와 동일한 이름을 지정하지 마시오. 팩토리 함수의 동작이 특별한 이유를 명확히 하는 고유한 이름을 사용하는 것을 선호한다. 특별한 의미가 없는 경우에만 클래스와 동일한 이름을 사용할 수 있다.

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

다른 슈퍼클래스 생성자를 호출하지 않고 기본 인수 값을 가진 단일 생성자로 축소할 수 없는 여러 오버로드된 생성자가 있는 객체가 있는 경우 오버로드된 생성자를 팩토리 함수로 바꾸는 것을 선호한다.

Platform types

플랫폼 유형의 표현식을 반환하는 public 함수/메소드는 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)
}

Scope functions apply/with/run/also/let

Kotlin은 주어진 오브젝트의 컨텍스트에서 코드 블록을 실행하기 위한 함수 세트를 제공한다: let, run, with, apply, also.

Coding conventions for libraries

라이브러리를 작성할 때 API 안정성을 보장하기 위해 추가 규칙 세트를 따르는 것이 좋다.

  • 항상 명시적으로 멤버 가시성을 지정한다(우발적으로 선언을 public API로 노출하는 것을 방지하기 위해).
  • 항상 명시적으로 함수 반환 타입 및 프로퍼티 타입을 지정한다(구현이 변경될 때 반환 타입이 실수로 변경되는 것을 방지하기 위해).
  • 새 문서가 필요하지 않은 재정의를 제외하고 모든 public 멤버에 대한 KDoc 주석 제공(라이브러리에 대한 문서 생성 지원)

원문

 

Coding conventions | Kotlin

 

kotlinlang.org