Contents
사소한 개선:계산/반영 분리 로직
   Jul 4, 2022     9 min read

4 . 사소한 개선 - 섞여 있는 계산 로직 분리

섞여 있는 계산 로직과 반영 로직 분리

frist

public void provideServicePeriod(Long ordNo, LocalDate loginDate){
	... period, order 구하고 검사하는 코드 생략
	LocalDate edate = null;
	LocalDate currDate = order.getDate();
	LocalDate nextDate = YearMonth.from(currDate.plusMonths(1)).atDay(1);
	if(order.getGubun().equals("AA")) {
		if(order.getPayType().equals("A") || (order.getPayMonth().equals("T")){
			edate = YearMonth.from(currDate).atEndOfMonth();
		}	else if (order.getPayMonth().equals("N")){
			edate = YearMonth.from(currDate).plusMonths(1).atEndOfMonth();
		}
			updatePeriod(period, currDate, edate);
	}else if(order.getPayType().equals("W") && order.getIncludePay().equals("2")){
		...생략
	}
}else {
	if (order.getUnit().equals("D")){
		edate = loginDate.plusDays(order.getQty());
	} else if(order.getUnit().equals("M")){
		edate = loginDate.pluMonths(order.getQty());
	}
	updataPeriod(period, loginDate, edate);
}

위의 코드는 서비스 기간을 구하는 코드

노란색 부분은 계산하고 결과를 반영하는 코드이다.


위의 코드를 단순화한 구조

if(조건) {
	if(조건){
		계산
		결과 반영
	}else{
		계산
		결과 반영
	}
} else if(조건) {
	계산
	결과 반영
} else {
	if(조건) {
		계산
		결과 반영
 	}else {
		계산
		결과 반영
	}
}
  • 다양한 조건으로 뭔가를 계산하고 그 결과를 반영
    • 각 분기마다 계산과 결과 반영
      • 즉 계산과 반영 코드가 섞여 있다.
  • 단점
    • 계산 로직을 이해하는데 결과 반영 코드가 방해가 된다.
    • 로직만 테스트하기가 어렵다.

코드 바꾸기

  • 계산 코드와 계산 결과를 사용하는 코드 분리

  • 이를 위해 다음 항복 확인

    • 계산하는데 필요한 값 → 즉 입력 값
    • 계산 결과로 생성되는 값 → 즉 출력 값

두번쨰

public void provideServicePeriod(Long ordNo, LocalDate loginDate){
	... period, order 구하고 검사하는 코드 생략
	LocalDate edate = null;
	LocalDate currDate = order.getDate();
	LocalDate nextDate = YearMonth.from(currDate.plusMonths(1)).atDay(1);
	if(order.getGubun().equals("AA")) {
		if(order.getPayType().equals("A") ||
			(order.getPayMonth().equals("W") && oder.getIncludePay()equals("1")) ){
			if(order.getPayType().equals("T")) {
				edate = YearMonth.from(currDate).atEndOfMonth();
		}	else if (order.getPayMonth().equals("N")){
			edate = YearMonth.from(currDate).plusMonths(1).atEndOfMonth();
		}
			updatePeriod(period, currDate, edate);
	}else if(order.getPayType().equals("W") && order.getIncludePay().equals("2")){
		...생략
	}
}else {
	if (order.getUnit().equals("D")){
		edate = loginDate.plusDays(order.getQty());
	} else if(order.getUnit().equals("M")){
		edate = loginDate.pluMonths(order.getQty());
	}
	updataPeriod(period, loginDate, edate);
}

각 색깔의 의미

  • 파란색 : 입력으로 사용되는 코드
  • 녹색 : 입력된 값의 바탕으로 로직을 수행해서 계산한 결과물로 나오는 코드
  • 노란색 : 계산한 결과를 반영하는 코드
  • 빨간색 : 계산 반영할 때 전달되는 값, 즉 계산한 결과값

first

Tip

  • 복잡하고 계산이 길어지면 그 코드를 A3 같은 곳에 출력을 해서 색칠로 구분하고 선으로 관계를 표시하면 분석하면 입력과 출력이 무엇인지 알아내는데 도움이 된다

계산한 로직의 입력과 출력을 구할 때

  • 코드가 너무 복잡
  • 연결고리가 잘 안될 때

해결법

시각화 하라 !

Tip

  • 값의 행선 관계를 시각적으로 표시하면 입력과 출력이 무엇인지 알아내는데 도움이 된다.

Ex) 위를 보고 해석해보면..

  • order는 currDate와 edate의 값을 만드는데 관여
  • loginDate는 edate의 값을 만드는데 관여한다.
  • currDate는 nextDate와 edate를 만드는데 관여

식별한 계산 입출력

스크린샷 2022-07-04 오후 12 59 56

시각화와 코드 분석을 통해 얻은 정보

서비스 기한을 계산하는데 필요한 입력 값은 order와 loginDate라는 것을 알게 되었다

그것의 출력 값은 sdate, edate 라는 것을 알게 됨

입력과 출력의 정보를 알았기 때문에 이제 남은 작업은 계산로직 분리

계산 로직 분리 모양

3가지 방법중에 고민하게된다. 그렇다면 어떤 방식이 더 나은 선택인가 ?

1. 메소드 파라미터을 통해서 전달하는 방식

// 1. 메소드 파라미터을 통해서 전달하는 방식
ServicePeriod period = calculatePeriod(
				order.getGubun(), order.getPayType(), ..., loginDate)

  • if절에서 사용되고 다른 용도로도 사용되었던 모든 값을 메소드 파라미터로 전달하는 방식

2. 객체 전체를 전달하는 방식

//2. 객체 전체를 전달하는 방식
ServicePeriod period = caclulatePeriod(order, loginDate)
  • 메소드를 객체를 전달하는 방식이다.

3. 같은 클래스의 메소드로 하지않고 별도의 객체로 분리해서 그 객체를 통해서 계산하는 방법

//3. 같은 클래스의 메소드로 하지않고 별도의 객체로 분리해서 그 객체를 통해서 계산하는 방법
ServicePeriod period = new ServicePeriodCalculator(order, loginDate).calculate();

//ServicePeriod
LocalDate sdate = period.getSdate();
LocalDate edate = period.getEdate();

3번을 채택한다.

이유는 계산 로직만 따로 떼어내서 테스트하는게 수월해지기 때문이다.


계산 로직 분리

sec 변경 전

public void provideServicePeriod(Long ordNo, LocalDate loginDate){
	... period, order 구하고 검사하는 코드 생략
	LocalDate edate = null;
	LocalDate currDate = order.getDate();
	LocalDate nextDate = YearMonth.from(currDate.plusMonths(1)).atDay(1);
	if(order.getGubun().equals("AA")) {
		if(order.getPayType().equals("A") ||
			(order.getPayMonth().equals("W") && oder.getIncludePay()equals("1")) ){
			if(order.getPayType().equals("T")) {
				edate = YearMonth.from(currDate).atEndOfMonth();
		}	else if (order.getPayMonth().equals("N")){
			edate = YearMonth.from(currDate).plusMonths(1).atEndOfMonth();
		}
			updatePeriod(period, currDate, edate);
	}else if(order.getPayType().equals("W") && order.getIncludePay().equals("2")){
		...생략
	}
}else {
	if (order.getUnit().equals("D")){
		edate = loginDate.plusDays(order.getQty());
	} else if(order.getUnit().equals("M")){
		edate = loginDate.pluMonths(order.getQty());
	}
	updataPeriod(period, loginDate, edate);
}

변경 후

public class servicePeriodCalcultor{
	private Order order;
	private LocalDate localdate;

	public ServicePeriodCalcultor(Order order, LocalDate localdate){
		this.order = order;
		this.loginDate = loginDate;
	}

	public ServicePeriod calculate(){
	LocalDate edate = null;
	LocalDate currDate = order.getDate();
	LocalDate nextDate = YearMonth.from(currDate.plusMonths(1)).atDay(1);
	if(order.getGubun().equals("AA")) {
		if(order.getPayType().equals("A") ||
			(order.getPayMonth().equals("W") && oder.getIncludePay()equals("1")) ){
			if(order.getPayType().equals("T")) {
				edate = YearMonth.from(currDate).atEndOfMonth();
		}	else if (order.getPayMonth().equals("N")){
			edate = YearMonth.from(currDate).plusMonths(1).atEndOfMonth();
		}
			updatePeriod(period, currDate, edate);
	}else if(order.getPayType().equals("W") && order.getIncludePay().equals("2")){
		...생략
	}
}else {
	if (order.getUnit().equals("D")){
		edate = loginDate.plusDays(order.getQty());
	} else if(order.getUnit().equals("M")){
		edate = loginDate.pluMonths(order.getQty());
	}
	return new ServicePeriod(loginDate, edate);
}
  • servicePeriodCalcultor 클래스 구현 - 입력으로 생성된 값들을 생성자를 통해서 필드에 할당
public class servicePeriodCalcultor{
	private Order order;
	private LocalDate localdate;

	public ServicePeriodCalcultor(Order order, LocalDate localdate){
		this.order = order;
		this.loginDate = loginDate;
	}
}
  • 계산하는 로직은 calculate() 메서드로 분리
public ServicePeriod calculate(){}
  • 결과를 받아서 사용하는부분
    • updataPeriod(period, loginDate, edate) 호출 했던 부분을 계산 결과를 리턴하는 코드로 변경 return new ServicePeriod(loginDate, edate)
updataPeriod(period, loginDate, edate); -> return new ServicePeriod(loginDate, edate);

분리 후 코드

public void provideServicePeriod(Long ordNo, LocalDate loginDate) {
	...period, order 구하고 검사하는 코드 생략
	ServicePeriod period =
		new ServicePeriodCalculator(order, loginDate)
				.calculate();
	updatePeriod(period, period.getSdate(), period.getEdate());
}

깔끔 ..

처음 코드와 비교해보면

  1. 계산하는 로직은 한 곳으로 모임
  2. updatePeriod도 한줄로 끝 (실제 원본 코드 updatePeriod는 2개 였는데 하나로 줄어들었다. )

분리 결과

  • 계산 로직만 테스트 가능
  • 계산 로직 리팩토링 수월해짐

후기

강의를 듣기 전 제목을 유추해 봤었다. 그냥 느낌으로는 아 계산하고 반영 로직을 따로 분리하겠구나 생각했다.

하지만 글쓴이는 그 분리 과정을 구체적으로 설명할 수준이 안된다. 구체적으로 코드로 설명해주니깐 더 쉽게 이해가 잘 되었지만 머리로만 이해이다. 이것을 의식적으로 연습해야한다.

중간 내용중 복잡한 코드에서 입력값과 출력값을 시각화해서 도출하는 부분이 인상깊었고 계산 로직을 분리하는 3가지의 모양도 인상깊었다. 앞으로 의식적으로 계산 로직과 반영로직을 분리하는 습관을 가져야겠다.

참고 :YouTube 채널 -최범균 https://www.youtube.com/watch?v=NaeXpswLvxk