목차9개 항목
출처와 참고
토스의 「Frontend Fundamentals: 코드 품질」 문서를 바탕으로 재구성한 노트다. 기준별 전략은 하위 네 노트에 나눠 정리했다. 품질을 챙기는 이유와 타이밍은 토스 「모닥불」 EP.1 「토스에서 말하는 “가독성 좋은 코드”란 무엇일까?」(2024.06)를 더했다. 「There's No Such Thing as Clean Code」의 내용을 더해 보강했다.
좋은 코드란
좋은 프론트엔드 코드는 변경하기 쉬운 코드다. 새 요구사항이 왔을 때 기존 코드를 고치고 배포하기 수월하면 좋은 코드다. 변경하기 쉬운지는 네 가지 기준으로 판단한다.
| 기준 | 묻는 질문 | 높이는 전략 |
|---|---|---|
| 가독성(Readability) | 한 번에 고려할 맥락이 적고, 위에서 아래로 읽히는가 | 맥락 줄이기, 이름 붙이기, 시점 이동 줄이기 |
| 예측 가능성(Predictability) | 이름·파라미터·반환 값만 보고 동작을 알 수 있는가 | 이름 겹치지 않게, 반환 타입 통일, 숨은 로직 드러내기 |
| 응집도(Cohesion) | 함께 수정돼야 할 코드가 항상 함께 수정되는가 | 함께 바뀌는 파일 모으기, 매직 넘버 없애기, 폼 응집 단위 고르기 |
| 결합도(Coupling) | 한 곳을 고쳤을 때 영향 범위가 좁고 예측되는가 | 책임 하나씩, 중복 허용, Props Drilling 지우기 |
각 기준의 구체적인 전략과 코드 예시는 하위 노트에 있다.
「깨끗한 코드」라는 말 대신
「깨끗하다(clean)」, 「좋다」는 결론처럼 들리지만 근거가 되지 못한다. 코드는 가독성, 테스트 용이성, 성능, 유지보수성처럼 서로 다른 이유로 좋을 수 있고, 그 이유들은 자주 충돌한다. 그래서 리뷰와 설계 논의에서는 막연한 품질 형용사 대신 구체적인 성질을 이름으로 부른다.
| 막연한 말 | 구체적인 말 |
|---|---|
| 이 코드가 더 깨끗하다 | 이 코드는 의존성을 주입받아 테스트하기 쉽다, 상태가 캡슐화돼 있다, 이름만으로 예측 가능하다 |
| 이게 더 좋은 구조다 | 함께 바뀌는 코드가 한곳에 있어 응집도가 높다. 대신 한 단계를 건너 읽어야 한다(비용) |
「이 프로젝트에 필요한 것은 깨끗한 코드」가 아니라 「이 프로젝트에 필요한 것은 ___한 코드」라고 빈칸을 채운다. 무엇이 좋아지는지, 그 대가는 무엇인지까지 말해야 판단이 된다.
네 기준은 서로 부딪힌다
네 기준을 한꺼번에 만족시키기는 어렵다.
flowchart LR common["공통화·추상화"] --> c1["응집도 ↑<br/>(함께 바뀌는 것이 한곳에)"] common --> c2["가독성 ↓<br/>(한 단계 건너 읽어야 한다)"] common --> c3["결합도 ↑<br/>(공통 코드를 쓰는 모든 곳이 영향받는다)"] dup["중복 허용"] --> d1["결합도 ↓<br/>(수정 영향이 한 곳에 머문다)"] dup --> d2["응집도 ↓<br/>(한쪽만 고치고 다른 쪽을 놓칠 수 있다)"]
판단 기준은 함께 수정되지 않았을 때의 위험이다.
| 상황 | 우선할 기준 | 행동 |
|---|---|---|
| 한쪽만 고치면 버그·장애가 난다 | 응집도 | 공통화·추상화한다 |
| 위험이 크지 않다, 앞으로 달라질 여지가 있다 | 가독성·결합도 | 중복을 허용한다 |
"응집도와 결합도가 충돌하면 응집도를 먼저"라는 일반 원칙도 있지만, 프론트엔드처럼 화면마다 요구사항이 갈라지기 쉬운 곳에서는 위험이 낮은 중복은 남겨 두는 편이 변경에 더 강하다. 결국 지금 상황에서 장기적으로 무엇을 우선할지 매번 고르는 일이다.
왜 품질을 챙기나
"코드는 비즈니스를 위한 수단"이라는 말은 맞다. 그래서 오히려 품질을 챙긴다. 코드는 컴퓨터만이 아니라 다음에 그 코드를 고칠 동료를 위한 것이고, 품질이 비즈니스 속도를 직접 바꾼다.
| 품질이 나쁠 때 | 품질이 좋을 때 |
|---|---|
| 무해해 보이는 함수·훅을 불렀는데, 그 안의 훅 안의 로깅 함수가 에러 시 라우터를 이동시켜 사용자를 엉뚱한 페이지로 "납치"한다. 원인 찾는 데 시간이 든다 | 한 곳에서 도메인을 떼어 내고 잘 추상화해 둔 모듈은 다른 화면에서 몇 분 만에 재사용된다 |
| 숨은 부수 효과를 아무도 기억하지 못해 같은 사고가 반복된다 | 규칙이 있는 코드베이스는 함수에 들어가 봐도 예측한 대로 동작해, 처음 보는 큰 코드도 빨리 이해된다 |
부수 효과를 내는 코드를 어디에 두느냐도 품질의 일부다. 쉽게 부를 수 있는 곳에 큰 부수 효과를 숨기지 않는다(예측 가능성을 높이는 전략).
가독성의 두 층
| 층 | 비유 | 무엇 | 지키는 법 |
|---|---|---|---|
| 눈이 편한 가독성 | 책의 글꼴·자간 | 세미콜론, early return 같은 스타일이 일관돼 산만하지 않다 | 린터·포매터에 맡긴다 |
| 머리가 편한 가독성 | 책의 내용 | 역할과 책임이 이해할 만한 범위로 나뉘어, 한 번에 하나만 고려하며 읽는다 | 린터가 못 잡는다. 설계와 리뷰로 지킨다 |
리뷰도 이 순서로 옮겨 간다. 한 파일 안에서 끝나는 지역적 지적은 린터가 대신하게 하고, 사람은 여러 파일을 넘나드는 맥락 깊은 리뷰에 집중한다. 같은 문제를 더 단순한 사고 흐름으로 풀 수 없는지 묻는 것도 머리가 편한 가독성이다. 생각을 코드로 추상화하고, 남이 그 코드를 다시 생각으로 되돌리는 동안 맥락이 빠지므로 맥락을 최대한 덜어 내 코드에 녹인다. 당장 재사용하지 않더라도 도메인 로직을 떼어 낸 순수한 컴포넌트로 만드는 습관이 여기에 해당한다.
언제 챙기나: 짬짬이
설계를 오래 하고 짤지, 짜고 나서 리팩터링할지에는 정답이 없다. 리팩터링할 며칠을 따로 받아 내려 하기보다 조금 짜고 조금 고치기를 반복하며 정리하기 좋은 순간을 놓치지 않는다. Kent Beck이 『Tidy First?』에서 권하는 방식이다.
일정 산정이 품질을 정한다. 보통 일주일 걸릴 일을 3일에 끝내면 칭찬받지만 설계 고민이 빠진다.
flowchart LR a["무리한 단축"] --> b["품질 저하"] b --> c["버그 대응·기능 추가에 시간 증가"] c -->|"#quot;예전엔 3일이었는데#quot; 압박으로 또 단축"| a
이 악순환에 들어가면 빨리 일하던 사람이 점점 느려진다. 그래서 품질을 유지할 수 있는 범위로 일정을 산정하고 그것으로 소통하는 능력이 시니어의 역량이다. 마감이 정말 빠듯한 제품이면 단축이 맞지만, 그때도 대가를 알고 고른다.
품질 감각 기르기
| 방법 | 내용 |
|---|---|
| 코드 스멜 페어 스터디 | 각자 냄새나는 코드를 가져와 둘씩 짝지어 왜 냄새가 나는지, 어떤 잠재 위험이 있는지, 왜 기능을 붙이기 어려운지 토론하고 추상적인 규칙을 뽑는다. 책으로 읽은 원칙이 실제 코드베이스에 어떻게 적용되는지 몸에 붙는다 |
| 규칙으로 흔들림 감지 | 인자 넘기는 방식, 반환 형태, 함수 책임의 범위를 규격화해 둔다. 규칙에서 벗어나거나 규칙으로 표현할 수 없는 요구가 오면 지금 품질이 흔들리고 있다는 신호다 |
| 필요할 때 오픈소스 참고 | 궁금해서 뜯어보기보다, 비슷한 것을 만들어야 할 때 잘 만든 오픈소스의 인터페이스를 본다. 예: SDK를 만들 때 Firebase가 무엇을 기본으로 열고 무엇을 옵션으로 숨겼는지. 당장 적용할 문제가 있을 때 가장 잘 흡수된다 |
| 평소 체화 | 바쁠 때 빨리 짜도 좋은 코드가 나오도록 평소에 리뷰를 진지하게 하고 책·스터디로 익혀 둔다 |
자주 논쟁되는 컨벤션
정답 없이 팀 합의가 필요한 주제다. 결론보다 기준을 정하고 린터로 강제하는 것이 중요하다.
| 주제 | 한쪽 | 다른 쪽 | 고를 때 볼 것 |
|---|---|---|---|
| 조건부 렌더링 | &&·삼항 연산자: 언어 기능 그대로, 직관적 | <If /> 같은 선언적 컴포넌트: 패턴 일관성 | 컴포넌트 방식은 TypeScript 타입 좁히기가 안 되고, children이 조건과 무관하게 먼저 평가된다(단락 평가 없음) |
| 전역 상태 도입 | props로 전달 유지 | 전역 상태 라이브러리 | 서버 상태는 TanStack Query 같은 서버 상태 도구로, URL·폼·UI 상태는 각자 자리에 둔다. Context는 상태 관리 도구가 아니라 값 전달 수단이다 |
enum vs as const | enum: 의도가 분명 | as const 객체 + 유니온 타입: 런타임 코드가 JS 그대로 | TypeScript 5.8의 --erasableSyntaxOnly와 Node.js의 타입 제거(type stripping) 실행은 enum 같은 런타임 문법을 허용하지 않는다. 새 코드는 as const가 안전하다 |
한 줄 if (A) return null; | 간결하다 | 항상 중괄호: 나중에 줄을 더해도 diff가 작다 | ESLint curly나 Biome 규칙으로 하나로 정한다 |
암묵적 불리언 변환 if (!value) | 간결하다 | value === undefined처럼 명시적 비교 | !value는 ""·0·null·undefined를 모두 거른다. 특정 값만 막으려면 명시적으로 비교하거나 isNil 같은 헬퍼를 쓴다. strict-boolean-expressions로 강제할 수 있다 |
정리
- 좋은 코드의 기준은 변경하기 쉬운가다.
- 가독성·예측 가능성은 "읽고 이해하기", 응집도·결합도는 "고치고 퍼뜨리기"에 관한 기준이다.
- 네 기준은 서로 부딪힌다. 함께 안 고치면 위험한 코드만 공통화하고, 나머지는 중복을 허용한다.
- 품질은 동료와 비즈니스 속도를 위한 것이다. 눈이 편한 가독성은 린터에, 머리가 편한 가독성은 설계와 리뷰에 맡기고, 짬짬이 정리하며 일정 산정으로 지킨다.
- 논쟁적인 스타일은 근거를 놓고 팀이 정한 뒤 린터에 맡긴다.
관련 노트: 클린 코드: 함수와 이름, 가독성을 높이는 전략, 코드 리뷰 원칙과 체크리스트, 조건부 렌더링, 리액트 상태 관리의 흐름과 고르는 기준