이안의 평일코딩

[SOLID] React에 개방폐쇄원칙 적용하기 본문

Front-end/React

[SOLID] React에 개방폐쇄원칙 적용하기

이안92 2024. 2. 11. 18:06
반응형

 

컴퓨터 프로그래밍에서 SOLID란 로버트 마틴이 2000년대 초반에 명명한 객체 지향 프로그래밍 및 설계의 다섯 가지 기본 원칙을 마이클 페더스가 두문자어 기억술로 소개한 것이다. SOLID 원칙들은 소프트웨어 작업에서 프로그래머가 소스 코드가 읽기 쉽고 확장하기 쉽게될 때까지 소프트웨어 소스 코드를 리팩터링하여 코드 냄새를 제거하기 위해 적용할 수 있는 지침이다.

 

단일 책임 원칙 (SRP: Single Responsibility Principle)

 

"모든 함수/모듈/컴포넌트는 정확히 한 가지 작업을 수행해야 한다"라는 정의라고 해석할 수 있는 원칙.

 

✍🏻 개방 폐쇄 원칙 (OCP: Open Close Principle)

 

OCP는 "소프트웨어의 구성요소(컴포넌트, 클래스, 모듈, 함수)는 확장에는 열려있고, 수정닫혀 있어야 한다"라고 말합니다. 새로운 기능의 추가가 일어 났을때에는 기존코드의 수정 없이 추가가 되어야 하고, 내부 매커니즘이 변경이 되어야 할때에는 외부의 코드 변화가 없어야 한다 라는 것입니다.

트럭이라는 운송수단과 뒤에 달리는 기구를 분리/결합 할 수 있는 구조를 만들어 두면 새로운 목적이 필요한 도구를 만들어야 할때 트럭 전체를 다시 만들지 않고서 뒤에 달리는 장치만 새롭게 만들어서 붙일 수 있게 됩니다.

 

함수형 프로그래밍에서 이 OCP를 가장 잘 느낄 수 있는 것은 바로 map, filter, reduce와 같은 Higher order Function(고차함수)와 webpack loader와 같은 플러그인 또는 middleware 개념입니다.

(고차함수: 일반적인 함수인데 함수를 인자로 받고 함수를 반환할 수 있는 추가적인 기능을 가진 것.)

 

Bad:

function getMutipledArray(array, option) {
  const result = []
  for (let i = 0; i < array.length; i++) {
    if (option === "doubled") {
      result[i] = array[i] * 2 // 새로운 방식으로 만들기 위해서는 수정이 필요하다.
    }
    if (option === "tripled") {
      result[i] = array[i] * 3 // 옵션으로 분기는 가능하나
    }
    if (option === "half") {
      result[i] = array[i] / 2 // 새로운 기능을 추가하려면 함수 내에서 변경이 되어야 한다.
    }
  }
  return result
}

 

Good:

// option을 받는게 아니라 fn을 받아보자.
// 이제 새로운 array를 만든다는 매커니즘은 닫혀있으나 방식에 대해서는 열려있다.
function map(array, fn) {
  const result = []
  for (let i = 0; i < array.length; i++) {
    result[i] = fn(array[i], i, array) // 내부 값을 외부로 전달하고 결과를 받아서 사용한다.
  }
  return result
}

// 얼마든지 새로운 기능을 만들어도 map코드에는 영향이 없다.
const getDoubledArray = (array) => map(array, (x) => x * 2)
const getTripledArray = (array) => map(array, (x) => x * 3)
const getHalfArray = (array) => map(array, (x) => x / 2)

 

하나의 함수의 기능이 여러가지 옵션들로 인해 내부에서 분기가 많이 발생하고 있다면 OCP와 SRP의 원칙에 맞게 함수를 매개 변수로 받는 방법을 통해서 공통 매커니즘의 코드와 새로운 기능에 대한 코드를 분리해서 다룰 수 있게 할 수 있습니다.

 

본인이 작성한 덩치가 큰 함수가 params에 option이나 flag가 많은 코드가 있다면 한번 SRP와 OCP 원칙을 기반으로 함수를 한번 점검 해보시기 바랍니다. 버그 수정이 아닌 새로운 기능을 개발할때 기존에 개발된 함수를 수정하면서 코드를 개발하고 있다면 OCP 원칙을 위배한 코드를 작성하고 있을 확률이 엄청 높습니다! 🚨

 

👨🏻‍💻 React에 적용해보자!

 

리액트에서는 컴포넌트 합성을 활용해서 OCP 원칙을 적용할 수 있습니다.

실제로 작동하는 모습을 보기 위해 다음 시나리오를 살펴보겠습니다.

const Header = () => {
  const { pathname } = useRouter();

  return (
    <header>
      <Logo />
      <Actions>
        {pathname === "/dashboard" && (
          <Link to="/events/new">Create event</Link>
        )}
        {pathname === "/" && <Link to="/dashboard">Go to dashboard</Link>}
      </Actions>
    </header>
  );
};

const HomePage = () => (
  <>
    <Header />
    <OtherHomeStuff />
  </>
);

const DashboardPage = () => (
  <>
    <Header />
    <OtherDashboardStuff />
  </>
);

 

여기에서 현재 페이지에 따라 다른 페이지 컴포넌트를 위한 링크를 렌더링합니다. 더 많은 페이지를 추가하기 시작할 때 어떤 일이 일어날지 생각하면 이 구현이 나쁘다는 것을 쉽게 알 수 있습니다. 새 페이지가 생성될 때마다 Header 컴포넌트로 돌아가서 렌더링할 작업 링크를 알 수 있도록 구현을 조정해야 합니다. 이러한 접근 방식은 Header 컴포넌트는 취약해지고 사용되는 컨텍스트와 긴밀하게 결합되어, 개방-폐쇄 원칙에 위배됩니다.

 

이 문제를 해결하기 위해 컴포넌트 합성(component composition)을 사용할 수 있습니다. Header 컴포넌트는 내부에서 무엇을 렌더링할지 신경 쓸 필요가 없으며 대신 children prop을 사용해서 Header를 사용할 컴포넌트에게 이 책임을 위임할 수 있습니다.

 

const Header = ({ children }) => (
  <header>
    <Logo />
    <Actions>{children}</Actions>
  </header>
);

const HomePage = () => (
  <>
    <Header>
      <Link to="/dashboard">Go to dashboard</Link>
    </Header>
    <OtherHomeStuff />
  </>
);

const DashboardPage = () => (
  <>
    <Header>
      <Link to="/events/new">Create event</Link>
    </Header>
    <OtherDashboardStuff />
  </>
);

 

이 접근 방식을 사용하면 Header 내부에 있던 변수 로직을 완전히 제거하고 이제 컴포넌트 자체를 수정하지 않고도 문자 그대로 원하는 모든 것을 입력할 수 있도록 합성(composition)을 사용할 수 있습니다.

 

✨✨개방-폐쇄 원칙에 따르면 컴포넌트 간의 결합을 줄이고 확장성과 재사용성을 높일 수 있습니다.✨✨

 

 👀 참고한 사이트 링크

Applying SOLID principles in React

Javascript에서도 SOLID 원칙이 통할까?

반응형
Comments