소스 코드 구성
디렉토리 구조
•
Kotlin 프로젝트에서 권장되는 디렉토리 구조는 공통 루트 패키지가 생략된 패키지 구조를 따른다.
•
JVM에서 Kotlin과 Java를 함께 사용하는 경우 동일한 소스 루트에 있어야 하며 동일한 디렉토리 구조를 따라야 한다.
소스 파일 이름
•
Kotlin 파일에 단일 클래스 또는 인터페이스가 포함된 경우 해당 이름은 클래스 이름과 동일해야한다.
•
Kotlin 파일에 여러 클래스 또는 최상위 선언만 포함된 경우 파일에 포함된 내용을 설명하는 이름을 지정해야한다.
•
첫 글자를 대문자로 시작하는 대문자 카멜 케이스(Pascal 케이스)를 사용한다.
•
파일의 확장자는 .kt 여야한다.
소스 파일 구성
•
여러 선언(클래스, 최상위 함수 또는 속성)을 동일한 Kotlin 소스 파일에 넣는 것은 이러한 선언이 의미적으로 서로 밀접하게 관련되어 있고 파일 크기가 적당한 한(수백 줄을 넘지 않음) 권장된다.
•
클래스의 모든 클라이언트와 관련된 클래스에 대한 확장 함수를 정의할 때는 클래스 자체와 같은 파일에 넣는다.
•
특정 클라이언트에게만 의미가 있는 확장 함수를 정의할 때는 해당 클라이언트의 코드 옆에 넣는다.
•
어떤 클래스의 모든 확장 함수를 보관하기 위한 파일을 만드는 것을 피한다.
클래스 레이아웃
•
클래스의 내용은 다음 순서를 따라야 한다.
1.
property 선언 및 초기화 블록
2.
secondary constructors
3.
method 선언
4.
companion object
•
메서드 선언을 알파벳순이나 가시성순으로 정렬하지 말고 일반 메서드와 확장 메서드를 분리하지 말고 관련된 내용을 모아서 클래스를 위에서 아래로 읽는 사람이 무슨 일이 일어나고 있는지의 논리를 따라갈 수 있도록 하라.
•
중첩된 클래스를 해당 클래스를 사용하는 코드 옆에 둬라.
•
클래스가 외부에서 사용되도록 의도되었고 클래스 내부에서 참조되지 않는 경우 companion object 뒤의 끝에 둬라.
인터페이스 구현 레이아웃
•
인터페이스를 구현할 때 구현 멤버를 인터페이스 멤버와 동일한 순서로 유지한다.
•
필요한 경우 구현에 사용되는 추가 개인 메서드를 삽입한다.
오버로드 레이아웃
•
클래스에서 오버로드는 항상 서로 옆에 둬라.
네이밍 규칙
•
패키지 이름은 항상 소문자이고 밑줄을 사용하지 않는다. (여러 단어일 경우 그냥 연결하거나 카멜 케이스를 사용한다.)
•
클래스와 객체의 이름은 대문자로 시작하고 카멜 케이스를 사용한다.
//org.example.myProject
open class DeclarationProcessor { /*...*/ }
object EmptyDeclarationProcessor : DeclarationProcessor() { /*...*/ }
Kotlin
복사
함수 이름
•
함수, 속성 및 로컬 변수의 이름은 소문자로 시작하고 카멜 케이스를 사용하며 밑줄을 사용하지 않는다.
fun processDeclarations() { /*...*/ }
var declarationCount = 1
Kotlin
복사
•
클래스 인스턴스를 생성하는데 사용되는 팩토리 함수는 인터페이스 반환 타입과 동일한 이름을 가질 수 있다.
interface Foo { /*...*/ }
class FooImpl : Foo { /*...*/ }
fun Foo(): Foo { return FooImpl() }
Kotlin
복사
테스트 메서드 이름
•
오직 테스트에서는 백틱(`)으로 묶인 공백이 있는 메서드 이름을 사용할 수 있다.
•
메서드 이름에 밑줄을 사용하는 것도 테스트 메서드에서 허용된다.
프로퍼티 이름
•
상수의 이름은 대문자와 밑줄로 구분된 형식(스크리밍 스네이크 케이스)를 사용해야 한다.
◦
변경 불가능한 데이터를 보유한 const로 표시된 프로퍼티
◦
사용자 지정 get 함수가 없는 최상위 또는 객체 val 프로퍼티
◦
등
const val MAX_COUNT = 8
val USER_NAME_FIELD = "UserName"
Kotlin
복사
•
동작이나 변경 가능한 데이터가 있는 객체를 보관하는 최상위 프로퍼티나 객체 프로퍼티의 이름은 카멜 케이스를 사용해야 한다.
val mutableCollection: MutableSet<String> = HashSet()
Kotlin
복사
•
object 싱글톤 객체에 대한 참조를 보유하는 속성의 이름은 선언과 동일한 네이밍 스타일을 사용할 수 있다.
val PersonComparator: Comparator<Person> = /*...*/
Kotlin
복사
•
열거형 상수의 경우 사용법에 따라 스네이크 케이스나 대문자 카멜 케이스를 사용해도 된다.
enum class Color { RED, GREEN }
Kotlin
복사
백킹 프로퍼티의 이름
•
클래스에 개념적으로 동일하지만 하나는 공개 API의 일부이고 다른 하나는 구현 세부 사항인 두 개의 프로퍼티가 있는 경우 비공개 프로퍼티의 이름에 대한 접두사로 밑줄을 사용한다.
class C {
private val _elementList = mutableListOf<Element>()
val elementList: List<Element>
get() = _elementList
}
Kotlin
복사
좋은 이름을 사용하라
•
클래스의 이름
◦
일반적으로 명사이거나 클래스가 무엇인지 설명하는 명사구이다.
▪
ex) List, PersonReader
•
메서드의 이름
◦
일반적으로 메서드가 무엇을 하는지 나타내는 동사구이다.
▪
ex) close, readPersons
◦
메서드가 객체를 변형하는지 아니면 새 객체를 반환하는지 여부도 이름에 암시해야한다.
▪
ex) sort는 제자리에서 정렬, sorted는 정렬된 복사본을 반환
•
이름을 통해 엔티티의 목적이 명확하게 드러나야 하므로 이름에 무의미한 단어는 사용하지 않는 것이 좋다.
◦
ex) Manager, Wrapper
•
선언 이름의 일부로 약어를 사용할 때 두 글자로 구성된 경우 대문자로 시작하고 두 글자가 더 길면 첫 글자만 대문자로 시작한다.
◦
ex) IOStream, XmlFormatter, HttpInputStream
서식 지정
들여쓰기
•
들여쓰기에는 4개의 공백을 사용합니다. (탭을 사용하지 마세요)
•
중괄호의 경우 여는 중괄호를 구문이 시작되는 줄 끝에 놓고 닫는 중괄호는 여는 중괄호와 수평으로 맞춰 별도의 줄에 놓습니다.
if (elements != null) {
for (element in elements) {
// ...
}
}
Kotlin
복사
•
Kotlin에서 세미콜론은 선택사항이므로 줄 바꿈이 중요하다.
수평 공백
•
이진 연산자 a + b 주위에 공백을 둬라
•
범위 연산자 0..i 주위에 공백을 두지마라.
•
단항 연산자 a++ 주위에 공백을 두지마라.
•
제어 흐름 키워드 if, when, for, while 과 여는 괄호 사이에 공백을 둬라.
•
(, [, ), ] 뒤나 앞에 공백을 두지마라.
•
기본 생성자 선언, 메서드 선언 또는 메서드 호출에서 여는 괄호 앞에 공백을 두지마라.
class A(val x: Int)
fun foo(x: Int) { ... }
fun bar() {
foo(1)
}
Kotlin
복사
•
. 또는 ?. 주위에 공백을 두지마라.
foo.bar().filter { it > 2 }.joinToString()
foo?.bar()
Kotlin
복사
•
// 뒤에 공백을 둬라.
// This is a comment
Kotlin
복사
•
타입 매개변수를 지정하는데 사용되는 <, > 주위에 공백을 두지마라.
class Map<K, V> { ... }
Kotlin
복사
•
:: 주위에 공백을 두지마라.
Foo::class
String::length
Kotlin
복사
•
? 키워드 앞에 공백을 두지마라.
String?
Kotlin
복사
콜론
•
: 앞에 공백을 두는 경우
◦
타입과 슈퍼 타입을 구분하는데 사용되는 경우
◦
슈퍼클래스 생성자 또는 동일 클래스의 다른 생성자에게 위임할 때
◦
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 { /*...*/ }
}
Kotlin
복사
클래스 헤더
•
몇 개의 기본 생성자 매개변수가 있는 클래스는 한 줄로 작성할 수 있다.
class Person(id: Int, name: String)
Kotlin
복사
•
헤더가 긴 클래스는 각 기본 생성자 매개변수가 들여쓰기가 있는 별도의 줄에 있도록 포맷해야한다.
•
또한 닫는 괄호는 새 줄에 있어야 한다.
•
상속을 사용하는 경우 슈퍼클래스 생성자 호출 또는 구현된 인터페이스 목록은 닫는 괄호와 같은 줄에 있어야 한다.
class Person(
id: Int,
name: String,
surname: String
) : Human(id, name) { /*...*/ }
Kotlin
복사
•
여러 인터페이스의 경우 슈퍼클래스 생성자 호출을 먼저 찾은 다음 각 인터페이스를 다른 줄에 찾아야 한다.
class Person(
id: Int,
name: String,
surname: String
) : Human(id, name),
KotlinMaker { /*...*/ }
Kotlin
복사
•
긴 슈퍼타입 목록이 있는 클래스의 경우 콜론 뒤에 줄 바꿈을 넣고 모든 슈퍼타입 이름을 가로로 맞춘다.
class MyFavouriteVeryLongClassHolder :
MyLongHolder<MyFavouriteVeryLongClass>(),
SomeOtherInterface,
AndAnotherOne {
fun foo() { /*...*/ }
}
Kotlin
복사
•
클래스 헤더가 길 때 클래스 헤더와 본문을 명확하게 구분하려면 클래스 헤더 뒤에 빈 줄을 넣거나 여는 중괄호를 별도의 줄에 넣는다.
class MyFavouriteVeryLongClassHolder :
MyLongHolder<MyFavouriteVeryLongClass>(),
SomeOtherInterface,
AndAnotherOne
{
fun foo() { /*...*/ }
}
Kotlin
복사
•
생성자 매개변수에 일반 들여쓰기(공백 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
Kotlin
복사
•
모든 어노테이션은 수정자 앞에 둬라.
@Named("Foo")
private val foo: Foo
Kotlin
복사
•
라이브러리에서 작업하지 않는 한 중복되는 수정자 (public) 은 생략하라.
어노테이션
•
어노테이션은 첨부된 선언문 앞에 별도 줄에 동일한 들여쓰기로 배치한다.
@Target(AnnotationTarget.PROPERTY)
annotation class JsonExclude
Kotlin
복사
•
인수가 없는 어노테이션은 같은 줄에 배치할 수 있다.
@JsonExclude @JvmField
var x: String
Kotlin
복사
•
인수가 없는 단일 어노테이션은 해당 선언과 같은 줄에 배치할 수 있다.
@Test fun foo() { /*...*/ }
Kotlin
복사
파일 어노테이션
•
파일 어노테이션은 주석 뒤, package 문장 앞에 배치하고 package 문장과 빈 줄로 구분한다.
•
package 파일을 대상으로 하고 패키지가 아니라는 점을 강조하기 위함
/** License, copyright and whatever */
@file:JvmName("FooBar")
package foo.bar
Kotlin
복사
함수
•
함수 매개변수가 한 줄에 들어가지 않으면 다음 구문을 사용하라.
•
함수 매개변수에는 일반 들여쓰기(공백 4개)를 사용한다.
•
생성자 매개변수와의 일관성을 보장하는데 도움이 된다.
fun longMethodName(
argument: ArgumentType = defaultValue,
argument2: AnotherArgumentType,
): ReturnType {
// body
}
Kotlin
복사
•
본문이 단일 표현식으로 구성된 함수의 경우 표현식 본문을 사용하는 것이 좋다.
fun foo(): Int { // bad
return 1
}
fun foo() = 1 // good
Kotlin
복사
표현체
•
함수의 첫 줄이 선언과 같은 줄에 맞지 않는 표현식 본문이 있는 경우 첫 번째 줄에 = 부호를 넣고 표현식 본문을 줄바꿈하여 4칸 들여쓴다.
fun f(x: String, y: String, z: String) =
veryLongFunctionCallWithManyWords(andLongParametersToo(), x, y, z)
Kotlin
복사
프로퍼티
•
매우 간단한 읽기 전용 속성의 경우 한 줄 서식을 고려하라.
val isEmpty: Boolean get() = size == 0
Kotlin
복사
•
더 복잡한 프로퍼티의 경우 항상 get, set 키워드를 별도의 줄에 넣어라.
val foo: String
get() { /*...*/ }
Kotlin
복사
•
초기화가 있는 프로퍼티의 경우 초기화가 길면 = 기호 뒤에 줄 바꿈을 추가하고 초기화를 공백 4개로 들여쓰기한다.
private val defaultCharset: Charset? =
EncodingRegistry.getInstance().getDefaultCharsetForPropertiesFiles(file)
Kotlin
복사
제어 흐름 문장
•
if or when 문의 조건이 여러 줄인 경우 항상 문의 본문 주위에 중괄호를 사용한다.
•
조건의 각 후속 줄은 문의 시작을 기준으로 4칸씩 들여쓰기한다.
•
조건의 닫는 괄호와 여는 중괄호를 별도의 줄에 함께 넣는다.
•
이는 조건문과 진술문 본문을 정렬하는데 도움이 된다.
if (!component.isSyncing &&
!hasAnyKotlinRuntimeInScope(module)
) {
return createKotlinNotConfiguredPanel(module)
}
Kotlin
복사
•
do-while의 while 키워드 뿐만 아니라 else, catch, finally 키워드도 앞의 중괄호와 같은 줄에 넣어라.
if (condition) {
// body
} else {
// else part
}
try {
// body
} finally {
// cleanup
}
Kotlin
복사
•
when 문장에서 분기가 한 줄 이상인 경우 빈 줄로 인접한 케이스 블록과 분리하는 것을 고려하라.
private fun parsePropertyValue(propName: String, token: Token) {
when (token) {
is Token.ValueToken ->
callback.visitValue(propName, token.value)
Token.LBRACE -> { // ...
}
}
}
Kotlin
복사
•
when 문장에서 분기가 짧은 경우 조건과 같은 줄에 중괄호 없이 짧은 분기문을 넣는다.
when (foo) {
true -> bar() // good
false -> { baz() } // bad
}
Kotlin
복사
메서드 호출
•
긴 인수 목록에서는 여는 괄호 뒤에 줄 바꿈을 넣고 인수를 4칸 들여쓰기한다.
•
여러 개의 밀접하게 관련된 인수는 같은 줄에 그룹화한다.
•
인수와 값을 구분하는 = 기호 주위에 공백을 넣는다.
drawSquare(
x = 10, y = 10,
width = 100, height = 100,
fill = true
)
Kotlin
복사
체인 메서드 호출
•
체인 메서드를 호출할 때는 . 문자나 ?. 연산자를 다음 줄에 한 번 들여쓰기하여 입력한다.
•
체인의 첫 번째 호출은 일반적으로 앞에 줄 바꿈이 있어야 하지만 없는게 더 의미가 있다면 생략해도 된다.
val anchor = owner
?.firstChild!!
.siblings(forward = true)
.dropWhile { it is PsiComment || it is PsiWhiteSpace }
Kotlin
복사
람다
•
람다 표현식에서 중괄호 주변과 매개변수와 본문을 구분하는 화살표 주변에 공백을 사용해야 한다.
•
호출이 단일 람다를 사용하는 경우 가능하면 괄호 밖으로 전달한다.
list.filter { it > 10 }
Kotlin
복사
•
람다에 대한 라벨을 지정하는 경우 라벨과 여는 중괄호 사이에 공백을 넣지 않는다.
fun foo() {
ints.forEach lit@{
// ...
}
}
Kotlin
복사
•
다중 줄 람다에서 매개변수 이름을 선언할 때는 첫 번째 줄에 이름을 쓰고 그 뒤에 화살표와 줄 바꿈 문자를 쓴다.
appendCommaSeparated(properties) { prop ->
val propertyValue = prop.get(obj) // ...
}
Kotlin
복사
•
매개변수 목록이 한 줄에 들어가기에는 너무 긴 경우 화살표를 별도의 줄에 넣는다.
foo {
context: Context,
environment: Env
->
context.configureEnv(environment)
}
Kotlin
복사
후행 쉼표
•
후행 쉼표는 일련의 요소에서 마지막 항목 뒤에 오는 쉼표 기호다.
class Person(
val firstName: String,
val lastName: String,
val age: Int, // trailing comma
)
Kotlin
복사
•
후행 쉼표를 사용 했을 때의 이점
◦
모든 초점이 변경된 값에 맞춰지므로 버전 제어 차이점이 더 깔끔해진다.
◦
요소를 조작할 때 쉼표를 추가하거나 삭제할 필요가 없으니 요소를 추가하고 재정렬하는 것이 쉽다.
◦
마지막 요소에도 쉼표가 있을 수 있으니 객체 초기화를 위한 코드 생성을 간소화한다.
•
후행 쉼표는 권장 사항이기 때문에 전적으로 선택 사항이고 후행 쉼표 없이도 코드는 작동한다.
문서 주석
•
더 긴 문서 주석의 경우 별도의 줄에 시작 부분을 입력하고 각 후속 줄을 별표로 시작하라.
/**
* This is a documentation comment
* on multiple lines.
*/
Kotlin
복사
•
짧은 주석은 한 줄에 넣을 수 있다.
/** This is a short documentation comment. */
Kotlin
복사
•
일반적으로 @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): Int { /*...*/ }
// Do this instead:
/**
* Returns the absolute value of the given [number].
*/
fun abs(number: Int): Int { /*...*/ }
Kotlin
복사
중복된 구조를 피하라
•
일반적으로 Kotlin의 특정 구문 구조가 선택 사항이고 IDE에서 중복으로 강조 표시되면 코드에서 생략해야 한다.
•
명확성을 위해 불필요한 구문 요소를 코드에 남기지마라.
Unit 반환 타입
•
함수가 Unit을 반환하는 경우 반환 타입은 생략해야 한다.
fun foo() { // ": Unit" is omitted here
}
Kotlin
복사
세미콜론
•
가능한 세미콜론을 생략하라.
문자열 템플릿
•
문자열 템플릿에 간단한 변수를 삽입할 때는 중괄호를 사용하지마라.
•
긴 표현식에만 중괄호를 사용하라.
println("$name has ${children.size} children")
Kotlin
복사
언어적 특징의 관용적 사용
불변성
•
변경 가능한 데이터보다 변경 불가능한 데이터를 사용하는 것을 선호하라.
•
초기화 후 수정되지 않는 경우 var 보다 val 을 사용하여 로컬 변수와 프로퍼티를 선언하라.
•
항상 불변 컬렉션 인터페이스를 사용하여 변형되지 않는 컬렉션을 선언하라.
•
팩토리 함수를 사용하여 컬렉션 인스턴스를 만들 때는 가능한 경우 항상 불변 컬렉션 타입을 반환하는 함수를 사용하라.
// Bad: use of a 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")
Kotlin
복사
기본 매개변수 값
•
오버로드된 함수를 선언하는 것보다 기본 매개변수 값을 사용하여 함수를 선언하는 것을 사용하라.
// Bad
fun foo() = foo("a")
fun foo(a: String) { /*...*/ }
// Good
fun foo(a: String = "a") { /*...*/ }
Kotlin
복사
타입 별칭
•
코드베이스에서 여러 번 사용되는 함수의 타입이나 타입 매개변수가 있는 타입이 있는 경우 해당 타입에 대한 타입 별칭을 정의하는 것이 좋다.
typealias MouseClickHandler = (Any, MouseEvent) -> Unit
typealias PersonIndex = Map<String, Person>
Kotlin
복사
•
이름 충돌을 피하기 위해 private 또는 internal 타입 별칭을 사용하는 경우 import … as … 를 사용하라.
람다 매개변수
•
짧고 중첩되지 않은 람다에서는 매개변수를 명시적으로 선언하는 대신 it 을 사용하는 것이 좋다.
•
매개변수가 있는 중첩된 람다에서는 항상 매개변수를 명시적으로 선언해야한다.
// 'it'을 사용하여 매개변수를 명시하지 않는 경우
val doubled = numbers.map { it * 2 }
// 중첩된 람다에서 매개변수를 명시적으로 선언하는 경우
val result = numbers.map { num ->
(1..num).map { innerNum ->
innerNum * 2
}
}
Kotlin
복사
람다로 반환
•
람다에서 여러 라벨이 붙은 리턴을 사용하지마라.
•
람다를 재구성하여 단일 종료 지점을 갖도록 하는 것을 고려하라.
•
이것이 불가능하거나 충분히 명확하지 않다면 람다를 익명 함수로 변환하는 것을 고려하라.
•
람다의 마지막 문장에는 라벨이 붙은 return을 사용하지마라.
//여러 라벨이 붙은 리턴
val found = numbers.map { number ->
run {
if (number == 3) return@map "Found 3"
"Not 3"
}
}
//단일 종료 지점을 갖도록 재구성
val found = numbers.map { number ->
if (number == 3) {
"Found 3"
} else {
"Not 3"
}
}
//람다를 익명 함수로 변환
val found = numbers.map(fun(number): String {
return if (number == 3) {
"Found 3"
} else {
"Not 3"
}
})
//라벨이 붙은 리턴을 사용하지 않고 단순히 값을 반환
val found = numbers.map { number ->
if (number == 3) "Found 3" else "Not 3"
}
Kotlin
복사
Named arguments
•
메서드가 동일한 기본 유형 또는 Boolean 타입의 여러 매개변수를 받는 경우 모든 매개변수의 의미가 문맥상 완벽하게 명확하지 않는 한 명명된 인수 구문을 사용하라.
drawSquare(x = 10, y = 10, width = 100, height = 100, fill = true)
Kotlin
복사
조건문 표현식
•
try, if, when의 표현식을 사용하는 것이 좋다.
// Good
return if (x) foo() else bar()
// Bad
if (x)
return foo()
else
return bar()
Kotlin
복사
// Good
return when(x) {
0 -> "zero"
else -> "nonzero"
}
// Bad
when(x) {
0 -> return "zero"
else -> return "nonzero"
}
Kotlin
복사
when 대신 if
•
바이너리 조건에 대해 when 대신 if를 사용하라
•
when 은 분기가 3개 이상인 경우 사용하는 것이 좋다.
// Good
if (x == null) ... else ...
// Bad
when (x) {
null -> // ...
else -> // ...
}
Kotlin
복사
조건에서 Nullable Boolean
•
조건문에서 nullable Boolean을 사용해야 하는 경우 true나 false로 명시적으로 비교하라.
•
명시적으로 비교해야 null에 안전하게 사용할 수 있다.
if (value == true)
if (value == false)
Kotlin
복사
반복문
•
반복문을 사용할 때는 filter, map 같은 고차 함수를 사용하라.
•
단, forEach는 nullable하거나 해당 함수가 긴 호출 체인의 일부로 상요된다면 전통적인 for문을 사용하라.
범위의 반복
•
..< 연산자를 사용하여 개방형 범위를 반복하라.
for (i in 0..n - 1) { /*...*/ } // bad
for (i in 0..<n) { /*...*/ } // good
Kotlin
복사
문자열
•
문자열 연결보다 문자열 템플릿을 사용하라.
•
일반 문자열 리터럴에 이스케이프 시퀀스를 내장하는 것보다 다중 줄 문자열을 사용하는 것이 좋다.
•
다중 줄 문자열에서 들여쓰기를 유지하려면 trimIndent(), trinMargin()을 사용하라.
◦
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)
Kotlin
복사
함수 vs 프로퍼티
•
몇몇 경우에 매개변수가 없는 함수는 read-only 프로퍼티와 서로 호환된다.
•
서로 문법은 비슷하지만 둘 중 어느 하나를 사용해야 하는 스타일적인 규칙이 존재한다.
•
함수보다 프로퍼티를 사용해야 하는 경우
◦
예외를 던지지 않는 경우
◦
계산 비용이 적거나 캐시되는 경우
◦
상태가 변경되지 않는 경우
//함수
class Circle(private val radius: Double) {
fun getArea(): Double {
return Math.PI * radius * radius
}
}
//프로퍼티
class Circle(private val radius: Double) {
val area: Double
get() = Math.PI * radius * radius
}
Kotlin
복사
확장 함수
•
확장 함수를 적극적으로 사용하라.
•
주로 객체에서 작동하는 함수가 있을 때마다 해당 객체를 수신자로 허용하는 확장 함수로 만드는 것을 고려하라.
•
API 오염을 최소화하려면 확장 함수의 가시성을 합리적인 한에서 제한하라.
•
필요에 따라 로컬 확장 함수, 멤버 확장 함수 또는 비공개 가시성이 있는 최상위 확장 함수를 사용하라.
//로컬 확장 함수
fun someFunction() {
fun String.localReverse(): String {
return this.reversed()
}
val result = "Kotlin".localReverse()
}
//멤버 확장 함수
class Example {
fun String.memberReverse(): String {
return this.reversed()
}
}
//최상위 확장 함수
private fun String.topLevelReverse(): String {
return this.reversed()
}
Kotlin
복사
중위 함수
•
비슷한 역할을 하는 두 객체가 있을 땐 infix 키워드로 함수를 정의하라.
◦
좋은 예 : and, to, zip
◦
나쁜 예 : add
•
infix 메서드가 수신 객체를 수정한다면 정의하지마라.
infix fun Int.isEqualTo(other: Int): Boolean {
return this == other
}
fun main() {
val result = 5 isEqualTo 5
println(result) // 출력: true
val result2 = 5 isEqualTo 10
println(result2) // 출력: false
}
Kotlin
복사
팩토리 함수
•
클래스에 팩토리 함수를 선언하는 경우 클래스 자체와 동일한 이름을 지정하지 마라.
•
고유한 이름을 사용하여 팩토리 함수의 동작이 특별한 이유를 명확히 하는 것이 좋다.
•
특별한 의미가 없는 경우에만 클래스와 동일한 이름을 사용할 수 있다.
class Point(val x: Double, val y: Double) {
companion object {
fun fromPolar(angle: Double, radius: Double) = Point(...)
}
}
Kotlin
복사
•
서로 다른 슈퍼클래스 생성자를 호출하지 않고 기본 인수 값을 가진 단일 생성자로 줄일 수 없는 여러 개의 오버로드된 생성자가 있는 객체인 경우 오버로드된 생성자를 팩토리 함수로 대체하는 것이 좋다.
플랫폼 타입
•
플랫폼 타입의 표현식을 반환하는 공개 함수/메서드는 Kotlin 타입을 명시적으로 선언해야 한다.
fun apiCall(): String = MyJavaApi.getProperty("name")
Kotlin
복사
•
플랫폼 타입의 표현식으로 초기화된 모든 프로퍼티(패키지 수준 또는 클래스 수준)은 해당 Kotlin 타입을 명시적으로 선언해야 한다.
class Person {
val name: String = MyJavaApi.getProperty("name")
}
Kotlin
복사
•
플랫폼 타입의 표현식으로 초기화된 로컬 변수는 타입 선언이 있을 수도 있고 없을 수도 있다.
fun main() {
val name = MyJavaApi.getProperty("name")
println(name)
}
Kotlin
복사
스코프 함수
•
Kotlin은 주어진 객체의 컨텍스트에서 코드 블록을 실행하는 함수 집합을 제공한다.
// let
fun main() {
val name: String? = "Kotlin"
name?.let {
println("The name is $it")
}
}
// run
fun main() {
val result = "Kotlin".run {
length * 2
}
println("The result is $result")
}
// with
fun main() {
val person = Person("Alice", 30)
val description = with(person) {
"$name is $age years old"
}
println(description)
}
data class Person(val name: String, val age: Int)
// apply
fun main() {
val person = Person("Alice", 30).apply {
name = "Bob"
age = 25
}
println(person)
}
data class Person(var name: String, var age: Int)
// also
fun main() {
val number = 10.also {
println("The number is $it")
}
println("The final number is $number")
}
Kotlin
복사
라이브러리에 대한 코딩 규칙
•
라이브러리를 작성할 때 API 안정성을 보장하기 위해 다음과 같은 추가 규칙을 따르는 것이 좋다.
◦
항상 멤버 가시성을 명시적으로 지정하라. (실수로 선언을 공개 API로 노출하는 것을 방지하기 위해)
◦
항상 함수 반환 타입과 프로퍼티 타입을 명시적으로 지정하라. (구현이 변경될 때 반환 타입이 실수로 변경되는 것을 방지하기 위해)
◦
새로운 문서가 필요하지 않은 오버라이드를 제외한 모든 공개 멤버에 대해 KDoc 주석을 제공한다. (라이브러리에 대한 문서 생성을 지원하기 위함)