소프트웨어 3대 원칙(DRY · KISS · YAGNI) — 도구지 교리가 아니다
DRY·KISS·YAGNI를 정의로 외우는 글은 많다. 이 글은 세 원칙이 서로 충돌하는 지점과, 원칙을 맹목적으로 따르다 코드가 더 나빠지는 경우를 정리한다.
세 줄 정의, 그리고 그 함정
- DRY (Don’t Repeat Yourself) — 같은 지식이 여러 곳에 흩어지지 않게 하라.
- KISS (Keep It Simple, Stupid) — 불필요한 복잡성을 피하라.
- YAGNI (You Aren’t Gonna Need It) — 지금 필요하지 않은 건 만들지 마라.
여기까지는 어디에나 있다. 문제는 이 세 원칙을 목적으로 착각할 때 생긴다. 중복 제거 자체, 함수를 짧게 쪼개는 것 자체, 기능을 빼는 것 자체가 목표가 되면 코드는 오히려 나빠진다. 원칙은 판단을 돕는 도구지, 따라야 할 교리가 아니다. 이 글은 정의보다 원칙이 배신하는 지점에 무게를 둔다.
1) DRY — 중복 제거가 목적이 아니다
같은 계산이 두 곳에 있다고 무조건 합치면 안 된다. 판단 기준은 겉모습(코드가 같은가)이 아니라 변경 이유다.
1
2
3
// 겉보기엔 같은 중복
function orderTotal(price) { return price + price * 0.1; } // 부가세 10%
function displayTax(price) { return price + price * 0.1; } // 표시용 세금 미리보기
두 0.1은 지금은 같지만, “주문 합계의 세율”과 “화면 미리보기의 세율”은 바뀌는 이유가 다르다. 표시 로직이 프로모션으로 세금을 다르게 보여줘야 하는 순간, 억지로 합친 공통 함수는 한쪽 요구가 다른 쪽을 깨뜨린다. 이게 우연한 중복(coincidental duplication) 이다.
Sandi Metz의 말이 정확하다 — “duplication is far cheaper than the wrong abstraction.” 잘못된 추상화는 중복보다 훨씬 비싸다.
- 진짜 중복(하나의 규칙이 여러 곳에 복사됨, 예: 세율 상수
TAX_RATE)만 합친다. - 비슷한 코드 3줄이 섣부른 추상화보다 낫다. 세 번째 반복이 같은 이유로 나타났을 때 추상화해도 늦지 않다.
- 공통 모듈을 만들 때마다 결합도가 올라간다는 걸 기억한다. 합치는 비용은 공짜가 아니다.
2) KISS — 짧은 함수가 단순한 게 아니다
“함수를 잘게 쪼개라”는 조언은 KISS와 자주 혼동된다. 하지만 한 곳에서 위→아래로 흐름이 읽히는 코드가, 의도를 좇아 여러 함수를 점프해야 하는 코드보다 단순하다. 한 번만 쓰이는 3줄을 이름만 그럴싸한 헬퍼로 빼면, 독자는 그 함수를 찾으러 파일을 뒤져야 한다 — 그게 복잡성이다.
- 진짜 KISS는 읽는 사람의 인지 부하를 줄이는 것이지, 함수 개수를 늘리는 게 아니다.
- 영리한 코드(한 줄 트릭, 과한 제네릭)보다 명백한 코드가 낫다. 한눈에 의도가 보이면 그게 단순한 것이다.
- 과도하게 일반화한 API는 KISS 위반이다. “무엇이든 처리하는” 함수는 대개 아무것도 명확히 처리하지 못한다.
3) YAGNI — 확장 포인트조차 미리 만들지 마라
“나중에 쓸 수 있으니까”는 대부분 오지 않는 미래에 지금 비용을 지불하는 것이다.
- “언젠가 멀티테넌시가 필요할지 모르니” 전 코드에 테넌트 추상화를 까는 것 — 안 한다.
- 단일 설정으로 충분한데 플러그인 시스템·설정 파서를 미리 짜는 것 — 안 한다.
- 흔한 오해: “확장 포인트는 미리 남겨두자”도 YAGNI 위반이다. 쓰이지 않는 인터페이스·추상 계층·의존성 주입은 그 자체가 유지보수 부담이다. 두 번째 구현이 실제로 필요해질 때 도입한다.
미래 요구를 위한 설계가 아니라, 되돌리기 쉬운 단순한 코드가 진짜 대비책이다. 요구가 오면 그때 리팩터링한다.
원칙이 서로 충돌할 때
세 원칙은 보완적이라기보다 자주 충돌한다. 충돌 지점을 아는 게 정의를 아는 것보다 실전에서 중요하다.
| 충돌 | 무엇이 문제인가 | 판단 기준 |
|---|---|---|
| DRY ↔ YAGNI | 중복을 없애려는 추상화가 미래를 위한 과잉설계가 된다 | 추상화는 지금 존재하는 여러 중복 지점을 해결할 때만. “혹시 나중에”는 근거가 아니다 |
| DRY ↔ KISS | 중복을 합치니 결합도·간접 참조가 늘어 오히려 복잡해진다 | 작은 스코프의 중복은 그냥 둔다. 중복이 버그 위험이나 변경 비용을 실제로 키울 때만 DRY |
| KISS ↔ (Clean Code) | 짧은 함수 규칙을 따르다 흐름이 파편화된다 | 함수 분리는 재사용·명명이 실제로 이득일 때만. 한 흐름은 한 곳에 |
마무리
DRY·KISS·YAGNI를 지키려다 코드가 더 복잡해진다면, 그 원칙 적용이 틀린 것이다. 세 원칙은 “이렇게 하면 대체로 낫다”는 경험칙이지 증명된 법칙이 아니다. 규칙을 경전처럼 따르는 대신, 매번 “왜 이걸 합치지/쪼개지/빼지?” 를 묻는 게 핵심이다. 답이 “규칙이 그러라고 해서”라면 멈추고 다시 생각할 때다.