본문 바로가기

책읽기/클린코드

창발성

창발(創發)또는 떠오름 현상은 하위 계층(구성 요소)에는 없는 특성이나 행동이 상위 계층(전체 구조)에서 자발적으로 돌연히 출현하는 현상이다.
또한 불시에 솟아나는 특성을 창발성 또는 이머전스라 한다.
- wikipedia

설계규칙 (중요도순)

1. 모든 테스트를 실행한다.

2. 중복을 없앤다.

3. 프로그래머 의도를 표현한다.

4. 클래스와 메서드 수를 최소로 줄인다.

 

1. 모든 테스트를 실행한다.

모든 테스트를 작성한다.

 

결합도가 높으면 테스트 케이스를 작성하기 어렵다.

모든 테스트를 작성하다 보면 테스트가 어려운 모듈이 존재할 것이고, 고칠 부분을 찾을 수 있다.

 

고칠 부분을 찾아서,

의존성 주입, 인터페이스, 추상화등을 이용하여 결합도를 낮추면 좋은 코드를 얻는다.

 

2. 리펙토링

나머지 2, 3, 4 규칙은 모두 리펙토링에 관한 것이다.

응집도를 높인다.

결합도를 낮춘다.

관심사를 분리한다.

시스템 관심사를 모듈로 나눈다.

함수와 클래스 크기를 줄인다.

이름으로 표현한다.

 

 

2-1. 중복을 없애라.

1) 똑같이 생긴 코드 :

당연히 중복이다. 없애면 된다.

비슷한 로직의 코드는 더 비슷하게 짜면 리펙토링이 쉽다.

 

2) 구현 중복

사이즈를 구하는 메서드가 있다고 쳐보자. (물론 이렇게 짜지는 않지만)

int size() {
	int count = 0;
	while (스택이 빌 때 까지) {
    	count++;
    }
    return count;
}

비었는지 아닌지를 구현하는 코드가 있다.

boolean isEmpty() {
    int count = 0;
	while (스택이 빌 때 까지) {
    	count++;
    }
    return 0 == count;
}

 

두 메서드가 공통적으로 구현하는 부분이 중복제거 대상이다.

중복을 제거하면, isEmpty 를 매우 간단하게 줄일 수 있다.

boolean isEmpty() {
    return 0 == size();
}

 

깔끔한 시스템을 만들려면 단 몇줄이라도 중복을 제거하겠다는 의지가 필요하다.

 

 

3) 공통 구현은 새로운 메서드로 추출

여러 메서드가 공통적인 로직을 포함한다면, 하나의 메서드로 추출하여 중복코드를 줄일 수 있다.

 

이 과정의 결과로 SRP (single response principle : 클래스를 수정할 이유는 하나여야 한다.) 를 지킬 기회가 생길 수 도 있다.

 

책의 예제에서는 공통 코드를 추출하니 그 공통코드가 SRP 에 맞지 않았고, 새로운 클래스를 만들어 이동시켜 객체지향적인 코드를 만들었다.

 

 

4) TEMPLATE METHOD 패턴

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() {
    // 유럽연합 최소 법정 일수를 사용한다.
  }
}

 

2-2. 표현하라

이 책의 초반부에서도 말했듯이, 개발자의 의도를 분명히 표현해야 한다.

 

1. 좋은 이름을 선택한다.
2. 함수와 클래스 크기를 최대한 줄인다. (클래스와 함수를 분리한다.)
3. 표준 명칭을 사용한다. (COMMON, VISTOR 같은 표준 패턴을 쓴다면 클래스이름에 표준 이름을 넣으면 이해하기 쉽다.)
4. 단위 테스트 케이스를 꼼꼼히 작성한다. 테스트는 이 일이 어떤 일을 하는지 한눈에 보기 쉽게 해준다.

 

이런 명시적인 방법도 좋지만 제일 중요한건 노력 이다.

 

 

 

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

모든 클래스마다 인터페이스를 만들라는 요구조건이 좋은 요구조건만은 아니다.

자료 클래스와 동작 클래스는 무조건 분리되어야 한다고 무조건 좋은것만은 아니다.

위의 두 방식은 코드의 양은 늘어날 수 있지만, 객체 지향적이고, 좋은 코드를 만들 수 도 있다.

하지만 그런 것들은 맹목적인 신앙같은것이 아니다.

 

독단적인 견해는 멀리하고, 실용적인 방식을 택하도록 한다.

하지만 이 규칙이 네가지중 가장 중요도가 낮은 만큼, 위의 규칙을 다 따르고 이 작업을 고려하도록 한다.

'책읽기 > 클린코드' 카테고리의 다른 글

Junit 들여다보기, SerialDate 리펙터링  (0) 2021.01.24
동시성  (0) 2021.01.22
시스템  (0) 2021.01.18
클래스  (0) 2021.01.15
단위 테스트  (0) 2021.01.14