본문 바로가기
개발&프로그래밍

[kotlin] 코딩 컨벤션 (coding conventions)

by 재아군 2024. 8. 9.

 

 

Kotlin 코딩 컨벤션 가이드

 

Kotlin 프로그래밍 언어를 사용할 때 따라야 할 코딩 컨벤션에 대한 종합적인 가이드입니다.

이 가이드라인을 따르면 코드의 일관성과 가독성을 높일 수 있습니다.

 

이 가이드 문서는 공식 홈페이지를 참고하였습니다.

https://kotlinlang.org/docs/coding-conventions.html

 

 

 

Kotlin 명명 규칙 및 포맷팅 가이드

Kotlin에서 코드를 작성할 때 일관성 있고 가독성 높은 코드를 만들기 위한 명명 규칙과 포맷팅 가이드라인을 소개합니다.

 

1. 명명 규칙

패키지와 클래스 이름

  • 패키지 이름은 항상 소문자로 작성하며 밑줄을 사용하지 않습니다. (예: org.example.project)
  • 여러 단어로 된 패키지 이름은 권장하지 않지만, 필요한 경우 카멜 케이스를 사용합니다. (예: org.example.myProject)
  • 클래스와 객체 이름은 대문자로 시작하고 카멜 케이스를 사용합니다.

 

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

 

함수와 변수 이름

  • 함수, 프로퍼티, 지역 변수의 이름은 소문자로 시작하고 카멜 케이스를 사용합니다.

 

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

 

 

 

테스트 메서드 이름

  • 테스트 코드에서는 백틱(`)으로 둘러싼 공백이 포함된 메서드 이름을 사용할 수 있습니다
class MyTestCase {
     @Test fun `ensure everything works`() { /*...*/ }
     @Test fun ensureEverythingWorks_onAndroid() { /*...*/ }
}

 

 

상수 이름

  • 상수(const로 표시된 프로퍼티 또는 불변 데이터를 담는 최상위/객체 val 프로퍼티)는 대문자와 밑줄로 구분된 이름을 사용합니다.

 

const val MAX_COUNT = 8
val USER_NAME_FIELD = "UserName"

 

백킹 프로퍼티 이름

  • 공개 API의 일부인 프로퍼티와 구현 세부사항인 프로퍼티가 개념적으로 같은 경우, 비공개 프로퍼티 이름 앞에 밑줄을 사용합니다.
class C {
    private val _elementList = mutableListOf<Element>()
    val elementList: List<Element>
         get() = _elementList
}

 

 

2. 포맷팅

들여쓰기

  • 4개의 공백을 사용하며, 탭은 사용하지 않습니다.
  • 중괄호는 구문이 시작하는 줄의 끝에 놓고, 닫는 중괄호는 별도의 줄에 놓습니다.
if (elements != null) {
    for (element in elements) {
        // ...
    }
}

 

가로 공백

  • 이항 연산자 주위에 공백을 넣습니다. (예: a + b)
  • 제어 흐름 키워드(if, when, for, while)와 여는 괄호 사이에 공백을 넣습니다.
  • 주 생성자 선언, 메서드 선언, 메서드 호출의 여는 괄호 앞에는 공백을 넣지 않습니다.
class A(val x: Int)
fun foo(x: Int) { ... }
fun bar() {
    foo(1)
}

 

콜론

  • 타입과 슈퍼타입을 구분할 때, 생성자 위임 시, object 키워드 뒤에 콜론 앞에 공백을 넣습니다.
  • 선언과 그 타입을 구분할 때는 콜론 앞에 공백을 넣지 않습니다.
  • 콜론 뒤에는 항상 공백을 넣습니다.
abstract class Foo<out T : Any> : IFoo {
    abstract fun foo(a: Int): T
}

 

 

클래스 헤더 포맷팅

  • 짧은 헤더는 한 줄에 작성합니다.
  • 긴 헤더는 각 주 생성자 매개변수를 별도의 줄에 작성하고, 닫는 괄호는 새 줄에 놓습니다.
class Person(id: Int, name: String)

class Person(
    id: Int,
    name: String,
    surname: String
) : Human(id, name) { /*...*/ }

 

 

수식어 순서

선언에 여러 수식어가 있는 경우, 다음 순서를 따릅니다:

  1. public / protected / private / internal
  2. expect / actual
  3. final / open / abstract / sealed / const
  4. external
  5. override
  6. lateinit
  7. tailrec
  8. vararg
  9. suspend
  10. inner
  11. enum / annotation / fun (fun interface의 수식어로)
  12. companion
  13. inline / value
  14. infix
  15. operator
  16. data

모든 애노테이션은 수식어 앞에 배치합니다.

 

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

 

애노테이션

  • 애노테이션은 해당 선언 앞에 별도의 줄에 같은 들여쓰기로 배치합니다.
@Target(AnnotationTarget.PROPERTY)
annotation class JsonExclude

 

인자가 없는 여러 애노테이션은 같은 줄에 배치할 수 있습니다.

 

@JsonExclude @JvmField
var x: String

 

인자가 없는 단일 애노테이션은 해당 선언과 같은 줄에 배치할 수 있습니다.

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

 

파일 애노테이션

파일 애노테이션은 파일 주석 다음, 패키지 문 앞에 위치하며, 패키지 문과는 빈 줄로 구분합니다.

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

package foo.bar

 

 

함수

  • 함수 시그니처가 한 줄에 맞지 않으면 다음과 같이 작성합니다:
fun longMethodName(
    argument: ArgumentType = defaultValue,
    argument2: AnotherArgumentType,
): ReturnType {
    // 본문
}

 

  • 함수 매개변수는 4칸 들여쓰기를 사용합니다.
  • 단일 표현식으로 구성된 함수는 표현식 본문을 사용합니다.
fun foo(): Int {     // 나쁜 예
    return 1
}

fun foo() = 1        // 좋은 예

 

 

표현식 본문

함수의 표현식 본문이 선언과 같은 줄에 맞지 않으면, = 기호를 첫 줄에 두고 표현식 본문을 4칸 들여씁니다.

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

 

속성

  • 매우 간단한 읽기 전용 속성은 한 줄로 포맷팅합니다:
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) {
    // 본문
} else {
    // else 부분
}

try {
    // 본문
} finally {
    // 정리
}

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() // 좋은 예
    false -> { baz() } // 나쁜 예
}

 

메소드 호출

긴 인자 목록은 여는 괄호 다음에 줄 바꿈을 하고, 인자를 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 }

체인의 첫 번째 호출은 보통 그 앞에 줄 바꿈을 하지만, 코드가 더 이해하기 쉬워진다면 생략해도 됩니다.

댓글