Compose Method 패턴

베루스·2022년 10월 18일
1

패턴을 활용한 리팩터링 을 읽다가 Compose Method 패턴에 대한 내용을 봤다. Compose Method 패턴은 책에서 간단하게 아래와 같이 설명되어 있다.

어떤 메서드의 내부 로직이 한 눈에 이해하기 어렵다면, 그 로직을 의도가 잘 드러나며 동등한 수준의 작업을 하는 여러 단계로 나눈다.

즉, 복잡한 내부 구현을 의도가 잘 드러내는 이름을 가진 메서드로 추출하는 리팩터링을 통해 복잡성을 낮추는 패턴이라고 생각할 수 있을 것 같다. 책에서는 Compose Method 패턴을 적용하는 절차를 적어놓았는데, 클린 코드의 3장. 함수의 내용과 비슷한 부분이 많아서 두 책의 내용을 비교해보려 한다.

패턴을 활용한 리팩터링 vs 클린 코드

패턴을 활용한 리팩터링

아래의 내용은 패턴을 활용한 리팩터링에서는 P.182 ~ 183을 참고했다. Compose Method 패턴을 적용하기 위한 주요 절차는 5가지인데

1. 작게 만든다.
코드가 10줄을 넘어가지 않도록 만든다. 역자의 주석도 참고하면 코드의 줄수에 기준을 둘게 아니라, 메서드가 단순해졌는지를 기준으로 봐야된다고 한다.

2. 사용되지 않거나 중복된 코드를 제거한다.
중복된 코드를 제거함으로써 메서드 내부의 코드량을 줄일 수 있고, 메서드를 단순하게 만들 수 있다. 경우에 따라 중복된 코드가 한눈에 드러나지 않아 유심히 살펴봐야 발견할 경우도 있다.

3. 코드의 의도가 잘 드러나도록 한다.
변수, 메서드, 파라미터의 이름을 적절히 지어 목적을 잘 표현하도록 만들어야 한다.

4. 단순화한다.
코드를 최대한 단순하게 변경하고, 기존의 코드와 비교해 장단점을 찾아보도록 한다. 그리고, 다른 대안이 있다면 시험해봐야 한다.

5. 동등한 수준으로 단계를 나눈다.
메서드가 여러 단계로 나누어진다면, 각 단계가 동등한 수준으로 만든다. 동등하지 않은 수준의 단계들이 있다면, 세부 로직을 메서드 추출을 통해 동등한 수준으로 맞춰야 한다.

클린 코드

아래의 내용은 클린 코드의 3장 함수 내용을 참고했다. 위의 내용과 겹쳐지는 부분이 굉장히 많다.

작게 만들어라!
함수를 작게 만들어야 한다. 하지만, 책에서는 해당 규칙에 대한 명확한 근거를 대지는 않고 있다. 저자가 오랜 시행착오를 통한 경험을 바탕으로 작은 함수가 좋았다고 얘기하고 있다. 개인적으로 함수가 작아질수록 단순한 구조를 가질 수 있는 장점을 가지고 있는 것 같다.

한 가지만 해라!

함수는 한 가지를 해야 한다. 그 한 가지를 잘 해야 한다. 그 한 가지만을 해야 한다.

클린 코드에서 함수가 한 가지만 하는지 판별하는 방법을 소개하고 있다. 함수 내부가 추상화 수준이 하나인 단계만 수행하고 있다면 한 가지만 한다고 생각할 수 있다. 또 다른 방법으로는 함수 내부를 의미 있는 이름을 가지는 함수로 추출할 수 있다면 해당 함수는 여러 작업을 한다고 판단할 수 있다.

함수 당 추상화 수준은 하나로!
위에서 언급했지만 함수가 확실하게 한 가지만 하려면 함수 내부가 동일한 추상화 수준을 유지해야한다. 추상화 수준이 섞여 있으면 근본 개념인지 세부 사항인지 구분하기 어려워 코드를 읽는 사람이 헷갈릴 수 있다. 책에서는 추상화 수즌을 하나로 하는 핵심은 한 가지만 하는 함수를 작성하기 위해서라고 한다.

서술적인 이름을 사용하라!
함수의 이름을 의도가 분면한 서술적인 이름을 사용할 경우, 설계가 뚜렷해지고 코드를 개선하기 쉬워진다. 책에서는 함수가 작고 단순할수록 서술적인 이름을 선택하기 쉬워진다고 한다.

반복하지 마라!
코드의 중복은 수정할 경우 변경점이 여러개가 되는 문제가 있다. 책에서는 중복은 악의 근원이라고 말하고 있다. 중복을 제거하고 나면 가독성도 좋아질 수 있다고 말하고 있다.

비교

클린 코드에는 함수와 관련해 이것외에도 많은 내용이 담겨 있지만, 패턴을 활용한 리팩터링과 겹쳐지지 않는 내용은 생략했다. 두 책에서 설명하는 주요 내용은 대부분 동일했다. 개인적으로 두 내용의 핵심은 메서드의 구조를 단순화하여 타인이 코드를 읽을 때 쉽게 로직을 파악할 수 있도록 하는 것 같다. 메서드를 작게 만들고, 추상화 수준이 동일하며, 한 가지만 하게 만드는 절차를 통해 단순하고 손쉽게 로직을 파악할 수 있는 메서드를 만들 수 있는 것 같다.

Compose Method 패턴 적용 예제

마지막으로, 패턴을 활용한 리팩터링에 나온 Compose Method 패턴의 예제를 살펴보고 글을 마치려 한다.

public void add(Object element) {
	if (!readOnly) {
    	int newSize = size + 1;
        if (newSize > elements.length) {
        	Object[] newElements = new Object[elements.length + 10];
            for (int i = 0; i < size; i++) {
            	newElements[i] = elements[i];
            }
            elements = newElements;
        }
        elements[size++] = element;
    }
}

위의 코드는 패턴을 활용한 리팩터링에서 나오는 예제이다. 책에 설명된 순서대로 리팩터링을 진행해보려 한다. 우선 if문의 조건절을 긍정문으로 변경하고 ealry return 하도록 리팩토링한다.

public void add(Object element) {
	if (readOnly) {
    	return;
    }
    int newSize = size + 1;
    if (newSize > elements.length) {
	    Object[] newElements = new Object[elements.length + 10];
    	for (int i = 0; i < size; i++) {
		    newElements[i] = elements[i];
	    }
    	elements = newElements;
    }
    elements[size++] = element;
}

다음으로 elements의 크기를 늘리기 위해 사용하는 매직 넘버 10을 상수로 변경한다.

private final static int GROWTH_INCREMENT = 10;

public void add(Object element) {
	if (readOnly) {
    	return;
    }
    int newSize = size + 1;
    if (newSize > elements.length) {
	    Object[] newElements = new Object[elements.length + GROWTH_INCREMENT];
    	for (int i = 0; i < size; i++) {
		    newElements[i] = elements[i];
	    }
    	elements = newElements;
    }
    elements[size++] = element;
}

배열의 크기를 늘릴 필요가 있는지 확인하는 표현식을 메서드 추출한다.

public void add(Object element) {
	if (readOnly) {
    	return;
    }
    if (atCapacity()) {
	    Object[] newElements = new Object[elements.length + GROWTH_INCREMENT];
    	for (int i = 0; i < size; i++) {
		    newElements[i] = elements[i];
	    }
    	elements = newElements;
    }
    elements[size++] = element;
}

private boolean atCapacity() {
	return size + 1 > elements.length;
}

배열의 크기를 증가시키는 부분을 메서드 추출한다.

public void add(Object element) {
	if (readOnly) {
    	return;
    }
    if (atCapacity()) {
	    grow();
    }
    elements[size++] = element;
}

private void grow() {
	Object[] newElements = new Object[elements.length + GROWTH_INCREMENT];
    for (int i = 0; i < size; i++) {
    	newElements[i] = elements[i];
   	}
    elements = newElements;
}

현재 코드에서 elements[size++] = element 구문이 다른 부분과 추상화 수준이 다르다. 추상화 수준을 맞춰주기 위해서 메서드를 추출한다.

public void add(Object element) {
	if (readOnly) {
    	return;
    }
    if (atCapacity()) {
	    grow();
    }
	addElement(element);
}

private void addElement(Object element) {
	elements[size++] = element;
}

간단한 예제를 통해서 Compose Method 패턴을 적용하는 과정을 살펴보았다. 함수를 작게 만들고, 추상화 수준을 동일하게 하여 적절한 이름을 가지도록 하니 확실히 리팩터링 전과 비교했을 때 가독성이 크게 향상된 것을 느낄 수 있다. 복잡한 구조를 가진 함수가 있을 때 책에서 설명한 함수의 조건들을 생각하고 리팩터링하면 좋을 것 같다.

0개의 댓글