상세 컨텐츠

본문 제목

[이펙티브 코틀린] 3장-재사용성

이펙티브 코틀린 정리

by Tabris4547 2023. 8. 17. 19:52

본문

728x90

프로그래밍 언어 기술이 발달했기 때문에

우리는 손쉽게 코드를 읽고 쓸 수 할 수 있습니다.

각 언어별 내장함수를 손쉽게 사용하고

누군가 작성한 코드를 손쉽게 복붙하는 등등.

이렇게 이전의 작성한 코드를 다시 사용하는 걸

코드의 '재사용성'이라고 부릅니다.

하지만 그렇다고 단순히 코드를 복붙만 한다면

프로그래밍 코드가 엄청나게 지저분해질 수 있습니다.

또, 나에게 맞춘 코드로 일부를 수정할려고했는데

그 일부분 때문에 전체동작이 망가지는 경우도 생깁니다.

그럼 어떻게 코드를 짜야하는걸까?

이펙티브 코틀린 3장은

코드를 재사용할 때 어떻게 해야하는지 가이드를 제시합니다.

 

아이템19

knowledge를 반복하여 사용하지 말라

 

프로젝트에서 이미 있던 코드를 복사해서 붙여넣고 있다면,
무언가 잘못된 것이다

책을 읽다가 이 문구를 보고

많은 반성을 했습니다.

저도 여러가지 이유로

제가 예전에 짰던 코드든, 남이 짠 코드든

별 생각없이 복붙 쓱쓱하는 경우가 있었습니다.

필자는 이 문구를

"knowledge를 반복하여 사용하지 말라"라는 규칙으로 표현합니다.

 

knowledge

 

프로그래밍에서 넓은 의미로 '의도적인 정보'를 뜻합니다.

다시 말해, 프로젝트를 진행할 때 정의한 모든 것을 knowledge라고 합니다.

그 중 중요한 2가지를 꼽는다면, 다음과 같습니다.

 

1. 로직(logic)

 

프로그램이 어떤 식으로 동작하고 프로그램이 어떻게 보이는지

 

2.공통 알고리즘(common algorithm)

 

원하는 동작을 하기 위한 알고리즘

 

설명만 들어보면 같은 말 같지만,

"시간에 따른 변화"라는 차이점이 있습니다.

비즈니스 로직은 시간이 지나면서 사용자의 니즈 등이 바뀌면서 변하지만

공통알고리즘은 크게 바꾸지 않습니다.

 

모든 것은 변화한다

 

프로그래밍에서 유일하게 유지되는 것은 

"변화한다는 속성"이라는 말이 있습니다.

당장 10년,20년 서비스를 해온 게임,웹사이트 등을 생각해볼까요?

게임을 예로 들면 이해하기 쉬울 수 있겠네요.

메이플스토리가 20년이나 이어져온 오래된 게임이고

기본적인 그래픽,UI등은 예전과 비슷하지만

안에 내부적으로 뜯어보면 초창기와 완전 다릅니다.

(전직시스템,퀘스트,스토리 등등)

책에서는 아인슈타인의 예시를 듭니다.

아인슈타인이 시험문제로 작년과 똑같은 문제를 낸 적이 있습니다.

이에 학생이 컴플레인을 넣자 

"ㅇㅇ 문제 같은 거 맞음. 근데 가르치는 게 다른데 답이 같겠음?"

이렇게 대꾸를 했다합니다.

법과 과학을 기반에 둔 것도 변하는데, 

사용자의 니즈에 따라 바뀌는 프로그램은 오죽할까요?

(이에 맞춘 관리시스템으로

애자일(agile)을 소개합니다.

현재 슬랙처럼 온라인게임이었다가 커뮤니티서비스로 변모하듯이

상황에 따라 유연하게 변화하는 것을 예로 들었습니다.)

 

프로그램을 변화시키는 과정에서

knowledge의 무분별한 사용이 문제가 될 수 있습니다.

'귀찮아서'비슷한 코드를 여러개 복붙했다고 쳐봅시다.

내 머릿속에서는 "이거 한 줄 고치면 된다"였는데

나중에 복사한 곳 전부를 찾아서 고칠려니 머리가 아픕니다.

또, 단순히 "이거 하나 고치면 해결되겠지!"하고 수정했는데

프로그램 전체가 나가버리는 경우도 발생하죠.

저는 예전에 서버쪽 구현하다가

단순히 class명을 rename해줬을 뿐인데

빌드 자체가 에러가 떠서 원래 이름대로 되돌린 적도 있습니다.

 

그럼 코드를 '절대'반복하면 안 되나?

'알잘딱깔센'

예를들면 안드로이드 코드를 IOS코드로 바꾼다할 때는

비슷한 부분이 상당히 맣습니다.

이럴때는 복사한 다음에 일부분을 수정하는 식으로

프로그램을 수정해볼 수 있습니다.

 

단일책임원칙

 

'클래스를 변경하는 이유는 단 한가지여야한다'

SOLID원칙 중 하나인 단일책임원칙입니다.

객체지향에 대해서 수 없이 많이 나오는 단어인데

그 의미를 제대로 파악하기가 쉽지 않았습니다.

"변경하는데 여러가지 이유가 있을 수 있나???"

이런 생각들이 머릿속에 자리잡았습니다.

 

class Student{
    //대충 인원체크하는 함수
}

class Boy{
    //대충 인원체크하는 함수
}

 

Student,Boy class각각에 인원을 체크하는 함수가 있다고 가정해보겠습니다.

프로그램을 짜다보니, 이 함수에 문제가 발생했다는 걸 알고 수정하려합니다.

그럼 각각 Student와 Boy 클래스 안의 함수를 수정해야합니다.

"함수 알고리즘이 비슷한데...귀찮아..."라고 생각할 수 있지만

학생과 소년의 수를 체크하는 방식이 일부 다른 점이 있을 수 있기 때문에

이렇게 나누는 것이 단일책임원칙에 맞는 방식입니다.

 

아이템20

일반적인 알고리즘을 반복해서 구현하지 말라

 

많은 개발자가 같은 알고리즘을 여러 번 반복해서 구현합니다.

귀찮다는 이유로 '복붙 복붙!'하면서 코드를 쓱쓱합니다.

하지만 어떤 알고리즘은 이미 라이브러리에 다 구현되있기도 합니다.

val percent=when{
    numberFromUser >100->100
    numberFromUser <0 -> 0
    else->numberFromUser
}
val percent2=numberFromUser.coerceIn(0,100)

 

coreceIn이라는 함수는

특정 범위를 넘어갈 때 값을 지정해주는 알고리즘을 동작시킵니다.

저렇게 내장함수를 사용한다면

1.코드 작성속도가 빨라짐

2.가독성 상승

(처음에는 무슨 함수인지 헷갈려도, 한번만 파악하면 그 후로는 파악가능)

3.직접 구현할 때의 실수를 줄일 수 있음

4. 내장함수가 최적화되면 저절로 모든 프로그램이 최적화

 

사실 저는 표준라이브러리의 내장함수를 쓰는 것에 부정적인 시각이 있었습니다.

어려운 알고리즘을 그냥 함수 불러서 쓰면

그게 창의적인 방식인가에 대해서 의문을 제기했습니다.

물론 처음에 공부를 할 때에는 직접 구현하면서 코드를 알아가는 것도 좋겠죠.

하지만 '창의적'을 핑계로 괜히 코드를 어렵게 짜는 것보다는

편안하게 있는 걸 가져다 쓰는 것도 좋죠.

예전에 알바했을 때 매니저님이

"수제라고 다 맛있는 거 아니다. 그냥 기성품 가져다가 쓰는게 더 좋을 수 있다"

라면서, '수제부심'만 있는 가계들을 깠던 게 생각나네요.

 

만약에 표준라이브러리에 없는 알고리즘이지만

내가 자주 쓸 거 같은 거라면??

그럴 때는 본인만의 유틸리티를 구현하라고 권장합니다.

쉽게 이해해서 '나만의 라이브러리 만들기'입니다.

자주 쓸 거 같은 함수들을 라이브러리화 시킨다면

나중에 다른 작업을 할 때도 편리하게 사용하며

유지보수가 매우 간결해집니다.

 

아이템21

일반적인 프로퍼티 패턴은 프로퍼티 위임으로 만들어라

 

코틀린은 코드 재사용과 관련해서 프로퍼티위엄이라는 기능이 있습니다.

프로퍼티 위엄을 사용하면 일반적인 프로퍼티의 행위를 추출해서 재사용할 수 있습니다.

이 책에서는 지연 프로퍼티인 lazy를 예로 들었습니다.

이 프로퍼티는 이후에 처음 사용하는 요청이 들어올 때

초기화되는 프로퍼티를 의미합니다.

val value by lazy {createValue()}

이를 사용하면 이후 변화를 감지하는

observer패턴도 쉽게 만들 수 있는데요

var items:List<Item> by
        Delegates.observable(listOf()){ _,_,_->
            notifyDataSetChanged()
        }
var key:String? by
        Delegates.observable(null){ _,old,new ->
            Log.e("key changed from $old to $new")
            
        }

 

 

 

 

아이템22

일반적인 알고리즘을 구현할 때 제네릭을 사용해라

 

타입 이규먼트를 사용해 함수에 타입을 전다랄 수 있습니다.

이런 함수를 제네릭함수(generic function)이라고 부릅니다.

이 책에서는 filter함수를 예로 들었습니다.

inline fun<T> Iterable<T>.filter(
    perdicate: (T)->Boolean
):List<T>{
    val destination=ArrayList<T>()
    for (element in this){
        if(predicate(element)){
            destination.add(element)
        }
    }
    return destination
}

타입 파라미터는 타입에 대한 정보를 제공하여

컴파일러가 타입을 조금이라도 정확하게 추측할 수 있도록 도와줍니다.

 

inline fun<T:List> Iterable<T>.filter(
    perdicate: (T)->Boolean
):List<T:List>{
    val destination=ArrayList<T>()
    for (element in this){
        if(predicate(element)){
            destination.add(element)
        }
    }
    return destination
}

variance에다가 제한을 걸어둘 수 있는데

보시는 코드에서 T:List라고 해뒀습니다.

이러면 추후에 수정을 했을 때

List 값을 리턴할 것을 명시합니다.

Int,String등등 어떤 것이든 List인 것만을 취급한다는 의미죠.

이런 제한을 통해

재사용시 기능을 명시할 수 있습니다.

아이템23

타임 파라미터의 섀도잉을 피하라

 

class Forest(val name: String) {
    fun addTree(name: String) {

    }
}

다음의 경우처럼

프로퍼티와 파라미터가 같은 이름을 가질 때

섀도잉(shadowing)이라고 부릅니다.

다행히 이런 경우는 쉽게 문제를 찾을 수 있지만

class Forest<T:Tree>{
    fun<T:Tree> addTree(tree:T){
        
    }
}

이런 식으로 코드가 조금만 더 복잡해지면

섀도잉으로 인한 혼란이 옵니다.

대체 addTree를 동작할 때의 T는

파라미터인제, 프로퍼티인지 헷갈립니다.

class Forest<T:Tree>{
    fun<ST:Tree> addTree(tree:ST){

    }
}

이런 식으로 이름을 다르게 선언해주면서

가독성을 높이고 코드에 대한 오해를 줄여주는 방법이 있습니다.

 

 

아이템24

제네릭 타입과 variance한전자를 활용하라

class Cup<T>

위의 코드에서 타입 파라미터 T는

variance한정자가 없으므로 기본적으로

invariant(불공변성)입니다.

invariant는 제네릭 타입으로 만들어지는 타입들이

서로 관련성이 없다는 의미인데요

Cup<Int> Cup<Number> Cup<Any>

모두 어떤 관련성이 없습니다.

만약에 관련성을 원한다면, out,in이라는 variance한정자를 사용합니다.

class Cup<out T>
open class Dog
class Puppy:Dog()

val a:Cup<Dog> =Cup <Puppy>() //ok
val b:Cup<Puppy>= Cup <Dog>() //오류
class Cup<in T>
open class Dog
class Puppy:Dog()

val a:Cup<Dog> =Cup <Puppy>() //오류
val b:Cup<Puppy> = Cup <Dog>() //ok

 

함수타입

fun printProcessedNumber(transition:(Int)->Any){
    print(transition(42))
}

Int를 받고 Any를 리턴하는 파라미터입니다.

 

(Int)->Number, (Number)->Any,(Number)->Number,(Number)->Int 등으로도 작용합니다.

 

 

(어려운 부분이 많은 챕터라

설명이 정말 어렵네요..ㅠ

이 포스트에 잘 적혀있지 않은 부분에 대해서는 지적부탁드려요)

728x90

관련글 더보기

댓글 영역