관심사의 분리 & Custom Hooks

장택진·2023년 9월 19일
0

이번 포스팅에선 클린코드, 관심사의 분리, SRP, Custom Hooks에 대해 얘기해보려고 한다.

클린 코드

클린코드란 여러 관점을 두고 말을 할 수 있지만 통상 가독성이 높고 유지보수가 용이한 코드를 의미한다.

그 밖에도 일관성, 모듈화, 단순성 등등 기준이 될 수 있지만 적어도 내가 생각하는 클린 코드는 변화에 유연하게 대응할 수 있고 수정과 확장에 용이한 코드 라고 스스로 정의했다 !

근데 ! 신입 감자인 내가 ! 흠 없는 코드를 작성할 수 있을까 ?

놉 ! 그건 있을 수 없다. (울 엄마도 콧방귀 낄 소리임)

물론 좋은 코드, 깨끗한 코드를 작성하기 위해 많이 고민하고 시도해봐야 한다 !
대신 완벽할 수 없다면 마이너스는 되면 안된다는 것 !
즉 , 나쁜 코드는 적지 말자는 게 목표다.

물론 여기서 말하는 나쁜 코드는 "나중에 봤을 때 나쁜 코드" 를 의미하는 건 아니다. 당시엔 React가 없었을 수도, 10년 뒤엔 또 새로운 언어가 자리 잡아 React를 쓰지 않을 수도 있으니,

그 당시에는 최선이었던 방법이 시간이 흐름에 따라 현재는 적합한 방법이 아닐 수 있다는 것이다. 그때 최선이였던 코드는 비교적 마이그레이션하거나, 개편하기가 쉽다. 하지만, 애초에 나쁜 코드는 이러한 작업들을 불가능에 가깝게 만든다.

따라서, 개발자는 반드시 좋고 깨끗한 코드를 작성하기 위해서 노력해야 한다. 좋은 코드를 작성할 수 있는 능력은 개발자의 실력을 평가할 수 있는 요소 중 하나이기도 하다. 여태껏 개발자들은 좋은 코드를 작성하기 위한 방법과 원칙들을 공유해왔다. 이를 좀 더 알아보도록 하자!

관심사 분리

개발에는 관심사의 분리(Seperation of Concerns) 이라는 용어가 있다.

이는 좋은 코드를 짜기 위한 가장 기본적인 원칙이며, 더 좋은 애플리케이션을 만들기 위한 여러 디자인 패턴, 기법, 아키텍쳐 등은 결국 모두 이 SoC를 가장 기본적인 원칙으로 삼고 있다.

“관심사"를 간단히 말하면 하나의 모듈이 수행하고자 하는 목적이다, 여기서 모듈이란 함수, 클래스 등의 단위로 해석할 수 있다.

따라서, 관심사의 분리란 각 모듈들이 한번에 여러 관심사를 처리하려고 하지 않고, 하나의 관심사만 처리하도록 분리하는 것을 의미한다.

관심사를 분리하는 이유

그렇다면 왜 관심사를 분리해야 할까?
하나의 모듈에서 여러 기능을 할 수 있으면 이득 아닌가.. ?
왜 하나의 모듈은 하나의 관심사만 처리해야 하나 싶지만 관심사를 분리하면 하나의 모듈은 하나의 목적만 가지게 된다.
하나의 목적만 가지게 된다는 말을 조금 다르게 해석해보면, 이 코드가 수정될 이유는 한가지만 존재하게 된다는 의미.

소프트웨어에서의 변화는 필연적이며, 좋은 소프트웨어 일수록 기존의 기능을 수정, 확장 하는 것에 유연해야 한다. 우리는 이를 "유지보수"라고 부른다.

  • 관심사를 분리하는 이유는 소프트웨어의 특정 부분이 변경되는 이유를 한가지로 한정하여 수정을 용이하기 위해서
  • 만약 여러 모듈들이 여러 관심사를 동시에 다룬다면 특정분야를 수정할 때 관련된 모든 모듈을 수정해야 할 것

예를 들어, 애플리케이션 내에서 인증&인가에 대해서 모든 모듈들이 관여하고 있다면, 추후 인증&인가의 동작을 수정해야 할 경우에는 모든 모듈들을 일일이 돌아다니며 수정을 해야 할 것이다.

하지만, 인증&인가를 다루는 핵심 모듈을 한가지로 제한해두고 나머지는 이 모듈을 사용하는 형식으로 설계되어 있다면 추후 인증&인가의 동작이 변경되었을 경우에는 해당 모듈만 수정하면 되기에 변화에 유연하게 대응할 수 있게 된다.

이처럼 관심사의 분리는 소프트웨어를 만드는 프로그래밍에서 가장 기본이 되는 원칙이다. 기본이 되는 원칙이기에 비슷한 개념을 표현하는 단일 책임 원칙(SRP), KISS 등이 생겨났다.

  • 단일 책임 원칙(Single Responsibility Principle): 관심사의 분리와 유사한 개념이지만, 관심사란 표현 대신 책임이란 용어를 사용한다. 각 모듈들은 책임(수행해야 하는 동작)을 가지고 있으며 각기 하나의 책임만을 가져야 한다는 원칙
  • KISS(Keep It Simple, Stupid): 각 모듈들은 간단하고, 단순하게 만들라는 의미로서 여러 기능을 포함시키면서 복잡하게 만들면 유지보수가 힘들어지기에, 하나의 기능만 수행하도록 하라는 의미. SoC, SRP등의 원칙과 유사한 의미를 가지고 있다.

Custom Hooks

리액트의 관심사

리액트가 가진 관심사는 어떤 것들이 있을까?
리액트는 UI를 구축하기 위한 라이브러리이다.
따라서 리액트가 가진 핵심적인 관심사는

  • UI
  • 로직 (UI를 변경시키는 부분)

위 두가지로 나눌 수 있다.

이 중 UI는 실제 코드상에서는 JSX라는 형태로 표현된다.
그리고 로직은 유저의 입력에 반응하고, API를 호출하고, 스크린의 변화에 반응하는 등 여러 동작들을 통해서 UI에 영향을 미치는 행위라고 할 수 있다.

Presentational - Container

초창기에 유명해진 기법인 Presentational - Container 패턴이다.
Presentational - Container 기법은 컴포넌트를 크게 두 계층으로 분리하는 방법이다.

  • Container는 로직들을 다루는 부분으로 UI에는 관여하지 않고 오로지 UI를 구성하고 변화하기 위한 로직에만 집중하는 컴포넌트이다.

  • Presntational은 반대로 로직은 상관하지 않고 UI가 어떻게 구성되어야 하는지에만 집중하는 컴포넌트이다.

이렇게 컴포넌트를 두 계층으로 나누어서 Presentational을 Container로 감싼 후, 필요한 정보들과 로직을 모두 props로 전달해주는 형태로 설계하는 방법이 Presentatinal - Container 패턴이다.

이 패턴은 Hook이 등장하기 전까지는 관심사를 분리하는 표준 패턴으로 사용되었지만 Hook이 등장한 후에는 더이상 Presentational - Container 패턴을 많이 사용하지 않는다.

어떤 단점이 있었고, Hooks은 어떤 장점이 있어서 바뀐걸까 ?

이 기법으로 프로젝트를 진행하며 느낀 단점은 이렇다.

  • 불필요한 props가 추가된다.
  • props를 단번에 파악하기 어렵다.
  • 초기 추상화 작업이 어렵다.

그렇다면 Hook은 어떻게 활용할 수 있을지 알아보자 !

Custom Hook

커스텀 훅은 리액트가 기본적으로 제공해주는 훅들을(useState, useEffect 등) 이용해서 만든 함수이다.

로직UI를 변경시키기 위함이고, 함수형 컴포넌트에서 로직은 대부분 useState, useEffect 등의 Hook을 통해서 구현된다.

훅을 통해서 편리하게 state를 선언하고 effect를 발생시킬 수 있게 되었지만,
컴포넌트 내부에 많은 로직들이 들어가게 되면 컴포넌트가 복잡해지고,
무엇보다 동일한 로직들을 여러 컴포넌트에 걸쳐서 재사용하기 힘들다는 단점이 있었다.

일적반으로 동일한 로직이 보일경우 함수로 추출하듯이,
리액트에서도 Hook들을 이용한 동일한 로직들을 별도의 함수로 추출해서
여러 컴포넌트에 걸쳐서 사용하고자 하는 시도가 있었고 결국 커스텀 훅이란 기법을 만들게 되었다.

커스텀 훅의 조건

  • React의 Hook들을 호출하는 함수여야 한다.
    • 말 그대로 Hooks를 커스텀 하는 것이기 때문에 Hooks를 호출해야 한다.
    • return이 Hooks일 필요는 없다.
  • 함수의 이름은 use로 시작해야 한다.
    • React에서 Hook의 동작을 처리하는 내부적인 규칙과도 관련이 되어 있고, 공식적인 컨벤션이기 때문에 Custom Hook을 작성할 때는 꼭 use 로 시작하는 이름을 지어야 한다.
    • eslint의 설정에서도 use로 시작한다면 useEffect 의존성 배열에 추가하지 않아도 된다.
  • 같은 Hook을 사용하는 두 개의 컴포넌트는 state를 공유하지 않는다.
    • 두 Custom Hook은 서로 호출되는 위치와 타이밍이 다르며, 애초에 서로 다른 스코프(유효범위)를 생성하기 때문에 컴포넌트를 여러번 호출하는 것처럼 완전히 독립적으로 작동한다.

예제

  • Custom Hooks 리팩토링 전
export default function Example() {
  const [isLightMode, setIsLightMode] = useState(true);

  function changeMode() {
    setIsLightMode((prev) => !prev);
  }

  return (
    <>
      <h1
        style={{
          backgroundColor: isLightMode ? "white" : "black",
          color: isLightMode ? "black" : "white"
        }}
      >
        current mode: {isLightMode ? "Light Mode" : "Dark Mode"}
      </h1>
      <button onClick={changeMode}>change mode</button>
    </>
  );
}
  • Custom Hooks 리팩토링 후
export default function App() {
  const [isLightMode, changeMode] = useToggle(true);

  return (
    <>
      <h1
        style={{
          backgroundColor: isLightMode ? "white" : "black",
          color: isLightMode ? "black" : "white"
        }}
      >
        current mode: {isLightMode ? "Light Mode" : "Dark Mode"}
      </h1>
      <button onClick={changeMode}>change mode</button>
    </>
  );
}

const useToggle = (defaultValue) => {
  const [toggle, setToggle] = useState(defaultValue);

  const changeToggle = () => {
    setToggle((prev) => !prev);
  };

  return [toggle, changeToggle];
};

중요하게 생각하는 점

  • 커스텀 훅은 추상적이여야 한다
    • 즉, 구체적인 구현 방법을 서로 몰라야 한다. ( 알 필요가 없다. )
    • 코드가 어떻든 제시된 요구사항을 충족했다는 것만 알고 있으면 된다.
  • 커스텀 훅을 함수로 만들어서 줄 때는 기본적으로 메모이제이션을 해서 주는게 좋다.
    • WHY?? 로직을 어떻게 쓸 지는 아무도 모른다.
      만약 useEffect에 함수를 넣는다면 무한루프가 돌 수도 있다 !

참고자료

profile
필요한 것은 노력과 선택과 치킨

0개의 댓글