티스토리 뷰

Java

[Java] 기본적인 리팩터링

snail voyager 2023. 12. 16. 18:55
728x90
반응형

함수 추출하기

'목적과 구현을 분리'하는 방식

함수의 목적이 눈에 확 들어오고, 본문 코드에 대해서는 더 이상 신경 쓸 일이 거의 없다.

함수를 짧으면 캐싱하기가 더 쉽기 때문에 컴파일러가 최적화하는 데 유리할 때가 많다.

#AS-IS
function printOwing(invoice) {
    printBanner();
    let outstanding = calculateOutstanding();
    
    console.log('고객명: ${invoice.customer}');
    console.log('채무액: ${outstanding}');
}

#TO-BE
function printOwing(invoice) {
    printBanner();
    let outstanding = calculateOutstanding();
    printDetails(outstanding);
    
    function printDetails(outstanding) {
    	console.log('고객명: ${invoice.customer}');
    	console.log('채무액: ${outstanding}');
    }
}

 

1. 함수를 새로 만들고 목적을 잘 드러내는 이름을 붙인다.

'어떻게'가 아닌 '무엇을' 하는지가 드러나야 한다.

2. 추출할 코드를 원본 함수에서 복사하여 새 함수에 붙여넣는다.

3. 추출한 코드 중 원본 함수의 지역 변수를 참조하거나 추출한 함수의 유효범위를 벗어나는 변수는 없는지 검사한다.

4. 변수를 다 처리했다면 컴파일한다.

5. 원본 함수에서 추출한 코드 부분을 새로 만든 함수를 호출하는 문장으로 바꾼다.

6. 테스트한다.

7. 다른 코드에서 방금 추출한 것과 똑같거나 비슷한 코드가 없는지 살핀다.

있다면 방금 추출한 새 함수를 호출하도록 바꿀지 검토한다.

함수 인라인하기

때로는 함수 본문이 이름만큼 명확한 경우도 있다.

간접 호출은 유용할 수도 있지만 쓸데없는 간접 호출은 거슬릴 뿐이다.

//AS-IS
function getRating(driver) {
	return moreThanFiveLateDeliveries(driver) ? 2 : 1;
}
function getRating(driver) {
	return driver.numberOfLateDeliveries > 5;
}

//TO-BE
function getRating(driver) {
	return driver.numberOfLateDeliveries > 5 ? 2 : 1;
}

 

1. 다형 메서드인지 확인한다.

2. 인라인할 함수를 호출하는 곳을 모두 찾는다.

3. 각 호출문을 함수 본문으로 교체한다.

4. 하나씩 교체할 때마다 테스트한다.

5. 함수 정의를 삭제한다.

변수 추출하기

표현식이 너무 복잡해서 이해하기 어려울 때 지역 변수를 활용하면 표현식을 쪼개 관리하기 더 쉽게 만들 수 있다.

디버거에 중단점을 지정하거나 상태를 출하는 문장을 추가해서 디버깅에 도움된다.

//AS-IS
return order.quantity * order.itemPrice - 
    Math.max(0, order.quantity - 500) * order.itemPrice * 0.05 +
    Math.min(order.quantity * order.itemPrice * 0.1, 100);
    
//TO-BE
const basePrice = order.quantity * order.itemPrice;
const quantityDiscount = Math.max(0, order.quantity - 500) * order.itemPrice * 0.05;
const shipping = Math.min(order.quantity * order.itemPrice * 0.1, 100);
return basePrice - quantityDiscount + shipping;

 

1. 추출하려는 표현식에 부작용은 없는지 확인한다.

2. 불변 변수를 하나 선언하고 이름을 붙일 표현식의 복제본을 대입한다.

3. 원본 표현식을 새로 만든 변수로 교체한다.

4. 테스트한다.

5. 표현식을 여러 곳에서 사용한다면 각각을 새로 만든 변수로 교체한다. 하나 교체할 때마다 테스트한다.

변수 인라인하기

//AS-IS
let basePrice = anOrder.basePrice;
return (basePrice > 1000);

//To-BE
return anOrder.basePrice > 1000;

 

1. 대입문의 우변에서 부작용이 생기지는 않는지 확인한다.

2. 변수가 불변으로 선언되지 않았다면 불변으로 만든 후 테스트한다.

3. 이 변수를 가장 처음 사용하는 코드를 찾아서 대입문 우변의 코드로 바꾼다.

4. 테스트한다.

5. 변수를 사용하는 부분을 모두 교체할 때까지 반복한다.

6. 변수 선언문과 대입문을 지운다.

7. 테스트한다.

함수 선언 바꾸기 (함수 이름 바꾸기, 시그니처 바꾸기)

간단한 절차

1. 매개변수를 제거하려거든 먼저 함수 본문에서 제거 대상 매개변수를 참조하는 곳은 없는지 확인한다.

2. 메서드 선언을 원하는 형태로 바꾼다.

3. 기존 메서드 선언을 참조하는 부분을 모두 찾아서 바뀐 형태로 수정한다.

4. 테스트한다.

 

마이그레이션 절차

1. 이어지는 추출 단계를 수월하게 만들어야 한다면 함수의 본문을 적절히 리팩터링한다.

2. 함수 본문을 새로운 함수로 추출한다.

3. 추출한 함수에 매개변수를 추가해야 한다면 '간단한 절차'를 따라 추가한다.

4. 테스트한다.

5. 기존 함수를 인라인 한다. (기존 함수를 호출하는 부분이 새 함수를 호출하도록 변경)

6. 이름을 임시로 붙여뒀다면 함수 선언 바꾸기를 한번 더 적용해서 원래 이름으로 되돌린다.

7. 테스트한다.

//AS-IS
function circum(radius) {
	return 2 * Math.PI * radius;
}

//TO-BE
function circum(radius) {
	return circumference(radius);
}
function circumference(radius) {
	return 2 * Math.PI * radius;
}

변수 캡슐화하기

데이터 캡슐화는 데이터를 변경하고 사용하는 코드를 감시할 수 있는 확실한 통로가 되어주기 때문에

데이터 변경 전 검증이나 변경 후 추가 로직을 쉽게 끼워 넣을 수 있다.

데이터의 유효 범위가 넓을수록 캡슐화해야 데이터에 대한 결합도가 높아지는 일을 막을 수 있다.

 

1. 변수로의 접근과 갱신을 전담하는 캡슐화 함수들을 만든다.

2. 정적 검사를 수행한다.

3. 변수를 직접 참조하던 부분을 모두 적절한 캡슐화 함수 호출로 바꾼다.

4. 변수의 접근 범위를 제한한다.

5. 테스트한다.

6. 변수 값이 레코드라면 레코드 캡슐화하기를 적용할지 고려해본다.

매개변수 객체 만들기

데이터 뭉치를 데이터 구조로 묶으면 데이터 사이의 관계가 명확해진다는 이점을 얻는다.

함수가 이 데이터 구조를 받게 하면 매개변수 수가 줄어든다.

 

1. 적당한 데이터 구조가 아직 마련되어 있지 않다면 새로 만든다.

2. 테스트한다.

3. 함수 선언 바꾸기로 새 데이터 구조를 매개변수로 추가한다.

4. 테스트한다.

5. 함수 호출 시 새로운 데이터 구조 인스턴스를 넘기도록 수정한다.

6. 기존 매개변수를 사용하던 코드를 새 데이터 구조의 원소를 사용하도록 바꾼다.

7. 기존 매개변수를 제거하고 테스트한다.

여러 함수를 클래스로 묶기

공통 데이터를 중심으로 긴밀하게 엮여 작동하는 함수 무리를 클래스 하나로 묶는다.

이 함수들이 공유하는 공통 환경을 더 명확하게 표현할 수 있고,

각 함수에 전달되는 인수를 줄여서 객체 안에서의 함수 호출을 간결하게 만들 수 있다.

이런 객체를 시스템의 다른 부분에 전달하기 위한 참조를 제공할 수 있다.

 

1. 함수들이 공유하는 공통 데이터 레코드를 캡슐화한다.

2. 공통 레코드를 사용하는 함수 각각을 새 클래스로 옮긴다.

3. 데이터를 조작하는 로직들은 함수로 추출해서 새 클래스로 옮긴다.

여러 함수를 변환 함수로 묶기

데이터를 입력받아서 여러 가지 정보를 도출하는 로직이 반복되기도 한다.

이런 도출 작업들을 한데로 모아두면 검색과 갱신을 일관된 장소에서 처리할 수 있고 로직 중복도 막을 수 있다.

변환함수는 원본 데이터를 입력받아서 필요한 정보를 모두 도출한 뒤, 각각을 출력 데이터의 필드에 넣어 반환한다.

 

1. 변환할 레코드를 입력받아서 값을 그대로 반환하는 변환 함수를 만든다.

2. 묶을 함수 중 함수 하나를 골라서 본문 코드를 변환 함수로 옮기고, 처리 결과를 레코드에서 새 필드로 기록한다.

그런 다음 클라이언트 코드가 이 필드를 사용하도록 수정한다.

3. 테스트한다.

4. 나머지 관련 함수도 위 과정에 따라 처리한다.

본질은 같고 부가 정보만 덧붙이는 변환 함수의 이름을 "enrich"라 하고,
형태가 변할 때만 "transform"이라는 이름을 쓴다

단계 쪼개기

두 대상을 한꺼번에 다루는 코드를 발견하면 각각을 별개 모듈로 나눈다.

코드를 수정할 때 두 대상을 동시에 생각할 필요 없이 하나에만 집중하기 위해서다.

이렇게 분리하는 가장 간편한 방법은 동작을 연이은 두 단계로 쪼개는 것이다.

 

1. 두 번째 단계에 해당하는 코드를 독립 함수로 추출한다.

2. 테스트한다.

3. 중간 데이터 구조를 만들어서 앞에서 추출한 함수의 인수로 추가한다.

4. 테스트한다.

5. 추출한 두 번째 함수의 매개변수를 하나씩 검토한다.

그중 첫번째 단계에서 사용되는 것은 중간 데이터 구조로 옮긴다.

6. 첫번째 단계 코드를 추출하면서 중간 데이터 구조를 반환하도록 만든다.

 

728x90
반응형

'Java' 카테고리의 다른 글

[Java] CPU 와 ThreadPool Size 결정 방법  (0) 2024.01.29
[Java] Enum Singleton Pattern  (0) 2023.12.27
[Java] 리팩터링 테스트  (0) 2023.12.16
[Java] 리팩터링 원칙  (0) 2023.12.03
[Java] CompletableFuture  (1) 2023.11.27
반응형
300x250