설계 프로세스
- 4.1 두 번 설계하기
- 4.2 주석의 필요성
- 4.3 주석의 종류
좋은 설계는 첫 번째 아이디어에서 나오지 않아. 뭔가를 설계할 때 최소한 두 가지 이상의 대안을 고려하라 — 이게 저자의 "Design it Twice" 원칙이야. 문자 그대로 두 번 구현하라는 게 아니라, 구현 전에 최소 두 가지 다른 접근 방식을 스케치하고 비교하라는 거지.
왜 이게 효과적인지 생각해 보자. 첫 번째 아이디어의 약점이 대안과 비교하면 드러나고, 각 접근의 트레이드오프가 명확해지고, 두 아이디어의 장점을 조합한 더 나은 설계가 나올 수 있어. 설계 공간을 탐색하면서 문제에 대한 이해 자체가 깊어지거든. 시간이 더 걸리지 않냐고? 거의 안 걸려. 대안을 떠올리는 데 몇 분이면 충분해. 근데 잘못된 설계로 구현한 후 리팩터링하는 데는 며칠이 걸릴 수 있잖아. 작은 투자로 큰 리스크를 줄이는 셈이지.
대안을 만들 때는 근본적으로 다른 접근을 시도해야 해. 첫 번째 아이디어의 변형이 아니라 완전히 다른 관점에서 문제를 보는 거야. 비교할 때는 인터페이스가 더 단순한가, 구현이 더 깔끔한가, 미래 변경에 유연한가를 기준으로 하면 돼. 모든 면에서 하나가 우월한 경우는 드물어. 보통 A가 이 측면에서 좋고 B가 저 측면에서 좋지. 이 트레이드오프를 명확히 인식하고 현재 상황에서 더 중요한 측면을 기준으로 선택하면 돼. 가끔은 두 대안의 장점을 합친 세 번째 설계가 나올 수도 있어.
저자는 "Design it Twice"를 실천하지 않는 가장 큰 이유가 **에고(ego)**라고 말해. 특히 실력 있는 개발자일수록 "내 첫 번째 아이디어가 최선이다"라고 생각하는 경향이 있거든. 근데 현실은, 아무리 뛰어난 개발자라도 첫 번째 아이디어가 최적인 경우는 드물어. 스탠포드 수업에서도 학생들에게 두 가지 대안을 만들게 했더니 거의 항상 첫 번째보다 나은 설계가 나왔다고 해. 자기 아이디어에 집착하지 않는 열린 마인드가 좋은 설계의 시작이야.
설계 다음은 주석 이야기야. 많은 개발자가 "좋은 코드는 주석이 필요 없다"고 하잖아. 저자는 이 통념에 정면으로 반박해. 주석을 안 쓰는 이유로 흔히 드는 변명들을 보자. "Self-documenting code" — 변수명과 메서드명을 잘 지으면 주석이 필요 없다는 주장인데, 저자는 부분적으로만 동의해. 좋은 이름이 중요한 건 맞지만 코드만으로 전달할 수 없는 정보가 분명히 있거든. "주석은 유지보수가 안 된다" — 어느 정도 맞지만, 이건 주석을 안 쓸 이유가 아니라 주석을 잘 쓸 이유지. 주석을 코드 가까이에 두고 추상화 수준을 맞추면 유지보수 부담이 크게 줄어들어. "시간이 없다" — 전형적인 전술적 프로그래밍 마인드셋이야. 주석을 안 쓰면서 절약한 시간은 나중에 다른 개발자가 코드를 이해하려고 허비하는 시간으로 돌아와.
그러면 주석으로 뭘 써야 하나? 코드에서 드러나지 않는 것을 써야 해. 코드가 잘 전달하는 건 무엇(what)을 하는지, 어떻게(how) 하는지야 — 코드를 읽으면 알 수 있지. 근데 코드가 잘 전달 못하는 게 있어. 왜(why) 이렇게 했는지, 메서드의 목적과 계약 같은 고수준의 what, "이 메서드는 스레드 안전하지 않다"같은 제약조건과 주의사항, 변수가 밀리초인지 초인지 같은 단위와 형식. 이런 정보가 주석에 있으면 개발자가 코드를 다 읽지 않아도 필요한 것을 빠르게 파악할 수 있어. 이건 인지적 부하를 줄이고 Unknown Unknowns를 제거하는 것과 직결되지.
가장 흥미로운 주장이 주석을 쓰는 행위 자체가 설계를 개선한다는 거야. 모듈의 인터페이스 주석을 쓰려면 그 모듈이 무엇을 하는지 명확하게 설명해야 하거든. 주석이 너무 길면 모듈이 너무 많은 일을 한다는 뜻이고, 주석이 구현 세부사항을 언급하면 추상화가 새어나가고 있다는 뜻이고, 주석을 어떻게 써야 할지 모르겠으면 모듈의 목적이 불명확하다는 뜻이야. 저자는 이걸 **"주석 기반 설계 검증"**이라고 불러. 주석이 깔끔하고 간결하게 나오면 설계가 좋은 거고, 뒤죽박죽이면 설계를 다시 생각해야 하는 거지.
주석의 종류도 구분해야 해. **저수준 주석(Lower-level comments)**은 코드의 세부사항에 대해 정밀성을 추가하는 주석이야. 변수의 단위, 경계 조건, 불변식, 관련 변수와의 관계 같은 것을 명시하지.
// 밀리초 단위의 타임아웃. 0이면 타임아웃 없음.
private int timeout;
// 이 맵의 키는 사용자 ID, 값은 해당 사용자의 마지막 로그인 시각(UTC)
private Map<String, Instant> lastLoginTimes;
반면 "timeout 값을 설정한다"처럼 코드를 그대로 반복하는 주석은 아무 가치가 없어. 좋은 저수준 주석은 코드에 없는 정보를 추가하는 거야.
**고수준 주석(Higher-level comments)**은 코드의 전체적인 목적이나 의도를 설명해. 코드를 읽지 않아도 무슨 일을 하는지 이해할 수 있게 해주지. 복잡한 정렬 알고리즘 위에 "마감일이 임박한 항목을 우선하되, 같은 마감일이면 우선순위가 높은 것을 먼저 처리한다"라는 한 줄이 있으면 20줄의 비교 로직을 안 읽어도 되잖아. 핵심은 구현 세부사항을 넣지 않는 거야. "B-트리에서 노드를 분할한다"가 아니라 "새 엔트리를 위한 공간을 확보한다"가 좋은 고수준 주석이지.
저자는 주석을 인터페이스 주석과 구현 주석으로도 나눠. 인터페이스 주석은 모듈을 사용하기 위해 알아야 하는 것 — 무엇을 하는지, 어떤 조건에서 사용하는지, 결과가 무엇인지를 사용자 관점에서 쓰는 거야. 구현 주석은 코드가 어떻게 동작하는지, 왜 이런 방식을 선택했는지를 유지보수자 관점에서 쓰는 거고. 둘 중 더 중요한 건 인터페이스 주석이야. 모듈 사용자가 구현을 안 읽어도 사용할 수 있게 해주니까 — 이게 바로 추상화를 제공하는 것이고 인지적 부하를 줄이는 핵심 메커니즘이지.
정리
4장 읽고 기억할 거 세 가지:
- 구현 전에 최소 두 가지 대안을 스케치하자. 첫 번째 아이디어가 최선일 가능성은 낮고, 대안을 비교하면 트레이드오프가 명확해져. 에고를 내려놓고 열린 마인드로 접근하는 게 중요해
- "좋은 코드는 주석이 필요 없다"는 미신이야. 코드가 전달 못하는 정보 — why, 제약조건, 단위, 고수준 목적 — 가 반드시 있고, 주석을 쓰는 행위 자체가 설계를 검증하는 도구가 돼
- 저수준 주석은 정밀함, 고수준 주석은 직관을 제공해. 그리고 인터페이스 주석이 가장 중요해 — 모듈 사용자가 구현을 안 봐도 사용할 수 있게 해주는 게 추상화의 핵심이거든