Kotlin 공식 문서 살펴보기 (3) - Coding conventions 1
07 Jan 2022소스 코드 구성
디렉토리 구조
- 공통 루트 패키지가 생략된 패키지 구조를 따르는 것을 권장
소스 파일 이름
- Kotlin 파일에 단일 클래스가 포함된 경우, 이름은
.kt
확장자가 추가된 클래스 이름과 동일해야 함 - 파일에 여러 클래스가 포함되거나 최상위 선언만 포함도니 경우, 파일에 포함된 내용을 설명하는 이름 선택
- 파일 이름은 파일 코드가 수행하는 작업을 설명해야 함
- Pascal case
소스 파일 구성
- 동일한 소스 파일에 여러 클래스, 최상위 함수, 속성들이 배치될 때는 의미적으로 연관되어야 함
- 클래스의 모든 클라이언트와 관련된 클래스에 대한 확장 함수를 정의할 때는 클래스와 동일한 파일에 넣도록 해야함
클래스 레이아웃
-
클래스의 순서
- 속성 선언 및 초기화 블록
-
보조 생성자
-
메소드 선언
- 컴패니언 객체
-
메소드는 관련된 내용을 모아서 논리적으로 따를 수 있도록 정렬
-
해당 클래스를 사용하는 코드 옆에 중첩된 클래스 배치
-
클래스가 외부에서만 사용되면 컴패니언 객체 뒤에 배치
인터페이스 구현 레이아웃
- 인터페이스 구성원을 인터페이스 구성원과 같은 순서로 유지
오버로드 레이아웃
- 항상 클래스의 옆에 오버로드를 배치
명명 규칙
패키지 및 클래스
- 소문자
- 밑줄은 사용하지 않음
- 여러 단어를 사용하는 이름은 권장되지 않지만, 사용할 경우 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, )