코틀린의 코딩 컨벤션

 

코딩 컨벤션 

코틀린의 문법을 사용하면서 코딩 컨벤션을 제대로 준수하고 있는지 알아보는 시간을 가졌습니다.

 

코딩 컨벤션은 각 언어 특성에 맞게 규칙을 적용하여

파일 이름, 줄 바꿈 간격, 변수와 클래스 등 식별자 이름의 규칙을 지정하는 것입니다.

한 프로젝트 내에서 팀으로 일하는 개발자끼리 특정한 도메인 개념들에 대하여 네이밍 컨벤션을 지정하기도 합니다.

 

코딩 컨벤션을 활용할 경우 가장 큰 장점은 일관된 코드 스타일을 유지할 수 있다는 점입니다.

camelCase를 활용해서 변수나 상수를 선언하는 규칙을 통해서 일관 된 코드 스타일과, 

타입 추론이 가능한 변수를 명시적으로 선언하는 방법이 있습니다.

 

질서있는 코드를 작성하고, 네이밍에 규칙을 적용한다면 원활한 협업에도 도움을 주게 됩니다.

일관된 코드 크타일을 통해서 불필요한 의사소통을 줄이고, 통일성을 보장할 수 있습니다.

 

위와 같은 장점으로 코딩 컨벤션을 활용하며, 코틀린에서는 코딩 컨벤션을 위해서 다양한 가이드라인을 제시하고 있습니다.

https://kotlinlang.org/docs/coding-conventions.html

 

Coding conventions | Kotlin

 

kotlinlang.org

https://developer.android.com/kotlin/style-guide?hl=ko

 

Kotlin 스타일 가이드  |  Android Developers

Kotlin 스타일 가이드 컬렉션을 사용해 정리하기 내 환경설정을 기준으로 콘텐츠를 저장하고 분류하세요. 이 문서에서는 Kotlin 프로그래밍 언어의 소스 코드와 관련된 Google의 Android 코딩 표준을

developer.android.com

 

네이밍 컨벤션

식별자나 파일명을 짓는데 네이밍 컨벤션을 활용합니다.

코틀린 뿐아니라 java나 python의 대부분의 언어가 이를 활용하고 있습니다.

 

1. camelCase : 처음은 소문자로 시작하며, 단어의 첫 글자는 대문자로 표기하는 방식입니다.

val name = "jin"

fun printMyName(){
	println(name)
}

 

2. camel case with an uppercase first letter 

camelCase에서 첫 글자도 대문자로 표기하며, 주로 클래스 명을 작성할때 사용합니다.

UserProfiles.kt

class UserProfiles()

 

3. uppercased undercore-separated : 주로 코틀린에서 상수를 정의할 때 사용합니다.

const val MAX_USER_COUNT = 100

 

4. snake_case, kebab-case

snake_case는 언더스코어(_)를 이용해 단어를 나눠주며, 소문자로 표기하는 특징이 있습니다.

kabab-case는 snake_case와 비슷하며, 중간에 하이픈(-)을 삽입해서 표기합니다.

 

코틀린의 코딩 컨벤션

Android Studio나 Intellij IDEA와 같은 통합개발환경을 사용하면, 스타일 가이드를 적용할 수 있습니다.

코틀린 스타일 가이드 설정창에서 우측 상단의 Set from...을 눌러서 Kotlin Style Guide를 지정할 수 있습니다.

Reformat 키(맥북은 command + option + l)를 통해서 스타일 가이드 설정에 따른 포맷을 진행할 수 있습니다.

 

소스코드 컨벤션

코틀린에서는 중복된 이름과 클래스 경로에서 원하는 클래스의 위치를 찾기 위해서 package를 사용합니다.

com.dnd_9th_3_android

위와 같이 패키지 이름에 온점(.)으로 분리하며, 각 이름은 디렉토리 구조에서 하나의 디렉토리를 의미해야 합니다.

위 네이밍에서는 com/dnd_9th_3_android가 됩니다.

 

소스코드이 파일 이름은 2가지 경우에 따라서 다르게 지정해주어야 합니다.

만약 클래스나 인터페이스, 파일이 하나의 User라는 클래스를 가진다면 파일의 이름을 User.kt로 지정합니다.

여러가지 클래스를 가지는 소스코드 파일일 경우, 모두를 대변할 수 있는 이름을 앞글자가 대문자인 camelCase로 지정합니다.

 

이 외에 소스코드 파일 구조를 설정할때 해당 파일의 위치는 서로 밀접하게 관련 있는 것들을 모아두는 것이 좋고,

한 파일에 몇백줄을 넘기지 않도록 작성하는 것이 확장 함수를 사용할 때 바람직한 코드를 작성할 수 있습니다.

 

클래스 

클래스 내에 여러가지 구성요소를 적절한 순서로 배치해야 합니다.

속성 선언과 초기화 블록, 부 생성자들, 메소드들, 컴패니언 객체와 같은 구성요소가 있습니다.

 

class User {
    // 속성
    private var age: Int? = null

    // 초기화 블록
    init {
        age = 24
    }

    // 부 생성자
    constructor(age: Int) {
        this.age = age
    }

    // 메소드
    fun printAge() = println(age)


    fun useNested() {
        NestedForInternal()
        // ...
    }

    class NestedForInternal {

    }

    // 컴패니언 객체
    companion object {
        const val DURATION = 300
    }

    class NestedForExternalClient {

    }
}

 

위와 같이 속성 - 초기화 블록 - 부 생성자 - 메소드 - 캠패니언 객체 순으로 선언해야 합니다.

단순히 사전순이나 접근지시 제어자 순으로 정렬하는 방법은 바람직하지 않습니다.

 

인터페이스 구현체 

반드시 인터페이스에 선언 된 순서대로 클래스에서 구현해야 합니다.

interface User {
    var age: Int
    var name: String
}

class GameUser : User {
    override var age = 24
    override var name = "MJ"
}

 

  함수 구조

함수는 인자가 최대 줄 수를 넘어가면 multi-line으로 처리해야 하며, 표준 가이드 라인에서는 120을 넘기지 않도록 하고 있습니다.

// Bad Code Style
fun longMethodName(argument: ArgumentType = defaultValue,
    argument2: AnotherArgumentType,
): ReturnType {
    // body
}
// Good Code Style
fun longMethodName(
    argument: ArgumentType = defaultValue,
    argument2: AnotherArgumentType,
): ReturnType {
    // body
}

 

또한 함수는 무조건 단일식 반환을 선호해야 합니다.

fun foo(): Int {
	retrun 1
} // bad code 

fun foo() = 1 // good code

 

속성 컨벤션

속성이 한 줄로 표현되지 않는다면, 들여쓰기와 함께 다음 줄에 표현해주어야 합니다.

val isEmpty: Boolean get() = size == 0 // 한줄로 표현이 가능
val foo: String // 한줄로 표현이 불가능
    get() { /*...*/ }

속성의 초기화가 한 줄 이상일 때는, = 연산자 뒤로 뛰어쓴 후 들여쓰기를 해주어야 합니다.

 

조건문

if 조건문의 경우 한 줄로 표현되지 않는다면, 조건문을 나누어서 들여쓰기를 해줍니다.

if (!component.isSyncing &&
    !hasAnyKotlinRuntimeInScope(module)
) {
    return createKotlinNotConfiguredPanel(module)
}

when 조건문의 경우 코드 내용이 짧아서 중괄호를 쓸 필요가 없다면, 생략하는 게 좋습니다.

when (foo) {
    true -> bar() // good Code
    false -> { baz() } // bad Code
}

또한 조건문에서는 statement가 아닌 expression을 선호하는 것이 좋습니다.

if문 보다는 if식을 사용해야 하며, 아래 예재 처럼 적용합니다.

// bad
var age: Int
if (true) {
    age = 24
} else {
    age = 10
}
// good
val age = if (true) {
    24
} else {
    10
}

 

함수 호출 

함수를 호출할 때 인자들이 길어서 한 줄에 표현되지 않는다면, 개행을 하는 것이 좋습니다.

연관있는 인자들끼리만 한 줄로 표현하고, 개행을 나타낸 들여쓰기를 적용해주어야 합니다.

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 }) // bad Code 
list.filter { it > 10 } // good Code

람다에 라벨링을 하는 경우 람다와 @ 사이에 여백을 두지 않습니다.

fun foo() {
    ints.forEach lit@{
        // ...
    }
}

람다에서 it 대신 다른 명칭을 사용한다면, -> 뒤에 개행을 해주고 람다식을 작성하는 것을 원칙으로 합니다.

appendCommaSeparated(properties) { prop ->
    val propertyValue = prop.get(obj)  // ...
}

 

 불변성을 선호

코틀린에서는 var or val 혹은 lateinit or by lazy 등 변하는 데이터와 변하지 않는 데이터에 대한 형식이 주어집니다.

변하지 않는 데이터는 무조건 val 형식으로 적용해야 하며, 가변성이 있는 변수보다 불변성을 선호하고 있습니다.

 

 

(!)

대부분의 코딩 컨벤션을 지키고 있지만, 가끔 빼먹는 경우도 있었던 것 같습니다.

항상 코딩 컨벤션을 준수하는지 확인하고 Reformat 키를 활용해서 스타일 가이드 라인을 준수하는 습관을 키우는 것이 중요합니다.

플러터나 ios도 학습 중인데, 새로운 언어를 배울 때도 코딩 컨벤션에 신경쓰는 것이 중요할 것 같습니다.