티스토리 뷰
메서드 정리
리팩토링의 주된 작업은 코드를 포장하는 메서드를 적절히 정리하는 것입니다. 대부분의 문제점은 장황한 메서드로 인해 발생합니다.
이 장황한 메서드에는 많은 정보가 들어가 있고, 복잡한 로직에 의해 우리들이 알아야하는 정보들이 묻혀버립니다. 핵심적인 리팩토링 기법은 이러한 장황한 메서드를 적절한 메서드 추출 기법과 메서드 내용 직접 삽입, 임시변수를 메서드 호출로 전환등의 기법을 통해 정리를 해야합니다. 지금부터 어떤 방법이 있는지 어떻게 적용할 수 있는지 예제를 통해 살펴보겠습니다.
메서드 추출
메서드 추출 기법은 장황한 메서드를 쪼개는 기법입니다. 메서드를 쪼개어 기능을 분리하고 명확한 메서드명을 짓는것입니다.
메서드 추출 기법을 통해 반환해야하는 변수가 2가지 이상인 경우 객체를 사용하여 반환을 할 수 있고, 각기 다른 값을 하나씩 반환하는 여러 개의 메서드를 만드는 방법을 사용할 수도 있습니다. 책의 저자는 하나씩 반환하는 메서드를 여러개를 만드는 것을 좋아한다고 합니다.
💡 직관적인 이름의 메서드명이 좋은 이유는?
- 메서드를 적절히 잘게 쪼개면 다른 메서드에서 이를 쉽게 사용할 수 있습니다.
- 재정의 하기 쉬워집니다.
💡 예제 코드
아래는 정말 간단한 예시 코드입니다. Order 클래스의 print 메서드에서는 상품의 전체 가격을 계산하고 마일리지에서 차감 후 사용자의 총 금액을 나타내고 있습니다. 이를 메서드 추출 기법과, 임시변수를 메서드 호출로 전환 등의 기법을 통해 수정해보겠습니다.
@Getter
public class Product {
private final String name;
private final int price;
public Product(String name, int price) {
this.name = name;
this.price = price;
}
}
public class Order {
private String name;
private List<Product> products;
public void print(int mileage) {
int totalPrice = 0;
for (Product product : products) {
totalPrice += product.getPrice();
}
totalPrice -= mileage;
System.out.println("고객명 : " + name);
System.out.println("구매한 상품의 총 금액 : " + totalPrice);
}
}
💡 리팩토링된 코드
일단 메서드 추출 기법을 통해 접근 제한자의 범위를 private로 한정 지었습니다. 또한 totalPrice 메서드를 통해 구매한 상품의 전체 가격을 알 수 있는 메서드로 분리했으며, 해당 메서드를 지역 변수에 대입한다면 메서드 호출 기법이며, 지역 변수를 굳이 선언하지 않을 수도 있습니다.
public class Order {
private String name;
private List<Product> products;
public void print(int mileage) {
// case 1: 지역 변수 사용
int totalPrice = totalPrice(mileage); // 지역 변수에 메서드 호출 대입
printDetails(totalPrice);
// case 2: 지역 변수 미사용
printDetails(totalPrice(mileage));
}
private int totalPrice(int mileage) {
int result = 0;
for (Product product : products) {
result += product.getPrice();
}
return result - mileage;
}
private void printDetails(int totalPrice) {
System.out.println("고객명 : " + name);
System.out.println("구매한 상품의 총 금액 : " + totalPrice);
}
}
메서드 내용 직접 삽입
메서드 내용이 너무 단순한 경우 해당 메서드를 호출하는 메서드에 내용을 넣어버리고 기존 메서드는 삭제하는 방법입니다.
🤔 동기
리팩토링의 핵심은 의도한 기능을 한눈에 파악할 수 있도록 직관적인 메서드명과 메서드를 간결하게 만드는 것입니다. 우리는 코드를 더 알아보기 쉽게 하기위해 마구잡이로 쪼개다보면 불필요한 인다이렉션이 많아질 수 있습니다. 이는 오히려 함께 하는 팀원에게 혼란을 줄 수 있으므로 그렇기 때문에 뭐든 적절히 나누어야 합니다.
💡 예제 코드
위의 예제에서 printDetails 메서드의 경우 내용이 단순함으로 메서드 내용 직접 삽입 기법을 적용할 수 있습니다.
public class Order {
private String name;
private List<Product> products;
public void print(int mileage) {
System.out.println("고객명 : " + name);
System.out.println("구매한 상품의 총 금액 : " + totalPrice(mileage));
}
private int totalPrice(int mileage) {
int result = 0;
for (Product product : products) {
result += product.getPrice();
}
return result - mileage;
}
}
임시변수 내용 직접 삽입
임시변수 내용 직접 삽입 기법은 임시변수를 메서드 호출로 전환 기법을 사용하면서 많이 발생할 수 있는 경우입니다.
🤔 동기
임시변수 내용 직접 삽입 기법이 순수하게 사용되는 경우는 오직 메서드 호출의 결과값이 임시변수에 대입될 때뿐입니다. 이러한 임시변수는 별다른 문제가 없으면 내버려둬도 되지만 다른 리팩토링 기법에 방해가 된다면 임시변수를 제거하고 임시변수 내용 직접 삽입을 사용해야 합니다.
💡 예제 코드
public class Order {
private String name;
private List<Product> products;
public void print(int mileage) {
System.out.println("고객명 : " + name);
System.out.println("구매한 상품의 총 금액 : " + totalPrice(mileage)); // 임시변수 내용 직접 삽입 기법 적용
}
private int totalPrice(int mileage) {
int result = 0;
for (Product product : products) {
result += product.getPrice();
}
return result - mileage;
}
}
임시변수를 메서드 호출로 전환
어떠한 결과값을 저장하는 임시변수가 있을 경우 결과를 저장하는 로직을 메서드 추출 기법을 통해 만든 후 임시변수에 값을 대입하는 것입니다.
🤔 동기
임시변수는 일시적이고 해당 메서드에서면 사용이 가능하므로 제한이 되는 단점이 있습니다. 이를 메서드 호출로 수정하면 클래스안에 있는 모든 메서드에서 그 정보에 접근할 수 있습니다. 이렇게 하면 코드가 깔끔해집니다.
또한 임시변수를 메서드 호출로 전환을 적용해야 하는 가장 간단한 상황은 임시변수에 값이 한번만 대입되고 수식이 간단한 경우입니다.
💡 예제 코드
public class Product {
private String name;
private int price;
private int quantity;
public double getPrice() {
int basePrice = quantity * price;
double discountFactor;
if (basePrice > 1000) discountFactor = 0.95;
else discountFactor = 0.98;
return basePrice * discountFactor;
}
}
💡 리팩토링된 코드
public class Product {
private String name;
private int price;
private int quantity;
public double getPrice() {
return basePrice() * discountFactor();
}
private int basePrice() {
return quantity * price;
}
private double discountFactor() {
if (basePrice() > 1000) return 0.95;
return 0.98;
}
}
직관적 임시변수 사용
🤔 동기
수식을 표현할 때 복잡하면 코드를 읽는 사람은 이해하기 힘듭니다. 이때 임시변수를 사용하면 가독성을 높일 수 있습니다. 다만 임시변수를 생각없이 남용한다면 관련 없는 임시변수들로 인해 메서드가 복잡해지고 코드를 읽는 사람은 지옥에 빠질 수 있습니다.
하지만 직관적 임시변수 사용 기법을 적용해야겠다는 생각이 들더라도 일단 자제하고 메서드 추출 기법을 사용하려고 노력해야 한다고 합니다. 그 이유는 앞서 말한거처럼 메서드 추출 기법을 사용하면 사용할 수 있는 범위가 넓어지고 다른 객체에서도 사용할 수 있기 때문입니다.
💡 예제 코드
public void method() {
if (platform.toUpperCase().indexOf("MAC") > -1 && browser.toUpperCase().indexOf("IE") > -1) {
// 추가 로직
}
boolean isMacOs = platform.toUpperCase().indexOf("MAC") > -1;
boolean isIEBrowser = platform.toUpperCase().indexOf("IE") > -1;
if (isMacOs && isIEBrowser) {
// 추가 로직
}
}
임시변수 분리
임시변수에 여러 값이 대입되는 경우 각 대입마다 다른 임시변수를 사용하는게 좋습니다.
🤔 동기
임시변수에 값이 여러번 대입되는 경우가 있습니다. 만약 값이 두 번 이상 대인된다는 건 해당 임시변수가 메서드 안에서 여러 용도(역할)로 사용된다는 것을 알 수 있습니다. 여러 용도로 사용되는 변수는 각 용도마다 다른 임시변수를 사용해야 합니다. 하나의 임시변수를 가지고 두 가지 이상의 용도로 사용하게 되면 코드를 읽는 사람에게 혼동을 줄 수 있습니다.
💡 예제 코드
public void method() {
double temp = 2 * (height + width);
temp = height * width;
// 임시변수 분리를 통한 개선
double perimeter = 2 * (height + width);
double area = height * width;
}
매개변수로의 값 대입 제거
매개변수에 값을 대입하는 코드가 있을 경우 임시변수를 만들어 해당 매개변수를 대입하는게 좋습니다.
🤔 동기 및 예제 코드
매개변수로의 값 대입이란 정말 쉽게 표현하자면 매개변수로 전달받은 인자에 다시 값을 할당하는 것입니다. 전달받은 매개변수에 다른 객체 참조를 대입하면 코드의 명료성뿐 아니라 pass by value, pass by Reference를 혼동하게 될 수 있습니다. 자바는 pass by value만을 사용합니다. pass by value, pass by Reference는 구글링을 통해 자세히 알 수 있습니다.
// pass By Reference에 의한 문제 발생
public void method(Product product) {
product.method(); // 상관없음
product = anotherProduct; // 문제가 발생할 수 있음
}
// 매개변수에 값을 대입하여 문제 발생
public int method(int inputVal, int quantity, int yearToDate) {
if (inputVal > 50) inputVal -= 2;
if (quantity > 100) inputVal -= 1;
if (yearToDate > 10000) inputVal -= 4;
return inputVal;
}
💡 리팩토링된 코드
public int method(int inputVal, int quantity, int yearToDate) {
int result = inputVal; // 임시변수를 사용하여 매개변수로의 값 대입 제거
if (inputVal > 50) result -= 2;
if (quantity > 100) result -= 1;
if (yearToDate > 10000) result -= 4;
return result;
}
💡 pass by Reference
public class Main {
public static void main(String[] args) {
LocalDate d1 = LocalDate.of(2022, 12, 21);
update(d1);
System.out.println("update 메서드 실행 후 d1 : " + d1 + ", " + d1.hashCode()); // 2022-12-21, 4141845
LocalDate d2 = LocalDate.of(2022, 12, 21);
replace(d2);
System.out.println("replace 메서드 실행 후 d2: " + d2 + ", " + d2.hashCode()); // 2022-12-21, 4141845
}
private static void update(LocalDate localDate) {
localDate.plusDays(1L);
System.out.println("update 메서드의 date 값 : " + localDate + ", " + localDate.hashCode()); // 2022-12-21, 4141845
}
private static void replace(LocalDate localDate) {
localDate = LocalDate.of(localDate.getYear(), localDate.getMonth(), localDate.getDayOfMonth() + 1);
System.out.println("replace 메서드의 date 값 : " + localDate + ", " + localDate.hashCode()); // 2022-12-22, 4141846
}
}
- Total
- Today
- Yesterday
- transactional outbox pattern spring boot
- spring boot 엑셀 다운로드
- spring boot poi excel download
- java userThread와 DaemonThread
- redis 대기열 구현
- 서비스 기반 아키텍처
- service based architecture
- @ControllerAdvice
- transactional outbox pattern
- JDK Dynamic Proxy와 CGLIB의 차이
- java ThreadLocal
- redis sorted set
- spring boot redisson sorted set
- space based architecture
- 자바 백엔드 개발자 추천 도서
- redis sorted set으로 대기열 구현
- spring boot redisson destributed lock
- 공간 기반 아키텍처
- pipe and filter architecture
- microkernel architecture
- 트랜잭셔널 아웃박스 패턴 스프링부트
- polling publisher spring boot
- pipeline architecture
- spring boot redisson 분산락 구현
- spring boot redis 대기열 구현
- spring boot excel download paging
- spring boot excel download oom
- 트랜잭셔널 아웃박스 패턴 스프링 부트 예제
- 람다 표현식
- 레이어드 아키텍처란
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | ||||||
2 | 3 | 4 | 5 | 6 | 7 | 8 |
9 | 10 | 11 | 12 | 13 | 14 | 15 |
16 | 17 | 18 | 19 | 20 | 21 | 22 |
23 | 24 | 25 | 26 | 27 | 28 |