이펙티브 코틀린 Item 12: 연산자 오버로드를 할 때는 의미에 맞게 사용하라

woga·2023년 4월 16일
0

코틀린 공부

목록 보기
15/54
post-thumbnail

연산자 오버로딩?

연산자 오버로딩은 말그래도 연산자로 쓰이고 있는 친구들을 오버로딩해서 정의해서 사용하는 방식이다.
이는 굉장히 강력하지만 위험할 수 있다.

만약 factorial을 정의해서 쓴다하면 아래와 같이 정의하고 싶을 수도 있다

fun Int.factorial(): Int = (1..this).product()

fun Iterable<Int>.product(): Int = fold(1) { acc, i -> acc * i }

operator fun Int.not() = factorial()

print(10 * !6) // 7200

과연 될까? 안 된다.

이 함수 이름이 not() 이므로 논리 연산에 사용해야지 팩토리얼 연산에 사용하면 안된다.
코드 가독성과 작성에도 굉장히 혼란스럽고 오해를 일으킨다.

코틀린에서 각 연산자의 의미는 항상 같게 유지된다.

스칼라와 같은 일부 프로그래밍 언어는 무제한 연산자 오버로딩을 지원하지만 이 정도의 자유는 많은 개발자가 이 기능을 오용하게 만든다.

+가 일반적으로 쓰이고 있지 않다면 연산자를 볼 때마다 개별적으로 이해하려고 노력을 해야한다. (+어렵기까지!)

코틀린에서는 각각의 연산자에 구체적인 의미가 있으므로 이러한 문제가 없다.
따라서 !을 사용하는 것은 관례에 어긋난다.

분명하지 않은 경우

관례를 충족하는지 아닌지 확실하지 않을 때는 어떨까?

만약 함수를 세 배한다는 것은 무슨 의미일까?

1) 같은 함수를 세번 반복하는 새로운 함수 생성

operator fun Int.times(operation: () -> Unit): ()->Unit = {
	repeat(this) { operation() }
 }
 
 val tripleHello = 3 * { print("Hello") }
 
 tripleHello() // HelloHelloHello

2) 이런 코드가 세 번 호출

operator fun Int.times(operation: () -> Unit): ()->Unit = {
	repeat(this) { operation() }
 }
 
 3 * { print("Hello") } // HelloHelloHello

이럴 때처럼 의미가 명확하지 않다면, infix를 사용하자

infix fun Int.timesRepeated(operation: () -> Unit) = {
	repeat(this) { operation() }
 }
 
 val tripleHello = 3 timesRepeated { print("Hello") }
 tripleHello() // HelloHelloHello

톱레벨 함수를 사용하는 것도 좋다
(사실 n번 함수를 호출하는 것은 아래와 같이 stdlib에 구현되어 있다...)

repeat(3) { print("Hello") } // HelloHelloHello

규칙을 무시해도 되는 경우

바로 DSL(도메인 특화 언어, Domain Specific Language)를 설계할 때 연산자 오버로딩 규칙을 무시해도 된다

고전적인 HTML DSL

body {
	div {
		+"Some text"
	}
}

문자열 앞에 String.unaryPlus가 사용된 것을 볼 수 있다. 이렇게 해도 되는 건 DSL 코드이기 때문~

정리

연산자 오버로딩은 그 이름의 의미에 맞게 사용하자
연산자 의미가 명확하지 않다면, 연산자 오버로딩을 사용하지 않는 것이 좋다. 대신 이름이 있는 일반 함수를 쓰자.

꼭 연사자 같은 형태로 사용하고 싶다면, infix 확장 함수 또는 톱레벨(클래스 또는 다른 대상 내부에 있지 않고, 가장 외부에 있는 함수) 함수를 쓰자

profile
와니와니와니와니 당근당근

0개의 댓글