데이터를 변경하다보면 예상치 못한 결과나 해결하기 어려운 버그 발생
함수형 프로그래밍 언어는 데이터를 변경하지 않고 복사본을 전달하며 그 밖의 프로그래밍 언어는 데이터 변경 허용
변경되는 데이터 사용시 발생할 수 있는 리스크를 관리할 수 있는 방법을 적용
double 타입의 acc 라는 임시 변수를 생성하여 두 번의 재할당으로 코드의 이해가 쉽지 않은 케이스
result 의 경우 두 번의 결과를 합쳐 리턴하므로 아래와 같이 사용하는게 맞음
acc 라는 임시 변수만 primaryAcceleration, secondaryAcceleration 라는 이름의
변수로 조깨 사용하여 코드의 이해를 도움
class Refactoring {
public double distanceTravelled(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 primaryVelocity = acc * delay;
acc = (primaryForce + secondaryForce) / mass;
result += primaryVelocity * secondaryTime + 0.5 * acc * secondaryTime + secondaryTime;
}
return result;
}
}
class Refactoring {
public double distanceTravelled(int time) {
double result;
final double primaryAcceleration = primaryForce / mass;
int primaryTime = Math.min(time, delay);
result = 0.5 * primaryAcceleration * primaryTime * primaryTime;
int secondaryTime = time - delay;
if (secondaryTime > 0) {
final double primaryVelocity = primaryAcceleration * delay;
final double secondaryAcceleration = (primaryForce + secondaryForce) / mass;
result += primaryVelocity * secondaryTime + 0.5 * secondaryAcceleration * secondaryTime + secondaryTime;
}
return result;
}
}
매개변수의 값을 재할당하여 리턴하는 케이스
double 타입의 임시 변수를 생성 및 초기화하고 분기에 따른 재할당과 리턴의 역할을
담당하도록 하여 코드의 이해를 돕는다
class Refactoring {
public double discount(double inputValue, int quantity) {
if (inputValue > 50) inputValue = inputValue - 2;
if (quantity > 100) inputValue = inputValue - 1;
return inputValue;
}
}
class Refactoring {
public double discount(double inputValue, int quantity) {
double result = inputValue;
if (inputValue > 50) result = result - 2;
if (quantity > 100) result = result - 1;
return result;
}
}
double 타입의 temp 임시 변수를 생성하고 메서드 안에서 2번의 할당이 발생하는 케이스
코드를 보면 각 할당에 대한 역할이 다르므로 각 역할에 해당하는 임시 변수를 생성한다
class Refactoring {
public void updateGeometry(double height, double width) {
double temp = 2 * (height + width);
System.out.println("Perimeter: " + temp);
perimeter = temp;
temp = height * width;
System.out.println("Area: " + temp);
area = temp;
}
}
class Refactoring {
public void updateGeometry(double height, double width) {
final double perimeter = 2 * (height + width);
System.out.println("Perimeter: " + perimeter);
this.perimeter = perimeter;
final double area = height * width;
System.out.println("Area: " + area);
this.area = area;
}
}
하나의 메소드에서 조회를 하며 메일도 전송하고 있는 케이스
getTotalOutstandingAndSendBill 메서드에서 sendBill 메서드를 제거하여 역할 분리
class Refactoring {
public double getTotalOutstandingAndSendBill() {
double result = customer.getInvoices().stream()
.map(Invoice::getAmount)
.reduce((double) 0, Double::sum);
sendBill();
return result;
}
private void sendBill() {
emailGateway.send(formatBill(customer));
}
}
class Refactoring {
public double getTotalOutstanding() {
return customer.getInvoices().stream()
.map(Invoice::getAmount)
.reduce((double) 0, Double::sum);
}
private void sendBill() {
emailGateway.send(formatBill(customer));
}
}
메서드에서 Miscreant 을 찾아 알람을 울리도록 두 가지 역할을 담당하는 케이스
Miscreant 을 찾는 역할, 알람을 울리는 역할 2개의 메서드로 분리
class Refactoring {
public String alertForMiscreant(List<Person> people) {
for (Person p : people) {
if (p.getName().equals("Don")) {
setOffAlarms();
return "Don";
}
if (p.getName().equals("John")) {
setOffAlarms();
return "John";
}
}
return "";
}
private void setOffAlarms() {
System.out.println("set off alarm");
}
}
class Refactoring {
public void alertForMiscreant(List<Person> people) {
if (!findMiscreant(people).isBlank()) {
setOffAlarms();
}
}
public String findMiscreant(List<Person> people) {
for (Person p : people) {
if (p.getName().equals("Don")) {
return "Don";
}
if (p.getName().equals("John")) {
return "John";
}
}
return "";
}
private void setOffAlarms() {
System.out.println("set off alarm");
}
}
class Refactoring {
private String name;
private int id;
// ... getter, setter 생성
}
class Refactoring {
private String name;
private int id;
after(int id) {
this.id = id;
}
// ... getter 생성, setter 는 name 만 있도록 수정
}
discountedTotal 은 discount, baseTotal 의 계산에 의해 파생된 변수이며
setDiscount 메서드에서 discountedTotal 변수에 할당을 하지 않고 dicount 가 있다면
getDiscountedTotal 에서 직접 값을 할당하더라도 문제가 되지 않고
불필요한 discountedTotal 필드를 제거하고 메서드에서 바로 값을 리턴받을 수 있다
class Refactoring {
private double discountedTotal;
private double discount;
private double baseTotal;
public Discount(double baseTotal) {
this.baseTotal = baseTotal;
}
public double getDiscountedTotal() {
return this.discountedTotal;
}
public void setDiscount(double number) {
this.discount = number;
this.discountedTotal = this.baseTotal - this.discount;
}
}
class Refactoring {
private double discount;
private double baseTotal;
public Discount(double baseTotal) {
this.baseTotal = baseTotal;
}
public double getDiscountedTotal() {
return this.baseTotal - this.discount;
}
public void setDiscount(double number) {
this.discount = number;
}
}
첫 번째 예시와 동일한 케이스
불필요한 production 필드를 제거하고 값을 직접 리턴하는 메서드를 생성
stream 을 이용하여 리스트를 순회하면서 값을 모두 더하여 리턴하는 2가지 방법 적용
class Refactoring {
private double production;
private List<Double> adjustments = new ArrayList<>();
public void applyAdjustment(double adjustment) {
this.adjustments.add(adjustment);
this.production += adjustment;
}
public double getProduction() {
return this.production;
}
}
class Refactoring {
private List<Double> adjustments = new ArrayList<>();
public void applyAdjustment(double adjustment) {
this.adjustments.add(adjustment);
}
public double getProduction() {
//return this.adjustments.stream().reduce((double) 0, Double::sum);
return this.adjustments.stream().mapToDouble(Double::valueOf).sum();
}
}
변경 전 A ~ C 까지의 클래스에서 동일한 파생 변수를 만들어내는 baseRate 메서드를 사용
record class, baseRate 결과 값을 리턴하는 새로운 record class 를 생성하여
각 각이 가지는 baseRate 메서드를 특정 record class 에서 관리하도록 하고
변경 후 A ~ C 클래스에서는 record 를 참조 하는 구조로 변경
class A {
double baseCharge;
public Client1(Reading reading) {
this.baseCharge = baseRate(reading.month(), reading.year()) * reading.quantity();
}
private double baseRate(Month month, Year year) {
return 10;
}
public double getBaseCharge() {
return baseCharge;
}
}
class B {
private double base;
private double taxableCharge;
public Client2(Reading reading) {
this.base = baseRate(reading.month(), reading.year()) * reading.quantity();
this.taxableCharge = Math.max(0, this.base - taxThreshold(reading.year()));
}
private double taxThreshold(Year year) {
return 5;
}
private double baseRate(Month month, Year year) {
return 10;
}
public double getBase() {
return base;
}
public double getTaxableCharge() {
return taxableCharge;
}
}
class C {
private double basicChargeAmount;
public Client3(Reading reading) {
this.basicChargeAmount = calculateBaseCharge(reading);
}
private double calculateBaseCharge(Reading reading) {
return baseRate(reading.month(), reading.year()) * reading.quantity();
}
private double baseRate(Month month, Year year) {
return 10;
}
public double getBasicChargeAmount() {
return basicChargeAmount;
}
}
public record Reading(String customer, double quantity, Month month, Year year) { }
class A {
double baseCharge;
public Client1(Reading reading) {
this.baseCharge = enrichReading(reading).baseCharge();
}
public double getBaseCharge() {
return baseCharge;
}
}
class B {
private double base;
private double taxableCharge;
public Client2(Reading reading) {
this.base = enrichReading(reading).baseCharge();
this.taxableCharge = Math.max(0, this.base - taxThreshold(reading.year()));
}
public double getBase() {
return base;
}
public double getTaxableCharge() {
return taxableCharge;
}
}
class C {
private double basicChargeAmount;
public Client3(Reading reading) {
this.basicChargeAmount = calculateBaseCharge(reading);
}
private double calculateBaseCharge(Reading reading) {
return enrichReading(reading).baseCharge();
}
public double getBasicChargeAmount() {
return basicChargeAmount;
}
}
class parent {
protected double taxThreshold(Year year) {
return 5;
}
protected double baseRate(Month month, Year year) {
return 10;
}
protected EnrichReading enrichReading(Reading reading) {
return new EnrichReading(reading, calculatedBaseCharge(reading));
}
private double calculatedBaseCharge(Reading reading) {
return baseRate(reading.month(), reading.year()) * reading.quantity();
}
}
public record Reading(String customer, double quantity, Month month, Year year) { }
public record EnrichReading(Reading reading, double baseCharge) { }
JDK 11 까지 JDK 14 ~ 17 두 가지 방법으로 나뉘며
14 ~ 17 버전의 경우 record 를 사용하는 방법 초기화시 값 변경이 불가하며 장점으로는
getter, setter, equals, hashcord 를 별도로 생성하지 않아도 된다
JDK 11 버전까지의 경우 필드를 final 로 생성하고 값의 변경이 필요할 때는 새로운 객체에 할당하여 리턴
class Person {
private TelephoneNumber officeTelephoneNumber;
public String officeAreaCode() {
return this.officeTelephoneNumber.areaCode();
}
public void officeAreaCode(String areaCode) {
this.officeTelephoneNumber.areaCode(areaCode);
}
public String officeNumber() {
return this.officeTelephoneNumber.number();
}
public void officeNumber(String number) {
this.officeTelephoneNumber.number(number);
}
}
class TelephoneNumber {
private String areaCode;
private String number;
public String areaCode() {
return areaCode;
}
public void areaCode(String areaCode) {
this.areaCode = areaCode;
}
public String number() {
return number;
}
public void number(String number) {
this.number = number;
}
}
class TelephoneNumber {
private final String areaCode;
private final String number;
public TelephoneNumber(String areaCode, String number) {
this.areaCode = areaCode;
this.number = number;
}
public String areaCode() {
return areaCode;
}
public String number() {
return number;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
TelephoneNumber that = (TelephoneNumber) o;
return Objects.equals(areaCode, that.areaCode) && Objects.equals(number, that.number);
}
@Override
public int hashCode() {
return Objects.hash(areaCode, number);
}
}
public record TelephoneNumber(String areaCode, String number) { }