Kotlin 공식 문서 살펴보기 (3) - Coding conventions 1

소스 코드 구성

디렉토리 구조

  • 공통 루트 패키지가 생략된 패키지 구조를 따르는 것을 권장

소스 파일 이름

  • Kotlin 파일에 단일 클래스가 포함된 경우, 이름은 .kt 확장자가 추가된 클래스 이름과 동일해야 함
  • 파일에 여러 클래스가 포함되거나 최상위 선언만 포함도니 경우, 파일에 포함된 내용을 설명하는 이름 선택
  • 파일 이름은 파일 코드가 수행하는 작업을 설명해야 함
  • Pascal case

소스 파일 구성

  • 동일한 소스 파일에 여러 클래스, 최상위 함수, 속성들이 배치될 때는 의미적으로 연관되어야 함
  • 클래스의 모든 클라이언트와 관련된 클래스에 대한 확장 함수를 정의할 때는 클래스와 동일한 파일에 넣도록 해야함

클래스 레이아웃

  • 클래스의 순서

    1. 속성 선언 및 초기화 블록
    2. 보조 생성자

    3. 메소드 선언

    4. 컴패니언 객체
  • 메소드는 관련된 내용을 모아서 논리적으로 따를 수 있도록 정렬

  • 해당 클래스를 사용하는 코드 옆에 중첩된 클래스 배치

  • 클래스가 외부에서만 사용되면 컴패니언 객체 뒤에 배치

인터페이스 구현 레이아웃

  • 인터페이스 구성원을 인터페이스 구성원과 같은 순서로 유지

오버로드 레이아웃

  • 항상 클래스의 옆에 오버로드를 배치

명명 규칙

패키지 및 클래스

  • 소문자
  • 밑줄은 사용하지 않음
  • 여러 단어를 사용하는 이름은 권장되지 않지만, 사용할 경우 camel case
  • 클래스 및 객체의 이름은 pascal case

함수 이름

  • Camel case

  • 클래스의 인스턴스를 생성할 때 사용되는 팩토리 함수는 추상 반환 타입과 같은 이름을 가질 수 있음

    interface Foo { ... }
      
    class FooImpl : Foo { ... }
      
    fun Foo(): Foo { return FooImpl() }
    

테스트 메소드

  • 백틱(`)을 사용해 공백이 있는 메서드 이름 가능
  • 밑줄 허용 가능]
class MyTestCase {
  	@Test fun `ensure everything works`() { ... }
  
  	@Test fun ensureEverythingWorks_onAndroid() { ... }
}

속성 이름

  • 상수, const로 표시된 속성, 최상위 속성 혹은 val 속성은 대문자 & snake case
const val MAX_COUNT = 9
val USER_NAME_FIELD = "Tom"
  • 변경 가능한 데이터가 있는 객체를 가진 최상외 또는 객체 속성은 camel case
val mutableCollection: MutableSet<String> = HashSet()
  • 싱글톤 객체 참조를 보유하는 속성의 이름은 객체 선언과 동일한 스타일 사용 가능
val PersonComparator: Comparator<Person> = ...
  • enum 타입은 대문자 snake case 또는 pascal case
enum class Color { RED, GREEN }

지원 속성 이름

  • 비공개 속성 이름은 접두사로 밑줄(_) 사용
class C {
  	private val _elementList = mutableListOf<Element>()
  
  	val elementList: List<Element>
  		get() = _elementList
}

좋은 이름

  • 클래스 이름은 보통 클래스가 무엇인지 설명하는 보통 명사/명사구
  • 메서드 이름은 보통 메서드 역할을 설명하는 동사/동사구
  • 이름에서 객체를 바꾸는 것인지 새로운 것을 반환하는 것인지 알 수 있어야 함
  • 이름에서 엔티티의 목적을 명확히 알 수 있어야 함
  • 이름에 두문자어를 쓸 경우 camel case(두 글자일 경우, 둘 다 대문자)

Formatting

들여쓰기

  • 4개의 공백
  • 탭을 사용하지 않는다
  • 중괄호는 구문이 시작하는 줄에 여는 중괄호를 놓고, 닫는 중괄호를 여는 구문과 수평으로 정렬된 별도의 줄에 놓는다
{if (elements != null) {
    for (element in elements) {
         ...
    }
}

가로 공백

  • 이항 연산자(a + b) 주위에 공백 (예외: 범위 연산자(0..10) 주위는 공백을 두지 않음)
  • 단항 연산자(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
  • null이 가능한 타입을 나타내는 ? 앞에는 공백을 두지 않음

Colon

  • : 앞에 공백이 있는 경우

    • 유형과 상위 유형을 구분할 때
    • 슈퍼클래스 생성자나 같은 클래스의 다른 생성자에게 위임할 때
    • 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 { ... }
      }
    

Class headers

  • 몇 개의 기본 생성자 파라미터가 있는 클래스는 한 줄로 작성 가능

  • 긴 헤더가 있는 클래스는 형식 필요

    • 각 매개변수마다 별도의 줄에 들여쓰기
    • 닫는 괄호는 새 줄에 있어야 함
    • 상속을 하는 경우, 슈퍼클래스 생성자 호출 또는 구현된 인터페이스 목록이 괄호와 같은 줄에 있어야 함
      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 MyFavourteVeryLongClassHolder :
          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
companion
inline / value
infix
operator
data
  • 모든 어노테이션은 제어자 앞에 배치

      @Named("Foo")
      private val foo: Foo
    
  • 라이브러리에서 작업하지 않는 한 중복 수정자는 생략 가능

Annotation

  • 어노테이션은 선언 전에 동일한 들여쓰기로 별도의 줄에 배치

      @Target(AnnotationTarget.PROPERTY)
      annotation class JsonExclude
    
  • 매개변수가 없는 어노테이션은 같은 줄에 배치 가능

      @JsonExclude @JvmField
      var x: String
    
  • 매개변수가 업는 단일 어노테이션은 선언과 동일한 줄에 배치 가능

      @Test fun foo() { ... }
    

File Annotation

  • 파일 어노테이션은 파일 코멘트가 있다면 그 뒤에 그리고 package 선언 전에 위치

  • package와는 한 줄의 구분이 있어야 함

      /* License, copyright and whatever */
      @file:JvmName("FooBar")
        
      package foo.bar
    

함수

  • 한 줄에 되지 않을 경우에 아래와 같이 사용

      fun longMethodName(
          argument: ArgumentType = defaultValue,
          argument2: AnotherArgumentType,
      ): ReturnType {
         // body
      }
    
  • 매개변수 들여쓰기는 공백 4개 사용

  • 단일 표현식으로 구성된 본문이 있는 함수는 표현식 본문을 사용하는 것 선호

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

표현식 본문

  • 함수의 첫번째 줄에 선언과 맞지 않는 표현식 본문이 있을 경우 첫번째 줄에 = 기호를 넣고 표현식 본문은 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 키워드와 while키워드를 do-while 앞의 중괄호와 같은 줄에 배치

      if (condition) {
          // body
      } else {
          // else part
      }
        
      try {
          // body
      } finally {
          // ..
      }
    
  • when 절에서 분기가 한 줄을 초과할 경우, 빈 줄로 다른 블록을 구분할 것 고려

      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)   
      }
    
  • 매개변수가 길어 한 줄에 맞지 않으면 화살표를 별도 줄에 배치

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

Trailing comma

  • 마지막 요소 뒤에 , 추가

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