
와 11장!
Ward Cunningham은 깔끔한 코드를 이렇게 정의한다
코드를 읽을 때 '아 당연히 이렇게 되어있겠지'라고 느끼는 것
모듈의 정의
클린코드에서 말하는 모듈은 '물리적 파일 구조'가 아닌 '논리적 응집력'
모듈은 함께 동작하는 함수와 변수의 묶음을 말한다
모듈을 또 다른 표현으로 '컴포넌트'라고 한다
Extract Till You Drop
10장에서 배운대로 이 원칙을 적용하고 나면
public 함수에서 많은 부분이 private 함수로 호출될 것이다
그럼 public 함수는 외부에 노출되는 인터페이스를 담당하고
private은 모듈의 구현을 나타낸다
한 마디로, 좁은 인터페이스와 깊은 구현을 가지게 된다
(A Philosophy of Software Design, John Ousterhout)
코드는 트리 구조처럼 위에서 아래로 추상화 수준이 내려간다
각 추상화 수준에서 함수 이름은 '무엇'을 하는지 알려주고, 함수 내부는 '어떻게' 하는지 보여줌
따라서, 모든 public 함수는 그보다 더 낮은 추상화 수준의 private 함수를 호출하는 트리를 만든다
여기서 또 The Stepdown Rule을 적용시켜야 하는데,
한 번에 한 단계 아랫 단계의 함수만 불러야지, 두세단계 도약 해버리면 안됨
The NewsPaper Metaphor
잘 쓰인 신문기사를 수직으로 읽는 상황을 생각해보자.
헤드라인: 기사가 무엇에 관한 것인지 알려줌 (독자가 이걸 읽을지 말지 결정하게 함)
첫 번째 단락: 큰 그림의 개념을 제공하며 전체 이야기 개요를 알려줌
이후 단락: 차차 세부 사항을 드러냄 (날짜, 이름, 인용문, 주장, 기타)
우리는 기사 전체를 읽기 보다,
헤드라인에서 시작해서 흥미로우면 마지막까지 읽고
기사가 지루해지자마자 멈춘다
내가 필요한 정보를 이미 습득했고, 더 이상 읽을 가치가 없다고 판단되면
그 즉시 기사 읽기를 멈추게 해주는 기사는 'polite'하다고 말한다
만약 이야기를 빙빙 돌려서 말하고, 핵심이 아닌 부가정보만 계속 던져주면서
독자의 시간을 낭비하게 만드는 기사는 'rude'하다고 말한다
(저자 아저씨가 개발자과 기자를 동시에 까는 고단수 스킬을 보여줌)
우리는 모듈의 소스코드가 '잘 쓰여진 신문기사 같기를 원한다'
- 모듈의 이름은 단순하지만 설명적이어야 한다
- 이름 자체만으로 우리가 올바른 모듈에 있는지 아닌지 알려주기 충분해야 한다
- 모듈의 최상위 부분은 높은 수준의 추상화 개념과 알고리즘을 제공해야 한다
- 아래로 이동할수록 디테일 구현 사항이 증가해야 한다
- 끝에서 모듈의 가장 낮은 추상화 수준의 함수와 세부사항을 보게 된다
두 개 이상의 다른 주제를 포함하는 모듈은 SRP(단일 책임 원칙)을 위반한다.
모듈 내의 클래스와 함수는 모두 단일주제(단일책임)과 관련되어야 한다.
또, 높은 추상화 수준에서 낮은 추상화 수준으로 이어지는 구조를 가져야 한다.
Be Polite
단일 추상화 수준을 캡슐화하는 함수를 작성하면,
잘쓰여진 신문기사처럼 독자가 중간에 필요한 시점에 빠져나갈 수 있게 해준다.
기능 유지보수를 위해 모든 코드를 다 읽거나 이해하는 것보다,
그들이 필요한 수준까지만 이해하도록 도와주는게 잘 짜인 코드이다.
예의 바른 코드 makeStatement()
1. 함수 이름이 이 함수가 무엇을 하는지 알려줌
2. 이 함수 내용이 궁금해서 들어가봄. 4줄의 간단한 코드로 내용 파악 완
3. 2초 후 다시 상위 함수로 돌아가서 아랫 부분을 계속 읽음
4. 딱히 중단된 느낌을 주기보단 '질문에 대한 답'을 얻은 기분
무례한 코드 statement()
1. 이름이 명사임. 어떤 동작을 하는지 알 수 없음
2. 내용을 읽어보다가 '아 make statement'를 의미하는군 하고 알게 됨
3. 하위 함수로 들어갔다가 30줄의 함수를 마주침
4. 1~2분 후 이 함수의 동작이 제대로 됐다는 확신을 얻고 다시 상위 함수로 돌아감
5. '어 내가 왜 이 함수를 보려고 했더라'하고 까먹음
The Stepdown Rule: Once Again
예의바른 코드를 위해 한 번에 한 수준의 추상화씩만 내려가자
1. 각 함수는 함수가 무엇을 의미하는지 정의하는 이름을 가짐
2. 각 함수는 구현을 설명하며, 모두 같은 추상화 수준에 있는 줄들의 집합으로 구성됨
3. 그 줄들 각각은 이미 가장 낮은 수준이 아니라면, 다음 수준 아래의 함수를 호출함
한 수준의 추상화가 적절한 크기인지 어떻게 아는가?
최선의 판단을 거쳐 이 추상화가 지금 적절한지 판단해라
(경험과 판단으로 님이 알아서 잘 해봐라)
10장에서 나왔던 내용과 같은 내용이지만, 일단 추출해보고 의미있으면 킵하라고 말함
The Abstraction Roller Coaster
추상화 수준을 오르내릴 때
'롤러코스터'가 느껴진다
높은 레벨의 추상화 함수에서 가장 낮은 레벨의 세부 구현을 마주친다면,
추상화 수준이 한 함수 내부에서 섞여있다면 독자들은 혼란에 빠짐!
claude가 만들어준 예시
추상화 수준을 넘나드는 함수를 볼 때 컴파일러는 이렇게 생각할 것이다
얘는 뭐 메모리에 알아서 잘 쌓아서 판단하겠지
func (customer *Customer) statement() string {
// 🔽 Line 1-2: 낮은 수준 (변수 초기화)
totalAmount := 0.0
frequentRenterPoints := 0
// 🔼 Line 3: 높은 수준 (헤더 생성)
result := "Rental Record for " + customer.name + "\n"
// 🔼 Line 4: 높은 수준 (루프 시작)
for _, rental := range customer.rentals {
// 🔽 Line 5: 낮은 수준 (변수 초기화)
thisAmount := 0.0
// 🔽🔽 Line 6-18: 매우 낮은 수준 (switch 세부 구현)
switch rental.movie.movieType {
case NewRelease:
thisAmount += float64(rental.daysRented * 3)
case Regular:
thisAmount += 2
if rental.daysRented > 2 {
thisAmount += float64(rental.daysRented-2) * 1.5
}
// ... 더 많은 케이스들
}
// 🔽 Line 19-22: 낮은 수준 (포인트 계산)
frequentRenterPoints++
if rental.movie.movieType == NewRelease && rental.daysRented > 1 {
frequentRenterPoints++
}
// 🔼 Line 23: 중간 수준 (결과 조립)
result += fmt.Sprintf("\t%s\t%.1f\n", rental.movie.title, thisAmount)
// 🔽 Line 24: 낮은 수준 (누적)
totalAmount += thisAmount
}
// 🔼 Line 25-28: 높은 수준 (footer 생성)
return result + fmt.Sprintf("Amount owed is %.1f\n...", totalAmount)
}
하지만 인간은 작업 기억에 한계가 있다
📍 Line 1 읽는 중: "totalAmount를 0으로... 왜?"
🧠 스택: [???]
📍 Line 3 읽는 중: "아, 헤더를 만드는구나"
🧠 스택: [헤더 만드는 중]
📍 Line 6 읽는 중: "switch문이네, 영화 타입별로..."
🧠 스택: [헤더 만드는 중 → 루프 도는 중 → switch 분석 중]
📍 Line 10 읽는 중: "rental.daysRented > 2면 1.5를 곱해서..."
🧠 스택: [헤더 → 루프 → switch → Regular 케이스 → 조건문...]
⚠️ 스택 깊이 5... 위험!
📍 Line 19 읽는 중: "frequentRenterPoints++... 잠깐, 이게 뭐였지?"
🧠 스택: [??? → ??? → ??? → ???]
💥 스택 오버플로우! 처음부터 다시 읽어야 함
세부 사항을 다루기 위해 생각의 흐름을 옆으로 밀어놓으면,
종종 그 생각의 흐름을 잃어버림
우리 팀에서 종종 같이 코드를 보다가 하는 말이
'원래 이걸 하려던게 아니었는데 엉뚱한 걸 보다가 2-3시간이 지났어요'라는 말을 했다
레거시의 이런 추상화 레벨이 엉망으로 섞인 코드 롤러코스터에 갇혀서 그랬을 듯
This Is How We Write, but Not How We Want to Read
그 statement() 함수를 쓰던 사람도 원래 그러고 싶진 않았을 거임
작성하다 보니까 그렇게 됐겠지
1. 일단 루프부터 짜자
2. 음 totalAmount를 0으로 만들어야지 (위로 올라가서 추가)
3. switch를 작성했는데, 세부 계산 누락함 (이미 1차 완성된 구조에 계산 끼워넣기)
처음부터 완벽한 구조를 짜는게 아니라
일단 짜고 -> 끼워넣고 -> 고치고 -> 아 맞다 이거 누락
이런 식으로 추상화와 세부사항이 공존하는 기복있는 트랙 위에서 코드를 짜면
롤러코스터 코드가 나온다는 것
하지만 여기서 Kent Beck의 꾸짖을 갈! 등장
'먼저, 작동하게 만들어라. 그런 다음, 올바르게 만들어라'
내가 저번에 이 문장에서 전자가 포인트가 아니라,
후자가 포인트인 것 같다고 쓴 적이 있는데 그게 진짜였음
코드가 작동한다고 끝나는게 아니다.
코드가 잘 읽힐 때 끝난 것이다.
Conclusion
저자는 최근들어 본 몇개의 장들에서
'내 원칙이 절대 룰은 아니지만'이라고 꼭 덧붙임
하지만 결론은 '그래도 이 룰을 지키지 않아야 하는 필수 이유가 없다면 웬만하면 나는 이 규칙을 따를 것'이라고 마무리함
이 규칙들은 저자의 기본값이기 때문이다.
작성: 2026.02.02. 00:32

'개발자 강화 > 개발 독서' 카테고리의 다른 글
| [Clean Code 2판] 1-13: Clean Classes (1) | 2026.02.23 |
|---|---|
| [Clean Code 2판] 1-12: Objects and Data Structures (1) | 2026.02.04 |
| [Clean Code 2판] 1-10: One Thing (0) | 2026.01.25 |
| [Clean Code 2판] 1-9: The Clean Method (0) | 2026.01.12 |
| [3차] Clean Code 스터디 (1) | 2026.01.06 |