Kotlin 확장 함수, 확장 프로퍼티의 활용 + 확장함수를 쓸 때 주의할점

Kotlin에는 확장 함수(Extension Function)와 확장 프로퍼티(Extension Property)라는 재미있는 문법이 있습니다.
이를 잘 활용하면 자바에서는 할 수 없었던 스타일로 코드를 짤 수 있는데요,
이 글에서는 제가 다른 분들에게 공유하고 싶었던 활용법을 공유해드리고자 합니다.
그리고 확장함수를 쓸 때 주의해야 할 점까지 알아보겠습니다.

1. dp to px

안드로이드 개발을 하다 보면 가끔 코드에서 레이아웃을 변경할 일이 생깁니다.
하지만 코드에서는 dp단위가 아닌 px단위를 사용하기 때문에 dp를 쓰기 위해선 px로 변환해주는 과정이 필요합니다.

자바를 쓸 땐 이렇게 변환해주는 함수를 만들어 썼습니다. (Activity나 Fragment 안에 있는 함수라고 가정하겠습니다.)

//Java
int toDp(int dp) {
    DisplayMetrics metrics = getResources().getDisplayMetrics();
    return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dp, metrics);
}

textView.setPadding(0, toDp(16), 0, toDp(16)); //사용 예시

코틀린 확장 함수를 쓰면 이렇게 변형할 수 있습니다.

//Kotlin 확장 함수
fun Int.dp(): Int { //함수 이름도 직관적으로 보이기 위해 dp()로 바꿨습니다.
    val metrics = resources.displayMetrics
    return TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, this.toFloat(), metrics).toInt()
}

textView.setPadding(0, 16.dp(), 0, 16.dp()) //사용 예시

이 정도만 돼도 자바에 비해선 확실히 좋아졌습니다.
그런데 여기서 확장 프로퍼티를 쓴다면 가독성을 더 높일 수 있습니다.

//Kotlin 확장 프로퍼티
val Int.dp: Int
    get() {
        val metrics = resources.displayMetrics
        return TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, this.toFloat(), metrics).toInt()
    }

textView.setPadding(0, 16.dp, 0, 16.dp) //사용 예시

16.dp()뒤에 괄호가 사라져서 xml에서 dp를 쓰는 것 같은 느낌을 줄 수 있습니다.

2. 리스트 매핑

어떤 리스트에서 특정 조건에 만족하는 item들만 골라서 써야할 때가 있습니다.
이럴때 쓰라고 filter라는 함수가 있죠.
간단한 예로 value와 isShowing이라는 필드를 가진 아이템이 있다고 칩시다.

data class Item(val value: Int, var isShowing: Boolean)
val items = arrayListOf(
        Item(64, true),
        Item(91, false),
        Item(34, false),
        Item(43, true)
)

여기서 isShowing이 true인 아이템들만 forEach문을 돌리고 싶다면 그때마다 items.filter { it.isShowing }.forEach { } 이런식으로 해야 할까요?
확장 프로퍼티를 사용하면 그러지 않아도 됩니다.

val ArrayList<Item>.filterInvisible
    get() = this.filter { it.isShowing }

이렇게 해놓으면 items.filterInvisible.forEach { }만으로 쓸 수 있게되어 재사용성이 높아지고 나중에 수정할 때도 편하겠죠.

저는 이것을 RecyclerView에서 특정 조건에 맞는 아이템들만 출력하고 싶을 때 사용했습니다.

주의할 점

확장함수는 위에서 보신 것 처럼 분명 많은 장점을 지니고 있습니다.
하져만 잘못 사용하면 문제가 발생하기 쉽습니다. (좋은 피드백 주신 sa-ryong님 감사합니다.)

  1. 단순히 코드의 양을 줄이기 위한 확장은 피하세요.
    코드가 짧다고 무조건 좋은 것은 아닙니다.
    코드의 양을 줄이는 목적으로 확장을 만드는 것은 코드의 가독성을 떨어뜨리고 유지보수를 어렵게 만들 수 있습니다.
    여러군데에 사용된 확장을 수정할 일이 생긴다면 사용된 모든 경우에 대해 적합한지 판단해야할 것이고, 한 부분에서 특수한 처리를 해야한다면 또 곤란해지겠죠.
    이와 관련한 좋은 글이 있으니 한번 읽어보시면 도움이 되실 것 같네요.
    Goodbye, Clean Code
  2. 함수나 프로퍼티의 이름이 겹치지 않도록 주의하세요.
    확장은는 원래 있던 클래스에 새로운 함수나 프로퍼티를 외부에서 추가하는 행위이기 때문에 이름이 겹칠 가능성이 있습니다.
    그러면 확장이 무시되고 원래 있던 것이 쓰이게 되니 혼란이 생길 수 있습니다.

결론

확장을 만드는 것은 프로젝트 전체에 영향을 주는 행위입니다.
확장을 만들 때는 프로젝트 엔지니어분들과 충분한 상의를 하고, 되도록 private으로 정의했다가 나중에 필요성이 생길 때 public으로 바꾸는 것을 추천드릴게요.

피드백은 언제나 환영입니다! 아래 댓글에 남겨주세요.
읽어주셔서 감사합니다🙂

P.S

글을 다 쓰고 알았는데 Android Studio 4.0에서 나오는 Jetpack Compose 코드를 보니 1번에서 소개해드린 방법을 쓰고 있더라구요.

내부 코드는 이렇게 생겼습니다.

/**
 * Create a [Dp] using an [Int]:
 *     val left = 10
 *     val x = left.dp
 *     // -- or --
 *     val y = 10.dp
 */
inline val Int.dp: Dp get() = Dp(value = this.toFloat())

프로퍼티에도 inline을 붙일 수 있다는 것을 알게 되었네요🤣

태그: ,

카테고리:

업데이트:

블로그에 기여하기

댓글남기기