코드를 읽기 쉽게 만들기
모든 개발자들은 코드를 읽기 쉽게 작성하는 방법은 알고 있다.
토스에서 진행했던 과제들의 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