Search
3️⃣

좋은 컴포넌트 분리란 (feat. The Wet Codebase)

Created
2023/08/17
Tags
Etc.
Category
Knowledge
Parent item
Sub-item
2 more properties
평소 ‘컴포넌트’라는 말을 많이 쓰는데, 정확히 어떤 것인지, 왜 써야 하는지 잘 모르고 쓰는 것 같다.
일단 GPT에게 물어보니 아래와 같이 답변해 준다.

 GPT가 말하는 컴포넌트

컴포넌트란?

컴포넌트(Component)는 소프트웨어 개발에서 재사용 가능하고 독립적인 기능 또는 모듈을 말한다. 이러한 컴포넌트는 소프트웨어 시스템을 구성하고 빌드하는 데 사용되며, 주로 모듈화와 추상화의 원칙을 따른다.

컴포넌트의 기능

컴포넌트는 다양한 분야에서 활용되며, 소프트웨어 개발에서는 사용자 인터페이스(UI) 컴포넌트, 데이터베이스 접근 컴포넌트, 네트워킹 컴포넌트 등 다양한 종류의 컴포넌트가 있다. 이러한 컴포넌트들을 조합하여 큰 시스템을 구축하고 관리하는 것이 가능하다.

컴포넌트의 주요 특징

(1) 재사용성
컴포넌트는 특정 기능 또는 작업을 수행하는 독립적인 단위로, 이를 다른 프로젝트나 시스템에서 재사용할 수 있다. 이는 개발 생산성을 향상시키고 중복 코드를 줄이는 데 도움이 된다.
(2) 독립성
컴포넌트는 자체적으로 동작하고 필요한 데이터와 상태를 포함할 수 있다. 이로 인해 각 컴포넌트는 다른 컴포넌트와 독립적으로 개발, 테스트 및 유지보수될 수 있다.
(3) 인터페이스
컴포넌트는 외부와 상호 작용하기 위한 인터페이스를 제공한다. 이를 통해 다른 컴포넌트나 시스템은 해당 컴포넌트의 기능을 호출하거나 데이터를 주고 받을 수 있다.
(4) 추상화
컴포넌트는 내부 동작의 구체적인 세부 사항을 숨기고 필요한 기능만 외부에 노출함으로써 추상화의 원칙을 따른다. 이로써 컴포넌트 사용자는 내부 동작을 알 필요 없이 컴포넌트를 활용할 수 있다.

하지만,

이 설명도 그리 와닿지는 않았다. 컴포넌트의 위와 같이 텍스트로 개념을 먼저 학습하고 사용해야 하는 것보다는 컴포넌트라고 불리우는 것들을 모아보면 그로부터 컴포넌트의 핵심 개념, 즉 프로토타입이 아닐까? 생각했다.

컴포넌트라고 불리우는 것들을 비교해 보자

바닐라 JS

일단 바닐라 자바스크립트란, 프레임워크나 라이브러리가 포함되지 않은 순수한 자바스크립트를 의미한다.
웹 컴포넌트란, HTML 엘리먼트를 위해 만들어진 재사용 가능한 자바스크립트 코드를 의미한다. 재사용이 가능하다는 것은 코드가 함수 등으로 작성되어 언제든 임의의 HTML 엘리먼트에 적용할 수 있다는 것을 의미한다.
즉, 바닐라 JS에서 웹 컴포넌트는 아래와 같이 함수와 Custom Elements API를 사용하여 만든다.
// 컴포넌트 함수 정의 function customComponent(elements) { for (const element of elements) { element.onClick = function() { element.textContent = "Already clicked"; }; } } customComponent(document.querySelectorAll(".custom"));
JavaScript
복사
// 커스텀 엘리먼트 클래스 정의 class CustomComponent extends HTMLElement { constructor() { super(); // Shadow DOM 생성 const shadow = this.attachShadow({ mode: 'open' }); // 컴포넌트 내용 생성 const paragraph = document.createElement('p'); paragraph.textContent = 'Hello from Custom Component!'; // Shadow DOM에 컴포넌트 내용 추가 shadow.appendChild(paragraph); } } // 커스텀 엘리먼트 등록 customElements.define(componentName, CustomComponent); } // 커스텀 엘리먼트 정의 함수 실행 defineCustomComponent();
JavaScript
복사

React

컴포넌트를 클래스 형식 뿐만 아니라 함수형으로도 생성할 수 있으며, JSX를 사용하여 UI를 표현한다. JSX는 JavaScript와 비슷한 문법을 사용하여 가독성 높은 UI 코드를 작성할 수 있도록 돕는 독특한 문법이다. 라이브러리. 클래스로도 컴포넌트를 만들 수 있고, 함수로도 만들 수 있다.
import React from 'react'; class ClassComponent extends React.Component { render() { return <p>Hello from Class Component!</p>; } } function FunctionComponent() { return <p>Hello from Function Component!</p>; } // JSX를 사용하여 컴포넌트 사용 const App = () => ( <div> <ClassComponent /> <FunctionComponent /> </div> ); export default App;
JavaScript
복사

Vue

Vue 라는 프레임워크도 마찬가지로 컴포넌트라는 개념을 사용하며, 템플릿 기반의 접근 방식을 사용한다. Vue 컴포넌트 역시 클래스 또는 객체 형태로 생성할 수 있으며, 템플릿을 사용하여 UI를 정의하고 컴포넌트를 조합할 수 있다.
<template> <div> <ClassComponent /> <FunctionComponent /> </div></template><script> import ClassComponent from './ClassComponent.vue'; import FunctionComponent from './FunctionComponent.vue'; export default { components: { ClassComponent, FunctionComponent } } </script>
JavaScript
복사

살펴보니,

겨우 3개만 봤는데도 컴포넌트는 바닐라 JS 또는 라이브러리 또는 프레임워크로 만들어져 있고, 클래스로도 할 수 있고 함수로도 할 수 있고, 심지어 View를 표현하는 방법 또한 제각기였다. 사실 공통점보다는 차이점이 더 많다 생각이 들기도 한다.

 중간 결론

그래서 컴포넌트가 또 무엇일까 의문이 든다.
그럼에도 불구하고 공통점이 하나 있었는데, 바로 레이아웃과 스타일을 나눠서 이름을 붙인다는 것이었다. 그렇다면 일단 아래와 같이 정리할 수 있겠다.
레이아웃과 스타일 → ‘View’
공통된 관심사를 묶어서 이름을 붙이는 것 → ‘추상화’
결국 컴포넌트의 핵심은 ‘View를 추상화하는 것’ 이라는 결론에 이른다.

잘 설계한, 잘 만든 컴포넌트는 뭘까

아래 예시 화면은 우아한테크: [10분 테코톡] 쵸파의 컴포넌트 영상의 일부를 캡쳐했습니다. 유익한 내용 전해주신 쵸파 님께 감사를 표하며, 영상을 통해 학습한 내용을 아래에 정리해 보았습니다.
컴포넌트는 View를 추상화하는 것인데, 이 추상화라는 개념은 굉장히 어려운 것 같다. 그래서 적절한 단위로 추상화를 잘하는 것이 == 컴포넌트를 잘 만드는 것이겠구나 라는 생각이 든다.
다만, ‘적절한 단위로’, ‘잘’이라는 말이 굉장히 추상적이고 사람마다 기준이 다르기 때문에, 어떻게 잘 나누는 게 좋을지 이 관점에 대해 예제를 통해서 이야기를 해보려고 한다.

예시

아래의 화면에서 어떻게 컴포넌트를 나눌까?
View를 추상화하는 관점에 따르면 우선 View를 기준으로 크게 두 가지로 나눠보고 싶다. 그렇다면 대충 이런 식으로 나눌 수 있을 것 같다.
그러면 이제 ProductList와 ProductItem이라는 컴포넌트가 만들어졌다.

개발자님 요청드려요 1

이때, 추가 요구사항이 들어온다. “개수를 사진 안으로 옮기고 가격을 가운데 정렬 해주세요” 라는 요구사항이다.
그렇다면 우리가 추상화 해놓은 ProductItem에서 CSS만 조금 수정하면 정말 쉽게 대응할 수 있을 것 같다.
하나의 컴포넌트만 바꾸면 재사용을 했기 때문에 전체적으로 잘 들어가는 모습을 볼 수 있고 우리는 추상화를 아주 잘한 것 같다.

개발자님 요청드려요 2..

두 번째 요구사항이 들어왔다. 장바구니 페이지와 주문 목록 상세 페이지 두 개의 페이지를 만들어야 하는 상황이다. 추상화를 해보자면, 대충 View를 보고 컴포넌트를 추상화하면 아래와 같이 나눌 수 있을 것 같다.
그런데 여기서 비슷해 보이는 View가 있다.
위 사진에서 초록색으로 표시한 컴포넌트는 하나의 컴포넌트로 추상화할 수 있을 것 같다. 우측의 ‘주문하기’ 부분만 다르기에, 주문하기 정도는 아래와 같이 분기를 통해서 나눌 수 있을 것 같다.

개발자님 요청드려요 3 ….

여기서 또 새로운 추가 요구사항이 들어왔다. “장바구니 결제예상금액에서 쿠폰을 적용하게 해주세요” 라는 요청이다. 그래서 아까 추상화한 컴포넌트를 아래와 같이 바꾸기로 했다.
쿠폰을 선택하고 할인금액을 계산해서 얼마인지까지 표현을 한다. 그런데 여기서 사이드 이펙트가 생긴다.
하나의 컴포넌트로 추상화를 했기 때문에 장바구니에서만 바뀌어야 되는데 여기에서는 이제 쿠폰이 안들어간다. 그래서 또 큰 단위의 분기를 추가한다.

개발자님 요청드려요 4 …….

그런데 여기서 또 추가 요구사항이 들어왔다. 장바구니의 결제예상금액에서는 마일리지를, 주문상세 결제금액에서는 결제한 날짜를 추가해 달라고 한다.

 🫨

처음에는 가벼운 컴포넌트로 한 추상화에서 쉽게 대응하려고 했으나, 점점 공통점보다는 차이점이 많아졌다. 동료 개발자가 이 컴포넌트를 보았을 때 바로 이해할 수 있을까? 음, 아무래도 어려울 것 같다. 게다가 추가적인 요구사항이 들어오면 또 어떻게 추상화를 해야 될까 고민을 하면서 어렵게 코딩을 할 것 같다.

 제안하는 첫 번째 관점

이쯤에서 제안하고 싶은 관점이 있다. 바로 UI가 아니라 ‘모델’을 기준으로 컴포넌트를 분리하는 것이다. 예시를 들어보면 아까 전에 분기가 갑자기 많이 생겨진 시점이 있다.
그 이유는 서로 다른 도메인이기 때문이다. 도메인이 다르면 보통 모델도 많이 달라질 확률이 높다. 따라서 모델이 다르다면 아래와 같이 두 개의 컴포넌트로 분리를 하는 것이 좋다.
여기서 공통된 부분에 따라 중복이 일부 있겠지만, 분리를 하면 마일리지가 추가되든 주문 날짜가 추가되든 다른 방향으로 가도 우리는 쉽게 대응할 수 있을 것이다.

하지만,

그런데 조금 불편한 게 있긴 하다. 다른 컴포넌트긴 한데 도메인이 다르긴 한데 비슷한 UI가 분명히 보인다. 그렇다면 이 동일한 UI를 범용 컴포넌트 또는 디자인 시스템으로 추출하는 것도 좋은데, 방금 같은 컴포넌트 같은 경우에는 디자인 시스템으로 추출하기는 애매하기도 했다.

 제안하는 두 번째 관점

DRY vs. WET

그렇다면 그냥 중복 구현을 하고 지켜본다는 또 새로운 제안을 하고 싶다. DRY(Don't Repeat Yourself) 원칙이라고 들어보았는가? 중복 구현을 하지 말라는 중요한 원칙이다.
반대되는 표현으로 WET(Write Everything Twice)이라는 말도 있다. 모든 것 두 번 적어라. 즉, DRY 하지 않은 코드를 놀리는 느낌의 밈같은 표현이다.

The Wet Codebase

이쯤에서 Dan Abramov의 <The Wet Codebase> 라는 컨퍼런스를 소개하고자 한다. 여기서 말하는 이 WET이라는 표현이 위에서 언급한 Write Everything Twice라는 중복 코드 구현을 의미하는 것이다.
참고로 Dan Abramov는 Redux와 CRA(Create React App)를 개발하고 기여했는데, JavaScript와 React 생태계에서 활동하는 개발자이자 프로그래밍 교육자로서도 활동하고 있다. 그가 한 말을 빌려보겠다.
Of course duplication isn't perfect in long term. But wrong abstraction is also not perfect in long term. 중복 구현은 장기적 관점에서 완벽하지 않다. 하지만 잘못된 추상화도 또한 장기적 관점에서 완벽하지 않다. - Dan Abramov

더불어, 컨퍼런스 발표 일부를 가져오겠다.

아래와 같이 세 개의 모듈이 하나의 추상화를 지금 의존하고 있다.
그런데 시간이 지나면 추상화가 너무 복잡해지고 너무 많은 것들이 의존하게 된다. 중복 코드는 하나도 없는 상태이지만, 이해하기 어렵고 복잡해졌다.
여기서 Dan이 좀 추천하는 것은 바로, 아래와 같이 중복 코드를 inline 시키는 것이다. 여기서 inline이라는 뜻은 그냥 복사 붙여 넣기를 뜻한다.
따라서 추상화가 너무 복잡해질 것 같다면 우선 복사 붙여넣기를 한 다음에 지켜보자는 것이다.
그러다 보면 도메인이 다르다 보니 이 중복 구현 코드들이 달라질 수도 있다.
이렇게 달라지다 보면 시간이 지나 더 유의미한 추상화가 나올 수도 있다는 관점이다.
오, 어느 정도 납득이 되어 가고 있다.

 최종 결론

지금까지 이야기한 내용을 바탕으로, 컴포넌트 분리의 핵심은 아래 세 가지로 요약할 수 있다.
1. 컴포넌트의 핵심은 View를 추상화하는 것이다. 2. 도메인이나 모델을 기준으로 분리하자. 3. 우선 중복 구현 후, 서로 다른 컴포넌트의 중복을 천천히 추상화 해보자.

 참고 자료