https://www.yes24.com/Product/Goods/89649360
1. 목적
이번 블로그 글에서는 마틴 파울러의 저서, "리팩터링 2판"에 대해 정리해보려고 합니다. 이 책은 소프트웨어 개발자가 선택한 프로그램 가치 향상의 최고 코드 관리 기술서로, 많은 개발자들에게 큰 도움을 주고 있습니다.
하지만 한 가지 알아두셔야 할 점은, "리팩터링 2판"의 예제 코드가 모두 JavaScript로 작성되어 있다는 것입니다. JavaScript는 매우 유용한 언어이지만, 저는 Java를 주로 사용하는 개발자입니다. 그래서 이번 글에서는 책의 JavaScript 코드를 Java로 변환하여 설명드리려고 합니다. 이 과정에서 Java 코드로 리팩터링하는 방법을 중점적으로 다룰 예정이니, Java 개발자분들께 많은 도움이 되기를 바랍니다.
이 내용은 1990년대 후반에 리팩터링을 처음 배운 이후로 계속 보강되었고, 지금도 다 기억하지 못해서 틈틈이 참고하고 있다. 예컨대 단계 쪼개기(6.11절)를 적용하려 할 때면 해당 항목을 펼쳐보며 안전하게 한 단계씩 진행하는 방법을 되새긴다. 이 책이 독자 여러분도 수시로 다시 찾아 읽게 되는 내용으로 가득하기를 바란다.
또한, 리팩터링 카탈로그(목록)를 블로그에 기록해 두는 두 번째 목적이 있습니다. 리팩터링은 매우 중요한 과정이지만, 그 절차를 모두 기억하기란 쉽지 않습니다. 따라서, 리팩터링 과정을 블로그에 정리함으로써 언제든지 쉽게 찾아 보고 참고할 수 있도록 하려 합니다. 이는 책을 수시로 찾아보지 않아도 된다는 장점이 있으며, 필요할 때마다 블로그에서 해당 내용을 찾아 활용할 수 있게 됩니다.
2. 시작! 공연료 청구서 프로그램
Play.java
package org.study.refactoringpractice.play;
import lombok.Getter;
@Getter
public class Play {
private String playID;
private String name;
private String type;
public Play(String playID, String name, String type) {
this.playID = playID;
this.name = name;
this.type = type;
}
}
Performance.java
package org.study.refactoringpractice.play;
import lombok.Getter;
@Getter
public class Performance {
private String playID;
private int audience;
public Performance(String playID, int audience) {
this.playID = playID;
this.audience = audience;
}
}
Invoice.java
package org.study.refactoringpractice.play;
import lombok.Getter;
import java.util.List;
@Getter
public class Invoice {
private String customerName;
private List<Performance> performances;
public Invoice(String customerName, List<Performance> performances) {
this.customerName = customerName;
this.performances = performances;
}
}
Theater.java
package org.study.refactoringpractice.play;
import java.text.NumberFormat;
import java.util.List;
import java.util.Locale;
import java.util.Map;
public class Theater {
public String statement(Invoice invoice, Map<String, Play> plays) {
int totalAmount = 0;
int volumeCredits = 0;
String result = String.format("statement for (customer: %s)\n", invoice.getCustomerName());
NumberFormat currencyFormatter = NumberFormat.getCurrencyInstance(Locale.US);
for (Performance performance : invoice.getPerformances()) {
Play play = plays.get(performance.getPlayID());
int thisAmount = 0;
switch (play.getType()) {
case "tragedy": // 비극
thisAmount = 40_000;
if (performance.getAudience() > 30) {
thisAmount += 1000 * (performance.getAudience() - 30);
}
break;
case "comedy": // 희극
thisAmount = 30_000;
if (performance.getAudience() > 20) {
thisAmount += 10_000 + 500 * (performance.getAudience() - 20);
}
thisAmount += 300 * performance.getAudience();
break;
default:
throw new IllegalArgumentException(String.format("Unknown genre: %s", play.getType()));
}
// 포인트를 적립한다.
volumeCredits += Math.max(performance.getAudience() - 30, 0);
// 희극 관객 5명마다 추가 포인트를 제공한다.
if ("comedy".equals(play.getType())) {
volumeCredits += Math.floor(performance.getAudience() / 5);
}
// 청구 내역을 출력한다.
;
result += String.format("%s: %s (%d seats)\n", play.getName(), currencyFormatter.format(thisAmount / 100), performance.getAudience());
totalAmount += thisAmount;
}
result += String.format("Total amount: %s\n", currencyFormatter.format(totalAmount / 100));
result += String.format("You earned: %s points\n", volumeCredits);
return result;
}
}
책에서는 연극 정보(plays.json)와 공연료 청구서(invoices.json)를 JSON 파일로 설정하였지만,
나는 일반적인 Java class로 설정하였습니다.
핵심 함수인 statement()는 Theater 클래스의 인스턴스 메서드로 설계하였습니다.
자바스크립트에서 사용하는 라이브러리는 자바에도 비슷하게 대응되는 것이 있기 때문에 적절하게 변환해주었습니다.
예를 들어, US 달러 통화 formatter 는 java.text.Numberformat API를 사용하였고,
알 수 없는 장르의 경우 예외를 던질 때, IllegalArgumentException.class 을 던지도록 하였습니다.
2 - 2. 예제코드 동작시키기
PlayMain.java
package org.study.refactoringpractice.play;
import java.util.List;
import java.util.Map;
public class PlayMain {
public static void main(String[] args) {
Theater theater = new Theater();
Play hamlet = new Play("hamlet", "Hamlet", "tragedy");
Play asYouLikeIt = new Play("as-like", "As You Like It", "comedy");
Play othello = new Play("othello", "Othello", "tragedy");
Performance hamletPerformance = new Performance("hamlet", 55);
Performance asYouLikeItPerformance = new Performance("as-like", 35);
Performance othelloPerformance = new Performance("othello", 40);
Map<String, Play> plays = Map.of(
"hamlet", hamlet,
"as-like", asYouLikeIt,
"othello", othello
);
Invoice invoice = new Invoice("BigCo",
List.of(hamletPerformance, asYouLikeItPerformance, othelloPerformance));
String result = theater.statement(invoice, plays);
System.out.println(result);
}
}
실행결과
statement for (customer: BigCo)
Hamlet: $650.00 (55 seats)
As You Like It: $580.00 (35 seats)
Othello: $500.00 (40 seats)
Total amount: $1730.00
You earned: 47 points
원하는 대로 동작하고 있습니다.
3. 마무리
시작이 반이라고 했던가요?
경험상, 처음부터 너무 빠르게 달리면 금방 지쳐 원하는 목적지에 도달하지 못하는 경우가 많았습니다.
오늘은 앞으로 리팩터링 해볼 예제코드를 Java로 변환하는 작업까지하고,
다음 게시글부터 본격적으로 리팩터링을 진행해보도록 하겠습니다.
감사합니다.
https://github.com/Griotold/refactoring-practice
'리팩토링' 카테고리의 다른 글
리팩터링 2판 - Java로 정리 - (3) statement() 함수 쪼개기 (0) | 2024.06.07 |
---|---|
리팩터링 2판 - Java로 정리 - (2) 테스트 코드 (0) | 2024.06.06 |