12장. 창발성

프라이마리모·2025년 3월 16일

Clean Code

목록 보기
11/15
post-thumbnail

이 장에서는 창발적 설계로 깔끔한 코드를 구현하는 것에 대해 알아본다.

창발성이란?

  • 작은 요소들이 모였을 때, 개별 요소에서는 볼 수 없는 새로운 특성이나 행동이 나타나는 현상
  • 복잡한 시스템에서 자발적으로 새로운 질서나 패턴이 생기는 것
  • 프로그램 개발에서는 여러 단순한 모듈, 함수, 알고리즘들이 상호작용하면서, 설계자가 의도하지 않았거나 예상하지 못한 복잡한 동작이나 시스템 특성이 자연스럽게 나타나는 것

착실하게 따르기만 하면 우수한 설계가 나오는 간단한 규칙 네 가지가 있다. 다음은 해당 규칙을 중요도 순으로 나열하였다.

  • 모든 테스트를 실행한다.
  • 중복을 없앤다.
  • 프로그래머 의도를 표현한다.
  • 클래스와 메서드 수를 최소로 줄인다.

1. 모든 테스트를 실행하라

테스트 케이스를 만들고 계속 테스트하라는 규칙을 따르면 시스템은 낮은 결합도와 높은 응집력이라는, 객체지향방법론이 지향하는 목표를 저절로 달성한다. 테스트 케이스를 작성하면 설계 품질이 높아진다.

테스트 가능한 시스템

  • 테스트를 철저히 거쳐 모든 테스트 케이스를 항상 통과하는 시스템
  • 테스트 가능한 시스템 만들기
    • 작은 크기, 한 가지 목적만 수행하는 클래스 생성
      • SRP(단일책임원칙) 준수하는 클래스는 테스트가 쉽다.
    • 테스트 케이스가 많을수록 개발자는 테스트가 쉽도록 코드 작성
      • DIP(의존관계역전원칙) 적용
      • 의존성 주입, 인터페이스, 추상화 등의 도구를 사용해 결합도 낮춤
      • 설계 품질 향상

2~4. 리팩터링

테스트 케이스 작성 후 코드와 클래스를 정리한다. 구체적으로, 코드를 점진적으로 리팩터링한다. 테스트 케이스가 있으니 코드를 정리하며 시스템이 깨질 걱정을 할 필요는 없다.

리팩터링 단계

  • 소프트웨어 설계 품질 높이는 기법 적용
    • 응집도 높이기
    • 결합도 낮추기
    • 관심사 분리하기
    • 시스템 관심사 모듈로 나누기
    • 함수와 클래스 크기 줄이기
    • 더 나은 이름 선택하기 등
  • 중복 제거
  • 프로그래머 의도 표현
  • 클래스, 메서드 수 최소로 줄이기

2. 중복을 없애라

비슷한 코드는 더 비슷하게 고쳐주면 리팩터링이 쉬워진다.
아래 예시에서는 두 메서드에서 일부 코드가 동일하다.

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;
}

위 코드에서 동일한 코드를 정리해 중복을 제거한다.

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.getScaledImage(image, degrees));
}

private void relplaceImage(RenderedOp newImage) {
	image.dispose();
    System.gc();
    image = newImage;
}

중복 제거 후에는 클래스가 SRP를 위반한다. 그러므로 새로 만든 relplaceImage메서드를 다른 클래스로 옮긴다. 그러면 새 메서드의 가시성이 높아지고, 추가적인 추상화를 통해 다른 맥락에서 재사용 역시 가능하다. 이러한 소규모 재사용은 시스템 복잡도를 극적으로 줄여준다. 소규모 재사용을 제대로 익히면 대규모 재사용이 가능하다.


3. 표현하라

코드는 개발자의 의도를 분명히 표현해야 한다. 개발자가 코드를 명백하게 짤수록 다른 사람이 그 코드를 이해하기 쉬워지며, 결함이 줄어들고 유지보수 비용이 줄어든다.

  • 좋은 이름 선택 : 이름과 기능을 일치하도록 이름을 정한다.
  • 함수와 클래스 크기를 가능한 줄이기 : 작은 클래스와 작은 함수는 이름 짓기도 쉽고, 구현하기도 쉽고, 이해하기도 쉽다.
  • 표준 명칭 사용 : 클래스가 command나 visitor와 같은 표준 패턴을 사용해 구현된다면 클래스 이름에 패턴 이름을 넣어준다. 그러면 다른 개발자가 클래스 설계 의도를 이해하기 쉬워진다.
  • 단위 테스트 케이스 꼼꼼히 작성 : 테스트 케이스는 예제로 보여주는 문서이다. 잘 만든 테스트 케이스를 읽어보면 클래스 기능이 한눈에 들어온다.

4. 클래스와 메서드 수를 최소로 줄여라

중복 제거, 의도 표현, SRP 준수라는 기본 개념도 극단으로 치달으면 득보다 실이 많다.
함수와 클래스 크기는 작게 유지하면서, 시스템 크기도 작게 유지하는 것이 궁극적인 목표이다. 그러므로 클래스와 함수 수를 줄이는 작업도 중요하지만, 테스트 케이스를 만들고 중복을 제거하고 의도를 표현하는 작업이 더 중요하다.

profile
개발공부 요약노트

0개의 댓글