Skip to content

Android Code Convention

SeungWon edited this page Sep 24, 2024 · 1 revision

Kotlin 스타일 가이드  |  Android Developers

[Coding conventions | Kotlin](https://kotlinlang.org/docs/coding-conventions.html)

1. 소스 코드 구성(Source code organization)

✨ 팀프로젝트에서 팀원의 코드 리뷰는 항상 수반되기 때문에 “최종, 찐최종, 찐찐최종” 같은 네이밍보다는 해당 기능을 유추할 수 있을 정도의 적절한 네이밍이 바람직하고 더욱 원활한 협업을 위해 어느 정도의 틀이 필요한데 코드 컨벤션이 그 출발로 적절한 것 같습니다.
  • 1.1 디렉토리 구조(Directory structure)

    • Kotlin 소스 파일은 Java 소스 파일과 동일한 소스 루트에 있어야 하며 동일한 디렉토리 구조를 따라야한다.
    • 공통 루트 패키지가 생략된 패키지 구조를 따라야한다.

    공통 패키지: com.example.aboutme → root 디렉토리

    서브 패키지: com.example.aboutme.agit.data → agit/data 디렉토리

    • 모든 소스 파일은 UTF-8로 인코딩 되어야 한다.
  • 1.2 소스 파일 이름(Source file names)

    • Kotlin 파일에 하나의 클래스만 포함된 경우 해당 파일의 이름은 곧 클래스 이름이다.
    • Kotlin 파일에 여러 클래스 또는 최상위 레벨(Top-level) 선언만 포함되어 있으면 파일에 포함된 것들을 설명하는 이름으로 파일 이름을 지정한다.
    • 카멜 표기법(CamelCase)
    • 파일 이름이 곧 코드가 하는 것들이 설명되어야한다. 의미 없는 단어는 사용을 자제한다.
  • 1.3 소스 파일 구성(Source file organization)

    • 서로 연관되어있는 여러 선언들을 하나의 Kotlin 소스 파일에 포함하나, 파일 크기가 너무 크지 않고 수백 줄을 초과하면 안된다.
    • 클래스에 모든 클라이언트와 관련된 클래스의 확장 함수를 넣을 때 클래스 자체에 정의 된 동일한 파일에 이 함수를 넣는 것이 좋다.
    • 특정 클라이언트에 대해서만 의미가 있는 확장 기능을 정의할 때는 해당 클라이언트 코드 옆에 확장 기능을 지정한다.
  • 1.4 클래스 레이아웃(Class layout)

    • 일반적으로 클래스의 내용은 다음 순서로 정렬한다.
    1. 속성 선언 및 Initializer 블록
    2. 보조 생성자(Secondary constructors)
    3. 메소드 선언(Method declarations)
    4. 동반자 객체(Companion Object)
    • 메소드 선언은 사전 순 또는 가시성에 따라 정렬하지 말고, 일반 메소드와 확장 메소드를 분리하지 않는다.
    • 연관된 메소드끼리 함께 위치하도록 구성해야 한다. 다른 사람이 봤을 때 로직을 이해하기 쉽도록 배치한다.
    • 중첩 클래스는 해당 클래스를 사용하는 코드 다음에 배치한다.
    • 중첩 클래스가 외부에서만 사용하도록 의도되어 있고, 클래스 내부에서 참조되지 않은 경우 Companion Object(동반자 객체) 뒤의 끝에 넣는다.
  • 1.5 인터페이스 구현 레이아웃(Interface implementation layout)

    • 인터페이스를 구현할 때 구현 멤버들의 순서를 인터페이스 멤버와 동일한 순서로 유지합니다.
    • 필요한 경우, 구현에 사용되는 privated method를 배치한다.
  • 1.6 오버로드 레이아웃(Overload layout)

    • 클래스 내에서 항상 오버로드된 메서드를 함께 둔다.

2. 이름 지정 규칙(Naming rules)

  • Java naming convention을 따름
  • Package → 항상 소문자
  • Class, Object: 카멜 표기법 사용
object EmptyDeclarationProcessor : DeclarationProcessor() { ... }
  • 2.1 함수 이름(Function names) ✨

    • 함수, 속성,및 지역 변수의 이름은 소문자로 시작하여 카멜 표기법을 사용하며, 밑줄은 사용하지 않는다.
    fun processDeclarations() { /*...*/ }
    var declarationCount = 1
    • 클래스의 인스턴스를 만드는데 사용되는 팩토리 함수는 생성되는 클래스 이름과 동일할 수 있다.
    interface Foo { /*...*/ }
    
    class FooImpl : Foo { /*...*/ }
    
    fun Foo(): Foo { return FooImpl() }
  • 2.2 테스트 메소드 이름(Names for test methods)

    • 테스트의 코드의 경우 메소드에 backtick(’), space, underscore(_)를 사용할 수 있다.
    class MyTestCase {
         @Test fun `ensure everything works`() { /*...*/ }
    
         @Test fun ensureEverythingWorks_onAndroid() { /*...*/ }
    }
  • 2.3 속성 이름(Property names) ✨

    • 상수: 대문자와 _(underscore) 사용 가능
    const val MAX_COUNT = 8  
    val USER_NAME_FIELD = "UserName"
    • 변경 가능한 데이터를 가진 객체: 카멜 표기법
    val mutableCollection: MutableSet<String> = HashSet()
    • 싱글톤 객체: 객체 선언과 동일하게 사용
    val PersonComparator: Comparator =
    • enum 상
    enum class Color { RED, GREEN }
  • 2.4 (Names for backing properties)

    • 클래스가 개념상으로 동일하지만 하나는 public API의 일부이고, 다른 하나는 구현 세부 정보가 있는 private property 이름은 접두사로 _(underscore)를 사용한다.
    • private 속성을 반환하는 public 속성을 사용하는 경우, private 속성 앞에 _(underscore)를 붙여 사용한다.
    class C {
        private val _elementList = mutableListOf<Element>()
    
        val elementList: List<Element>
             get() = _elementList
    }
  • 2.5 좋은 이름 선택(Choosing good names) ✨

    • 클래스

    → 무엇을 하는 클래스인지 의미를 표현,

    → 명사 또는 명사구의 이름을 사용(List, PersonReader)

    • 메소드

    → 어떤 변화 또는 객체의 반환 작업을 하는 메소드인지 표현

    → 동사 또는 동사구의 이름을 사용(close, readPersons)

    • 선언 이름의 일부가 약어인 경우

    → 약어가 두 문자인 경우는 대문자로 표기(IOStream)

    → 더 긴 경우는 첫번째 문자만 대문자로 사용(XmlFomatter, HttpInputStream)

3. 서식 지정(Formatting)

  • 대부분의 경우 Java code convention을 따름
  • 4 spaces 사용, 탭은 사용하지 않는다.
  • 중괄호의 경우 구성 시작 부분의 끝줄에 여는 중괄호를 입력하고 열린 구성과 수직적으로 정렬된 별도의 줄을 닫는 중괄호를 입력한다.
  • 세미콜론은 선택사항이지만 생략하는 것을 권장
if (elements != null) {
    for (element in elements) {
        // ...
    }
}
  • 3.1 가로 공백(Horizontal whitespaces) ✨

    • 연산자 앞, 뒤에 공백을 추가한다.
    • a + b, a / b
    • 예외
    • “range to” 연산 주위에도 공백을 넣지 말아야 한다. (0..I)
    • 단항 연산 주위에도 공백을 넣지 말아야 한다. (a++)
    • 중괄호 앞에 공백을 추가한다.
    • if, when, for and while
    • 괄호 앞에는 공백을 추가하지 않는다.
    class A(val x: Int)
    
    fun foo(x: Int) { ... }
    
    fun bar() {
        foo(1)
    }
    • (, [ 뒤에 또는 ), ] 전에 공백을 추가하지 않는다.
    • . 또는 ?. 주위에 공백을 추가하지 않는다.
    foo.bar().filter { it > 2 }.joinToString()  
    foo?.bar()
    • 주석 // 뒤에 공백을 추가한다.
    // 주석을 추가한다.
  • 3.2 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 { /*...*/ }
    }
  • 3.3 Class header formatting ✨

    • 기본 생성자의 매개 변수가 몇 개 있는 클래스는 한 줄로 작성할 수 있다.
    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<MyFavouriteVeryLongClass>(),
        SomeOtherInterface,
        AndAnotherOne {
    
        fun foo() { /*...*/ }
    }
    • 클래스 헤더가 길 때 클래스 헤더와 본문을 명확하게 구분하려면 클래스 헤더 다음에 빈 줄을 넣거나 여는 중괄호를 별도의 줄에 넣는다.
    class MyFavouriteVeryLongClassHolder :
        MyLongHolder<MyFavouriteVeryLongClass>(),
        SomeOtherInterface,
        AndAnotherOne
    {
        fun foo() { /*...*/ }
    }
    • 생성자의 매게 변수에는 일반 들여 쓰기(4 공백)을 사용한다.
    • 이유 : 기본 생성자에서 선언 된 속성이 클래스 본문에 선언 된 속성 만큼 들여 쓰기가 되도록 해야한다.
  • 3.4 수정자(Modifiers) ✨

    • 선언에 여러 수정자가 있는 경우 항상 다음 순서로 입력해야한다.
    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) 를 생략한다
  • 3.5 Annotation formatting

    • 어노테이션은 일반적으로 별개의 줄에 붙는다. 단, 어노테이션이 첨부 된 문장과 같은 들여쓰기가 있어야한다.
    @Target(AnnotationTarget.PROPERTY)
    annotation class JsonExclude
    • 인수가 없는 어노테이션은 같은 줄에 배치할 수 있다.
    @JsonExclude @JvmField
    var x: String
    • 인수가 없는 단일 어노테이션은 해당 선언과 동일한 줄에 배치할 수 있다.
    @Test fun foo() { /*...*/ }
  • 3.6 File annotation

    • 파일 어노테이션은 package 명령문 앞에 배치되며 빈 줄이 있는 package 와 구분한다. (package가 아닌 파일을 대상으로 한다는 사실을 강조하기 위함)
    /** License, copyright and whatever */
    @file:JvmName("FooBar")
    
    package foo.bar
  • 3.7 Functions formatting ✨

    • 함수 시그니쳐가 한 줄에 들어가지 않으면 다음 구문을 사용한다.
    fun longMethodName(
        argument: ArgumentType = defaultValue,
        argument2: AnotherArgumentType,
    ): ReturnType {
        // body
    }
    • 함수 매개 변수에 일반 들여 쓰기(4공백)을 사용하도록 한다.
    • 생성자와 매개 변수와의 일관성을 두기 위함
    • 하나의 표현식으로 구성된 본문이 있는 함수에 표현식 본문을 사용하는 것이 좋다.
    fun foo(): Int {     // bad
        return 1
    }
    
    fun foo() = 1        // good
  • 3.8 Expression bodies formatting

    • 함수에 선언된 동일한 행에 맞지 않는 표현식 본문이 있다면 첫 행에 =부호를 붙인다.
    • 표현 본문을 4칸만큼 들여쓴다.
    fun f(x: String, y: String, z: String) =
        veryLongFunctionCallWithManyWords(andLongParametersToo(), x, y, z)
  • 3.9 Property formatting

    • 매우 간단한 읽기 전용 속성의 경우 한 줄 형식을 고려하도록 한다.
    val isEmpty: Boolean get() = size == 0
    • 복잡한 속성의 경우 항상 get 및 set 키워드를 별도의 줄에 넣는다.
    val foo: String
        get() { /*...*/ }
    • initializer가 있는 속성의 경우 initializer가 길면 등호 다음에 줄 바꿈을 추가하고 initializer를 4칸만큼 들여 다.
    private val defaultCharset: Charset? =
        EncodingRegistry.getInstance().getDefaultCharsetForPropertiesFiles(file)
  • 3.10 Formatting control flow statements ✨

    • if 또는 when문의 조건이 여러 행인 경우 항상 명령문 본문 주위에 중괄호를 사용한다.
    • 명령문 begin에 상대적인 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 문에서 분기가 단일 행 이상인 경우 case 블록과 빈 행을 구분하도록 한다.
    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
    }
  • 3.11 Method call formatting ✨

    • 긴 인수 목록에서는 여는 괄호 다음에 줄 바꿈을 넣도록 한다.
    • 인수는 4칸 들여쓴다.
    • 같은 줄에 여러 가지 밀접하게 관련된 인수를 그룹화 하도록 한다.
    • 인수 이름과 값을 구분하는 = 기호 주위에 공백을 넣도록 한다.
    drawSquare(
        x = 10, y = 10,
        width = 100, height = 100,
        fill = true
    )
  • 3.12 Chained call wrapping ✨

    • 별도로 묶여있는 변수를 선언할 때는 문자 또는 ?. 연산자 다음 행에 하나의 들여쓰기를 하도록 한다. 체인의 첫 번째 호출은 일반적으로 앞에 줄 바꿈이 있어야 하지만 코드를 생략도 무방합니다.
    val anchor = owner
        ?.firstChild!!
        .siblings(forward = true)
        .dropWhile { it is PsiComment || it is PsiWhiteSpace }
  • 3.13 Lamda formatting

    • 람다식에서 매개 변수를 본문에 분리하는 화살표 주위와 중괄호 주위에 공백을 사용한다.
    • 호출이 단일 람다로 사용되면 가능할 때 마다 괄호 밖으로 전달이 되어야 한다.
    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)
    }
  • 3.14 Trailing commas

    class Person(
        val firstName: String,
        val lastName: String,
        val age: Int, // trailing comma
    )
    • 버전 컨트롤 diff를 더 깔끔하게 만든다.

    → 모든 초점이 변경된 값에 집중된다.

    • 요소를 추가하고 재정렬하는 것이 쉬워진다.

    → 요소를 조작하더라도 콤마를 추가하거나 삭제할 필요가 없다.

    • 코드 생성을 단순화한다.
    enum class Direction {
        NORTH,
        SOUTH,
        WEST,
        EAST, // trailing comma
    }
    fun shift(x: Int, y: Int) { /*...*/ }
    shift(
        25,
        20, // trailing comma
    )
    val colors = listOf(
        "red",
        "green",
        "blue", // trailing comma
    )
    class Customer(
        val name: String,
        val lastName: String, // trailing comma
    )
    class Customer(
        val name: String,
        lastName: String, // trailing comma
    )

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): Int { /*...*/ }

// Do this instead:

/**
 * Returns the absolute value of the given [number].
 */
fun abs(number: Int): Int { /*...*/ }

5. 중복 구성 피하기(Avoiding redundant constructs)

  • 일반적으로 Kotlin의 특정 구문 구조가 선택사항이며 IDE에서 중복으로 강조 표시된 경우 코드에서 생략해야 한다.

  • 명확성을 위해 코드에 불필요한 구문 요소를 남겨두지 않는다.

  • 5.1 Unit

    • 함수가 Unit을 반환하면 반환 유형을 생략해야 한다.
    fun foo() { // ": Unit" is omitted here
    
    }
  • 5.2 Semicolons

    • 가능한 경우 세미콜론을 생략하도록 한다.
  • 5.3 String templates ✨

    • 문자열 템플릿에 간단한 변수를 삽입할 때는 중괄호를 사용하지 않는다.
    • 더 긴 표현식에 대해서만 중괄호를 사용한다.
    println("$name has ${children.size} children")

6. 언어 기능의 관용적 사용(Idiomatic use of language features)

  • 6.1 Immutability

    • 변경할 수 없는 데이터를 가변적으로 사용하는 것이 바람직하다.
    • 초기화 후 로컬 변수와 속성이 수정되지 않으면 항상 var 대신 val을 사용하도록 한다.
    • 변경되지 않는 컬렉션을 선언하려면 항상 불변 컬렉션 인터페이스를 사용한다. (Collection, List, Set, Map)
    • 팩토리 함수를 사용하여 컬렉션 인스턴스를 만들 때는 가능한 불변 컬렉션 유형을 반환하는 함수를 사용한다.
    // 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")
  • 6.2 Default parameter values

    • Overload 된 함수를 선언할 때는 기본 매개 변수 값을 사용하여 함수를 선언하는 것이 좋다.
    // Bad
    fun foo() = foo("a")
    fun foo(a: String) { /*...*/ }
    
    // Good
    fun foo(a: String = "a") { /*...*/ }
  • 6.3 Type aliases

    • 코드베이스에서 여러 번 사용되는 유형의 매개 변수가 있다면 typealias를 사용하는 것이 좋다.
    typealias MouseClickHandler = (Any, MouseEvent) -> Unit
    typealias PersonIndex = Map<String, Person>
  • 6.4 Lamda parameters

    • 짧고 중첩되지 않은 람다에서는 매게 변수를 명시적으로 선언하는 대신 it 규칙을 사용하는 것이 좋다.
    • 매개 변수가 있는 중첩 된 람다에서 매개 변수는 항상 명시적으로 선언되어야 한다.
  • 6.5 Returns in a lambda

    • 람다에서 라벨이 지정된 여러 개의 리턴을 사용하지 않도록 한다.
    • 람다를 재구성하여 단일 리턴을 갖도록 해야 한다.
    • 단일 리턴이 불가능한 경우 익명 함수로 변환하는 것을 고려하도록 한다.
    • 람다에서 마지막 문장에 레이블이 지정된 리턴 값을 사용하지 않도록 한다.
  • 6.6 Named arguments

    • 모든 매개 변수의 의미가 문맥에서 절대적으로 명확하지 않다면 메소드가 동일한 기본 유형의 여러 매개 변수 또는 Boolean 유형의 매개 변수를 사용하는 경우 Named arguments로 개수와 상관없이 원하는 위치에 값을 대입할 수 있다.
    drawSquare(x = 10, y = 10, width = 100, height = 100, fill = true)
  • 6.7 Conditional statements ✨

    • if, 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"
    }
  • 6.8 if or when

    • binary conditions의 경우 when 대신에 if를 사용하는것이 좋다.
    when (x) {
        null -> ...
        else -> ...
    }
    
    if (x == null) ... else ...
    • 세 가지 이상의 옵션이 있는 경우는 when의 사용을 선호한다.
  • 6.9 Nullable Boolean values in conditions

    • 조건문에 Nullable Boolean를 사용해야 하는 경우라면 if (value == true) 또는 if (value == false)를 사용한다.
  • 6.10 Loops

    • higher-order 함수를 루프에 사용하는 것이 좋다.(filter, map 등) 예외 : forEach(forEach 의 수신자가 nullable 이거나 더 긴 체인 호출의 일부로 사용되지 않는 한 일반적으로 for loop를 대신 사용하는 것을 선호한다.
  • 6.11 Loops on ranges

    • until 함수를 사용해서 열린 범위를 반복하도록 한다.
    for (i in 0..n - 1) { /*...*/ }  // bad
    for (i in 0..<n) { /*...*/ }  // good
  • 6.12 Strings

    • 문자열 템플릿을 사용하는 것이 좋다.
    • \n escape sequences 을 사용하는 대신에 다중 행을 사용하는 것을 권장한다.
    • 여러 줄의 문자열에서 들여쓰기를 유지하려면 trimMargin, trimIndent 를 사용한다.
    fun main() {
        println("""
        Not
        trimmed
        text
        """
               )
    
        println("""
        Trimmed
        text
        """.trimIndent()
               )
    
        println()
    
        val a = """Trimmed to margin text:
              |if(a > 1) {
              |    return a
              |}""".trimMargin()
    
        println(a)
    }
    
    // 결과값
    
        Not
        trimmed
        text
        
    Trimmed
    text
    
    Trimmed to margin text:
    if(a > 1) {
        return a
    }
  • 6.13 Functions vs Properties

    • 인수가 없는 함수는 읽기 전용 속성과 호환될 수 있다.
    • 의미는 비슷하지만 서로를 선호할 때는 규칙이 있다.
    • 기본 알고리즘이 다음과 같은 경우 함수에 대한 속성을 선호한다.
    • throw 하지 않는다.
    • 쉬운 계산 또는 첫 번째 실행에서 캐싱된다.
    • 객체 상태가 변경되지 않은 경우 호출에 대한 동일한 결과를 반환한다.
  • 6.14 Extension functions

    • 확장 기능을 자유롭게 사용한다.
    • 주로 객체에 대해 작동하는 함수가 있을 때 마다 객체를 receiver로 받아들이는 확장 함수를 고려해야한다.
    • API 오염을 최소화 하기 위해 확장 함수의 가시성을 가능한 많이 제한해야한다.
    • 필요한 경우 로컬 확장 함수, 멤버 확장 함수 또는 비공개 가시성이 있는 최상위 확장 함수를 사용하도록 한다.
  • 6.15 Infix functions

    • 비슷한 역활을 하는 두 개의 객체에서 작동 할 때만 infix function을 선언한다.
    • 좋은 예 : and, to, zip
    • 나쁜 예 : add
    • 메소드가 receiver object를 변경하면 삽입을 선언하면 안된다.
  • 6.16 Factory functions

    • 클래스에 대한 팩토리 함수를 선언하는 경우 클래스 자체와 동일한 이름을 지정하면 안된다.
    • 고유한 이름을 사용하여 팩토리 함수의 이유를 명확히 해야한다.
    class Point(val x: Double, val y: Double) {
        companion object {
            fun fromPolar(angle: Double, radius: Double) = Point(...)
        }
    }
    • 다른 슈퍼 클래스 생성자를 호출하지 않고 기본 인수값을 사용하여 단일 생성자로 축소할 수 없는 여러 Overload 된 생성자가 있는 경우 Overload 된 생성자를 팩토리 함수로 대체하는 것이 좋다.
  • 6.17 Platform types

    • 플랫폼 유형의 식을 리턴하는 경우 공용 함수/메소드는 Kotlin 유형을 명시적으로 선언해야 한다.
    fun apiCall(): String = MyJavaApi.getProperty("name")
    • 플랫폼 유형의 표현식으로 초기화 된 모든 패키지(패키지 또는 클래스 수준)는 명시적으로 Kotlin 유형을 선언해야한다.
    class Person {
        val name: String = MyJavaApi.getProperty("name")
    }
    • 플랫폼 형식으로 초기화 된 값은 형태 선언을 가질 수도 있고 가지지 않을 수도 있다.
    fun main(args: Array) {
        val name = MyJavaApi.getProperty("name")
        println(name)
    }
  • 6.18 Scope functions apply/with/run/also/let)

    • Kotlin은 주어진 객체의 Context에서 코드 블록을 실행하는 다양한 함수를 제공합니다.

    [Scope functions | Kotlin](https://kotlinlang.org/docs/scope-functions.html)

7. 라이브러리의 코딩 규칙(Coding conventions for libraries)

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