Language/Java

[클린 코드] Ch.12 - 창발성

비소_ 2022. 8. 22.

클릭하시면 네이버 Book으로 연결됩니다!

해당 서적을 참고하여 개인 공부용으로 정리한 글입니다.


Intro.

창발성(創發性)이란?
창발(創發) 또는 떠오름 현상은 하위 계층(구성 요소)에는 없는 특성이나 행동이 상위 계층(전체 구조)에서 자발적으로 돌연히 출현하는 현상이다. 또한 불시에 솟아나는 특성을 창발성(emergent property) 또는 이머전스(emergence)라고도 부른다.
[출처] 위키피디아, 창발, https://ko.wikipedia.org/wiki/%EC%B0%BD%EB%B0%9C

네 가지 단순 설계 규칙

  1. 모든 테스트를 실행한다
  2. 중복을 제거한다
  3. 의도를 제대로 표현한다
  4. 클래스와 메서드 수를 최소로 줄인다.

위 규칙은 중요도 순이다.

켄트 벡이 제시한 이 규칙은 소프트웨어 설계 품질을 크게 높여준다.

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

설계는 의도대로 돌아가는 시스템을 내놓아야 한다.

하지만, 검증할 방법이 없다면, 문서 작성에 들인 시간을 인정받기 힘들다.

따라서, 검증이 불가능한 시스템은 절대 출시하면 안 된다.

 

또한, 철저한 테스트가 가능한 시스템을 만들면 더 나은 설계가 나온다.

결합도가 높으면 테스트 케이스를 작성하기 어렵기 때문에, DI, 인터페이스, 추상화 등을 통해 결합도를 낮추게 된다.

이 규칙을 따르면 시스템은 낮은 결합도와 높은 응집력이라는 객체 지향 목표를 저절로 달성한다.

2 ~ 4. 리팩터링

테스트 케이스를 모두 작성했다면 점진적으로 리팩터링 해나간다.

테스트 케이스가 있으므로 정리하면서 시스템이 깨질까 걱정할 필요가 없다.

중복을 없애라

똑같은 코드뿐만 아니라 구현 중복도 중복의 한 형태다.

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);
  
  RenderedOpnewImage = ImageUtilities.getScaledImage(image, scalingFactor, scalingFactor);
  image.dispose();
  System.gc();
  image = newImage;
}

public synchronized void rotate(int degrees) {
  RenderedOpnewImage = ImageUtilities.getRotatedImage(image, degrees);
  image.dispose();
  System.gc();
  image = newImage;
}

위 코드는 아래 4줄이 겹친다.

따라서, 다음과 같이 중복을 제거해준다.

public void scaleToOneDimension(float desiredDimension, float imageDimension) {
  if (Math.abs(desiredDimension - imageDimension) < errorThreshold)
    return;
  float scalingFactor = desiredDimension / imageDimension;
  scalingFactor = (float) Math.floor(scalingFactor * 10) * 0.01f);
  replaceImage(ImageUtilities.getScaledImage(image, scalingFactor, scalingFactor));
}

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

private void replaceImage(RenderedOpnewImage) {
  image.dispose();
  System.gc();
  image = newImage;
}

이렇게 되면 클래스가 SRP를 위반한다.

따라서, 새로 만든 replaceImage를 다른 클래스로 옮긴다.

이러한 '소규모 재사용'은 시스템 복잡도를 극적으로 줄여준다.

 

템플릿 메서드 패턴은 고차원 중복을 제거할 목적으로 자주 사용된다.

public class VacationPolicy {
  public void accrueUSDDivisionVacation() {
    // 지금까지 근무한 시간을 바탕으로 휴가 일수를 계산하는 코드
    // ...
    // 휴가 일수가 미국 최소 법정 일수를 만족하는지 확인하는 코드
    // ...
    // 휴가 일수를 급여 대장에 적용하는 코드
    // ...
  }
  
  public void accrueEUDivisionVacation() {
    // 지금까지 근무한 시간을 바탕으로 휴가 일수를 계산하는 코드
    // ...
    // 휴가 일수가 유럽연합 최소 법정 일수를 만족하는지 확인하는 코드
    // ...
    // 휴가 일수를 급여 대장에 적용하는 코드
    // ...
  }
}

국가마다 법정 일수를 계산하는 코드를 제외하고는 나머지 코드는 동일하다.

따라서 템플릿 메서드 패턴을 적용해 중복을 제거한다.

abstract public class VacationPolicy {
  public void accrueVacation() {
    caculateBseVacationHours();
    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() {
    // 유럽연합 최소 법정 일수를 사용한다.
  }
}

하위 클래스는 accrueVacation 알고리즘에서 빠진 내용을 채운다.

표현하라

프로젝트 비용 중 대다수는 장기적인 유지보수에 들어간다.

개발자가 코드를 명백하게 짤수록 다른 사람이 그 코드를 이해하기 쉬워진다.

다음과 같은 방법으로 표현을 더욱 명확히 할 수 있다.

  1. 좋은 이름을 선택한다.
  2. 함수와 클래스 크기를 가능한 줄인다.
  3. 표준 명칭을 사용한다. 디자인 패턴같은 표준 패턴을 사용했다면 클래스 이름에 넣어 의도를 표현한다.
  4. 단위 테스트 케이스를 꼼꼼히 작성한다.

무엇보다 나중에 읽을 사람을 고려해 조금이라도 읽기 쉽게 만들려는 노력이 필요하다.

무엇보다 나중에 읽을 사람이 내가 될 확률이 가장 높다.

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

중복 제거, 의도 표현, SRP 준수 같은 기본적 개념도 극단으로 치달으면 득보다 실이 많아진다.

무조건적인 구현 표준보단 그때에 맞는 실용적인 방식을 택한다.

 

함수와 클래스 크기를 작게 유지하면서 동시에 시스템 크기도 작게 유지하는 것이 중요하다.

이 규칙은 4개 중 우선순위가 가장 낮다.

즉, 테스트 케이스를 잘 작성하고 중복 제거, 의도 표현 작업이 더 중요하다. 

댓글