bebusl 님의 상세페이지[2팀 이진희] Chapter 2-1. 클린코드와 리팩토링

심화과제 배포 링크 : bebusl.github.io/front_6th_chapter2-1/

과제 체크포인트

기본과제

  • 코드가 Prettier를 통해 일관된 포맷팅이 적용되어 있는가?
  • 적절한 줄바꿈과 주석을 사용하여 코드의 논리적 단위를 명확히 구분했는가?
  • 변수명과 함수명이 그 역할을 명확히 나타내며, 일관된 네이밍 규칙을 따르는가?
  • 매직 넘버와 문자열을 의미 있는 상수로 추출했는가?
  • 중복 코드를 제거하고 재사용 가능한 형태로 리팩토링했는가?
  • 함수가 단일 책임 원칙을 따르며, 한 가지 작업만 수행하는가?
  • 조건문과 반복문이 간결하고 명확한가? 복잡한 조건을 함수로 추출했는가?
  • 코드의 배치가 의존성과 실행 흐름에 따라 논리적으로 구성되어 있는가?
  • 연관된 코드를 의미 있는 함수나 모듈로 그룹화했는가?
  • ES6+ 문법을 활용하여 코드를 더 간결하고 명확하게 작성했는가?
  • 전역 상태와 부수 효과(side effects)를 최소화했는가?
  • 에러 처리와 예외 상황을 명확히 고려하고 처리했는가?
  • 코드 자체가 자기 문서화되어 있어, 주석 없이도 의도를 파악할 수 있는가?
  • 비즈니스 로직과 UI 로직이 적절히 분리되어 있는가?
  • 코드의 각 부분이 테스트 가능하도록 구조화되어 있는가?
  • 성능 개선을 위해 불필요한 연산이나 렌더링을 제거했는가?
  • 새로운 기능 추가나 변경이 기존 코드에 미치는 영향을 최소화했는가?
  • 코드 리뷰를 통해 다른 개발자들의 피드백을 반영하고 개선했는가?
  • (핵심!) 리팩토링 시 기존 기능을 그대로 유지하면서 점진적으로 개선했는가?

심화과제

  • 변경한 구조와 코드가 기존의 코드보다 가독성이 높고 이해하기 쉬운가?
  • 변경한 구조와 코드가 기존의 코드보다 기능을 수정하거나 확장하기에 용이한가?
  • 변경한 구조와 코드가 기존의 코드보다 테스트를 하기에 더 용이한가?
  • 변경한 구조와 코드가 기존의 모든 기능은 그대로 유지했는가?
  • (핵심!) 변경한 구조와 코드를 새로운 한번에 새로만들지 않고 점진적으로 개선했는가?

과제 셀프회고

과제를 하면서 내가 제일 신경 쓴 부분은 무엇인가요?

이번 과제는 의도적으로 작성된 복잡하고 읽기 어려운 코드를 클린하게 리팩토링하는 것이 핵심이었기 때문에, 구현 자체보다는 기존 코드의 흐름을 먼저 파악하고, 그 안에 숨겨진 책임들을 분리해나가는 데 많은 시간을 들였습니다.

처음에는 구조가 정해져 있지 않은 상태였기 때문에, 기능을 옮기면서도 "이 책임은 어디에 두는 게 가장 자연스러울까?"를 계속 고민하게 되더라고요. 함수나 상태들이 전역으로 뒤섞여 있는 구조를 controller, view, usecase로 나누는 식으로 관심사를 분리하고, 각 레이어가 어떤 역할을 가져야 할지 기준을 세워가며 리팩토링을 진행했습니다.

특히 view에서 단순 UI 조작만 하게 할지, controller에서 데이터 조합과 조작까지 담당하게 할지, usecase에서 계산만 하고 결과만 넘겨야 할지 등의 판단을 계속하면서 구조를 잡아나갔습니다.

심화과제에서는 타입 안정성을 확보하면서도 구조적으로 앞에서 고민했던 것들을 그대로 녹여낼 수 있도록 노력했습니다. 기존 로직이 이미 usecase, controller, view로 나뉘어 있었기 때문에, React 환경으로 바뀌어도 함수들을 조금만 변형하고 상당수 재활용할 수 있었던 점은 의미 있게 느껴졌습니다.

과제를 다시 해보면 더 잘 할 수 있었겠다 아쉬운 점이 있다면 무엇인가요?

처음 구조를 정리해나갈 때 명확한 기준 없이 "이건 여기 두는 게 낫지 않을까?" 같은 느낌 위주로 판단하는 경우가 많았습니다. 그러다 보니 중간에 책임이 뒤섞이거나, 되돌리는 리팩토링이 반복되기도 했습니다.

또 구조 분리를 해가면서 props나 상태가 여러 단계로 전달되는 상황이 생기면서, "관심사를 분리했지만 오히려 복잡해진 건 아닐까?" 싶은 고민도 들었습니다. 이런 경우 어떤 방식으로 균형을 잡아야 하는지에 대한 감이 아직 부족하다고 느꼈고, 다음에는 좀 더 기준을 세운 뒤 구조를 잡는 연습을 해보고 싶습니다.

마지막으로, 구조적으로 나누는 것에 집중하다 보니 오히려 디테일한 구현(예: DOM 조작 방식, 역할 명확한 변수명 등)에서 놓친 부분들도 있었던 것 같아 아쉽습니다.

리뷰 받고 싶은 내용이나 궁금한 것에 대한 질문 편하게 남겨주세요 :)

구조에 대한 질문입니다!

클린코드 기본과제를 진행하면서 폴더 구조를 제 나름대로 아래와 같이 나눠봤습니다:

- domain/
  - product/  // getProductById같은 도메인 개념 중심 유틸까지 포함 (비즈니스 로직은 제외)
  - order/

- usecase/
  - 할인 계산
  - 세일 이벤트 트리거 등 핵심 비즈니스 로직

- controller/
  - usecase 실행 후 뷰 업데이트 로직 처리

- view/
  - 실제 렌더링 컴포넌트

- utils/
  - 범용 유틸 함수

구조를 나누면서도 제일 고민이 컸던 부분은 controller 레이어 부분입니다.

원래는 usecase 안에서 바로 뷰 조작 함수를 호출하려고 했지만,

"비즈니스 로직에서 뷰 관련 코드를 호출하는 건 관심사 분리에 어긋나지 않나?" 싶어

뷰 업데이트는 controller 레이어로 넘겨서 처리하는게 좋겠다는 판단에 controller폴더를 따로 둬야겠다는 생각을 했습니다.

// controller 예시
export const handleLightningSale = () => {
  const result = checkLightningSaleTrigger(productList);

  if (result.shouldTrigger) {
    triggerLightningSaleEffect(); // 뷰 조작 (예: 배너 띄우기)
  }
};

그런데 이렇게 나누다 보니 구조는 명확해진 것 같아보이는데

오히려 파일/레이어가 늘어나면서 복잡도가 증가하고,

흐름을 추적하거나 디버깅할 때 오히려 불편해지는 느낌도 있었습니다.

질문 정리

  1. 이런 식의 폴더 구조/레이어링 방식이 괜찮은 접근인지, 혹은 불필요하게 복잡도를 높이는 구조인지 궁금합니다.

  2. 도메인 레이어에 getProductById 같은 유틸 함수가 들어가는 구조도 괜찮은지 궁급합니다.

  • "유틸이지만 도메인 종속적이면 도메인 폴더에 둬도 된다"는 제 판단이 맞는 걸까요?
  1. controller 레이어의 역할을 따로 두는 게 실제로 클린한 구조인지
  • 아니면 작은 프로젝트나 특정 기능 단위에서는 usecase 함수에서 직접 뷰 함수를 호출해도 괜찮은지

  • 실무에서는 이런 뷰-로직 분리 및 결합을 어떻게, 어디에서 하시는지 궁금합니다.

추가 질문 (과제와 직접적인 관련은 없지만… 혹시 시간 여유가 되신다면 한 번 봐주시면 정말 감사하겠습니다 🙏)

클린코드 관련 과제를 진행하다 보니, 평소 회사 코드에서 항상 고민하던 부분이 이번 주제와 잘 맞는 것 같아 질문 남겨봅니다~!

현재 회사에서 20개 이상의 필드를 가진 복잡한 폼을 다루고 있는데요. 필드 간 의존성도 많고, 조건부 렌더링이나 밸리데이션 로직도 복잡하게 얽혀 있는 상황입니다.

현재 구조는 다음과 같습니다:

  • 최상위 폼 컴포넌트가 모든 필드 상태를 useState로 일괄 관리하고

  • 각 필드 컴포넌트에는 value, onChange, error, disabled, visible 등 다양한 props를 넘겨주는 구조

// 현재 방식 (최상위가 모든 걸 관리)
<Form>
  <NameField
    value={form.name}
    onChange={handleChange}
    error={errors.name}
    disabled={!isNameEnabled}
    visible={showName}
  />
  <EmailField
    value={form.email}
    onChange={handleChange}
    error={errors.email}
    // ...
  />
</Form>

그런데 이렇게 하다 보니 점점 아래와 같은 문제가 생깁니다:

  • props가 너무 많아지고,

  • 상위 컴포넌트에서 모든 의존성과 유효성 조건을 관리하려다 보니 상위 컴포넌트의 코드가 점점 비대해지고,

  • 필드 하나 바꾸는 것도 너무 많은 부분을 건드려야 해서 유지보수 부담이 큽니다.

그래서 아래와 같은 구조를 검토해보고 있습니다.:

  • 각 필드 또는 필드 그룹 단위로 상태, 유효성 검사, 렌더링까지 자체적으로 관리

  • 상위 컴포넌트는 흐름만 제어하거나 제출 시점에만 전체 데이터를 조립 + 전체 데이터에 대한 유효성 검사

// 새로 고민 중인 방식
<Form>
  <NameFieldBlock /> // 내부에서 상태, validation, 렌더링 모두 처리
  <EmailFieldBlock />
</Form>

// 예: NameFieldBlock 내부
const NameFieldBlock = () => {
  const [name, setName] = useState('');
  const [error, setError] = useState('');

  const handleChange = (e) => {
    const value = e.target.value;
    setName(value);
    if (value.length < 2) {
      setError('이름은 2자 이상이어야 합니다');
    } else {
      setError('');
    }
  };

  return <input value={name} onChange={handleChange} />;
};

이렇게 하면 컴포넌트 단위로 더 독립적이고 테스트 가능해질 것 같다는 판단이 드는데, 반대로 "하나의 컴포넌트가 너무 많은 책임을 지게 된다"는 피드백도 있어서 고민이 됩니다.

질문정리 이런 상황에서 제가 생각한 방식처럼 필드 단위로 상태와 로직, 밸리데이션을 묶는 구조가 괜찮은 선택일까요?

만약 제가 생각한 개선안에 단점이 많다면, 코치님은 현재 코드를 어떻게 분리하거나 개선하실 것 같으신가요?

과제 피드백

진희님 고생하셨습니다~ 과제 전반적으로 많은 고민하시고 잘 적용해주신 것 같은데요! 아쉬운점에서 남겨주신것처럼 이번 큰 규모의 리팩토링을 진행할때에는 명확한 기준 또는 규칙없이 진행이 되다보면 시간이 많이 들게 되는것 대비 명확한 개선을 이뤄내는게 참 어려운 것 같아요. 다음 과제를 진행하실 때는 이런 부분들에 대해서 작업들을 명확하게 작게 작게 나누고 피드백을 거치면서 진행해가시면 더 좋을 것 같습니다.

그럼 이어서 질문 주신거 답변 드려볼게요!

이런 식의 폴더 구조/레이어링 방식이 괜찮은 접근인지, 혹은 불필요하게 복잡도를 높이는 구조인지 궁금합니다.

좋은 접근인 것 같아요. 다만 말씀주신 것 처럼 프로젝트 규모에 맞게 잘못된 또는 너무 복잡한 아키텍처를 선택하는건 복잡성만 높이는 안좋은 선택일 수 있어요. 폴더가 텅텅 빈 채로 유지가 될수도 있겠죠. 지금은 과제를 진행하는 부분이기 때문에 여러 방식으로 구현해보는것도 방법일 수 있겠지만, 실무였다면 현재 시점에서(또는 예측가능한 시점까지 고려해서) 가장 효율적인 작은 방식으로 운영을 하면서 점진적으로 개선하는게 좋지 않을까 싶습니다.

도메인 레이어에 getProductById 같은 유틸 함수가 들어가는 구조도 괜찮은지 궁급합니다.

넵 적절합니다! 좋은 접근이에요.

controller 레이어의 역할을 따로 두는 게 실제로 클린한 구조인지

이 부분에 대한 답변도 처음과 같아요. 컨트롤러 레이어가 생기면 결국 뷰랑 비즈니스로직을 명확하게 구분할 수 있게 되는데 작은 규모의 프로젝트에서는 불필요하게 느껴질 수 있겠죠. 명확하게 구분이 필요할 때 도입하면 충분할 것 같아요. 대신 일관되게 작성하면 될 것 같습니다 ㅎㅎ

추가 질문

좋은 접근인데요. 이미 알고 계시는 것 처럼 블록별로 분리를 하게 되면 해당 로직에 대해서만 컴포넌트에서 관리할 수 있기 때문에 이상적인 상황이면 좋을 것 같아요. 다만, 아시겠지만 해당 데이터를 최종적으로 수집해서 전송해줘야 하는 케이스들이 있고 한 필드의 값이 다른 필드에 영향을 줘야 하는 케이스가 생긴다면 결국 이상태를 어디에 둘 것인가에 대한 고민은 동일할 것 같아요. 이런 문제들을 쉽게 다루기 위해서 RHF같은 라이브러리들을 쓰는 것 같은데요. 개인적으로 이런 상황에 적용한다면 개선이 크게 느껴질 것 같아서 고민해보셔도 좋을 것 같아요.

이번 주도 고민하셨고 남은 과제도 화이팅입니다!