생산성을 높이려다, 생산성이 떨어졌다

프로젝트 경험기 시리즈의 이전 글에서는, 컴포넌트의 재사용성과 확장성, 컴포넌트 인터페이스(props의 종류, 확장 방법 등)의 복잡성과 일관성, 네 마리의 토끼를 다 잡으려 발버둥치다, 멘탈과 함께 생산성이 나락으로 간 필자의 경험을 전했었다.

이미지 출처: https://brunch.co.kr/@ktkcs99/108

이 과정을 거치면서, 정말 위의 성질을 갖는 컴포넌트를 개발하는 것이 도움이 되는건지 의구심이 들기 시작했다. 물론, 위의 성질을 다 갖춘 컴포넌트를 완성할 수만 있다면, 페이지를 구성할 때 편할 것이 명백했지만, 이런 컴포넌트를 완성하기 위해, 초기 개발속도를 너무 많이 희생해야 한다고 생각했다. 아니 애초에, 이런 컴포넌트가 존재할 수 있는건지, '용을 잡을수만 있다면, 인생이 핀다'는 헛된 믿음으로, 존재하지도 않는 용을 잡기위해 고달픈 삽질을 하고있는 건 아닐지, 고민이 됐다.

컴포넌트 재사용을 포기하고, 재사용 단위를 줄이다

'그래도 별 방법이 없으니 어쩔 수 없다'는 마인드로 꾸역 꾸역, 사이드바 컴포넌트를 개발하던 중에, 이 문제를 해결하기 위해서는, 초기 개발 난이도를 높이지 않으면서, 동시에 재사용 가능한 방법이 필요하다는 생각이 들었다. 기존에는, 한 번 컴포넌트의 개발이 완료되고 나서, 새로운 사용 사례에 맞춰, 주먹구구식으로 수정하다 보면, 컴포넌트의 인터페이스와 확장 방법 그리고 확장성의 일관성이 떨어지며, 난잡해질 것이라 생각했다. 또한, 컴포넌트가 재사용되는 사례가 추가될 때, 컴포넌트의 상당 부분을 재작성해야 하는 상황이 생길수도 있다. 그래서 처음에 잘 설계해야 한다는 생각에, 힘을 주고 컴포넌트를 어떻게 구성할지 고민했고, 이것이 생산성의 저하로 이어졌었다.

이것은, 재사용 단위가 컴포넌트였기 때문에 생긴 일이었다. 컴포넌트를 재사용하려 하지 않고, 컴포넌트의 일부를 재사용하려 한다면, 컴포넌트가 이미 구현된 상태에서, 컴포넌트를 크게 수정하지 않더라도, 부분적으로 재사용하는 것이 가능해질테니, 처음 컴포넌트를 개발할 때, 깊게 고민하지 않아도 되리라는 생각이 들었다.

어떻게 재사용할 수 있을까 고민하다가, 컴포넌트의 View코드와 Controller 코드를 재사용할 수 있다면, 컴포넌트 내부 코드의 상당 부분을 재사용하는 것이라 생각했다.

View 코드는 크게 스타일링 코드와 이벤트 처리 코드로 나뉜다고 생각한다. 스타일링 코드는 css 클래스로 재사용할 수 있고, 이벤트 처리 코드는 일반적인 자바스크립트 함수이기 때문에, 얼마던지 재사용할 수 있다. Controller 코드는, 커스텀 훅을 사용해서 재사용할 계획을 세웠다.

장점

  1. 초기 생산성 증대
    컴포넌트를 재사용하지 않기 때문에, 가벼운 마음으로 컴포넌트를 개발할 수 있고 이는 초기 구현에 들어가는 시간과 노력을 줄여준다. 컴포넌트를 개발한 후에, 컴포넌트의 내부 로직 중 일부를 재사용할 필요가 있다는 판단이 들면, css 클래스와 자바스크립트 함수 그리고 커스텀 훅으로, 해당 코드를 추출하여 재사용할 수 있다.
  2. 컴포넌트를 수정할 필요가 없다.
    합성 컴포넌트 패턴의 경우, 컴포넌트를 개발한 후에, 확장의 필요를 느끼고 합성 컴포넌트로 변경하려면, 컴포넌트를 재구성해야 한다. 하지만 이와는 다르게, 재사용하기 위해 컴포넌트 구조를 수정하지 않아도 된다는 점이 편했다.

단점

  1. 번거로운 스타일링 일관성 유지.
    컴포넌트를 재사용할 때는, 컴포넌트 내부에 상당부분 스타일링 코드가 포함된다. 그래서 스타일링 일관성이 이슈가 되지 않았는데, 컴포넌트보다 더 작은 단위로 재사용하니, 스타일링의 일관성을 유지하기 위해, 매번 어떤 css class가 있고, 비슷한 경우에 어떤 class를 사용했는지 찾아가며 적용해야 했다.

  2. 컴포넌트를 개발하지 않게 된다.
    리액트를 사용하는 이유는 많지만, 컴포넌트를 개발하여 조합함으로써 페이지를 구성할 수 있다는 점도, 리액트의 큰 강점 중에 하나라 생각한다. 그런데, 컴포넌트의 내부 로직과 스타일링 코드를 재사용 단위로 보기 시작하면서, 컴포넌트를 개발하지 않고, 페이지를 구성한다는 느낌이 들었다.

  3. 알아야할 것이 많아진다
    필자가 인터페이스의 단순함과 일관성을 중요하게 여겼던 이유는, 인터페이스가 복잡해지면 알아야할 게 늘어나기 때문이다. 그런데 스타일링, 이벤트 핸들링 로직, 컨트롤러 로직을 재사용하기 시작하면서, 알아야할 게 줄어든다는 느낌을 받지 못 했다. 예시로 어떤 css 클래스가 존재하는지, 각 클래스가 어떤 스타일링을 추가해주는지 알아야했다. 이에 더해, babel을 사용해서 css 스타일시트를 추가하면,

  4. 중복 코드가 많아진다.

import Button from "../../../components/Button/Button";
import { useState } from "react";

const SidebarNav = () => {
  //TODO: 모아보기 아이콘 구현
  //TODO: isSectionOpen 상태에 따라서, 접혔다 폈다 하는 모션을 구현해야 함.
  const [isSection1Open, setIsSection1Open] = useState(false);
  const [isSection2Open, setIsSection2Open] = useState(false);

  return (
    <div className="sidebar__scrollable">
      <div
        className={
          "sidebar__section " +
          (isSection1Open
            ? "sidebar__dropdown_opened"
            : "sidebar__dropdown_closed")
        }
      >
        <div
          className="sidebar__list-item sidebar__list-title"
          onClick={(e: MouseEvent) => {
            setIsSection1Open(!isSection1Open);
          }}
        >
          모아보기{" "}
          <button
            className={
              "button " +
              (isSection1Open ? "sidebar__arrow-up" : "sidebar__arrow-down")
            }
            style={{ backgroundColor: "rgb(44, 42, 52)" }}
          ></button>
        </div>
        <Button className="sidebar__list-item sidebar__list-item_dark">
          전체 업무
        </Button>
        <Button className="sidebar__list-item sidebar__list-item_dark">
          간트 차트
        </Button>
        <Button className="sidebar__list-item sidebar__list-item_dark">
          캘린더
        </Button>
        <Button className="sidebar__list-item sidebar__list-item_dark">
          파일함
        </Button>
        <Button className="sidebar__list-item sidebar__list-item_dark">
          북마크
        </Button>
        <Button className="sidebar__list-item sidebar__list-item_dark">
          나를 언급
        </Button>
        <Button className="sidebar__list-item sidebar__list-item_dark">
          내 게시물
        </Button>
        <Button className="sidebar__list-item sidebar__list-item_dark">
          임시저장
        </Button>
      </div>
      <div className="sidebar__section">
        <div className="sidebar__list-item sidebar__list-title">
          최근 업데이트{" "}
          <button
            className={
              "button " +
              (isSection1Open ? "sidebar__arrow-up" : "sidebar__arrow-down")
            }
            style={{ backgroundColor: "rgb(44, 42, 52)" }}
          ></button>
        </div>
        <Button className="sidebar__list-item sidebar__list-item_dark">
          {/* api 콜로 받은 정보를 여기에 기입한다 */}
          테스트1
        </Button>
      </div>
    </div>
  );
};

export default SidebarNav;

위의 코드 조각은, '프로젝트 홈' 페이지의 사이드바 구현 코드인데, 사이드바 내부의 섹션이 여러 개 존재한다. 여러 개의 섹션이 사실상 동일한 element 구조를 갖고 isSection1Open과 isSection2Open, 두 개의 상태를 보면 알 수 있듯이, 섹션들이 동일한 상태에 의존했다. 그렇기 때문에 합성 패턴을 사용하여, 서브 컴포넌트로 만들기 정말 적합한 케이스였다고 생각한다. 하지만 컴포넌트의 재사용을 포기하면서, 섹션 두 개를 전부 다 작성해야 했고, 이는 코드의 중복으로 이어졌다. 심지어는, 각 섹션이 각기 다른 상태에 의존해야 했기 때문에, 이벤트 핸들러도 두 개를 작성해야 했다.

다시 컴포넌트를 재사용하다

결국에는 돌고 돌아, 다시 컴포넌트를 재사용하기로 결심했다. 이전에 겪었던 시행착오를 기반으로, 아래의 규칙을 새로 정립했다.

  1. props를 제한하지 않는다. 단, props가 너무 많아지거나 컴포넌트가 복잡해진다고 판단되면, 합성 패턴을 사용하도록, 리팩토링한다.
  2. 합성 컴포넌트의 서브 컴포넌트를 확장하지 않는다.
  3. 컴포넌트를 재사용하지만, 컴포넌트보다 작은 단위또한 적절하게 재사용한다.
  4. 재사용성과 확장성에 대한 고민이 생산성을 크게 낮추고 있다 생각되는 시점에는, 우선 컴포넌트의 구체적인 사용 사례 몇 가지를 구현해본다.

다시 돌아온 합성 패턴과 props의 사용, 이전의 시행착오가 있었으니, 이번 만큼은 더 잘할 수 있으리라 생각했다. 하지만 이 또한 오만이였는데, 이번에는 위의 2번 규칙이 나의 발목을 잡았다. 컴포넌트를 확장하기 위해, 서브 컴포넌트를 개발하고, 서브 컴포넌트를 확장하기 위해, 서브 컴포넌트의 서브 컴포넌트를 개발하는, 기괴한 일이 벌어질 것이 예상되어, 2번 규칙을 만들었었다.

하지만, 서브 컴포넌트를 확장시키지 않다보니, 필연적으로 각 서브 컴포넌트가 커지고 props 개수가 늘어났다. 이렇게 커진 서브 컴포넌트를 분리시키면서, 서브 컴포넌트의 수가 늘어나게 됐다. 이것이 문제라 생각하여, 합성 컴포넌트의 서브 컴포넌트 또한, 필요하다면 확장을 할 수 있도록, 규칙을 바꿨다.

이에 더해, 사이드바 컴포넌트를 추가적으로 개발하면서, 규칙을 더 만들었다. 그것은, '1. 합성 컴포넌트의 layout을 결정짓는 props는 없어야 하고 2. 합성 컴포넌트의 확장되지 않는 서브 컴포넌트는, layout 특히 크기를 결정짓는 props는 존재할 수 있다'는 것.

우선, '합성 컴포넌트의 layout, 특히 크기를 결정짓는 props가 없어야 한다'는 규칙을 만든 이유는 아래와 같다.
props로 크기를 결정짓는 이유는, 다른 여러 요인들이 크기에 영향을 받기 때문이다. 예시로, width를 정하면, 적절한 사용성과 심미성을 유지하기 위해 height와 padding 그리고 내부 컨텐츠의 크기와 위치를 자동으로 결정하도록 만들 수 있다. 하지만 합성 컴포넌트의 경우, 합성 컴포넌트가 서브 컴포넌트의 스타일링을 결정할 방법을 떠올릴 수 없었다. 그나마 할 수 있는 건, 서브 컴포넌트를 감쌀 컨테이너의 스타일링을 결정하는 것 정도다. 그래서 합성 컴포넌트의 props를 통해 크기를 정하더라도, 이에 맞춰서 하위 컴포넌트의 크기와 위치를 자동으로 설정하는 게 힘들기 때문에, 크기를 결정짓는 props를 갖도록 만드는게 쓸모가 없다.

또한, 같은 맥락에서 '확장하지 않는 서브 컴포넌트의 layout, 특히 크기를 결정짓는 props는 존재할 수 있다'는 규칙은, 확장하지 않는 서브 컴포넌트가, 내부 요소의 레이아웃을 결정하도록 구현할 수 있기 때문이다.

마무리

다양한 사용 사례를 커버할 수 있으면서, 사용하기 쉬운 api를 갖는 컴포넌트를 디자인하는 건 정말 어려운 일이다. 그래도 삽질하며 아래와 같은 규칙을 만들 수 있었다.

  1. props를 제한하지 않는다. 단, props가 너무 많아지거나 컴포넌트가 복잡해진다고 판단되면, 합성 패턴을 사용하도록 리팩토링한다.
  2. 합성 컴포넌트의 서브 컴포넌트 또한 확장할 수 있다.
  3. 컴포넌트를 재사용하지만, 컴포넌트보다 작은 단위 또한 적절하게 재사용한다.
  4. 재사용성과 확장성에 대한 고민이 생산성을 크게 낮추고 있다 생각되는 시점에는, 우선 컴포넌트의 구체적인 사용 사례 몇 가지를 구현해본다.
  5. 합성 컴포넌트는, 레이아웃을 결정짓는 props를 갖지 않는다.

0개의 댓글

Powered by GraphCDN, the GraphQL CDN