2025-08-31 개발 생산성에 관한 메모

최초 작성 2025-08-07
업데이트 내역
2025-08-10
2025-08-31

최근에 생산성을 올리는 것에 관심이 많이 생겼다. 더 잘 일하기 위해서 그리고 같은 일이라면 더 빨리 해낼 수 있는 구조를 만들기 위해서이다. 단축키, 레이캐스트, 자동화 스크립트 등의 단편적인 지식들은 효율적인 개발을 위한 메모에 꾸준히 기록 중이다.

여기에서는 그런 단편적인 지식보다는 좀더 큰 단위에 대해 했던 생각들에 대해 적고자 한다. 개발에서의 원칙과 추구해야 할 바에 대한 현재의 생각을 남긴다.

배경 #

나는 회사에 한 명뿐인 프론트엔드 전담 인력으로 입사했다. 그런데 분명 전담으로 프론트를 하는 TO는 한 명뿐임에도 불구하고 5명이 넘는 전임자들의 흔적이 남아 있었다. 알아보기 힘든 코드, 곳곳에서 일관성이 깨진 폴더 구조, 역할이 중복되거나 모호한 채로 널려 있는 컴포넌트들을 마주했다.

이들을 틈틈이 고치고 정리했고 지금도 계속 노력하고 있다. 또 오로지 리팩토링에만 시간을 쏟을 수는 없기 때문에 그 위에 자잘하게 새로운 기능들을 덧붙이고 또 여러 버그들을 고쳤다.

그럴 때 이런 규칙들을 지키고자 했다. 지금도 마찬가지다.

그런데 코드들을 고치다 보니 생각하게 되었다. 아니, 내가 지키려고 하는 이 규칙들은 사실 당연한 말들의 연속이지 않은가? 나도 개발을 시작한 지 얼마 되지 않았을 때부터 이런 말들을 다 들어 알고 있었다. 내가 특별히 영특해서가 아니다. 코딩 입문서만 펴도 흔하게 볼 수 있는 말들이기 때문이다. 그런데 내 전임자들, 더 나아가 소위 말하는 "쓰레기같은 레거시 코드"의 작성자들은 이렇게 뻔한 말들을 전혀 몰랐을까?

내가 보는 레거시 코드들은 말이 레거시지 채 2년도 안된 리액트 코드들이다. 그럼 이 코드를 짠 사람들도 최근 2년간의 추운 개발자 시장에서 어쨌든 합격해서 일을 한 사람들이란 말이다. 그 정도면 저런 당연한 말들은 한번쯤 들어봤을 테다.

그런데도 나는 파악하기 힘든 이 코드 더미 앞에 서게 되었다. 왜 이런 것일까? 처음에는 그들이 코드 퀄리티 같은 걸 전혀 생각하지 않는 개발자라서 그렇다고 생각했다. 하지만 채 한 달도 지나지 않아 컨벤션을 깨는 땜질식 코드를 짜거나 일관성있게 고치던 폴더 구조를 내던지고 난잡하게 만들어 버리는 나 스스로를 종종 발견하게 되었다. "급하다" 혹은 "이걸 지금 다 완벽하게 하려면 공수가 너무 많이 든다" 혹은 "협업에 적용하는 비용이 너무 크다"등의 이유였다. 내 전임자들 또한 각자의 이유가 있었을 거라 믿는다.

그럼 모두가 알고 있는데 아무도 제대로 지키지 않는 것 같은 이런 원칙들은 대체 무엇을 위한 것이고, 원칙적이고 깨끗한 코드만 고집할 수 없는 모두의 환경 속에서 진짜로 무엇을 추구해야 하는 걸까? 나는 진짜로 어떤 방향으로 나아가야 하고 뭘 기준으로 결정해야 할까? 나도 답을 아직 모르지만, 거기에 가까이 다가가기 위해 한 생각들을 여기 적는다. 다들 알고만 있는 원칙이 아니라 구체적으로 손에 잡힐 만한 뭔가로 풀어내고 싶었다. 그래야 나도 그것들을 회사에서 지키려고 노력할 것이기에.

몇 년이 지나면 이것 또한 낡아 보일 것이다. 지금까지 여러 번 스스로 느꼈듯이 말이다. 그럼에도 불구하고 생각의 기록은 언제나 의미있었다.

나는 지금까지 확신에 차서 어떤 방향이 옳기에 이렇게 결정했다고 하는 글들을 간간이 써왔다. 시간이 지나 내가 생각했던 건 완전 멋모르고 한 헛소리였다는 걸 깨닫는 경험도 그만큼 했다. 부끄러운 일이었지만 도움이 되었다.

절대 틀리지 않고 싶다면 대부분의 것이 확실하지 않고 케이스 바이 케이스라는 허울좋은 말을 하면 된다. 그러면 모두가 떨떠름하게나마 동의해야 한다는 걸 나도 알고 있다. 하지만 내가 그 말을 함으로써 배울 수 있는 건 아무것도 없다. 나도 이 글을 쓰면서 확실하지 않은 경우들이 떠오르고 이게 맞나 싶은 구절들도 있지만, 최대한 확신에 찬 척, 내딴에는 최대한 구체적으로 떠벌여본다. 그래야 깨짐으로써 배울 수 있을 테니까.

본질에 대한 생각 - 선택과 집중, 그리고 예측 가능한 코드 #

코드를 왜 정리할까? 왜 시간을 써가면서 회의를 통해 컨벤션을 정하고 리팩토링에 시간을 쏟을까? 왜 좋은 코드를 짜기 위한 규칙들이 있고 그걸 지키기 위해 생각하는 시간을 써야 할까? 그 시간에 컴포넌트 하나를 더 만들거나 페이지를 하나 더 찍어낸다는 선택을 할 수도 있는데 말이다.

나는 그렇게 좋은 코드를 짜기 위한 노력을 하는 게 결과적으로 더 빠르게 일할 수 있는 방법이기 때문이라고 생각한다. 그 이유와 그걸 위해 무엇을 실천할 수 있을지에 대해 단편적으로나마 느낀 점을 적고자 한다.

프로젝트가 기획을 마치고 개발 단계에 진입해 개발자의 손에 닿았다면 대부분 어느 정도 얼개는 주어진 기능을 구현하게 된다.[1] 그럼 이 단계에서 더 빠른 구현과 개발을 위해 필요한 건 뭘까? 나는 "집중해야 할 일에 집중할 수 있는 환경"이 꽤 중요하다고 감히 생각한다. 한 번에 하나의 맥락만 다룰 수 있고 맥락이 되는 주변 코드가 예측 가능하게 짜여 있으며 팀원의 작업물을 어느 정도 믿고 재사용할 수 있도록 컨벤션이 잡혀 있는 그런 환경 말이다.

예를 들어 내가 채팅 기능에 뭔가 새로운 걸 추가하고 싶다고 하자. 채팅에는 단순 API 연동보다는 복잡한 코드가 들어가 있는 경우가 많으므로 생각해야 할 게 많다. 그런데 다른 팀원이 구현해 둔 UI 컴포넌트를 갖다 쓰려고 보니까 이런! 팀원이 여기에 댓글 다는 기능에 관련된 로직을 넣어 두었다. 그럼 채팅 관련 개발을 잠시 멈추고 해당 UI부터 리팩토링한 후 돌아와야 할까? 아니면 같은 역할을 하는 새로운 컴포넌트를 구현해서 사용해야 할까? 어느 쪽이든 "채팅 기능"의 맥락에서는 벗어나 버렸다. 만약 기존 UI 컴포넌트를 그냥 갖다 사용할 수 있었으면 이런 주의 분산 없이 채팅 기능의 개발의 맥락을 그대로 이어갈 수 있었을 것이다.

그럼 구체적으로 어떻게 이런 "집중해야 할 일에 집중"할 수 있는 환경을 만들어 나갈 수 있을까? 말 자체는 간단하다. 직관적으로 만들어져서 금방 익힐 수 있는 규칙을 만들고, 해당 규칙은 코드에서 최대한 많은 정보를 얻을 수 있는 방향을 추구하도록 하며 또한 그 규칙들을 프로젝트 전체에 걸쳐 준수하여서 생각한 곳에 생각한 내용이 있도록 하기. 그러니까 코드가 충분히 예측 가능하도록 하기. 그리고 각 함수나 컴포넌트들이 각자의 역할과 맥락을 벗어나지 않는다는 걸 보장하고 그들의 역할과 맥락을 직관적으로 알 수 있는 이름과 props들을 짓기.(Leaky abstraction 피하기)

흐름을 정리하자면, 우리는 주어진 일을 더 빠르고 문제없이 해결해야 한다. 그러기 위해서는 한번에 하나의 맥락만 다룰 수 있는 환경이 필요하다. 그러기 위해서는 규칙을 잘 만들고 일관되게 지켜 나가야 한다. 이것들을 풀어 정량적인 규칙으로 만든 게 우리가 흔히 듣는 깨끗한 코드를 위한 지침들이다. 개발을 잘하는 것이란 그런 규칙들을 알고 있는 걸 넘어서 결국 더 빠르게 해내기 위해서라는 본질의 이해, 그리고 언제나 빠듯한 일정과 언제나 쉽지 않은 환경 속에서 이런 것들을 지켜나갈 수 있는 힘에서 오지 않나 싶다.

다만 "일관성을 지키는 힘"에 대해서는 의식적인 노력 이상 크게 할 수 있는 말이 없는 거 같다. 그러니 이다음부터는 코드와 개발 전반에 있어서 어떤 방향을 추구해야 할지 내가 느꼈던 단편적인 내용을 적는다.

코드에서 - 버튼의 예시 #

코드를 짤 때도 앞선 규칙을 따른다. 팀 그리고 스스로가 생각하는 부하를 줄이고 진짜 집중해야 하는 작업에 매진할 수 있도록 하기. 그걸 위해서는 작업을 할 때 거쳐야 하는 잡다한 생각의 과정을 최대한 줄일 수 있는 방식으로 컴포넌트를 짜야 한다.

프로젝트 전체에 걸쳐서 해당 컴포넌트가 각자의 역할과 맥락을 벗어나지 않는다는 걸 믿을 수 있도록. 가령 프로젝트에서 <Button> 컴포넌트를 보면 "아, 버튼 디자인이 적용된 클릭 가능한 요소 역할을 하는 컴포넌트겠구나"하고 믿을 수 있게 하기. 프로젝트가 그렇게 이상적이고 예측 가능한 구조로 짜여 있다면 기능을 만드는 건 어쩌면 퍼즐 맞추기에 지나지 않는다. 다른 사람이 짠 코드들이라도 어디에 어떤 역할을 하는 함수가 있고 어떤 컴포넌트가 있으니 조합해서 이러저러하게 만들면 되겠다-할 수 있으므로.

그럼 어떻게 해야 할까? 당연한 말이지만 각 컴포넌트가 본질적인 역할만 다하도록 하는 것이다. 이건 생각보다 쉽지 않다.

가장 간단한 버튼 컴포넌트를 만든다고 하자. 버튼의 요구사항이란 보통 버튼에 텍스트가 있고, 클릭하면 어떤 액션이 실행되도록 하는 데에서 시작한다. 그럼 components 폴더에 Button같은 컴포넌트를 이렇게 만들면 어떨까?

// components/Button.tsx
type ButtonProps = {
  text: string;
  onClick: () => void;
};

function Button({ text, onClick }: ButtonProps) {
  return <button onClick={onClick}>{text}</button>;
}

이건 사실 좋지 않다. 프론트엔드 개발을 좀 해본 사람이라면 경험적으로 이해할 것이다. 버튼 내부에 들어가는 요소를 문자열 하나로 한정하고 있는데 "내부에 무엇을 넣는지"는 버튼 컴포넌트에서 담당할 부분이 아니기 때문이다. 그렇게 되면 요구사항 변경에 취약해짐을 알 수 있다. 예를 들어 텍스트 오른쪽에 아이콘을 넣는 요구사항이 생겼다면? 이렇게 고칠까?

type ButtonProps = {
  text: string;
  onClick: () => void;
  rightIcon?: React.ReactNode;
};

function Button({ text, onClick, rightIcon }: ButtonProps) {
  return (
    <button onClick={onClick}>
      {text}
      {rightIcon && <span>{rightIcon}</span>}
    </button>
  );
}

텍스트 왼쪽에 아이콘을 추가해야 한다는 요구사항도 추가되었다면? leftIcon props를 추가할까? 뭐 그럴 수도 있다. 하지만 감이 있는 사람이라면 지나치게 복잡해지고 있다는 느낌이 들 거라 생각한다. 또 이런 요구사항들은 끊임없이 생길 수 있다는 게 문제다. 버튼 안에 이미지가 들어가야 하면? 혹은 어떤 상태에 따라서 바뀌는 검증 결과 문구 같은 걸 넣어야 하면 어떤가? 비동기로 가져오는 데이터가 버튼에 들어가야 하면 어쩌지?

이건 버튼에 disabled를 추가해야 한다든지 같은 거랑은 다른 문제다. 굳이 aria role("사용자가 활성화했을 때 응답을 트리거하는 클릭 가능한 요소" MDN, ARIA: button role)까지 들먹이지 않더라도, 내부에 어떤 데이터가 들어가야 할지는 "클릭해서 뭔가 동작을 할 수 있는 요소" 즉 "버튼"의 맥락을 벗어나는 것까지 포함하는 것이기 때문이다.

물론 표준적인 버튼의 명세를 절대 벗어나면 안된다는 말은 아니다. 가령 디자인 시스템 상의 버튼이라면 size="sm" | "md" | "lg"와 같은 props를 가질 수도 있을 것이다. 하지만 이런 컨벤션은 팀에서 정하는 것이고, 본질적으로는 버튼이 버튼의 역할 선에서 상식적으로 동작하도록 코드를 짜야 한다고 생각한다.

그리고 이렇게 컴포넌트가 많은 역할을 하게 되면 많은 경우 props가 늘어나게 된다. 이럴 경우 역할 분리 관점에서도 문제지만 코드를 한눈에 알아보고 신뢰하기 힘들어진다.

가령 나는 이런 식으로 짜인 버튼을 본 적이 있다.

<Button
  text="Add Task"
  initialValue="..."
  leftIcon={/* 버튼 내부 왼쪽에 들어갈 아이콘 컴포넌트 */}
  rightIcon={/* 버튼 내부 오른쪽에 들어갈 아이콘 컴포넌트 */}
  iconAlign="..."
  imagePath={"./src/assets/example.png"}
  onClick={() =>
    // onClick 로직
  }
/>

이 정도로 많은 정보가 외부에서 주입되는 버튼이라면 무언가 다른 역할이 있을 수도 있다는 생각이 드는 게 오히려 정상이라고 생각한다(특히 단순 스타일링에 쓰이는 것으로는 보이지 않는 initialValue가 이러한 사고 흐름에 큰 역할을 한다). 이럴 경우 버튼 같은 가벼운 컴포넌트까지 내부를 까보면서 페이지를 짜야 하게 되는데, 대체 얼마나 비효율적인 일인가!

그럼 어떻게 해야 버튼 컴포넌트가 버튼의 역할만 하도록 할 수 있을까? 여러 방법이 있겠지만 고차 컴포넌트를 사용해볼 수 있다. 대략 이런 식으로 말이다.

type ButtonProps = {
  children: React.ReactNode;
  onClick: () => void;
};

function Button({ children, onClick }: ButtonProps) {
  return <button onClick={onClick}>{children}</button>;
}

이렇게 하면 해당 컴포넌트를 사용하는 위치에서 버튼 내부의 내용을 직접 주입하게 된다. 다음과 같이 말이다.

<Button onClick={() => alert("프리미엄 업그레이드!")}>
  <div>프리미엄 업그레이드</div>
  <div>월 9,900원</div>
</Button>

이렇게 하면 버튼 내부의 내용은 외부에서 주입하는 걸 "버튼을 사용하는 위치의" 코드에서 볼 수 있기 때문에 버튼 컴포넌트는 그 내부 내용에는 관여하지 않고 버튼의 역할만 잘 하고 있다는 걸 직관적으로 알 수 있다.

따라서 다음과 같이 고차 컴포넌트로 가는 게 "클릭으로써 어떤 액션을 만들 수 있는 요소"라는 버튼의 역할을 제대로 수행할 수 있도록 해주는 거라고 생각한다. 버튼은 버튼의 역할만 잘 하고 있으니 외부에서 어떻게 다루든 해당 동작을 믿고 쓸 수 있게 하는 것.

radix UI, daisy UI등에서 이런 고차 컴포넌트 방식을 채택하고 있다. 특히 radix 같은 경우 base ui 컴포넌트들이 딱 역할만 잘하도록 설계하는 걸 아주 잘했다고 생각하기 때문에 버튼 이상의 역할 분담 예시를 보고 싶다면 radix를 참고하는 게 좋다. UI를 구성하는 각 요소의 역할에 따라 요소들이 잘게 쪼개져 있고 이들을 조립해 역할에 따라 새로운 컴포넌트들을 만들 수 있게 되어 있어서 컴포넌트 설계 면에서 배울 점이 많다.

그리고 본질은 button을 반드시 하나로 통일해야 한다거나 props를 줄여야 한다는 게 아니다. 가령 mui에서는 button에 variant props("text" | "contained" | "outlined")를 전달해서 버튼 스타일을 정하게 한다. 하지만 버튼이 버튼의 역할만 다하고 있다면 OutlineButtonSolidButton이 따로 있다 해도 별 상관 없다.

중요한 건 각 버튼이 본질적인 역할을 제대로 해냄으로써 모든 프로젝트 참여자가 "아, 지금 내가 필요한 건 이 역할이니 이 버튼을 갖다 쓰면 되겠구나"하고 바로 결정할 수 있도록 하는 것이다. 채팅 기능을 개발하다가 버튼 컴포넌트 코드를 분석하는 그런 일이 없도록 하는 것이다. 또한 각 컴포넌트에 어떤 숨어 있는 로직 같은 게 없고 본질적인 역할만 다한다는 걸 믿고 쓰게 함으로써 다른 정말 중요한 작업에 집중할 수 있도록 하는 것이다.

코드에서 - 스타일링의 예시 #

코드 가독성을 위해서는 코드 자체가 많은 정보를 담고 있어야 한다. 가장 단순한 예시로는 a 같은 변수명보다는 xIsNext 같이 의미를 드러내는 변수명을 지어야 한다는 걸 들 수 있겠다.

그런데 변수명 등 명확히 언어를 통해 명시해 주지 않더라도 적절한 규칙을 지키는 구조를 통해서 직관적으로 역할을 드러내 줄 수 있는 경우도 있다. 어느 정도 감이 있거나 컨벤션을 알고 있다면 따로 주석 없이도 훨씬 더 빠르게 코드를 파악할 수 있도록 말이다.

예를 들어 styled-component와 같은 css-in-js에서는 일반적인 컴포넌트와 스타일링 컴포넌트를 이름만 보고는 구분할 수 없다. 예를 들어 다음과 같이 페이지가 짜여 있다고 하자. (시맨틱 태그는 핵심이 아니므로 고려하지 않았다)

<Container>
  <Header title="화면 제목" />

  <ContentWrapper>
    <CoverImage
      src={displayCoverImageUrl}
      alt="series cover"
      width={100}
      height={100}
    />
  </ContentWrapper>
</Container>;

// ...

const Container = styled.div`...`;

const ContentWrapper = styled.div`...`;

const CoverImage = styled.img`...`;

Header는 여러 로직이 내장되어 있는 컴포넌트이고 아래의 styled-component 정의를 보면 Container, ContentWrapper, CoverImage는 스타일링만 해주는 css-in-js 컴포넌트다. 하지만 위의 컴포넌트 구조만 보고서는 딱 보고 구분할 수 없다.

만약 Wrapper로 끝나는 컴포넌트들은 모두 스타일링 컴포넌트로 간주한다든지 하는 규칙을 정한다면 훨씬 빠르게 코드를 파악할 수 있을 것이다. 다음과 같이 말이다.

<PageWrapper>
  <Header title="화면 제목" />

  <ContentWrapper>
    <ImageWrapper>
      <img
      // ...
      />
    </ImageWrapper>
  </ContentWrapper>
</PageWrapper>;

// ...

const PageWrapper = styled.div`...`;

const ContentWrapper = styled.div`...`;

const ImageWrapper = styled.div`...`;

더 나아갈 수도 있다. 스타일링 컴포넌트를 웬만하면 따로 만들지 않는 것이다. 컴포넌트의 최상위에 Container와 같은 단 하나의 스타일링 컴포넌트만 두고 후손 셀렉터를 사용해서 나머지를 스타일링하는 것이다. 다음과 같이 말이다.

function App() {
  return (
    <Container>
      <Header title="화면 제목" />

      <div className="content-wrapper">
        <div className="image-wrapper">
          <img
          // ...
          />
        </div>
      </div>
    </Container>
  );
}

// 스타일링 컴포넌트들
const Container = styled.div`
  .content-wrapper {
    ...
  }

  .image-wrapper {
    ...
  }

  .image-wrapper img {
    ...
  }
`;

Container 내부의 className 셀렉터를 사용함으로써 스타일링만을 위한 컴포넌트는 divp와 같은 HTML 태그가 그대로 드러나게 했다. 스타일링을 위한 컴포넌트는 HTML 태그를 그대로 드러냄으로써 어떤 컴포넌트가 "오로지 스타일링만을 위해 쓰이는 요소"인지를 보여준 것이다. css className을 통해 전달할 수 있는 건 오직 스타일뿐이니까.

CSS Module, 혹은 vanilla-extract, panda같은 제로런타임 css를 써본 사람이라면 이렇게 스타일링 컴포넌트와 진짜 로직이 들어간 컴포넌트의 분리가 익숙할 거라 생각한다. 이렇게 하면 각 태그, 컴포넌트의 역할을 좀 더 한눈에 직관적으로 알아볼 수 있다.

물론 성능 저하도 자연스럽게 예상할 수 있다. 하지만 (애니메이션도 아니고 단순 스타일링을 위한) CSS까지 쥐어짤 정도의 성능 최적화가 필요한 페이지라면 애초에 css-in-js가 아니라 다른 라이브러리로 넘어가는 게 맞다고 생각한다.

이외에도 컴포넌트의 역할을 구분할 수 있는 여러 방식이 있을 것이다. 예를 들면 Flex, Grid(radix, mui 등 여러 메이저 UI 라이브러리에서 사용), Stack, Group(수평/수직 Flex 컨테이너로 Mantine에서 사용)등의 레이아웃 컴포넌트를 도입할 수도 있다. 대부분의 레이아웃이 display:flex를 통해 해결되며 프론트에서 상식과도 같은 부분이라는 걸 생각하면 직관적인 설계에도 좋다.

물론 정답은 없다. 하지만 중요한 건 어떻게 직관적으로 역할을 보여줄 수 있는지 계속 고민하고 정한 규칙을 "계속" 지켜나가는 것이다.

폴더 구조의 예시 #

폴더 구조 관점에서는 특히 예측 가능한 규칙을 만들고 따르는 게 효율을 만든다. 이런 일관성 있는 규칙이 있는 프로젝트에서 작업을 하면 일이 점점 빨라지는 걸 경험할 수 있다. 내가 짠 코드가 아니더라도 생각한 곳에 생각한 내용이 있고 어떤 역할을 할지 충분히 대강 예측할 수 있어서 갖다 쓰기만 하면 되기 때문이다. 그리고 다른 곳에 어떤 뜬금없는 컴포넌트가 있어서 내가 갖다 써야 할 거라고 생각하지 않을 수 있기 때문이다.

FSD(Feature-Sliced Design)과 같은 폴더 구조 컨벤션도 어떤 혁신적인 분류 체계라서라기보다는 좀 더 팀에 빠르게 흡수될 수 있고 예측 가능한 하나의 공통 구조를 제시한다는 점에서 의미를 갖는다고 생각한다. 이런 폴더 구조의 일관성이 깨질 경우 업무의 효율이 떨어지는 걸 여러 번 경험했기 때문이다. 정답은 없고 그저 역할에 따른 적절한 분류와 철저한 일관성 준수뿐이다. 꼭 지키면 좋겠는 부분이라고 한다면 "공통으로 쓰이는 컴포넌트"의 좋은 설계와 철저한 분류라고 생각한다.

이걸 지키지 않아서 힘들었던 경험을 단순화하고 예시를 위한 변형을 거쳐 제시한다.

프로젝트를 진행하며 공통 컴포넌트들은 global/components 폴더에서 관리하고 있었다. 각 페이지에 필요한 컴포넌트들은 페이지에 해당하는 모듈 폴더에 넣어 두었다. 가령 로그인 페이지에 쓰이는 로그인 폼 컴포넌트 같은 경우 src/page/login/LoginForm.tsx에 있었다.

그런데 로그인 폼은 다른 곳에서도 쓰이기 쉽다. 사용자에게 로그인을 요구하는 경우는 많기 때문이다.

당장 비로그인 사용자가 게시판에 글 작성을 시도할 경우 로그인을 요구하는 모달을 띄우는 요구사항이 생겼다. 그래서 게시글 작성 페이지에 해당하는 모듈에 모달을 만들었다. src/page/create-post/components/LoginModal.tsx를 만들고 LoginForm을 갖다 썼다. 나쁘지 않..다?

하지만 진짜 나쁘지 않은 선택인가? 나는 나중에 src/page/create-post/에 로그인 모달을 만든 걸 까먹고 로그인을 요구하는 다른 페이지에 또 LoginModal.tsx을 만들었다. 나중에 누군가가 "src/page/create-post/에 로그인 모달 있었지 않나요?"를 말해준 걸 듣고 머리를 탁 쳤다.

LoginModal을 만드는 시점에서 LoginModal 혹은 LoginFormglobal/components에 넣어 놓았다면 좋았을 것이다. 다른 페이지 모듈까지 챙겨보지는 못해도 보통 전역 컴포넌트 폴더 정도는 많이들 훑어보면서 작업하기 때문이다. 또한 전역 컴포넌트 폴더의 변경사항은 일반적으로 팀 내에서 훨씬 쉽게 공유되어서 모두가 알고 있을 가능성이 높다. 내가 create-post 작업을 할 당시 파일을 한번 클릭해서 이동하는 정도의 노력만 했더라면 나도 훨씬 더 적은 노력으로 컴포넌트를 재사용할 수 있었을 것이다.

로그인 모달은 예시일 뿐이지만 전 회사에서 내가 비슷하게 많이 겪었던 일이다. 따라서 이런 일을 최소화하기 위해서, "기본적으로 프로젝트를 훑어보기만 하더라도 최대한 많은 정보를 얻을 수 있도록 하는 것"이 중요하고 그걸 위해서는 일관성 있는 규칙을 지켜나가는 게 가장 중요하다고 생각한다. 여러 페이지에서 재사용한다면, 전역 컴포넌트 혹은 어떤 모듈 폴더에라도 넣어 놓는 게 당연하잖아?

라이브러리의 예시 #

라이브러리를 왜 사용하는가? 내가 코드를 덜 짜기 위해서도 있지만 내가 신경써야 할 부분을 줄여 주는 것이다.

tanstack query를 사용한다면 서버 데이터를 가져와서 사용하는 코드를 작성하는 데에 신경쓸 필요가 매우 줄어든다. 또한 서버 데이터 - 클라이언트 데이터의 관리 역할을 거의 분리시켜준다. 경우에 따라 서버와의 통신을 완전히 담당하도록 할 수도 있다.

Radix primitive, mui/base-ui 같은 헤드리스 UI를 사용하면 접근성과 UI 컴포넌트를 구성하는 작은 요소들의 구성에 신경쓸 필요가 줄어든다. shadcn/ui나 radix themes, daisyUI처럼 스타일링까지 제공하는 UI 라이브러리를 쓴다면(회사의 디자인대로 커스텀해야 한다면 그건 그거대로 힘들지만) 스타일링까지도 맡겨버릴 수도 있다.

tailwind가 개인적으로 취향은 아니지만 css className이나 스타일링 컴포넌트명을 짓는 데에서 오는 부담을 줄여주는 면이 분명히 있다.

이런 걸 이용해서 내가 진짜 구현해야 할 기능에만 집중할 수 있는 게 라이브러리를 쓰는 목적이고, 이걸 고려해서 라이브러리를 선택하면 좋을 것이다.

생산성 툴로 선택과 집중 #

나는 개인적으로 단축키나 raycast 등의 도구를 익히고자 하고 있다. 그 결과물을 효율적인 개발을 위한 메모에 정리하고 있기도 하다.

그런데 생산성 툴 같은 걸 왜 쓰고 왜 단축키를 사용하면 좋을까? 물론 극단적으로 단축키를 단 하나도 쓰지 않는 조건과 비교한다면 쓰는 게 일반적으로 낫다. 저장(command + s)나 복사/붙여넣기(command + c,v), 새로고침(command + r)같은 단축키들은 엄청나게 많이 쓰는 기능이기 때문에 외워 놓는다면 마우스를 사용하는 것에 비해 작업시간을 많이 단축해 줄 거라 생각한다.

하지만 마우스에 손을 가져가는 걸 최소화하고 작업한다고 하면서(그리고 약간의 간지를 목적으로) vim 사용법을 일부러 익히는 사람도 존재한다. 하루에 한번 5분 하는 작업 자동화를 위해서 해머스푼의 Lua 스크립트나 레이캐스트 스크립트를 짜는 수준의 매니아들도 있다. 이쯤 가면 단순히 "작업 시간 단축" vs "해당 기능을 익히거나 자동화하는 데 들어가는 시간"을 비교했을 때 배보다 배꼽이 더 클 수도 있다.

근데 정말 이런 자동화를 하는 목적이 같은 작업을 몇 초 더 빠르게 처리하는 게 전부였을까? "자동화 작업으로 단축할 수 있는 시간 vs 자동화를 위해 들어가는 시간"을 비교해 보았을 때 자동화에 걸리는 시간이 더 길다면 그걸 하지 않는 게 반드시 합리적인 선택일까? 그 시간에 조그만 피쳐 하나라도 더 찍는 게 나았을까? 나는 꼭 그렇지는 않을 거라 생각한다. 여기는 내 아카이브이고 이 결론에 대해 논쟁할 생각은 없다. 다만 그걸 좀 더 구체적으로 서술하고 왜 그런지 기록하는 일이 필요하다고 생각한다.

시간 단축이 모든 것의 기준이 되어서는 안 되는 이유는 저런 원칙과 작업들이 단순히 10초 걸리던 걸 5초로 단축해 주기 위해서 존재하는 게 아니기 때문이다. 진짜 목적은 코드를 짜고 필요한 작업을 함에 있어서 다른 곁가지들이 주는 부하를 줄이고 진짜 집중이 필요한 작업에 집중할 수 있도록 하기 위해서라고 생각한다. 집중은 너무 쉽게 분산되기 때문이다.

코드를 짜다가 다른 무언가를 참고할 일이 생겼다고 하자. 깃헙이든 웹 검색이든 로컬 파일을 찾아보는 것이든 뭐든 상관없다. 중요한 건 코드를 작성하던 맥락에서 벗어나서 생각을 다른 어디론가 향해야 한다는 사실이다.

거기까지 가는 과정에는 집중을 분산시키는 장애물이 너무 많다. dock에서 브라우저를 클릭해 열거나, 웹 검색을 하거나 finder를 열어서 폴더들을 뒤지면서 원하는 프로젝트를 찾거나 또는 새로운 터미널을 열고 프로젝트 경로로 이동하는 등의 과정들 말이다.

만약 raycast 기능이나 다른 익스텐션 등을 이용해서 이를 한번에 해결한다면 그런 장애물들이 훨씬 줄어든다.

실제로 절약한 시간은 10초 미만이라고 해도, 불필요하게 생각을 분산하는 걸 막고 최대한 필요한 단계만 거칠 수 있다는 게 핵심이다. finder를 열고 파일을 검색하다가 어떤 파일을 무엇을 위해 검색할지 까먹어 버리는 경험이 생각보다 흔하다. 이게 파일 검색이 엄청나게 오래 걸리는 작업이라서 그런 건 아니다. 생각이 그 짧은 사이 분산되어 버렸을 뿐이다.

단순히 속도를 향상시키고 마우스에 손을 덜 가져가기 위해서만이 아니라 이런 생각의 단계와 인지 부하를 줄이기 위해서 자동화나 도구들을 사용한다고 생각한다.

즉 이런 기능을 설계하고 사용함에 있어서도 최대한 직관적으로, 내가 생각을 전환하지 않고 무의식 속에서도 쓸 수 있도록 간단하게 동작을 설계해야 한다. 이러한 결정 과정에 대해선 효율적인 개발을 위한 메모 - 세팅에 관한 결정들에서도 조금 명시한 바 있다.

예를 들어 raycast 단축키를 지정한다고 하자. 그럼 일단 한 손으로 웬만하면 누를 수 있도록 지정해야 한다. 머슬 메모리(박성훈의 표현)만으로도 타닥 하고 누를 수 있도록 말이다. 그리고 자동으로 연상될 수 있도록 지정해야 한다. 이 사이의 트레이드오프를 적절히 고려해야 한다. 예를 들어 가장 누르기 쉬운 핫키라면 command + command(연속)이나 shift + shift같은 것들이다. 터미널, finder처럼 아주 많이 쓰는 것들을 보통 여기 지정한다. 하지만 직관성으로 따진다면 hyperkey + t가 터미널의 머리글자를 사용함으로써 훨씬 직관적이다.

이런 걸 다 고려해서 생산성을 위한 루틴을 만들면 훨씬 사고를 덜 분산하면서 작업할 수 있다. 이런 자동화나 머슬 메모리 활용은 단순히 1초 단축이 아니라, 1개의 집중력 분산을 막아주는 장치다.

정리 #

일을 잘하는 팀은 작업물이 쌓여가면서 작업 속도가 점점 빨라지고 반대로 일을 못하는 팀은 작업물이 쌓여갈수록 점점 느려진다는 말을 우연히 링크드인에서 보았다. 맞는 말이라고 생각한다. 그런데 이상한 일이다. 사람들에게 "작업이 점점 빨라지고 편해지기"와 "작업이 점점 느려지고 머리 아파지기" 둘 중에 고르라면 상식적으로 전자를 고를 것이다. 그런데 왜 다들 원하는 대로 가지 못할까?

나는 "일을 빠르게 처리하기"와 좋은 개발을 위한 규칙들을 멀리 떨어진 것처럼 생각하고, 속도와 좋은 코드가 트레이드오프인 것처럼 생각하는 데에서 많은 게 시작된다고 생각한다. 물론 복잡해질 수밖에 없는 코드도 당연히 있다. 5개의 페이지에서 한번에 입력을 받고 수많은 분기 처리가 있는 보험 가입 페이지 같은 코드가 아주 깔끔하기는 힘들다. 아니 깔끔하게 할 수 있더라도, 그런 페이지를 100개 찍어야 하는데 그 하나를 깔끔하게 하기 위해 오래 붙잡고 있을 시간이 주어질 리 없다.

하지만 많은 경우 당장의 속도에 매몰되어 "그거 설정할 시간에 코드 한 줄 더 짜지"나 "그거 정리할 시간에 다른 거 하면..."같은 소리가 작업 속도를 더 느리게 한다고 생각한다. 물론 헥사고날 아키텍처, TDD나 compound component 구조, 렌더링 최적화 같은 걸 완벽하게 지키기 위해서 시간을 쏟는 건 낭비적인 부분이 있다는 걸 인정한다. 하지만 극단적인 예시이고 대부분의 기법들은 결국 개발자의 허영심을 위해서가 아니라 더 빠르게 무언가를 해내기 위해서 나왔다.

하루이틀 코드 짜고 끝일 거라면 상관없지만 계속 들여다볼 코드를 짠다면 좋은 규칙을 정하고 미치도록 지키는 게 작업 속도를 점점 올려줄 거라고 믿는다. 효율적인 개발은 뭐 엄청나게 어려운 문제를 잘 푸는 데서만 나오는 게 아니라고 생각한다. 현실적으로 개발자들의 일 대부분은 비트 레벨까지 내려가서 DB를 해킹하기같은 대단히 기술적인 게 아니기 때문이다. 비슷한 도구를 가지고 비슷한 페이지들을 찍는 일의 반복 속에서 효율을 만드는 것은 사소한 규칙들을 만들고 지킴으로써 프로젝트 전체의 코드를 예측할 수 있도록 만들고 사소한 시간 허비들을 쭉 없애버리는 데에서 나온다고 생각한다.

일관성을 지키는 게 최고다.


  1. 프로젝트 방향이 완전히 선회하는 경우가 없는 건 아니다. 특히 프로젝트가 POC단계에 있다면 수없이 갈아엎어지는 기획을 볼 수도 있다. 하지만 그럴 경우에도 나는 역할이 명확히 분리되고 알아보기 쉬운 코드를 작성하는 게 도움이 되리라고 믿는다. 가령 <Button>같은 공통 컴포넌트에 일종의 비즈니스 로직까지 담겨 있다면 프로젝트 기획 변화에 따라 해당 컴포넌트는 버려지거나 대격변을 맞이할 것이다. 하지만 이러한 base component에 비즈니스 로직이 전혀 들어가 있지 않다면 다음 프로젝트에서도 재사용이 가능할 수도 있다. ↩︎