Clean Code · Ch.12

창발적 설계

창발적 설계의 4가지 원칙

단순한 규칙 4가지를 지키는 것만으로도 좋은 설계가 창발한다.

🧪

① 모든 테스트를 실행한다

테스트가 가능한 시스템을 만들려면 SRP를 준수해야 한다. 결합도를 낮추고 응집력을 높이게 된다.

♻️

② 중복을 없앤다

작은 중복도 제거하라. TEMPLATE METHOD 등 패턴이 여기서 발생한다.

📖

③ 의도를 표현한다

좋은 이름, 작은 함수와 클래스, 표준 패턴 사용으로 의도를 명확히 하라.

✂️

④ 클래스와 메서드를 최소로

SRP를 극단으로 적용하면 오히려 클래스가 너무 많아진다. 실용적 균형을 찾아라.

우선순위 — 위 순서대로 중요하다. 테스트가 없으면 리팩터링도 의미 없다. 중복 제거와 의도 표현은 테스트가 있어야 안전하게 할 수 있다.

중복 제거 → 더 많은 책임 발견

중복을 제거하다 보면 숨어있던 SRP 위반이 드러난다.

❌ BEFORE — 중복된 dispose/replace 로직
public void scaleToOneDimension(
    float desiredDimension, float imageDimension) {
  if (Math.abs(desiredDimension - imageDimension)
      < errorThreshold)
    return;
  float scalingFactor = desiredDimension / imageDimension;
  scalingFactor = (float)(
      Math.floor(scalingFactor * 100) * 0.01f);
  RenderedOp newImage = ImageUtilities.getScaledImage(
      image, scalingFactor, scalingFactor);
  image.dispose();
  System.gc();
  image = newImage;
}

public synchronized void rotate(int degrees) {
  RenderedOp newImage =
      ImageUtilities.getRotatedImage(image, degrees);
  image.dispose();
  System.gc();
  image = newImage;
}
✅ AFTER — replaceImage 추출
public void scaleToOneDimension(
    float desiredDimension, float imageDimension) {
  if (Math.abs(desiredDimension - imageDimension)
      < errorThreshold)
    return;
  float scalingFactor = desiredDimension / imageDimension;
  scalingFactor = (float)(
      Math.floor(scalingFactor * 100) * 0.01f);
  replaceImage(ImageUtilities.getScaledImage(
      image, scalingFactor, scalingFactor));
}

public synchronized void rotate(int degrees) {
  replaceImage(
      ImageUtilities.getRotatedImage(image, degrees));
}

private void replaceImage(RenderedOp newImage) {
  image.dispose();
  System.gc();
  image = newImage;
}
발견 — scaleToOneDimension과 rotate는 SRP를 위반하고 있었다. 이미지를 dispose하고 교체하는 것은 각 함수의 책임이 아니다. 중복 제거 과정에서 이 사실이 드러났다.

Template Method Pattern

알고리즘 전체 구조는 고정, 구현 세부는 하위 클래스가 정의한다.

구조 — "이런 방식으로 진행할 거야. 근데 중간중간 세부 동작은 니가 채워 넣어봐." 추상적 리더 역할.
❌ BEFORE — 중복 구조
public class VacationPolicy {
  public void accrueUSDDivisionVacation() {
    // 지금까지 근무한 시간을 바탕으로
    // 휴가 일수를 계산하는 코드
    // ...
    // 미국 최소 법정 일수를 만족하는지 확인
    // ...
    // 급여 대장에 적용하는 코드
    // ...
  }

  public void accrueEUDivisionVacation() {
    // 지금까지 근무한 시간을 바탕으로
    // 휴가 일수를 계산하는 코드
    // ...
    // 유럽연합 최소 법정 일수를 만족하는지 확인
    // ...
    // 급여 대장에 적용하는 코드
    // ...
  }
}
✅ AFTER — Template Method
abstract public class VacationPolicy {
  public void accrueVacation() {
    calculateBaseVacationHours();
    alterForLegalMinimums();
    applyToPayroll();
  }

  private void calculateBaseVacationHours() { /* ... */ }
  abstract protected void alterForLegalMinimums();
  private void applyToPayroll() { /* ... */ }
}

public class USVacationPolicy extends VacationPolicy {
  @Override
  protected void alterForLegalMinimums() {
    // 미국 최소 법정 일수를 사용한다.
  }
}

public class EUVacationPolicy extends VacationPolicy {
  @Override
  protected void alterForLegalMinimums() {
    // 유럽연합 최소 법정 일수를 사용한다.
  }
}
Template Method 핵심 — 공통 알고리즘(흐름)은 상위 클래스에서 정의하고, 변하는 부분만 하위 클래스에서 구현한다. 코드 중복이 사라지고 확장점이 명확해진다.

Ch.12 핵심 요약

창발적 설계가 만들어지는 방식과 그 가치

🏗️

창발적 설계

복잡한 설계를 처음부터 완벽하게 만들 수 없다. 단순한 규칙을 지키면 좋은 설계가 자연스럽게 나타난다.

🔍

중복 = 설계 문제의 단서

중복을 제거하다 보면 숨어있던 책임 문제가 드러난다.

📐

패턴은 중복에서 탄생

Template Method, Strategy 등 많은 패턴이 중복 제거 과정에서 자연스럽게 등장한다.

⚖️

실용적 균형

클래스와 메서드를 최소화하되, 독단적인 SRP 적용으로 클래스 수가 폭발하지 않게 균형을 찾아라.