본문 바로가기
프로그래밍/Kotlin

Kotlin - 코딩 관습(Coding Conventions)

by pentode 2019. 6. 9.

Kotlin을 Java, C, C++, PHP, C#등의 언어만 봐온 상태에서 배워보려니까 상당히 생소한 문법 형태가 많이 나오는것 같습니다. 코틀린 문법에 대해서 전혀 모르는 상태에서 이 코딩 컨벤션 문서를 보면서 문법을 모르면 이해하기 힘든 것들이 많이 있었습니다. 하지만 그냥 무시하고, 문서를 봤습니다. 다른 방법이 없어서 그랬다는게 맞겠죠.^^ 앞으로 문법을 배워 나가면서 이 문서를 다시보고 수정하던가 해야 겠습니다.

코딩 관습(Coding Conventions)은 띄어쓰기 이름작성시 대소문자등 문법오류와 관계 없으나 많은 사람들이 따르는 일반적인 코드작성규칙을 말합니다. 이런 규칙을 따르는 것이 다른 사람의 코드를 분석하거나 자신이 작성한 코드이더라도 가독성을 높이는 방법이 됩니다. 이 페이지에는 현재 코틀린 언어의 코딩 관습을 포함하고 있습니다.

1. 소스 코드 구성
2. 이름 지정 규칙
3. 서식 지정
4. 문서 주석
5. 불필요한 구성 사용하기 않기
6. 언어의 기능들을 관용적으로 사용하기
7. 라이브러리를 위한 코딩 관습


스타일 가이드 적용하기

스타일 가이드에 따라 이클립스(eclipse) 포맷터를 구성하려면 코틀린(kotlin) 플러그인을 설치하고, Window -> Preferences -> Kotlin -> Code style에서 Code style을 "Kotlin style guide"를 선택합니다.(이클립스에 코틀린 플러그인을 설치하는 방법은 다음글 "Eclipse에 Kotlin(코틀린) 플러그인 설치 및 간단한 프로그램 실행해보기"를 참조하세요.)(원본글에는 Intellij 에서 스타일 가이드를 적용하는 방법이 나와 있습니다.)


1. 소스 코드 구성

- 디렉토리 구조

코틀린과 자바를 같이 사용하는 프로젝트에서 Kotlin 소스 파일은 Java 소스 파일과 동일한 소스 루트에 있어야 하며 동일한 디렉토리 구조를 따라야 합니다(각 파일은 각 패키지 명령문에 해당하는 디렉토리에 저장되어야 합니다).

순수 Kotlin 프로젝트에서 권장되는 디렉토리 구조는 공통 루트 패키지가 생략된 패키지 구조를 따르는 것입니다(예 : 프로젝트의 모든 코드가 "org.example.kotlin" 패키지 및 하위 패키지에 있는 경우 "org.example.kotlin" 패키지는 소스 루트 바로 아래에 있어야 하고 "org.example.kotlin.foo.bar"의 파일은 소스 루트의 "foo/bar" 서브 디렉토리에 있어야 합니다.


- 소스 파일 이름

Kotlin 파일에 하나의 클래스(관련된 최상위 수준 선언이 포함될 수 있음)가 포함된 경우 해당 이름은 클래스의 이름과 동일해야 하며 .kt 확장자가 사용되어야 합니다. 파일에 여러 클래스 또는 최상위 레벨 선언만 있으면 파일에 포함된 것을 설명하는 이름을 선택하고 이에 따라 파일의 이름을 지정합니다. 카멜 표기법을 사용하고 첫 글자는 대문자 로 사용합니다(예: ProcessDeclarations.kt).

파일의 이름은 파일의 코드가 수행하는 작업을 설명해야합니다. 따라서 파일 이름에 "Util"과 같은 의미없는 단어를 사용하지 마십시오.


- 소스 파일 구성

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

특히, 이 클래스의 모든 클라이언트와 관련된 클래스에 대한 확장 기능을 정의할 때는 클래스 자체가 정의된 동일한 파일에 배치하십시오. 특정 클라이언트에 대해서만 의미가 있는 확장 기능을 정의할 때는 해당 클라이언트의 코드 다음에 확장 코드를 둡니다. "모든 Foo의 확장"을 보관하기 위한 파일을 만들지 마십시오.


- 클래스 레이아웃

일반적으로 클래스의 내용은 다음 순서로 정렬됩니다.

+ 속성 선언 및 초기화 블록
+ 보조 생성자
+ 메소드 선언
+ 동반자 객체

메서드 선언을 사전순 또는 가시성(visibility)에 따라 정렬하지 말고 일반 메서드와 확장 메서드를 분리하지 마십시오. 대신, 연관된 항목을 모아서 클래스를 위에서 아래로 읽는 사람이 무슨 일이 일어나고 있는지 논리를 따라갈 수 있도록 하십시오. 순서를 선택하고 선택한 순서를 지키도록 노력 하십시오(더 높은 수준의 항목을 먼저 또는 그 반대로).

중첩 클래스를 해당 클래스를 사용하는 코드 옆에 넣으십시오. 클래스가 외부에서 사용되도록 의도되어 있고 클래스 내부에서 참조되지 않는 경우에는 동반 개체 뒤의 끝에 넣으십시오.


- 인터페이스 구현 레이아웃

인터페이스를 구현할 때 구현 멤버를 인터페이스 멤버와 동일한 순서로 유지합니다(필요한 경우 구현에 사용되는 추가 private 메서드가 배치될 수 있음).


- 오버로드 레이아웃

클래스 내에서 항상 오버로드된 되는 메서드 다음에 둡니다.


2. 이름 지정 규칙

Kotlin은 Java 이름 지정 규칙을 따릅니다.

특히, 패키지 이름은 항상 소문자이며 밑줄은 사용하지 마십시오(org.example.myproject). 여러 단어로된 이름을 사용하는 것은 일반적으로 권장되지 않지만 여러 단어를 사용해야 할 경우 간단히 붙여서 사용하거나 카멜 표기법을 사용할 수 있습니다(org.example.myProject).

클래스와 객체의 이름은 대문자로 시작하고 카멜 표기법을 사용합니다.

open class DeclarationProcessor { ... } 

object EmptyDeclarationProcessor : DeclarationProcessor() { ... } 


- 함수 이름

함수, 속성 및 지역 변수의 이름은 소문자로 시작하고 카멜 표기법을 사용합니다. 밑줄은 사용하지 않습니다.

fun processDeclarations() { ... } 
var declarationCount = ... 

예외 : 클래스의 인스턴스를 생성하는데 사용되는 팩토리 함수는 생성되는 클래스와 동일한 이름을 가질 수 있습니다.

abstract class Foo { ... } 

class FooImpl : Foo { ... } 

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


- 테스트 메소드 이름

테스트에서 (그리고 테스트에서만) 백틱(`)으로 둘러싸서 메소드 이름에 공백을 사용하는 것이 허용됩니다.(이러한 메소드 이름은 현재 Android 런타임에서 지원하지 않습니다.) 메소드 이름의 밑줄도 테스트 코드에서 사용할 수 있습니다.

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

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


- 속성 이름

상수의 이름 (const로 표시된 속성이나 최상위(top-level)변수 또는 커스텀 get함수를 가지지 않는 object의 val로 선언된 속성 처럼 불변의 데이터)은 밑줄로 분리되는 대문자를 사용합니다.

const val MAX_COUNT = 8 
val USER_NAME_FIELD = "UserName" 


행위(behavior) 또는 상수를 가지는 객체를 가지는 최상위(top-level) 또는 object 속성의 이름은 일반적인 카멜 표기법을 사용합니다.

val mutableCollection: MutableSet = HashSet() 


싱글톤 객체에 대한 참조를 가지는 속성의 이름은 object선언과 동일한 명명 스타일을 사용할 수 있습니다.

val PersonComparator: Comparator = ... 


열거형 상수(enum class Color { RED, GREEN })의 경우 밑줄로 구분되는 대문자 이름 또는대문자로 시작하는 일반적인 카멜 표기법 이름을 사용할 수 있습니다.


- 후위 속성(backing properties)의 이름

클래스에 개념적으로는 동일하지만 하나는 public API의 일부이고, 다른 하나는 상세 구현으로 private 일 경우, private 속성의 이름에는 접두사로 밑줄(_)을 사용합니다.

class C { 
    private val _elementList = mutableListOf() 

    val elementList: List 
         get() = _elementList 
} 


- 좋은 이름 선택하기

클래스의 이름은 보통은 명사 또는 클래스가 무엇인지 설명하는 명사구이다 이다: List, PersonReader.

메서드의 이름은 일반적으로 동사 또는 메서드가 무엇을 하려는지 말하는 동사 구문입니다: close, readPersons. 메서드는 메서드가 개체를 변형 시키거나 새 개치를 반환하는지 알려줘야 합니다. 예를 들어 sort는 컬렉션 그 자체를 정렬하는 것이고, sorted는 컬렉션의 정렬된 복사본을 반환하는 것입니다.

이름은 엔티티의 목적이 무엇인지 분명히 해야합니다. 그래서 이름에 Manageer, Wrapper등의 의미없는 단어를 사용하지 않는것이 좋습니다.

약어 이름을 선언 이름의 일부로 사용하는 경우, 그 약어가 두개의 문자로 구성되는 경우 대문자로 시작합니다(IOStream). 약어가 두자 이상으로 길면, 첫 글자만 대문자로 합니다(XmlFormatter, HttpInputStream).


3. 서식 지정(Formatting)

대부분의 경우 Kotlin은 자바 코딩 관습을 따릅니다.

들여쓰기는 공백 4개를 사용합니다. 탭을 사용하지 않습니다.

중괄호(curly braces)의 경우, 문장 행의 끝에 여는 괄호를 사용하고, 닫는 괄호는 별도의 라인에 여는 괄호가 있는 문장의 시작 열의 위치에 둡니다.

if (elements != null) { 
    for (element in elements) { 
        // ... 
    } 
} 


(의미 설명: Kotlin에서 세미콜론은 선택사항입니다. 그러므로 줄바꿈이 중요합니다. 언어의 디자인이 자바 스타일의 괄호를 사용하므로 다른 스타일을 사용하면 다를 결과가 생길 수 있습니다.


- 수평 공백(Horizontal whitespace)

이항 연산자 주위에 공백을 넣습니다(a + b). 예외: 범위 연산자 주위에는 공백을 넣지 않습니다(0..i).

단항 연산자 주위에는 공백을 넣지 않습니다(a++).

흐름 제어 키워드(if, when, for and while)와 대응되는 여는 괄호 사이에 공백을 둡니다.

주 생성자 선언, 메서드 선언 또는 메서드 호출에서 여는 괄호 앞에 공백을 넣지 않습니다.

class A(val x: Int) 

fun foo(x: Int) { ... } 

fun bar() { 
    foo(1) 
} 


(, [, or before ], ) 다음에는 절대로 공백을 넣지 않습니다.

. 또는 ?.: foo.bar().filter { it > 2 }.joinToString(), foo?.bar() 주위에는 절대로 공백을 넣지 않습니다.

단일행 주석 // 뒤에는 공백을 넣습니다: // This is a comment

유형 매개변수를 지정하는데 사용되는 꺽쇠 괄오 주위에는 공백을 넣지 않습니다: class Map<K, V> { ... }

:: 주위에는 공백을 넣지 않습니다: Foo::class, String::length

Nullable 유형을 표시하는데 사용된 ? 앞에는 공백을 넣지 않습니다: String?

일반적으로 모든 종류의 수평 맞춤(Horizontal alignment)을 피합니다. 식별자의 이름을 다른 길이로 변경하는 것은 선언이나 사용법의 형식에 영향을 주지 않습니다.


- 콜론(:)

다음과 같은 경우에 콜론(:) 앞에 공백을 넣습니다:

- 타입과 수퍼타입을 분리하는데 사용될때.
- 수퍼클래스 생성자 또는 같은 클래스의 다른 생성자에 위임할때.
- object 키워드 뒤에.

콜론이 선언과 그것의 타입을 분리하는데 사용될 때 콜론 앞에 공백을 두지 않습니다.

콜론(:) 뒤에는 항상 공백을 둡니다.

abstract class Foo : IFoo { 
    abstract fun foo(a: Int): T 
} 

class FooImpl : Foo() { 
    constructor(x: String) : this(x) { ... } 
     
    val x = object : IFoo { ... }  
} 


- 클래스 헤더 서식

주 생성자 매개변수가 몇개 밖에 되지 않는 클래스는 한줄에 작성할 수 있습니다.

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(), 
    SomeOtherInterface, 
    AndAnotherOne { 

    fun foo() { ... } 
} 


클래스 헤더가 길 때 클래스 헤더와 본문을 명확하게 구분하려면 클래스 헤더 다음에 빈 줄을 넣거나 (위 예에서 처럼) 여는 중괄호를 별도의 줄에 넣습니다.

class MyFavouriteVeryLongClassHolder : 
    MyLongHolder(), 
    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 
companion 
inline 
infix 
operator 
data 


모든 아노테이션은 수정자 앞에 둡니다:

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


라이브러리에서 작업하지 않는한 중복 수정자를 생략합니다(예: public).


- 아노테이션 서식

아노테이션은 일반적으로 아노테이션이 붙여질 선언 이전에 같은 들여 쓰기로 별도의 줄에 위치합니다.

@Target(AnnotationTarget.PROPERTY) 
annotation class JsonExclude 


인수가 없는 아노테이션은 여러개를 같은 행에 위치할 수 있습니다.

@JsonExclude @JvmField 
var x: String 


인수가 없는 단일 아노테이션은 선언과 같은 행에 위치할 수 있습니다.

@Test fun foo() { ... } 



- File 아노테이션

파일 어노테이션은 파일 주석 다음에(있는 경우), package명령문 앞에 놓이며 package와 빈행으로 구분됩니다(패키지가 아닌 파일을 대상으로 한다는 사실을 강조하기 위해).

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

package foo.bar 



- 함수 서식

함수 서명(signature)이 한 줄에 들어 가지 않으면 다음 구문을 사용하십시오.

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) = 
    x.length 


- 속성 서식

매우 간단한 읽기 전용 속성의 경우 한 줄 형식을 사용합니다.

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


보다 복잡한 속성의 경우 항상 키워드 get와 set키워드를 별도의 줄에 입력합니다.

val foo: String 
    get() { ... } 


이니셜라이저가 있는 속성의 경우 이니셜라이저가 길면 등호 다음에 줄 바꿈을 추가하고 이니셜 라이저를 네 자리까지 들여 씁니다.

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문장에서 실행 부분이 한 라인 이상일 경우 인접한 실행 블록을 빈줄로 분리합니다.

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 } 


연결된 호출에서 첫 번째 호출에는 보통 은 앞에 줄 바꿈이 있어야 하지만, 없는게 더 이해하기 쉽다면 빼도 됩니다.


- 람다 (Lambda) 서식

람다 식에서는 매개 변수를 본문에서 분리하는 화살표 주위뿐만 아니라 중괄호 주위에 공백을 사용해야 합니다. 호출이 매개변수로 람다 하나를 받는다면, 람다는 괄호 밖에 적을 수 있습니다(맨 마지막 인자가 람다이던가, 인자가 람다 하나뿐이던가 하는 경우)(람다의 매개변수가 하나뿐이고, 그 타입을 컴파일러가 추론 가능한경우 it 을 바로 쓸 수 있습니다).

{ x: Int, y: Int -> x + y } 

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



4. 문서 주석(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) = ... 



5. 불필요한 구성 사용하지 않기

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

- Unit

함수가 Unit을 반환하면 반환 유형을 생략해야 합니다.

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

} 


- 세미콜론(;)

가능하다면 세미콜론을 생략합니다.

- 문자열 템플릿

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

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



6. 언어의 기능들을 관용적으로 사용하기

- 불변성

변경할 수 없는 데이터를 사용하는것을 선호합니다. 로컬 변수와 속성이 초기화된 후에 수정되지 않는다면 항상 var 보다는 val로 선언합니다.

항상 불변 컬렉션 인터페이스 (사용하여 Collection, List, Set,를 Map돌연변이되지 수집 선언). 팩토리 함수를 사용하여 콜렉션 인스턴스를 생성 할 때는 가능한 경우 불변 콜렉션 유형을 반환하는 함수를 사용하십시오.
변경되지 않은 컬렉션을 정의하는데는 항상 불변의 컬렉션 인터페이스(Collection, List, Set, Map)를 사용하십시오. 팩토리 함수를 사용하여 컬렉션 인스턴스를 생성할 때는 가능하면 항상 불변의 컬렉션 유형을 반환합니다.

// Bad: use of mutable collection type for value which will not be mutated 
fun validateValue(actualValue: String, allowedValues: HashSet) { ... } 

// Good: immutable collection type used instead 
fun validateValue(actualValue: String, allowedValues: Set) { ... } 

// Bad: arrayListOf() returns ArrayList, which is a mutable collection type 
val allowedValues = arrayListOf("a", "b", "c") 

// Good: listOf() returns List 
val allowedValues = listOf("a", "b", "c") 


- 기본 매개변수 값

오버로드 함수를 선언하는 것보다 기본 매개변수 값이 있는 함수를 선언하는 것을 선호합니다.

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

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


- 타입 별칭(Type aliases)


코드내에서 여러 번 사용되는 함수 타임 또는 타입 매개변수가 있는 경우 해당 타입 별칭을 정의하는 것을 선호합니다.

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


- 람다(Lambda) 매개변수

짧고 중첩되지 않은 람다에서는 매개변수를 명시적으로 선언하는 대신 it 을 사용하는 것이 좋습니다. 매개변수가 있는 중첩되는 람다에서는 매개변수를 항상 명시적으로 선언해야 합니다.


- 람다(lambda)에서의 반환

람다에 라벨이 여러 개 붙어 있는 리턴을 사용하지 마십시오. 람다에 하나의 출구를 갖도록 구조를 조정하는것을 고려하십시오. 만약 그것이 가능하지 않거나 충분히 명확하지 않다면, 람다를 익명 함수로 변환하는 것을 고려합니다.

람다에서 마지막 문장에 대해 라벨로 표시된 반환을 사용하지 마십시오.


- 명명된 인수

모든 매개변수의 의미가 문맥에서 완전히 명확하지 않은 한 메서드가 동일한 원시 타입의 여러 매개변수를 가지거나 또는 부울 유형의 매개변수를 취할 때 명명된 인수 구문을 사용하십시오.

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


- 조건문의 사용

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 대 when

양자택일 조건(binary condition)에서는 when 보다 if 를 선호합니다.

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


위에 문장 대신에 if (x == null) ... else ...를 사용합니다.

세개 이상의 옵션을 가지는 경우 if 보다 when 을 선호 합니다.


- 조건에 null 가능한(nullable) Boolean 값 사용

조건문에 null 가능한(nullable) Boolean 값을 을 사용해야 하는 경우 if ( value == true) 또는 if (value == false) 검사를 사용하십시오.


- 반복문(loop) 사용하기

반복문에는 고차함수(filter, map etc)를 사용하는게 좋습니다. 예외: forEach( forEach의 수신자(receiver)가 nullable이거나 forEach가 긴 호출 연쇄의 한 부분으로 사용된것이 아니라면 일반적인 for 루프를 사용하는것이 선호됩니다).

※ 고차함수(higher-order function) : 하나 이상의 함수를 인수로 취하거나, 함수를 결과로 반환하는 함수.
※ 수신자(reciever) : Kotlin에 receiver라는 개념이 있네요. 찾아 봤지만 잘 모르겠습니다. 배워나가 면서 알아 봐야 겠습니다.T.T

복잡한 표현식에서 다중의 복잡한 고차함수를 사용할지 루프를 사용하지를 선택할때 실행에드는 비용(cost)과 성능 문제를 고려해야 합니다.


- 범위를 사용하는 반복문

루프에서 끝 범위가 하나 작을때 until을 사용합니다.

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


※ closed-range : (i in 1..5) - 의미는 1 <= i 이면서 i >= 5 입니다.
※ half-open range : (i in 1 until 5) - 의미는 1 <= i 이면서 i > 5 입니다.

- 문자열 사용하기

문자열 연결보다 문자열 템플릿을 사용하는것을 선호합니다.

다중라인 문자열을 사용하는것이 일반 문자열 리터를에 \n 이스케이프 시퀀스를 넣는것보다 선호됩니다.

다중라인 문자열에서 결과 문자열이 내부에서 들여쓰기가 필요하면 trimIndent를 사용하고, 필요하지 않으면 trimMargin을 사용합니다.

assertEquals( 
    """ 
    Foo 
    Bar 
    """.trimIndent(),  
    value 
) 

val a = """if(a > 1) { 
          |    return a 
          |}""".trimMargin() 


- 함수 vs 속성

경우에 따라 인수가 없는 함수는 읽기 전용 속성과 호환될 수 있습니다. 비록 의미는 유사하지만, 둘중에 하나를 선택하는 양식적인 관습이 있습니다.

아래의 알고리즘일 경우 함수보다 속성을 선호합니다.

- throw 하지 않는다.
- 계산이 쉽다(또는 첫번째 실행에서 캐쉬됨)
- 객체의 상태가 변경되지 않는다면 반복 호출에 대해서 동일한 결과를 반환한다.


- 확장함수 사용하기

확장함수를 자유롭게 사용하십시오. 항상 하나의 객체를 대상으로 주된 작업을 하는 함수를 가지고 있다면, 그 객체를 수신자(receiver)로 받아들이는 확장 함수를 고려하십오. API의 오염을 최소화 하기 위해 확장 항수의 가시성을 필요한 범위내로 제한 합니다. 필요에 따라 로컬 확장 함수, 멤버 확장 함수 또는 private 가시성을 가진 최상위(top-level) 확장 함수를 사용하십시오.


- infix 함수의 사용

유사한 역할을 수행하는 두 객체에서만 infix 함수를 선언하십시오. 좋은예: and, to, zip. 나쁜예: add.

수신자(receiver)객체를 변경하는 함수는 infix 로 선언하면 안됩니다.

※ infix 표기법 : Kotlin에서 어떤 함수는 . 이나 () 없이 호출되어 지는데, 이것을 infix 메서드라고 부릅니다.


- 팩토리(factory) 함수

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

예:

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


여러 개의 오버로드된 생성자가 있는 객체가 서로 다른 수퍼 클래스 생성자를 호출하지 않고 기본 인수 값을 가진 단일 생성자로 축소할 수 없는 경우, 오버로드된 생성자를 팩토리 함수로 대체하는 것이 좋습니다.


- 플랫폼 타입(Platform types)

플랫폼 타입의 표현(expression)을 반환하는 public function/method는 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 함수 apply/with/run/also/let 사용

코 틀린 주어진 개체의 컨텍스트에서 코드의 블록을 실행하는 다양한 기능을 제합니다(let, run, with, apply, also). 필요한 경우에 적합한 Scop 함수를 선택하는 방법에 대한 지침은 범위 기능을 참조하십시오.



7. 라이브러리를 위한 코딩 관습

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

- 항상 멤버 가시성을 명시적으로 지정하십시오(선언을 실수로 공개 API에 노출시키지 않도록).
- 항상 함수 반환 형식 및 속성 형식을 명시적으로 지정하십시오(구현이 변경 될 때 실수로 반환 형식을 변경하지 않도록).
- 새로운 문서를 필요로 하지 않는 재정의를 제외하고 모든 public 멤버에 대한 KDoc 코멘트 제공하십시오(라이브러리에 대한 문서 생성 지원).

출처 : https://kotlinlang.org/docs/reference/coding-conventions.html

반응형