Kotlin 고유의 기능과 패러다임을 최대한 활용하여, 간결하고 읽기 쉽고 안정적이며 유지보수하기 좋도록 코드를 작성하는 팁을 정리합니다.

1. 불변성(Immutable)을 우선시하기

val 사용

  • 가능한 한 가변형 변수(var) 대신 변경 불가능한 변수(val)를 사용합니다.
  • 이는 함수형 프로그래밍의 순수성을 유지하여 코드 안정성을 높이고, 상태 변화를 예측 가능하게 만들어 줍니다.
    val name = "황용호"

data class 활용

  • 단순한 데이터를 저장하기 위해 data class를 활용하면 equals(), hashCode(), toString() 등을 자동으로 생성해 줍니다.
    data class User(val id: Int, val name: String)

2. 스코프 함수 활용

  • let, apply, run, also, with 같은 스코프 함수들을 사용하여 객체 초기화나 null 체크를 간결하게 작성할 수 있습니다.

  • 이러한 함수들을 활용하면 불필요한 임시 변수 선언 없이 연산을 체인으로 구성할 수 있습니다.

    // 예시: null 안전한 객체 활용
    val length = name?.let { it.length } ?: 0
    
    // 예시: 객체 초기화 후 self 반환 (빌더 패턴 효과)
    class User(val no: Int) {
        lateinit var name: String
    }
    
    fun getUser(no: Int) = User(no).apply {
        name = "황용호"
    }
    
    fun main() {
        val user = getUser(10)
        println(user.no)
        println(user.name)
    }

3. 일급 함수와 고차 함수 사용

  • kotlin에서는 함수를 값으로 다루고, 인자로 전달하거나 반환할 수 있습니다.

  • 람다식을 이용하면 함수형 프로그래밍 스타일로 코딩할 수 있습니다.

     fun operateOnNumbers(a: Int, b: Int, operation: (Int, Int) -> Int): Int {
          return operation(a, b)
      }
      
      fun main() {
          fun add(x: Int, y: Int): Int {
              return x + y
          }
    
          fun subtract(x: Int, y: Int): Int {
              return x - y
          }
    
          println(operateOnNumbers(5, 3, ::add)) // 8
          println(operateOnNumbers(5, 3, ::subtract)) // 2
          println(operateOnNumbers(5, 3) { x, y -> x * y }) // 15
      }

4. 컬렉션 함수 활용

  • map, filter, reduce, forEach 등 함수형 연산을 활용하여 컬렉션을 간결하게 처리합니다.
    val numbers = listOf(1, 2, 3, 4, 5)
    val evenNumbers = numbers.filter { it % 2 == 0 }

5. 널 안정성(Null Safety) 활용

  • 안전 호출 연산자 (?.)와 엘비스 연산자(?:)를 활용하여 null 값에 안전하게 접근합니다.
    val result = someNullableValue?.someMethod() ?: defaultValue
  • 단언 연산자(!!)는 사용을 최소화합니다.

6. 확장 함수 및 프로퍼티 확장 활용

  • 기존 클래스를 수정하거나 상속받지 않고도 확장 함수를 추가할 수 있습니다.
    fun String.isEmailValid(): Boolean {
        return this.contains("@")
    }
    // 사용 예:
    val email = "jogakdal@gmail.com"
    println(email.isEmailValid())
  • 클래스 외부에서 새로운 프로퍼티를 추가할 수도 있습니다.
    // 문자열이 숫자로만 이루어졌는지 검사
    val String.isDigit: Boolean 
        get() = this.all { it.isDigit() }

7. 지연 초기화 기능 활용

  • by lazy를 사용하여 val 프로퍼티를 처음 접근 시 초기화합니다.

    val heavyComputationResult: String by lazy {
        println("실제 연산을 수행합니다.")
        "계산 결과"
    }
    
    fun main() {
        println("프로그램 시작")
        println("중간 작업")
        println("결과: $heavyComputationResult")
        println("다시 결과: $heavyComputationResult")
    }
  • lateinitvar 프로퍼티의 초기화를 늦출 때 사용합니다.

    class MyClass {
        lateinit var message: String
    
        fun initializeMessage() {
            message = "Hello, Kotlin!"
        }
    
        fun printMessage() {
            if (::message.isInitialized) println(message)
            else println("message 변수는 아직 초기화되지 않았습니다.")
        }
    }
    
    fun main() {
        val myObject = MyClass()
    
        myObject.printMessage() // 출력: message 변수는 아직 초기화되지 않았습니다.
        myObject.initializeMessage()
        myObject.printMessage() // 출력: Hello, Kotlin!
    }
  • 또한, Sequence를 활용하여 컬렉션의 연산을 지연 평가 방식으로 처리할 수 있습니다.

    fun main() {
        println("using collection")
        // 총 205번 연산
        (100..200).map {
            println("doubling $it")
            it * 2
        }.filter {
            println("filtering $it")
            it % 3 == 0
        }.first()
    
        println("using sequence")
        // 총 6번 연산
        (100..200).asSequence().map {
            println("doubling $it")
            it * 2
        }.filter {
            println("filtering $it")
            it % 3 == 0
        }.first()
    }

8. 표현식 기반 코딩

  • when 표현식을 활용하여 조건 분기를 간결하게 작성합니다.
    val result = when (input) {
        0 -> "Zero"
        1 -> "One"
        else -> "Unknown"
    }
  • 단일 표현식 함수로 값을 반환하면 더욱 간결합니다.
    fun computeValue(x: Int) = x * 2

9. Sealed class 사용

  • 제한된 계층 구조를 가지는 sealed class를 활용하여 안전하게 다형성을 구현할 수 있습니다.

    sealed class PersonState {
        // data object는 Kotlin 1.9 이상에서 지원. (하위 버전에서는 object만 사용)
        data object Running : PersonState()
        data object Walking : PersonState()
        data object Idle : PersonState()
    }
    
    fun getStateMessage(state: PersonState): String = when (state) {
        is PersonState.Running -> "달린다."
        is PersonState.Walking -> "걷는다."
        is PersonState.Idle -> "쉰다."
    }

10. 기타

스마트 캐스팅 기능 활용

  • 타입 체크 후 별도의 캐스팅 없이 해당 타입으로 바로 사용할 수 있도록 해줍니다.
    if (obj is String) {
        // obj는 자동으로 String 타입으로 스마트 캐스팅됨.
        println(obj.length)
    }

컴패니언 오브젝트(Companion Object) 활용

  • Kotlin에서 정적 멤버 대신 컴패니언 오브젝트를 사용합니다.
    class MyClass {
        companion object {
            const val CONSTANT = "상수값"
            fun create(): MyClass = MyClass()
        }
    }

인라인 함수 및 reified 키워드 활용

  • 함수형 프로그래밍과 성능 최적화를 위해 inline 함수를 사용할 수 있습니다.

  • 또한 Generic의 실제 타입 확인을 위해 reified 타입 파라미터를 사용할 수 있습니다.

    inline fun <reified T> isInstance(value: Any): Boolean {
        return value is T
    }
    
    fun main() {
        println(isInstance<String>("Kotlin"))  // true
        println(isInstance<Int>("Kotlin"))     // false
    }

코루틴(Coroutines) 활용

  • 코루틴을 사용하면 비동기 코드를 동기 코드처럼 작성할 수 있어 가독성이 높아지며, 복잡한 동시성 문제를 해결할 수 있습니다.
    fun main() = runBlocking {
        launch {
            delay(1000L)
            println("World!")
        }
        println("Hello,")
    }

kotlin 코딩 컨벤션 준수

0개의 댓글