코틀린 스터디/이펙티브 코틀린

아이템20) 일반적인 알고리즘을 반복해서 구현하지 말라

막이86 2024. 4. 17. 10:45
728x90
  • 많은 개발자는 같은 알고리즘을 여러 번 반복해서 구현
    • 수학적인 연산, 수집 처리처럼 별도의 모듈 또는 라이브러리로 분리할 수 있는 부분을 의미
  • 숫자를 특정 범위에 맞추는 간단한 알고리즘
val percent = when {
	numberForUser > 100 -> 100
	numberForUser < 0 ->
	else -> numberForUser
}
  • stdlib의 coerceIn 확장 함수로 이미 존재
val percent = numberFromUser.coerceIn(0, 100)
  • 이미 있는 것을 활용하면 다양한 장점이 있음
    • 코드 작성 속도가 빨라짐
      • 알고리즘을 만드는 것보다 빠름
    • 구현을 따로 읽지 않아도, 함수의 이름 등만 보고도 무엇을 하는지 확실하게 알 수 있음
      • 처음 보더라도 한 번 보고 나면, 그 이후로는 함수의 이름만 보아도 무엇을 하는지 쉽게 알 수 있음
    • 직접 구현할 때 발생할 수 있는 실수를 줄일 수 있음
      • sortedBy, sortedByDescending은 내부가 거의 비슷하지만, 정렬 방향이 반대
      • 직접 반복적으로 구현하면 해깔리게 되는 경우가 있음
    • 제작자들이 한 번만 최적화하면, 함수를 사용하는 모든 곳이 최적화의 해택을 받을 수 있음

표준 라이브러리 살펴보기

  • 가장 대표적인 라이브러리는 stdlib
    • 확장 함수를 활용해서 만들어진 괴장히 거대한 유틸리티 라이브러리
    • stdlib의 함수들을 하나하나 살펴보는 것이 굉장히 어려울 수 있지만, 그럴만한 가치가 있는 일
  • 오픈소스에서 일부 사용한 코드
override fun saveCallResult(item: SorceResponse) {
	var sourceList = ArrayList<SourceEntity>()
	item.sources.forEach {
		var sourceEntity = SourceEntity()
		sourceEntity.id = it.id
		sourceEntity.category = it.category
		sourceEntity.country = it.country
		sourceEntity.description = it.description
		sourceList.add(sourceEntity)
	}
	db.insertSources(sourceList)
}
  • 위 코드에서 forEach를 사용하는 것은 좋지 않음
    • for 반복문을 사용하는 것과 아무런 차이가 없음
    • 다른 자료현으로 매핑하기 때문에 map 함수를 사용하는 것이 좋음
    • 최소한 apply를 활용해서 모든 단일 객체의 프로퍼티를 암묵적으로 설정하는 것이 좋음
override fun saveCallResult(item: SorceResponse) {
	val sourceEntries = item.sources.map(::sourceToEntry)
	db.insertSources(sourceEntries)
}

private fun sourceToEntry(source: Source) = SourceEntity().apply {
	id = source.id
	category = source.category
	country = source.country
	description = source.description
}

나만의 유틸리티 구현하기

  • 상황에 따라서 표준 라이브러리에 없는 알고리즘이 필요할 수 있음
  • 컬렉션에 있는 모든 숫자의 곱을 계산하는 라이브러리
    • 널리 알려진 추상화이므로 범용 유틸리티 함수로 정의하는 것이 좋음
    • 여러번 사용되지 않는다고 해도 이렇게 만드는 것이 좋음
    fun Iterable<Int>.product() = fold(1) { acc, i -> acc * i }
    
  • 동일한 결과를 얻는 함수를 여러 번 만드는 것은 잘못된 일
  • 모든 함수는 테스트되어야 하고, 기억되어야 하며, 유지보수되어야 함
  • 필요 없는 함수를 중복해서 만들지 않게, 기존에 관련된 함수가 있는지 탐색하는 과정이 필요
  • 코틀린 stdlib에 정의된 대부분의 함수처럼, 앞 코드의 product도 확장 함수로 구현되어 있음
  • 많이 사용되는 알고리즘을 추출하는 방법으로 톱레벨 함수, 프로퍼티 위임, 클래스 등이 있음
  • 확장 함수는 여러 장점을 가지고 있음
    • 함수는 상태를 유지하지 않으므로, 행위를 나타내기 좋음
      • side-effect가 없는 경우에 더 좋음
    • 톱레벨 함수와 비교해서, 확장 함수는 구체적인 타입이 있는 객체에만 사용을 제한할 수 있음
    • 수정할 객체를 아규먼트로 전달받아 사용하는 것보다는 확장 리시버로 사용하는 것이 가독성 측면에서 좋음
    • 확장 함수는 객체에 정의한 함수보다 객체를 사용할 때 자동 완성 기능 등으로 제안이 이루어지므로 쉽게 찾을 수 있음

정리

  • 일반적인 알고리즘을 반복해서 만들지 말기
  • 대부분 stdlib에 정의되어 있을 가능성이 높음
  • stdlib에 없는 일반적인 알고리즘이 필요하거나, 특정 알고리즘을 반복해서 사용해야하는 경우에는 프로젝트 내부에 직접 정의
  • 일반적으로 이런 알고리즘들은 확장 함수로 정의하는 것이 좋음
728x90