Kotlin in action 2/e: 함수 정의와 호출(2) 로컬 함수, 코드를 확장 함수로 추출하기

2026. 2. 9. 13:04·Kotlin

이번 포스팅에서는 로컬 함수에 대해 정리해보려고 한다, 책에서는 다음과 같이 언급하고 있다. 

많은 개발자가 좋은 코드의 중요한 특징 중 하나가 중복이 없는 것이라 믿는다. 
심지어 이런 원칙에 대해 반복하지 말라.(DRY, Don't Repeat Yourself) 라는 이름도 붙어있다.

 

DRY 원칙에 대해 처음 들어보게 되어 자세히 찾아봤는데, 유명한 소프트웨어 개발 원칙 중 하나였다. 

주로 KISS, YAGNI, DRY 원칙을 함께 설명하는 것 같았다. 

이에 대해서는 더 자세히 조사하여 정리하려고 한다. 

 

책에서는 자바 코드를 작성할 때, DRY 원칙, 즉 중복된 코드를 만들지 않기를 따르기는 쉽지 않다고 언급하고 있다.

 

사실 지금까지 내 프로그래밍을 생각해보면 중복된 코드가 많을 경우 단순히 메서드를 추출하여 재활용하는 방식을 거의 대부분 사용했던 것 같다.

 

하지만 책에서는 이러한 방법을 계속 사용하게 되면 하나의 클래스 안에 작은 메서드가 많아지며, 각 메서드 사이의 관계를 파악하기 힘들어서 코드를 이해하기 더 어려워 진다. 라고 설명하고 있다. 

 

책에 자세히 언급되어 있지 않지만, 다른 언어의 경우에는 리팩터링을 더 진행해서 추출한 메서드를 별도의 내부 클래스로 안에 넣는 방법이 해답 중 하나로 생각된다. 

이에 대해서는 더 자세히 조사하여 정리하려고 한다. 

 

하지만 코틀린은 더 깔끔하게 해당 문제를 해결할 수 있다. 바로 함수에서 추출한 함수를 원래의 함수 내부에 내포시키는 방법이다.

class User(val id: Int, val name: String, val address: String)

fun saveUser(user: User) {
    if (user.name.isEmpty()) {
        throw IllegalArgumentException(
            "Can't save user ${user.id}: empty Name")
    }

    if (user.address.isEmpty()) {
        throw IllegalArgumentException(
            "Can't save user ${user.id}: empty Address")
    }

    // Save user to the database
}

fun main() {
    saveUser(User(1, "", ""))
}

 

먼저 위 코드는 사용자 정보를 데이터베이스에 저장하는 코드이다. 사용자의 필드를 검증하는 부분이 중복되는 것을 볼 수 있다. 

이런 경우에는 검증 코드를 로컬 함수로 분리해볼 수 있을 것이다. 

class User(val id: Int, val name: String, val address: String)

fun saveUser(user: User) {

    fun validate(user: User,
                 value: String,
                 fieldName: String) {
        if (value.isEmpty()) {
            throw IllegalArgumentException(
                "Can't save user ${user.id}: empty $fieldName")
        }
    }

    validate(user, user.name, "Name")
    validate(user, user.address, "Address")

    // Save user to the database
}

fun main() {
    saveUser(User(1, "", ""))
}

 

이렇게 작성하게 되면 다음과 같은 이점을 얻을 수 있을 것이다. 

 

코드가 간결해진다. (검증 로직 중복이 사라진다)

 

User의 다른 필드에 대한 검증을 쉽게 추가할 수 있다. 

 

하지만 이를 처음 보고, 나는 오히려 함수내에서 함수를 정의하는 것이 코드의 depth를 증가시키기에 바깥으로 빼는 방법이 좋지 않을까? 하는 생각이 들었다. 책을 계속 읽어보니, 이와 같은 궁금점을 해소할 수 있었다.

 

우선 User 객체를 로컬 함수에 전달해주는 부분이 번거롭고 중복된다고 할 수 있는데, 코틀린의 로컬 함수에서는 이를 위해 로컬 함수가 바깥 함수의 모든 파라미터와 변수를 사용할 수 있다고 한다. 이를 고려하면 코드를 다음과 같이 수정해도 문제 없이 작동할 것이다. 

class User(val id: Int, val name: String, val address: String)

fun saveUser(user: User) {
    fun validate(value: String, fieldName: String) {
        if (value.isEmpty()) {
            throw IllegalArgumentException(
                "Can't save user ${user.id}: " +
                    "empty $fieldName")
        }
    }

    validate(user.name, "Name")
    validate(user.address, "Address")

    // Save user to the database
}

fun main() {
    saveUser(User(1, "", ""))
}

 

더 나아가서 검증 로직이 User 클래스를 확장하도록 할 수도 있다. 

class User(val id: Int, val name: String, val address: String)

fun User.validateBeforeSave() {
    fun validate(value: String, fieldName: String) {
        if (value.isEmpty()) {
            throw IllegalArgumentException(
               "Can't save user $id: empty $fieldName")
        }
    }

    validate(name, "Name")
    validate(address, "Address")
}

fun saveUser(user: User) {
    user.validateBeforeSave()

    // Save user to the database
}

fun main() {
    saveUser(User(1, "", ""))
}

 

이렇게 되면 다음과 같은 이점을 얻을 수 있다. 

 

검증 기능은 User를 사용하는 다른 곳에서는 사용하지 않을 가능성이 높으므로, 확장 함수를 사용하면 User에 포함시키지 않으면서 User에서 정의한 것처럼 사용할 수 있다. 

 

결론적으로 다음과 같이 정리해볼 수 있다. 

 

한 객체만을 다루면서(예제처럼 User 객체 하나만 다루면서), 

 

객체의 비공개 데이터를 다룰 필요가 없는 로직이고, (확장 함수는 비공개 프로퍼티를 못다루므로..)

 

특히 해당 객체에 포함할지 말지 애매한 경우이면, (검증 로직처럼 사용처가 한정적인 경우)

 

해당 코드, 혹은 함수를 확장 함수로 뽑아내면 유용하다.

 

확장 함수, 로컬 함수의 사용에 대해 정리했는데, 물론 확장 함수를 로컬 함수로 정의할 수도 있다. 하지만 이는 코드의 깊이를 증가시켜 가독성을 떨어뜨릴 수 있다. 책에서도 일반적으로 한 단계의 함수 내포만 권장한다고 기술되어 있다. 

 

 

3장을 마무리하며..

 

사실 3장, 함수정의와 호출을 학습하기 전에는 좋은 코드를 만들기 위해서 중복되는 코드를 함수로 추출하기, 이정도만 머릿속에 있었다면, 확장 함수, 로컬 함수, 약간의 제네릭스 맛보기를 통해 좋은 코드를 만드는 방법을 좀 더 습득한 것 같다. 

 

'Kotlin' 카테고리의 다른 글

Kotlin in action 2/e: 클래스, 객체, 인터페이스(2) 기본 구현 메서드, by, object  (0) 2026.02.13
Kotlin in action 2/e: 클래스, 객체, 인터페이스(1) 인터페이스, 클래스, 생성자  (0) 2026.02.09
Kotlin in action 2/e: 함수 정의와 호출(1) joinToString() 함수 직접 구현  (0) 2026.02.07
Kotlin in Action 2/e: 코틀린 기초(2): 스마트 캐스트, if를 when으로 리팩터링, while, for, 예외처리  (0) 2026.02.06
Kotlin in Action 2/e: 코틀린 기초(1): 함수, 변수, 클래스, 프로퍼티, 이넘, when()  (0) 2026.02.05
'Kotlin' 카테고리의 다른 글
  • Kotlin in action 2/e: 클래스, 객체, 인터페이스(2) 기본 구현 메서드, by, object
  • Kotlin in action 2/e: 클래스, 객체, 인터페이스(1) 인터페이스, 클래스, 생성자
  • Kotlin in action 2/e: 함수 정의와 호출(1) joinToString() 함수 직접 구현
  • Kotlin in Action 2/e: 코틀린 기초(2): 스마트 캐스트, if를 when으로 리팩터링, while, for, 예외처리
Kirbyyy
Kirbyyy
개인적인 일상과 회고를 기록하는 블로그입니다.
  • Kirbyyy
    커브볼의 생존일지
    Kirbyyy
  • 전체
    오늘
    어제
    • 분류 전체보기 (53)
      • 우아한테크코스 (8)
      • 프로덕트 빌드 (0)
      • Problem Solving (20)
      • C++ (0)
      • Kotlin (19)
      • Java (3)
      • CS (2)
        • AI (2)
      • 취미생활 (0)
        • 서평 (0)
        • 프라모델 (0)
  • 블로그 메뉴

    • 홈
    • 태그
    • 방명록
  • 링크

  • 공지사항

  • 인기 글

  • 태그

    백준
    ProblemSolving
    백준 알고리즘
    그리디 알고리즘
    C++
    백준 31575
    백준 33272
    백준 16173
    너비 우선 탐색
    우테코 8기
    백준 11123
    다이나믹 프로그래밍
    백준 RGB 거리
    백준 16174
    백준 파도반 수열
    BFS
    분할 정복
    Problem Solving
    백준 연속 합
    백준 1356
  • 최근 댓글

  • 최근 글

  • hELLO· Designed By정상우.v4.10.5
Kirbyyy
Kotlin in action 2/e: 함수 정의와 호출(2) 로컬 함수, 코드를 확장 함수로 추출하기
상단으로

티스토리툴바