본문 바로가기

TIL

좋은 코드의 기준이란? feat. 토스 Frontend Fundamentals

이 글은 https://frontend-fundamentals.com/code/ 를 학습하고 정리한 글입니다. 

 

변경하기 쉬운 코드

Guidelines for easily modifiable frontend code

frontend-fundamentals.com

 

변경하기 쉬운 코드 

좋은 프론트 엔드 코드는 변경하기 쉬운 코드

코드가 변경하기 위운지에 대한 4가지 기준은 아래와 같다. 

  1. 가독성(Readability)  
    •  코드가 읽기 쉬운 정도 
  2. 예측 가능성(Predictability) 
    • 함께 협업하는 동료들이 함수나 컴포넌트의 동작을 얼마나 예측할 수 있는지 
  3. 응집도(Cohesion)
    • 수정되어야 할 코드가 항상 같이 수정되는지. 
    • 응집도가 높은 코드는 코드의 한 부분을 수정해도 의도치 않게 다른 부분에서 오류가 발생하지 않는다.  
  4. 결합도(Coupling)
    • 코드를 수정했을 때의 영향범위. 코드를 수정했을 때 영향범위가 적어서 변경에 따른 범위를 예측할 수 있는 코드 

 

 

1. 가독성 

맥락 줄이기 

A. 같이 실행되지 않는 코드 분리하기 

동시에 실행되지 않는 코드가 하나의 함수 또는 컴포넌트에 있으면 동작을 한눈에 파악하기 어렵고, 구현 부분에 많은 숫자의 분기가 들어가서 어떤 역할을 이해하기 어려움. 

ex) SubmitButton : 사용자 보기전용 viewer, 일반 사용자 전용 버튼 -> SubmitButton 내부에서 각각의 전용 컴포넌트로 분리할 수 있다. 

 

B. 구현 상세 추상화하기 

불필요한 맥락을 추상화하기

HOC(Higher-Order Component)나 Wrapper 컴포넌트로 분리하여 코드를 읽는 사람이 한 번에 알아야 하는 맥락을 줄일 수 있음 -> 가독성 올라감. 

 

ex) LoginStartPage 컴포넌트 : 사용자가 로그인되었는지 확인하고, 로그인이 된 경우 홈으로 이동시키는 로직을 가지고 있음 

1. Wrapper 컴포넌트 사용 

function App() {
  return (
    <AuthGuard>
      <LoginStartPage />
    </AuthGuard>
  );
}

function AuthGuard({ children }) {
   /* ...로그인 상태에 따라 페이지 이동처리 로직  ... */

  return status !== "LOGGED_IN" ? children : null;
}

function LoginStartPage() {
  /* ... 로그인 관련 로직 ... */

  return <>{/* ... 로그인 관련 컴포넌트 ... */}</>;
}

 

2. HOC(Higher-Order Component) 

function LoginStartPage() {
  /* ... 로그인 관련 로직 ... */

  return <>{/* ... 로그인 관련 컴포넌트 ... */}</>;
}

export default withAuthGuard(LoginStartPage);

// HOC 정의
function withAuthGuard(WrappedComponent) {
  /* ...로그인 상태에 따라 페이지 이동처리 로직  ... */

    return status !== "LOGGED_IN" ? <WrappedComponent {...props} /> : null;
  };
}

 

코드에서 구현 상세를 지나치게 드러내는 경우, 이 코드가 어떤 역할을 하는지 정확하게 파악하기 어렵다. 한 번에 6~7개 정도의 맥락을 한 번에 고려해 가면서 읽을 수 있도록 보다 작은 단위로 추상화하는 것이 필요함. 

 

C. 로직 종류에 따라 합쳐진 함수 쪼개기 

쿼리 파라미터, 상태, API 호출과 같은 로직의 종류에 따라서 함수나 컴포넌트, Hook을 만들지 말 것. 한 번에 다루는 맥락의 종류가 많아져서 이해하기 힘들고 수정하기 어려운 코드가 됨. 

 

ex) 페이지 전체의 URL 쿼리 파라미터를 관리하는 Hook -> 쿼리 파라미터 별로 별도의 Hook을 작성함으로써 Hook이 담당하는 책임을 분리.  

 

이름 붙이기 

A. 복잡한 조건에 이름 붙이기

ex) 

const isPriceInRange = product.prices.some(
      (price) => price >= minPrice && price <= maxPrice
    )

 

복잡한 조건식을 따라가지 않고도 코드의 의도를 명확히 드러낼 수 있다. 

 

조건식에 이름을 붙이는 기준 

  • 조건에 이름이 붙이는 조건이 좋을 때 : 복잡한 로직을 다룰 때 / 재사용성이 필요할 때 / 단위 테스트가 필요할 때 
  • 조건에 이름을 붙이지 않아도 괜찮을 때 : 로직이 간단할 때 / 한 번만 사용될 때  

B. 매직 넘버에 이름 붙이기 

매직 넘버(Magic Number) : 정확한 뜻을 밝히지 않고 소스 코드 안에 직접 숫자 값을 넣는 것 

ex) 

const ANIMATION_DELAY_MS = 300;

async function onLikeClick() {
  await delay(ANIMATION_DELAY_MS);
  await refetchPostLike();
}

 

위에서 아래로 읽히게 하기 

A. 시점 이동 줄이기 

시점 이동 : 코드를 읽을 때 코드의 위아래를 왔다갔다 하면서 읽거나, 여러 파일이나 함수, 변수를 넘나들면서 읽는 것 

  • 조건을 펼처서 그대로 드러내기
 switch (user.role) {
    case "admin":
      return (
        <div>
          <Button disabled={false}>Invite</Button>
        </div>
      );
    case "viewer":
      return (
        <div>
          <Button disabled={true}>Invite</Button>
        </div>
      );
  • 조건을 한눈에 볼 수 있는 객체로 만들기
function Page() {
  const user = useUser();
  const policy = {
    admin: { canInvite: true, canView: true },
    viewer: { canInvite: false, canView: true }
  }[user.role];

  return (
    <div>
      <Button disabled={!policy.canInvite}>Invite</Button>
      <Button disabled={!policy.canView}>View</Button>
    </div>
  );
}

 

B. 삼항 연산자 단순하게 하기 

삼항 연산자를 복잡하게 사용하면 조건의 구조가 명확하게 보이지 않아서 코드를 읽기 어려울 수 있기 때문에 if 문으로 풀어서 사용하면 보다 간단하게 조건을 드러낼 수 있다. 

 

const status = (() => {
  if (A조건 && B조건) return "BOTH";
  if (A조건) return "A";
  if (B조건) return "B";
  return "NONE";
})();

2. 예측 가능성 

A. 이름 겹치지 않게 관리하기 

B. 같은 종류의 함수는 반환 타입 통일하기 

return 타입을 동일하게 맞추기

C. 숨은 로직 드러내기 

함수의 이름과 파라미터, 반환 타입으로 예측할 수 있는 로직만 구현 부분에 남기고 숨은 로직을 하는 코드는 별도로 분리하기 

3. 응집도 

A. 함께 수정되는 파일을 같은 디렉토리에 두기 

함께 수정되는 소스 파일을 하나의 디렉토리에 배치하면 코드의 의존 관계를 명확하게 드러낼 수 있음. 

ex) 

- src 

    - components

    - containers 

    - hooks 

    - utils 

    - 등 등 

    - domains 

       - domain 1 

           - components 

           - containers 

           - hooks 

           - utils . .

       - domain 2 

           - components

           - containers 

           - hooks 

           - utils . .

 

B. 매직 넘버 없애기 

C. 폼의 응집도 생각하기 

 

필드 단위 vs 폼 전체 단위 응집도 

  • 필드 단위 응집도 선택하면 좋을 때 
    • 독립적인 검증이 필요할 때 
    • 재사용이 필요할 때 
  • 폼 전체 단위 응집도를 선택하면 좋을 때 
    • 단일 기능을 나타낼 때 
    • 단계별 입력이 필요할 때 
    • 필드 간 의존성이 있을 때 

4. 결합도 

A. 책임을 하나씩 관리하기 

B. 중복 코드 허용하기 

여러 페이지나 컴포넌트에 걸친 중복 코드를 하나의 Hook이나 컴포넌트로 공통화하는 경우, 불필요한 결합도가 생겨서, 공통 컴포넌트나 Hook을 수정함에 따라 영향을 받는 코드의 범위가 넓어져서 오히려 수정이 어려워질 수도 있음. 

C. Props Drilling 지우기 

Composition 패턴을 활용할 수 도 있다.