목록으로

코드를 읽기 쉽게 만들기

모든 개발자들은 코드를 읽기 쉽게 작성하는 방법은 알고 있다.

토스에서 진행했던 과제들의 PR, 그리고 관련한 글 들을 읽고 학습 후 직접 기능을 구현하면서 코드에 적용을 해보았다.

이름짓기

모든 코드에는 의도가 있다. 그 의도는 간단하게 이름으로 표현할 수 있다.

아래 코드의 의도를 알려면 한 줄 한 줄 읽으면서 머릿속에 채워넣어야 한다.

 for (let i = 0; i < paragraphs.length; i++) {
      const paragraph = paragraphs[i];
      const downloadUrl = getDownloadUrl(paragraph);
      if (!downloadUrl) continue;
      const baseName = buildTtsHubParagraphBasename(stamp, i + 1);
      await downloadAudio(downloadUrl, baseName);
    }

for 루프를 따라가며 변수를 할당하고, getDownloadUrl로 URL을 추출한 뒤 빈 값이면 continue로 넘어가고, 최종적으로 baseName을 만들어 downloadAudio를 호출한다.

그리고 본질을 알 수 있다. 이 코드는 paragraph를 하나씩 다운로드를 하는 역할이구나!

하지만 코드를 읽는 입장에서는 이 세부사항까지 정확히 알 필요가 없고 이 코드의 본질만 알면된다.

// 호출자(사용하는 쪽)의 코드: What만 명확하게 드러남
await downloadParagraphAudios(paragraphs, stamp);

// 구현부: How는 내부로 숨김
// How 내부에서도 이름이 필요한것들은 각각 이름을 지어줬다. `getDownloadUrl` `buildTtsHubParagraphBasename` `downloadAudio`
async function downloadParagraphAudios(paragraphs: Paragraph[], stamp: string) {
    for (let i = 0; i < paragraphs.length; i++) {
      const paragraph = paragraphs[i];
      const downloadUrl = getDownloadUrl(paragraph);
      if (!downloadUrl) continue;
      const baseName = buildTtsHubParagraphBasename(stamp, i + 1);
      await downloadAudio(downloadUrl, baseName);
    }
}

예측 가능성 높이기

자식 컴포넌트를 보았을 때, 보편적인 언어를 사용한 인터페이스일 경우 어떤 역할을 하는지 예측하기 쉬워진다.

// ❌ 독자적인 이름의 인터페이스 (작동 방식을 예측하기 위해 내부 코드를 열어봐야 함)
<SearchBox text={keyword} setKeyword={setKeyword} isTyping={isTyping} />

// ✅ 웹 표준 패턴을 따른 보편적인 인터페이스 (HTML <input>과 동일하게 작동할 것을 누구나 즉시 예측 가능)
<SearchInput value={keyword} onChange={setKeyword} disabled={isTyping} />

보편적인 인터페이스로 컴포넌트를 나타내면 코드를 읽는 입장에서 세부 구현 사항을 몰라도 어떤 역할을 하는 컴포넌트인지 에측이 가능하다.

역할에 따라 훅(hook) 분리하기

역할에 따라 잘 분리된 훅(hook)은 응집도를 높여준다. 하지만 코드가 너무 길다고 무작정 훅으로 나눠버리면, 오히려 좋지 않은 코드가 되어버린다.

// ❌ Before: UI 상태, 생성 로직, 플레이어 제어가 하나로 섞임
const {playbackState, setHasClickedGenerate,
    generateAudio, isGenerating, generationProgress ...} = useBundleAudio()

// ✅ after: 역할에 따라 분리(함께 변경되는 것들을 같이 두기)
// 재생 상태 담당
  const { playbackState, setHasClickedGenerate } =
    useTtsHubPlaybackState(bundleId);

// 생성 관련 담당
  const { generateAudio, isGenerating, generationProgress, isMergingBundle } =
    useBundleAudioGeneration({
      bundleId,
      availableVoices,
      voiceParamOverrides,
    });

// 오디오 재생 관련 담당
  const {
    containerRef,
    wavesurfer,
    isPlaying,
    pause,
    togglePlayback,
    autoPlay,
  } = useAudioController();

지금보니 제대로 분리를 하지 못한 거 같다 불편함이 느껴진다.

가장 중요한건 코드가길다고 무작정 훅으로 분리를 하면 안된다는 것. 이건 평소에 코드를 짤 때도 주의해야할 점이다.

상태 응집도 높이기

함께 변경되는 것들은 같은 곳에 두는 것 이 법칙을 state에도 적용할 수 있다.

// 서버에서 실시간으로 보내는 처리 상태를 관리 (진행/완료)
const [current, setCurrent] = useState(0);
const [total, setTotal] = useState(0);

// 하나로 묶어서 응집도를 높일수 있다.
 const [generationProgress, setGenerationProgress] = useState({
    current: 0,
    total: 0,
  });

2가지를 기억하는 것 보다는 하나의 덩어리로 기억하는게 인지 부하를 낮춰주고 인간의 뇌 사용량을 줄여준다.


아마도 모든 개발자들은 코드를 읽기 쉽게 작성하는 방법은 알고 있다.

하지만 당장 수정을 하려고 코드를 보는 순간 뇌가 거부를 한다. 당장 잘 돌아가는 코드를 힘들게 정리를해도 이후 찾아오는 보상이 느껴지지 않기 때문이다.

이러한 행동에 대한 거부감을 느끼지 않도록 항상 의도적으로 생각하는 훈련이 필요하다.

END OF ARTICLE