리팩토링
2019.09.30
Chapter 6. 메서드 정리
메서드 추출
어떤 코드를 그룹으로 묶어도 되겠다고 판단될 땐 그 코드를 빼내어 목적을 잘 나타내는 직관적 이름의 메서드로 만들자
void printOwing(double amount) {
printBanner();
System.out.println("name:" + name);
System.out.println("amount" + amount);
}
void printOwing(double amount) {
printBanner();
printDetails(amount);
}
void printDetails(double amount) {
System.out.println("name:" + name);
System.out.println("amount" + amount);
}
메서드 추출
메서드가 너무 길거나 코드에 주석을 달아야만 의도를 이해할 수 있을 때 사용
직관적인 이름의 간결한 메서드가 좋은 이유
- 메서드가 적절히 잘게 쪼개져 있으면 다른 메서드에서 쉽게 사용
- 상위 계층의 메서드에서 주석 같은 더 많은 정보를 읽어들일 수 있다
- 재정의 수월
메서드 추출
예제: 지역변수 사용 안함
void printOwing() {
Enumeration e = orders.elements();
double outstanding = 0.0;
// 배너 출력
System.out.println("*****************");
System.out.println("*****고객 외상*****");
System.out.println("*****************");
// 외상액 계산
while(e.hasMoreElements()) {
Order each = (Order) e.nextElement();
outstanding += each.getAmount();
}
// 세부 내역 출력
System.out.println("고객명: " + name);
System.out.println("외상액: " + outstanding);
}
메서드 추출
예제: 지역변수 사용 안함
void printOwing() {
Enumeration e = orders.elements();
double outstanding = 0.0;
printBanner();
// 외상액 계산
while(e.hasMoreElements()) {
Order each = (Order) e.nextElement();
outstanding += each.getAmount();
}
// 세부 내역 출력
System.out.println("고객명: " + name);
System.out.println("외상액: " + outstanding);
}
void printBanner() {
System.out.println("*****************");
System.out.println("*****고객 외상*****");
System.out.println("*****************");
}
메서드 추출
예제: 지역변수 사용
void printOwing() {
Enumeration e = orders.elements();
double outstanding = 0.0;
printBanner();
// 외상액 계산
while(e.hasMoreElements()) {
Order each = (Order) e.nextElement();
outstanding += each.getAmount();
}
// 세부 내역 출력
System.out.println("고객명: " + name);
System.out.println("외상액: " + outstanding);
}
메서드 추출
예제: 지역변수 사용
void printOwing() {
Enumeration e = orders.elements();
double outstanding = 0.0;
printBanner();
// 외상액 계산
while(e.hasMoreElements()) {
Order each = (Order) e.nextElement();
outstanding += each.getAmount();
}
printDetails(outstanding);
}
void printDetails(double outstanding) {
System.out.println("고객명: " + name);
System.out.println("외상액: " + outstanding);
}
메서드 추출
예제: 지역변수를 다시 대입
void printOwing() {
Enumeration e = orders.elements();
double outstanding = 0.0;
printBanner();
// 외상액 계산
while(e.hasMoreElements()) {
Order each = (Order) e.nextElement();
outstanding += each.getAmount();
}
printDetails(outstanding);
}
메서드 추출
예제: 지역변수를 다시 대입
void printOwing() {
printBanner();
double outstanding = getOutstanding();
printDetails(outstanding);
}
double getOutstanding() {
Enumeration e = orders.elements();
double result = 0.0;
while(e.hasMoreElements()) {
Order each = (Order) e.nextElement();
result += each.getAmount();
}
return result;
}
메서드 추출
예제: 지역변수를 다시 대입
void printOwing(double previousAmount) {
Enumeration e = orders.elements();
double outstanding = previousAmount * 1.2;
printBanner();
while(e.hasMoreElements()) {
Order each = (Order) e.nextElement();
outstanding += each.getAmount();
}
printDetails(outstanding);
}
지역 변수에 연산이 있는 경우
메서드 추출
예제: 지역변수를 다시 대입
void printOwing(double previousAmount) {
double outstanding = previousAmount * 1.2;
printBanner();
outstanding = getOutstanding(outstanding);
printDetails(outstanding);
}
double getOutstanding(double initialValue) {
double result = initialValue;
Enumeration e = orders.elements();
while(e.hasMoreElements()) {
Order each = (Order) e.nextElement();
result += each.getAmount();
}
return result;
}
지역 변수에 연산이 있는 경우
메서드 추출
예제: 지역변수를 다시 대입
void printOwing(double previousAmount) {
printBanner();
double outstanding = getOutstanding(previousAmount * 1.2);
printDetails(outstanding);
}
지역 변수에 연산이 있는 경우
메서드 추출
변수를 두 개 이상 반환해야하는 경우
- 각기 다른 값을 하나씩 반환하는 여러 개의 메서드를 만든다
- 출력 매개변수 사용(비추)
임시변수가 너무 많은 경우 추출이 어려움
- 임시변수를 메서드 호출로 전환 기법을 실시해서 임시변수의 수를 줄인다
- 어떤 방법을 사용해도 메서드 추출이 어렵다면 메서드를 메서드 객체로 전환 기법 실시
메서드 내용 직접 삽입
메서드 기능이 너무 단순해서 메서드명만 봐도 너무 뻔할 땐 그 메서드 기능을 호출하는 메서드에 넣어버리고 그 메서드는 삭제하자
int getRating() {
return (moreThanFiveLateDeliveries()) ? 2 : 1;
}
boolean moreThanFiveLateDeliveries() {
return numberOfLateDeliveries > 5;
}
int getRating() {
return (numberOfLateDeliveries > 5) ? 2 : 1;
}
메서드 내용 직접 삽입
과도한 인다이렉션과 동시에 모든 메서드가 다른 메서드에 단순히 위임을 하고 있어서 코드가 지나치게 복잡할 땐 주로 메서드 내용 직접 삽입을 실시한다
임시변수 내용 직접 삽입
간단한 수식을 대입받는 임시변수로 인해 다른 리팩토링 기법 적용이 힘들 땐 그 임시변수를 참조하는 부분을 전부 수식으로 치환하자
double basePrice = anOrder.basePrice();
return (basePrice > 1000);
return (anOrder.basePrice() > 1000);
임시변수 내용 직접 삽입
- 임시변수를 메서드 호출로 전환 기법을 실시하는 도중에 병용하게 되는 경우가 태반
- 임시변수가 메서드 추출등 다른 리팩토링에 방해가 된다면 적용
double basePrice = anOrder.basePrice();
return (basePrice > 1000);
return (anOrder.basePrice() > 1000);
임시변수를 메서드 호출로 전환
수식의 결과를 저장하는 임시변수가 있을 땐 그 수식을 빼내어 메서드로 만든 후, 임시변수 참조 부분을 전부 수식으로 교체하자.
새로 만든 메서드는 다른 메서드에서도 호출 가능하다.
double basePrice = quantity * itemPrice;
if(basePrice > 1000)
return basePrice * 0.95;
else
return basePrice * 0.98;
if(basePrice() > 1000)
return basePrice() * 0.95;
else
return basePrice() * 0.98;
...
double basePrice() {
return quantity * itemPrice;
}
임시변수를 메서드 호출로 전환
- 클래스 안 모든 메서드가 그 정보에 접근 가능, 코드가 깔끔해짐
- 메서드 추출을 적용하기 전에 반드시 적용
임시변수를 메서드 호출로 전환
예제
double getPrice() {
int basePrice = quantity * itemPrice;
double discountFactor;
if(basePrice > 1000) discountFactor = 0.95;
else discountFactor = 0.98;
return basePrice * discountFactor;
}
임시변수를 메서드 호출로 전환
예제
double getPrice() {
final int basePrice = quantity * itemPrice;
final double discountFactor;
if(basePrice > 1000) discountFactor = 0.95;
else discountFactor = 0.98;
return basePrice * discountFactor;
}
임시변수를 메서드 호출로 전환
예제
double getPrice() {
final int basePrice = basePrice();
final double discountFactor;
if(basePrice > 1000) discountFactor = 0.95;
else discountFactor = 0.98;
return basePrice * discountFactor;
}
private int basePrice() {
quantity * itemPrice;
}
임시변수를 메서드 호출로 전환
예제
double getPrice() {
final int basePrice = basePrice();
final double discountFactor;
if(basePrice() > 1000) discountFactor = 0.95;
else discountFactor = 0.98;
return basePrice() * discountFactor;
}
임시변수를 메서드 호출로 전환
예제
double getPrice() {
final double discountFactor = discountFactor();
return basePrice() * discountFactor;
}
private double discountFactor() {
if(basePrice() > 1000) return 0.95;
else return 0.98;
}
임시변수를 메서드 호출로 전환
예제
double getPrice() {
return basePrice() * discountFactor();
}
직관적 임시변수 사용
사용된 수식이 복잡할 땐 수식의 결과나 수식의 일부분을 용도에 부합하는 직관적 이름의 임시변수에 대입하자
if((platform.toUpperCase().indexOf("MAX") > -1) &&
(browser.toUpperCase().indexOf("IE") > -1) &&
wasInitialized() && resize > 0)
{
// 기능 코드
}
final boolean isMacOs = platform.toUpperCase().indexOf("MAC") > -1;
final boolean isIEBrowser = browser.toUpperCase().indexOf("IE") > -1;
final boolean wasResized = resize > 0;
if(isMacOs && isIEBrowser && wasInitialized() && wasResized) {
// 기능 코드
}
직관적 임시변수 사용
- 조건문에서 각 조건 절을 가져와서 그 조건의 의미를 설명할 때 사용
- 메서드 추출을 사용하기 어려울 때 사용
직관적 임시변수 사용
예제
double price() {
// 결제액(price) = 총 구매액(base price) -
// 대량 구매 할인(quantity discount) + 배송비(shipping)
return quantity * itemPrice -
Math.max(0, quantity - 500) * itemPrice * 0.05 +
Math.min(quantity * itemPrice * 0.1, 100.0);
}
직관적 임시변수 사용
예제
double price() {
// 결제액(price) = 총 구매액(base price) -
// 대량 구매 할인(quantity discount) + 배송비(shipping)
final double basePrice = quantity * itemPrice;
return basePrice -
Math.max(0, quantity - 500) * itemPrice * 0.05 +
Math.min(basePrice * 0.1, 100.0);
}
직관적 임시변수 사용
예제
double price() {
// 결제액(price) = 총 구매액(base price) -
// 대량 구매 할인(quantity discount) + 배송비(shipping)
final double basePrice = quantity * itemPrice;
final double quantityDiscount = Math.max(0, quantity - 500) * itemPrice * 0.05;
return basePrice - quantityDiscount + Math.min(quantity * itemPrice * 0.1, 100.0);
}
직관적 임시변수 사용
예제
double price() {
// 결제액(price) = 총 구매액(base price) -
// 대량 구매 할인(quantity discount) + 배송비(shipping)
final double basePrice = quantity * itemPrice;
final double quantityDiscount = Math.max(0, quantity - 500) * itemPrice * 0.05;
final double shipping = Math.min(basePrice * 0.1, 100.0);
return basePrice - quantityDiscount + shipping;
}
직관적 임시변수 사용
예제: 메서드 추출
double price() {
// 결제액(price) = 총 구매액(base price) -
// 대량 구매 할인(quantity discount) + 배송비(shipping)
return quantity * itemPrice -
Math.max(0, quantity - 500) * itemPrice * 0.05 +
Math.min(quantity * itemPrice * 0.1, 100.0);
}
직관적 임시변수 사용
예제: 메서드 추출
double price() {
return basePrice() - quantityDiscount() + shipping();
}
private double basePrice() {
return quantity * itemPrice;
}
private double quantityDiscount() {
return Math.max(0, quantity - 500) * itemPrice * 0.05;
}
private double shipping() {
Math.min(basePrice() * 0.1, 100.0);
}
임시변수 분리
루프 변수나 값 누적용 임시변수가 아닌 임시변수에 여러 번 값이 대입될 땐 각 대입마다 다른 임시변수를 사용하자
double temp = 2 * (height + width);
System.out.println(temp);
temp = height * width;
System.out.println(temp);
final double perimeter = 2 * (height + width);
SYstem.out.println(perimeter);
final double area = height * width;
System.out.println(area);
임시변수 분리
임시변수 하나를 두 가지 용도로 사용하면 코드를 분석하는 사람에게 혼동을 줄 수 있다
임시변수 분리
예제
double getDistanceTravelled(int time) {
double result;
double acc = primaryForce / mass;
int primaryTime = Math.min(time, delay);
result = 0.5 * acc * primaryTime * primaryTime;
int secondaryTime = time - delay;
if(secondaryTime > 0) {
double primaryVel = acc * delay;
acc = (primaryForce + secondaryForce) / mass;
result += primaryVel * secondaryTime + 0.5 *
acc * secondaryTime * secondaryTime;
}
return result;
}
임시변수 분리
예제
double getDistanceTravelled(int time) {
double result;
final double primaryAcc = primaryForce / mass;
int primaryTime = Math.min(time, delay);
result = 0.5 * primaryAcc * primaryTime * primaryTime;
int secondaryTime = time - delay;
if(secondaryTime > 0) {
double primaryVel = primaryAcc * delay;
final double secondaryAcc = (primaryForce + secondaryForce) / mass;
result += primaryVel * secondaryTime + 0.5 *
secondaryAcc * secondaryTime * secondaryTime;
}
return result;
}
매개변수로의 값 대입 제거
매개변수로 값을 대입하는 코드가 있을 땐 매개변수 대신 임시변수를 사용하게 수정하자
int discount(int inputVal, int quantity, int yearToDate) {
if(inputVal > 50) inputVal -= 2;
}
int discount(int inputVal, int quantity, int yearToDate) {
int result = inputVal;
if(inputVal > 50) result -= 2;
}
매개변수로의 값 대입 제거
- 자바는 '값을 통한 전달'만 사용하므로 매개변수에 값을 대입해서는 안된다
매개변수로의 값 대입 제거
예제
int discount(int inputVal, int quantity, int yearToDate) {
if(inputVal > 50) inputVal -= 2;
if(quantity > 100) inputVal -= 1;
if(yearToDate > 1000) inputVal -= 4;
return inputVal;
}
매개변수로의 값 대입 제거
예제
int discount(final int inputVal, final int quantity, final int yearToDate) {
int result = inputVal;
if(inputVal > 50) result -= 2;
if(quantity > 100) result -= 1;
if(yearToDate > 1000) result -= 4;
return result;
}
메서드를 메서드 객체로 전환
지역변수 때문에 메서드 추출을 적용할 수 없는 긴 메서드가 있을 땐 그 메서드 자체를 객체로 전환해서 모든 지역변수를 객체의 필드로 만들자. 그런 다음 그 메서드를 개첵 안의 여러 메서드로 쪼개면 된다.
- 임시변수를 메서드 호출로 전환이 어려울 때 메서드 객체로 수정한다.
메서드를 메서드 객체로 전환
예제
Class Account {
int gamma(int inputVal, int quantity, int yearToDate) {
int importantValue1 = (inputVal * quantity) + delta();
int importantValue2 = (inputVal * yearToDate) + 100;
if((yearToDate - importantValue1) > 100)
importantValue2 -= 20;
int importantValue3 = importantValue2 * 7;
// 기타 작업
return importantValue3 - 2 * importantValue1;
}
}
메서드를 메서드 객체로 전환
예제
Class Gamma {
private final Account account;
private int inputVal;
private int quantity;
private int yearToDate;
private int importantValue1;
private int importantValue2;
private int importantValue3;
Gamma(Account source, int inputValArg, int quantityArg, int yearToDateArg) {
account = source;
inputVal = inputValArg;
quantity = quantityArg;
yearToDate = yearToDateArg;
}
int compute() {
importantValue1 = (inputVal * quantity) + account.delta();
importantValue2 = (inputVal * yearToDate) + 100;
if(yearToDate - importantValue1) > 100)
importantValue2 -= 20;
int importantValue3 = importantValue2 * 7;
// 기타 작업
return importantValue3 - 2 * importantValue1;
}
}
메서드를 메서드 객체로 전환
예제
int gamma(int inputVal, int quantity, int yearToDate) {
return new Gamma(this, inputVal, quantity, yearToDate).compute();
}
메서드를 메서드 객체로 전환
예제
int compute() {
importantValue1 = (inputVal * quantity) + account.delta()
importantValue2 = (inputVal * yearToDate) + 100;
importantThing();
int importantValue3 = importantValue2 * 7;
// 기타 작업
return importantValue3 - 2 * importantValue1;
}
void importantThing() {
if(yearToDate - importantValue1) > 100)
importantValue2 -= 20;
}
알고리즘 전환
알고리즘을 더 분명한 것으로 교체해야 할 땐 해당 메서드의 내용을 새 알고리즘으로 바꾸자
String foundPerson(String[] people) {
for(int i = 0; i < people.length; i++) {
if(people[i].equals("Don")) {
return "Don";
}
if(people[i].equals("John")) {
return "John";
}
if(people[i].equals("Kent")) {
return "Kent";
}
}
return "";
}
알고리즘 전환
String foundPerson(String[] people) {
List candidates = Arrays.asList(new String[] {"Don", "John", "Kent"});
for(int i = 0; i < people.length; i++)
if(candidates.contains(people[i]))
return people[i];
return "";
}
알고리즘 전환
- 어떤 기능을 수행하는데 더 간단하다면 더 간단한 방법으로 교체해야 한다
- 메서드를 최대한 잘게 쪼개야 수정 작업이 편해진다
리팩토링 - 메서드 정리
By Sungbin, Song
리팩토링 - 메서드 정리
- 118