본문 바로가기

개발자 강화/개발 독서

[3차] Clean Code 스터디

  • 스터디 회차: 3차
  • 날짜: 2026.01.07 점심먹으면서!
  • 범위: Chapter 7 (Clean Functions) & Chapter 8 (Function Heuristics)

핵심 요약 (Summary)

7장 - Clean Functions

함수는 작을수록 좋다(2-4줄 이상적).

함수 내부는 동일한 추상화 수준을 유지하고, 위에서 아래로 자연스럽게 읽히도록 작성해야 한다.

Switch문은 다형성 객체를 생성하는 팩토리에만 숨겨둬야 한다.

깨끗한 함수는 Contextual, Nameable, Insulated, Homogenous, Pure 다섯 가지 속성을 갖춰야 한다.

 

8장 - Function Heuristics

인수는 3개 이하로 유지하고, 많으면 객체로 묶어야 한다.

Boolean 플래그 인수는 함수가 두 가지 일을 하게 만드므로 피해라.

명령(Command)과 쿼리(Query)를 분리하라.

오류 코드보다 예외 처리를 선호해야 한다.

DRY 원칙을 지키되, 우연적 중복과 본질적 중복을 구분해야 한다.

 


인상 깊었던 원칙 / 문장

7장

  • "함수는 작을수록 좋다" - 저자가 60년 경력에서 3천 줄 → 300줄 → 30줄 → 12줄 이하로 줄여온 과정
  • Kent Beck과의 경험 - TDD로 작성된 2-4줄짜리 함수들이 산문처럼 자연스럽게 읽히는 것에 충격받음
  • "일단 작동하는 코드를 쓰고, 그 다음에 정리하라" - 처음부터 완벽할 필요 없음
  • "읽기 쉬운 코드를 만들 수 있는데, 지금 50나노세컨드 차이가 중요하냐??" - 성능 타령하는 사람들에게 일침

8장

  • "인수는 입력용으로만 쓰고, 출력은 반환값으로 받을 것"
  • "함수는 뭔가를 하거나(명령), 뭔가를 답하거나(쿼리) 둘 중 하나만 해야 한다"
  • "먼저 작동하게 만들어라. 그런 다음 올바르게 만들어라."
  • "코드는 프로그램이 아니라 스토리다. 함수는 그 스토리를 이야기하는 동사다!"

좋은 코드 vs 나쁜 코드

1. 추상화 수준 혼합 (7장)

나쁜 코드

function 주문처리() {
    주문검증();                           // 높은 수준
    if (card.number.length !== 16) {...}  // 낮은 수준
    배송요청();                           // 높은 수준
    smtp.send(email, template);           // 낮은 수준
}

개선된 코드

function 주문처리() {
    주문검증();      // 높은 수준
    결제진행();      // 높은 수준  
    배송요청();      // 높은 수준
    알림발송();      // 높은 수준
}

왜 더 좋은가: 함수 내부가 모두 같은 추상화 수준이라 읽는 사람이 혼란스럽지 않다. 세부 구현은 각각의 하위 함수에서 처리한다.

 

2. Switch문 → 다형성 (7장)

나쁜 코드

// 결제 수단마다 switch문이 여러 곳에 반복됨
function calculateFee(payment) {
    switch (payment.type) {
        case "card": return payment.amount * 0.03;
        case "cash": return 0;
        case "point": return payment.amount * 0.01;
    }
}

function processPayment(payment) {
    switch (payment.type) { /* 또 switch... */ }
}

개선된 코드

// 인터페이스 정의
interface PaymentMethod {
    calculateFee(): number;
    process(): void;
}

// 각 결제 수단이 자신의 로직을 담당
class CardPayment implements PaymentMethod {
    calculateFee() { return this.amount * 0.03; }
    process() { callCardAPI(this); }
}

// 팩토리에만 switch 존재
class PaymentFactory {
    static create(type: string, data: PaymentData): PaymentMethod {
        switch (type) {
            case "card": return new CardPayment(data);
            // ...
        }
    }
}

왜 더 좋은가: 새 결제수단 추가 시 기존 코드 수정 없이 새 클래스와 팩토리 한 줄만 추가하면 됨 (OCP 준수). Switch문이 시스템 전체에 퍼지지 않음.

 

3. 인수 과다 → 객체로 그룹화 (8장)

나쁜 코드

function createUser(name: string, email: string, age: number, address: string, phone: string) {
    // ...
}
createUser("홍길동", "hong@example.com", 30, "서울시", "010-1234-5678");

개선된 코드

interface UserData {
    name: string;
    email: string;
    age: number;
    address: string;
    phone: string;
}

function createUser(userData: UserData) { /* ... */ }

createUser({
    name: "홍길동",
    email: "hong@example.com",
    age: 30,
    address: "서울시",
    phone: "010-1234-5678"
});

왜 더 좋은가: 인수 순서를 외울 필요 없고, 읽을 때 각 값의 의미가 명확하다. 인수 추가/삭제 시 호출부 수정이 최소화된다.

 

4. CQS 위반 → 명령/쿼리 분리 (8장)

나쁜 코드

// set이 동사인지 형용사인지 모호함
public boolean set(String attribute, String value);

if (set("username", "jay")) { ... }  // 설정? 확인?

개선된 코드

// 쿼리: 존재 여부 확인
if (attributeExists("username")) {
    // 명령: 값 설정
    setAttribute("username", "unclebob");
}

왜 더 좋은가: 함수가 한 가지 일만 하고, 코드를 읽을 때 의도가 명확하다.

 

5. 오류 코드 → 예외 처리 (8장)

나쁜 코드

const fileResult = openFile("data.txt");
if (fileResult.error === null) {
    const readResult = readContent(fileResult.file);
    if (readResult.error === null) {
        // 중첩 지옥...
    }
}

개선된 코드

try {
    const file = openFile("data.txt");
    const content = readContent(file);
    const data = parseJSON(content);
    console.log("성공:", data);
} catch (e) {
    console.log("실패:", e.message);
}

왜 더 좋은가: 성공 로직과 오류 처리가 분리되어 가독성이 높아진다.


토론 포인트

  1. Entanglement(얽힘) 논쟁: Robert Martin은 "약간의 얽힘은 용납해도 된다"고 하고, John Ousterhout는 "얽힘이 있으면 두 함수를 합쳐야 한다"고 주장한다. 실무에서는 어느 쪽이 더 현실적일까?
  2. 함수 2-4줄 원칙: 저자가 제안하는 2-4줄 함수가 현실적으로 가능한가? React 컴포넌트나 복잡한 비즈니스 로직에서는 어떻게 적용할 수 있을까?
  3. 우연적 중복 vs 본질적 중복: "같은 액터가 요청하면 합치고, 다른 액터가 요청하면 분리하라"는 원칙이 프론트엔드에서는 어떻게 적용될 수 있을까?
  4. Switch문 다형성 전환: 모든 switch문을 다형성으로 바꿔야 할까? 간단한 케이스에서는 오히려 과도한 설계가 아닐까?
  5. 순수 함수와 Side Effects: React에서 useEffect 같은 side effect를 다루는 훅과 이 원칙을 어떻게 조화시킬 수 있을까?

개인 의견 / 느낀 점

  • 동의: 함수를 작게 쪼개고 추상화 수준을 맞추면 확실히 코드가 산문처럼 읽힌다는 점. 실제로 props drilling 끊고 객체로 넘기는 습관이 "본능적 클린코드"였다는 게 재밌었음 ㅋㅋ
  • 비동의/의문: 2-4줄 함수는 이상적이지만, 실무에서 모든 함수를 그렇게 만들기는 어려울 것 같다. 특히 프론트엔드에서 UI 로직과 비즈니스 로직이 섞이는 경우가 많아서...
  • 실무 적용 난이도:
    • Switch → 다형성: 중~상 (설계 변경이 필요)
    • 인수 객체화: 하 (바로 적용 가능)
    • CQS: 중 (습관 바꾸기)
    • 추상화 수준 통일: 중 (리팩토링 필요)
  • 인상적이었던 점: "50나노세컨드 아끼려고 가독성 포기하지 마라"는 말이 찔렸다. 그리고 "코드는 스토리, 함수는 동사"라는 결론이 7-8장 전체를 관통하는 핵심인 것 같다.

실무 적용 아이디어

적용 가능 영역

  • 긴 함수들 조각내기 (과연 가능할까)
  • API 호출 함수의 에러 처리 통일 (이건 슬슬 해가고 있음)
  • 컴포넌트 props가 많은 경우 객체로 그룹화 (엉엉 넘어야 할 산)

당장 바꿔볼 코드

  • Boolean 플래그 인수 받는 함수 찾아서 두 개로 분리
  • 3개 이상 인수 받는 함수를 객체 파라미터로 변경 (이건 이미 선례가 있지 https://developer-dreamer.tistory.com/186)
  • switch문이 여러 곳에 반복되는 패턴 있는지 확인

주의할 점

  • 과도한 함수 분리로 오히려 Entanglement가 생기지 않도록 주의
  • 팀 컨벤션과 맞지 않으면 점진적으로 적용
  • 성능이 정말 중요한 부분(애니메이션, 렌더링)에서는 트레이드오프 고려

참고 / 추가 자료


스터디 진행 직전이나 진행 후에 보충해서 더 정리할 것

1차 게시 시점: 2025.01.06. 23:59

728x90