응집도있는 컴포넌트 설계란?

HongBeen Lee·2022년 6월 21일
4


[B3] 우리는 응집도에 대하여 이야기할 필요가 있다

넘블 챌린지 중, 챌린지 호스트님께서 방향을 잡는데 좋은 영상을 추천해주셨다.

그리고 내가 리액트로 개발한 페이지의 성능측정을 해보았는데, 컴포넌트가 제대로 나눠져 있지 않아서 성능저하가 일어나고 있었다.
성능개선을 위해 리팩토링이 필요했다.
좋은 공부가 될 것 같군!

추천해주신 넘블 호스트님과 좋은 영상을 제공해주신 마켓컬리에게 감사합니다 👍🏻


응집도를 나누는 기준

상위의 기준을 가질수록 좋은 응집도

  1. 기능적 응집도
    • 모듈이 하나의 기능만 하고, 작은단위로 기능할 때. 단일책임원칙과 비슷.
  2. 순차적 응집도
    • 루틴이 특정순서로 수행되어야하고, 단계마다 정보를 공유하며, 동시에 수행되면 완전한 기능을 제공하지 못할 때. 하나의 로직을 분리하여 하나의 일만 수행하도록 하면 기능적 응집도를 갖게할 수 있다.
  3. 통신적 응집도
    • 각 모듈이 같은 데이터를 사용하지만, 서로 관련성이 없을 때.
  4. 시간적 응집도
    • 여러 연산이 동시에 수행되어야 해서 하나의 로직으로 결합될 때.
  5. 절차적 응집도
    • 특정 순서대로 실행.
  6. 논리적 응집도
    • 여러 기능을 하나의 루틴에서 수행할 때, 루틴에 전달되는 조건에 따라 수행하는 기능이 다른 경우. 이름과 번호를 받는 컴포넌트에서, 번호만 -(dash)를 지우는 작업이 필요하다면 플래그를 사용해서 한 함수로 사용하지 않고, 함수를 분리해야한다. 예측이 쉽고, 한 함수가 하나의 일을 하도록
  7. 우연적 응집도
    • 놓을곳을 몰라서 util에 마구 넣어진 경우.. 반드시 피해야 함.

컴포넌트 예시로 알아보기

페이지네이션 컴포넌트의 응집도를 높여보자!

페이지를 클릭하면 onChangePage이벤트가 발생한다.

페이지 당 갯수를 변경하면 onChangeSize이벤트가 발생한다.


기능별로 분리해보자

  • 페이지네이션 정보를 보여주는 컴포넌트 Pagination
  • 페이지 당 개수를 변경하는 컴포넌트 PagingSize

각 컴포넌트가 하나의 일만 하고있다.

데이터와 메서드가 끈끈하게 연결되어있다.

높은 응집도를 가진 컴포넌트가 될 것이다.


PagingSize 컴포넌트에서 onChangeSize를 실행한 뒤, 현재 페이지가 1로 초기화되어야한다면?

그림처럼 onChangeSize 이벤트가 page에도 의존하므로, 외부의 page에 의존하게 된다.

따라서 PagingSize는 응집도가 낮은 컴포넌트가 된다.


어떻게 개선할 수 있을까?

그래서 size, page, total을 더 작게 나눌수없다고 판단한다면, paging이라는 데이터로 표현할 수 있다.

이제 Pagination컴포넌트는 paging 데이터를 받고, 페이징이 변경되었을 때 onChange가 발생한다.

데이터가 메서드가 밀접하게 응집되어있으므로 응집도가 높은 컴포넌트이다.


데이터와 메서드가 연관이 있다고 해서 응집도가 높은 컴포넌트일까?

새로고침을 하더라도 이전에 선택한 페이지를 기억하기 위해서 Pagination정보를 수정할 때마다 URL 쿼리를 수정하는 기능을 추가하려고 한다.

onChange가 발생할 때, 브라우저의 location API를 사용하여 쿼리를 수정한다.

  • 페이지를 바꾸면? onChange함수에서 paging.page를 변경, 쿼리 수정
  • 사이즈를 바꾸면? onChange함수에서 paging.size를 바꾸고 page를 1로 변경, 쿼리 수정

그럼 이 때의 의문.

이 컴포넌트의 목적이 뭔가?

  1. 페이지네이션을 수정하는 것
  2. URL 쿼리를 수정하는 것

데이터와 메서드의 연관관계를 보면 응집도가 높다고 할 수 있다.

하지만 목적이 두 가지이기 때문에 기능적 응집도가 낮아지게 된다.

높은 응집도의 컴포넌트를 만들려면, 컴포넌트의 목적을 명확히 하고 이 목적을 위해서 데이터와 메서드가 똘똘 뭉치게 해야한다.


왜 높은 응집도가 중요할까?

  1. 이해하기 쉽다.
    필요한 데이터를 한번에 파악하여 이해하기 쉬워 일처리를 빠르게 한다.

  2. 의도파악이 쉽다.
    PR 목적에 맞지않는 commit이 있을 때. 이걸 왜 했지? 싶음. 그리고 PR을 분리해야 나중에 코드리뷰 후 수정하기도 수월하다.

  3. 테스트하기 쉽다.
    페이지네이션을 담당하는 컴포넌트에서 쿼리를 조작한다면? 쿼리 조작이 어디에서 이루어지는지 추적하기 쉽지않다. 테스트도 URL관련한 테스트를 분산되게 해야한다.


Pagination 컴포넌트를 테스트한다면 어떨까?

새로운 쿼리를 추가하거나, 페이징 관련 버튼을 추가하면 이 컴포넌트의 로직을 봐가며 수정해야한다. 테스트도 수정해야한다.

즉, 페이징 변경과 쿼리 변경 두가지를 수행하므로 응집도가 낮아진다.

컴포넌트 내부에서 쿼리 변경 함수와 페이징 변경 함수를 나눈다고 하더라도, 같은 데이터를 사용하는 통신적 응집도가 존재하게 된다.
동시에 컴포넌트가 두 가지 일을 하므로 기능적 응집도는 낮다.

컴포넌트가 하나의 일만 하도록 URL 쿼리를 갱신하는 로직을 바깥으로 뺀다면?

Pagination 컴포넌트는 페이징을 변경하는 일과 페이지를 보여주는 일만 담당하게 된다.

테스트도 간단해졌다. 테스트가 많아지고 복잡해질 수록 응집도에 대해 의심해보는것도 좋다.

대부분의 문제는 내가 만든 코드에 어떤 변경 사항이 일어나거나, 다른 사람이 사용할 떄 일어난다.

다른사람이 내 코드를 볼 때, 이해하고 사용해야 하는데 응집도가 낮으면 사용하기 어려워진다.

테스트 코드를 작성하면, 내 코드를 사용하는 설명서 역할을 하므로 이해하기 쉽게 만들어준다. 그런데 테스트가 아주 많다면, 100장짜리 설명서는 아무도 읽고싶지 않을 것…. 내 코드를 사용하기 어려운건 아닐지..

코드 작성 전, 먼저 테스트를 작성한다면 어떻게 사용할지 미리 고려할 수 있기 때문에 처음부터 사용하기 좋은지 설계할 수 있다.

테스트는 단순히 원하는대로 동작하는 것 뿐 아니라 응집도있는 설계, 사용하기 좋은 코드를 만드는데 도움이 되는 도구로 활용할 수 있다.

하지만 무조건 높은 응집도가 좋은 것은 아니고 목적에 맞게 필요한 만큼만 응집되어 있어야 한다.


응집도는 컴포넌트를 나누는 기준이 된다!

높은 응집도를 설계하는 것도 중요하지만

낮은 응집도를 발견💡 하는 것이 중요하다.

마치 리팩토링하는 방법은 모두 알지만,
언제 리팩터링해야할지 모르는 것과 같다.


Paging 컴포넌트를 응집도있는 코드로 만들어보자

테스트로 검증해야할 내용은 다음과 같다.

테스트가 늘어간다…
→ 컴포넌트를 더 작게 나눠야하지 않나? 로 생각해볼 수 있다.

그래서 더 작게 나눠본 컴포넌트는 이렇게 된다.

  • PagingSizeSelect
    • 사이즈를 전달받고, 변경된 사이즈를 전달하는 역할
  • PreviousButton
    • 클릭했을 때 실행할 onClick과 첫페이지일 때 비활성화하는 disabled
    • 비활성화하는 부분은 컴포넌트 내부에서 처리한다. Pagination컴포넌트는 관심없기 때문…!
  • PageButton
    • 몇번째 page인지 받고, 선택되었다면 어떻게 표시될지 내부에서 결정하여 보여준다.
    • 클릭되면 클릭된 페이지를 넘겨준다.
  • NextButton

와우~~
각 모듈이 기능이 명확하고, 하나의 일만을 담당하고 있다!
데이터와 메서드도 적절한 응집도를 가지게 되었다.


정리

  • 응집도는 모듈의 목적을 위해 요소들이 밀접하게 연관되어 있는 것을 말한다.

  • 높은 응집도 모듈은 이해하기 쉽고, 의도파악이 쉽고, 테스트하기 쉽게 만들어 준다.

  • 테스트를 작성하여 응집도가 높은지 낮은지 파악할 수 있으며 응집도에 따라 컴포넌트를 분리할 수 있다.


그동안 컴포넌트를 나누는 기준이 없었던 것 같다.
반성....
이런 기준으로 나누는거구나..
적당히 재사용할 수 있으면서 시각적으로 나눌 수 있으면 나눴는데 이런 시각에서는 생각하지 못했다.

현재 내 프로젝트에서 리팩토링이 필요한 컴포넌트가 위 예시에서 Pagination과 비슷하다. 언뜻 보기에 페이지네이션 하나의 기능을 담당하지만, 분석해보면 그 안에서도 여러가지 일을 하고있어서 분리가 필요하다.
(물론 분리안하고 짰음..반성ㅠ)

내일 리팩토링 하러간닷!

profile
🏃🏻‍♀️💨

0개의 댓글