[개발 서적] 쏙쏙 들어오는 함수형 코딩(2)

4 분 소요

🔗 들어가며

지난 게시글에서는 함수형 프로그래밍에서 중요한 액션/계산/데이터의 개념에 대해 다루고 이를 잘 활용할 수 있는 방법을 정리해보았다.

이번 게시글에서 중심적으로 다룰 내용은 계층형 설계이다. 함수형 프로그래밍에서 계층형 설계란 무엇이며, 왜 필요한지를 한 번 살펴보자!


🔗 계층형 설계?

계층형 설계

  • 아래 계층의 함수로 지금 계층의 함수를 만드는 방법
  • 즉, 모든 계층은 바로 아래 계층에 의존해야 한다.

계층형 설계하면 기억해야 하는 것

  • (게시글을 통해 계층형 설계의 핵심이 무엇인지를 한 번 찾아보자!)

함수형 프로그래밍에서 계층형 설계는 말 그대로 함수를 사용해 계층 구조를 만드는 것이다. 해당 책에서는 계층형 설계를 만들 수 있는 방법으로 다음 네 가지 패턴을 소개한다.

  • 직접 구현
  • 추상화 벽
  • 작은 인터페이스
  • 편리한 계층

우선 위 패턴을 다루기 전에 계층구조를 호출 그래프를 통해 시각화하는 것을 먼저 알아보자! 다음 코드에 대한 호출 그래프를 확인해보자!


🔗 호출 그래프?

fun freeBookPromotion(cart: Array<Item>) {
  for(i in 0 until cart.size) {
    ... // 카트 안 물건들의 가격 합 구하기
  }

  if (total >= FREEBOOK_MIN_AMOUNT) {
    val freeBk = getFreeBook() // 받을 수 있는 새 책 중 하나 가져오기
    addBookToCart(cart, freeBk) // 카트에 새 책 추가
  }

  return cart
}

freeBookPromotion

그렇다면 위에서 표시한 for loopgetFreeBook, addBookToCart는 모두 같은 계층(동일한 추상화 수준)일까?

아니다! 언어 기능과 직접 만든 함수는 추상화 수준이 다르다. 여기서 언어 기능이 더 낮은 추상화 단계에 해당한다.

freeBookPromotion 함수에는 서로 다른 추상화 단계가 존재한다. 이처럼 하나의 함수에 서로 다른 추상화 단계가 있다면 코드를 이해하기 어렵게 된다.


🔗 직접 구현하기 패턴 적용

여기서 위에서 언급한 패턴 중 직접 구현 패턴을 적용할 수 있다. 직접 구현 패턴을 활용해 함수 속 서로 다른 계층을 없앨 수 있다. 여기서는 for loop을 돌며 카트 안의 물건들의 합을 구하는 코드를 별도의 함수로 만들어 호출하면 된다. 이렇게 됐을 때 다음과 같이 freeBookPromotion 함수 속에는 동일한 계층의 함수들만이 존재하게 된다.

여기서 새로운 함수를 만든 것은 결국 “구체적인 것을 숨겨 화살표 길이를 줄인 것”임을 기억하자. 따라서 이렇게 직접 구현하기를 통해 새로운 함수를 만들다 보면 화살표 갯수가 늘어나는 경우도 존재한다. 하지만 화살표 갯수를 줄이는 것보다 화살표의 길이를 줄이는 것이 중요하다는 것을 인식하자.

calcAmount

물론 위와 같이 간단한 코드에서는 서로 다른 계층 구조가 있다는 것을 한눈에 파악할 수 있기 때문에 굳이 호출 그래프를 그려보지 않아도 된다. 하지만 코드 규모가 점점 커진다면 호출 그래프를 그림으로써 복잡해지는 계층 구조를 전체적으로 파악하는 데 도움이 될 것이다.

✏️ 그렇다면 서로 다른 코드가 같은 계층인지 어떻게 알 수 있을까?

  • 만약 동일한 하위 박스를 가르키고 있다면 동일한 계층으로 취급한다.
    • 위 예제에서는 getFreeBook에서는 freeBook 배열 중 하나를 가져오기 때문에 array index가 하위에 존재한다.
    • addBookToCart도 마찬가지로 새로운 book을 cart 배열에 추가하기 위해 array index가 하위에 존재한다.
    • 결과적으로 둘은 같은 계층이라고 볼 수 있다.

✏️ 직접 구현의 방식

  • 직접 구현은 위와 같이 낮은 계층을 감싸 상위 계층의 블록을 만드는 것을 의미하기도 하지만 함수 내 구체적인 내용을 하위 계층의 블록으로 빼내는 것(일반적인 함수 추출)을 의미하기도 한다.
  • 일반적인 함수는 재사용에 좋다.


🔗 추상화 벽 패턴 적용

여기서 말하는 추상화 벽이라는 것이 무엇인지 먼저 살펴보자.

추상화 벽

  • 데이터 구조를 몰라도 함수를 사용할 수 있다는 것을 의미한다.

다음의 상황을 생각해보자. 마케팅 관련 개발팀과 서비스 개발팀이 협업하는 경우이다. 여기서 추상화 벽을 활용한다면 서비스 개발팀이 제공하는 인터페이스를 활용해 마케팅 개발팀은 내부가 어떻게 구현되어 있는지와 상관없이 원하는 작업을 진행할 수 있다.

abstract_wall

  • 마케팅 개발팀 입장
    • 제공되는 인터페이스를 활용해 내부 구현을 몰라도 원하는 작업을 진행할 수 있다.
  • 서비스 개발팀 입장
    • 제공되는 인터페이스가 어떻게 활용되고 있는지와 상관없이 내부 구현을 수정할 수 있다.

✏️ 추상화 벽의 핵심

  • 추상화 벽은 벽 위 코드가 가지는 아래 코드에 대한 의존성을 없애는 역할을 한다.
  • 추상화 벽을 사용하면 코드를 쉽게 고칠 수 있다.
    • 하지만 코드를 쉽게 고치기 위해 추상화 벽을 사용하는 것은 아니다.
    • 팀 간 커뮤니케이션 비용을 줄이고, 복잡한 코드를 명확하게 하기 위해 전략적으로 활용해야 한다.


🔗 작은 인터페이스 패턴 적용

해당 패턴은 새로운 코드를 추가할 위치에 관한 것이다.

새로운 기능을 만들 때 하위 계층에 기능을 추가하거나 고치는 것보다 상위 계층에 만드는 것작은 인터페이스 패턴이라고 할 수 있다. 즉, 하위 계층에 불필요하게 많은 기능을 넣어두지 않아야 한다.

✏️ 계층이 가진 함수는 완전하고, 적고, 시간이 지나도 바뀌지 않아야 한다.


🔗 편리한 계층 패턴 적용

계층형 설계에 적용할 수 있는 마지막 패턴이다. 위 세 가지 패턴은 어떻게 하면 계층형 설계를 잘 할 수 있냐에 대한 내용이다. 해당 패턴은 언제 패턴을 적용하고 멈출지에 대한 판단의 근거를 제공해준다.

이 책에서는 다음의 질문을 스스로에게 하라고 한다.

? 현재 작업하는 코드가 편리한가

그렇다면 설계는 잠시 멈춰도 된다. 서로 다른 계층이 하나의 함수에 존재하고, 화살표의 길이가 길더라도 상관없다.

하지만 구체적인 것을 너무 많이 알아야 하거나, 코드가 지저분하다면 다시 패턴을 적용하자.


🔗 계층형 설계로 보는 유지보수 / 테스트 / 재사용에 용이한 함수

이제 마지막으로 계층형 설계를 통해 유지보수성 / 테스트성 / 재사용성에 대해 살펴보자.

1️⃣ 유지보수성

유지보수는 변화에 있어 쉽게 수정할 수 있는지를 의미한다.

상위 계층의 함수들은 하위 계층의 함수들로 이루어진다. 따라서 하위 계층 함수들은 일반적이어야 하며, 쉽게 변경되어서는 안된다. 반면에 상위 계층 함수들은 호출되는 곳이 적기 때문에 다른 코드에 주는 영향이 적어 쉽게 변경할 수 있다.

따라서 그래프 가장 위에 있는 코드가 고치기가 가장 쉽다.

✏️ 자주 바뀌는 코드는 상위 계층에 존재해야 한다.


2️⃣ 테스트성

하위 계층 코드 테스트가 중요하다.

하위 계층 코드는 쉽게 바뀌지 않는다. 따라서 작성한 테스트도 쉽게 바뀌지 않는다.

✏️ 제한된 시간에 테스트를 작성해야 한다면 상위 코드보다 하위 코드에 대한 테스트를 중심으로 작성하는 것이 더욱 가치가 있다.


3️⃣ 재사용성

위에서 한 번 언급했듯이 하위 계층 코드는 더욱 일반적은 코드들이다. 따라서 재사용하기 쉽다.


🔗 마무리하며

이번 게시글에서는 계층형 설계에 대해 깊이 다뤄보았다. 함수형 프로그래밍을 하면서 작은 단위로 함수를 구성하다가도 어느 정도의 작은 단위로 함수를 구성하는 것이 맞을까라는 의문을 들 때가 있었다. 이번 계층형 설계에 대한 부분은 이러한 고민에 대한 해답이 되어 주었다.

정답이 없기 때문에 코드를 많이 작성하면서 감각을 익히는 것이 중요한 것 같다. 함수를 작성하면서 해당 함수는 어느 정도의 세부적인 구현을 알고 있어야 하는지를 스스로 판단하고, 그에 맞게 새로운 함수를 구현하거나 추출하는 과정을 반복하며 함수형 프로그래밍에 조금 더 익숙해지는 시간을 가져야겠다.

댓글남기기