Java Refactoring -1, gathering final static fields

박태건·2021년 7월 14일
3

리팩토링-자바

목록 보기
1/13
post-thumbnail

레거시 코드를 클린 코드로 누구나 쉽게, 리팩토링

위 책을 보면서 정리한 글입니다.

final static 필드를 모아 뚱뚱해진 클래스 개선

유일한 값 : 상수

개발 초기 : 각기 다른 클래스에 분산
개발 진행 : 하나의 클래스에서 관리

위의 방법은 상수가 적은 구조에서는 유용하지만, 기능이 점차 늘어날수록 상수도 많아지게 되어 문제가 발생한다.

  • 상수를 이용할 때, 상수의 값이 무엇인지 관리 클래스에서 찾아야 한다.
    -(특정 용도의 상수의 값을 이용하기 위해서는, 먼저 상수를 찾아야 하는 경우가 발생)
  • 같은 값이지만, 용도가 다르게 정의된 상수는 무엇인지 확인하기 힘들어진다.
    -(이미 같은 값의 상수가 선언되어 있지만, 다른 용도로 사용해야 하는 경우가 발생하여 생기는 문제)
  • 특정 기능을 담당하는 모듈을 만들어도, 상수를 관리하는 클래스의 종속적 구조에서 벗어나지 못한다.
    -(결국 상수를 사용하기 위해서는 다른 클래스에서 가져와 사용하는 종속적인 구조가 만들어진다.)

결국은 기능이나 로직이 확장될수록, 의도치 않은 곳에서 상수를 사용하거나, 통합 관리 목적과는 다르게 진행된다.
상수의 통합 관리에 초점이 맞추어져, 유지보수 측면에서 클래스의 종속성, 의존성이 높아져 분류하기 어려운 상수 클래스가 생성된다.

개선방향

상수 클래스를 분류하고 책임 클래스로 이동

문제는 상수 관리 클래스에 정의된 상수들이 찾기 어려운 만큼 많이 모여 있기 때문에 발생한다.
↪ 그렇다면, 의도된 상수들을 사용하도록, 상수 관리 클래스에 정의된 상수들을 목적에 맞게 분류하고, 필요에 따라 상수를 해당 객체로 이동시키자!

Q&A

Q. 상수 관리 객체를 만드는 것이 좋을까
A. 상수 관리 객체를 만드는 것은 필요한 일.
서비스의 데이터베이스 접속 정보와 같은 자료는 관리 객체에서 공통으로 접속하여 사용하는 것이 좋으나, 관리하는 상수의 범위가 일정하지 않으면 유지보수성이 모호해진다.
↪ 특정 로직에 상수가 필요할 때마다 시스템 상수를 관리하는 곳에 추가하게 되면, 코드량이 늘어나면서
상수 관리 객체는 시스템 상수만을 취급하는 것이 아닌, 서비스의 부분에서만 사용하는 상수들과 섞이게 된다.

Q. 왜 상수를 목적에 맞게 객체로 이동해야 할까
A. 가장 큰 이유는 의존성. 모듈이 별도의 애플리케이션으로 이동할 경우, 모듈에서 사용하는 상수가 속한 클래스도 함께 이동하게 된다.
↪ 이럴 경우 의도하지 않았던 상수들도 같이 딸려오게 되고, 구조적으로도 좋지 않게 된다.

Q. 어떤 기준으로 상수를 이동해야 할까
A. 상수를 이용하는 객체의 기준으로 생각. 상수를 반환하기 위해 사용하는 객체, 반환받아 사용하는 객체가 있을 때 대개 반환받아 사용하는 객체가 좀 더 범용성이 있는 객체 (저자의 생각)
↪ 반환하기 위해 사용하는 로직은 여러 곳에 분산되지만, 반환받아 사용하는 곳은 한곳인 경우가 많아, 주로 상수를 사용하는 객체에 정의해서 사용
1. 상수를 사용하는 곳이 어느 패키지에 집중되어 있는지 확인
2. 상수를 사용하는 곳이 집중된 객체 중 제일 포괄적인 성격을 지닌 곳을 확인

레거시 코드

ConstValue 클래스

  public class ConstValue {
  	/** DataBase Info -- System **/
  	public static final String DATABASE_URL = "SMP1";
  	public static final String DATABASE_SCHEMA = "SMP2";
  	public static final String DATABASE_USER = "SMP3";
  	public static final String DATABASE_PASSWORD = "SMP4";
  
  	/** Req Info  -- System **/
  	public static final int REQ_SUCCESS = 10;
  	public static final int REQ_FAIL = 19;
  
  	/** CheckInService Info **/
  	public static final int CHK_IN_RESULT_SUCCESS = 30;
  	public static final int CHK_IN_RESULT_FAIL = 39;
  	
   	public static final int CHK_IN_FAIL_ALREADY_IN = 301;
  	public static final int CHK_IN_FAIL_NOT_REVERSED = 302;
  	public static final int CHK_IN_FAIL_DEBT_CHARHED = 303;
  	public static final int CHK_IN_FAIL_SYSTEM_ERR = 399;
  
	/** CheckOutService Info **/
  	public static final int CHK_OUT_RESULT_SUCCESS = 40;
  	public static final int CHK_OUT_RESULT_FAIL = 49;
  
  	public static final int CHK_OUT_FAIL_ALREADY_IN = 401;
  	public static final int CHK_OUT_FAIL_NOT_REVERSED = 402;
  	public static final int CHK_OUT_FAIL_DEBT_CHARHED = 403;
  	public static final int CHK_OUT_FAIL_SYSTEM_ERR = 499;
  
  
  	/** PayService Info **/
  	public static final int PAY_RESULT_SUCCESS = 50;
  	public static final int PAY_RESULT_FAIL = 51;
  
  	public static final int PAY_FAIL_DEUBT_CHARGE = 501;
  	public static final int PAY_FAIL_DEUBT_RETURN = 502;
  	public static final int PAY_FAIL_DEUBT_CHARGE = 503;
  
  	/** RusultPageNumber Info -- System **/
  	public static final int RESULT_CODE_200 = 200;
  	public static final int RESULT_CODE_400 = 400;
  	public static final int RESULT_CODE_404 = 404;
  	public static final int RESULT_CODE_500 = 500;
  }

위 상수 클래스의 문제점은, 시스템 상수와 일반 로직에서 사용하는 상수가 섞여 있다는 것이다.
일반 로직에서 사용할 상수를 추가할 때마다, 이 클래스에 추가될 가능성이 높고, 유지보수 할 대마다 이 객체의 코드량은 점점 늘어가게 되어, 어느 순간 내가 원하는 상수를 찾을 수 없는 상황이 발생하게 된다.

시스템 상수와 일반 로직 상수를 구분하고 분리해야 한다.

레거시 코드 개선 과정

ConstValue의 상수를 목적에 맞게 내부 클래스로 묶기.

  public class ConstValue {
  	/** DataBase Info -- System **/
  	public class Database {
  		public static final String DATABASE_URL = "SMP1";
  		public static final String DATABASE_SCHEMA = "SMP2";
		public static final String DATABASE_USER = "SMP3";
  		public static final String DATABASE_PASSWORD = "SMP4";
  	}
  
  	/** Req Info  -- System **/
  	public static final int REQ_SUCCESS = 10;
  	public static final int REQ_FAIL = 19;
  
  	/** CheckInService Info **/
  	public class CheckIn {
 		public static final int CHK_IN_RESULT_SUCCESS = 30;
  		public static final int CHK_IN_RESULT_FAIL = 39;
   	
  		// 실패 원인
  		public class Fail {
  			public static final int CHK_IN_FAIL_ALREADY_IN = 301;
  			public static final int CHK_IN_FAIL_NOT_REVERSED = 302;
  			public static final int CHK_IN_FAIL_DEBT_CHARHED = 303;
  			public static final int CHK_IN_FAIL_SYSTEM_ERR = 399;
  		}
   	}
  
	/** CheckOutService Info **/
  	public class CheckOut {
  		public static final int CHK_OUT_RESULT_SUCCESS = 40;
  		public static final int CHK_OUT_RESULT_FAIL = 49;
  
  		public class Fail {
  			public static final int CHK_OUT_FAIL_ALREADY_IN = 401;
  			public static final int CHK_OUT_FAIL_NOT_REVERSED = 402;
  			public static final int CHK_OUT_FAIL_DEBT_CHARHED = 403;
  			public static final int CHK_OUT_FAIL_SYSTEM_ERR = 499;
  		}
  	}
  
  	/** PayService Info **/
  	public class Pay {
  		public static final int PAY_RESULT_SUCCESS = 50;
  		public static final int PAY_RESULT_FAIL = 51;
  
  		public class Fail {
  			public static final int PAY_FAIL_DEUBT_CHARGE = 501;
  			public static final int PAY_FAIL_DEUBT_RETURN = 502;
  			public static final int PAY_FAIL_DEUBT_CHARGE = 503;
  		}
  	}
 
  	/** RusultPageNumber Info -- System **/
  	public class HttpStatus {
  		public static final int RESULT_CODE_200 = 200;
  		public static final int RESULT_CODE_400 = 400;
  		public static final int RESULT_CODE_404 = 404;
  		public static final int RESULT_CODE_500 = 500;
  	}
  }

ConstValue 내부 클래스의 이동

내부 클래스를 만든 이유는 외부 클래스의 상수들과 연관이 없고, 내부 클래스의 사용범위가 매우 한정적이기 때문이다. 이런 경우에는 사용 목적에 맞는 곳과 가깝게 배치하여 검색이나 수정이 쉬워지도록 분리한다.

ConstValue

 public class ConstValue {
 	/** DataBase Info -- System **/
 	public class Database {
 		public static final String DATABASE_URL = "SMP1";
 		public static final String DATABASE_SCHEMA = "SMP2";
		public static final String DATABASE_USER = "SMP3";
 		public static final String DATABASE_PASSWORD = "SMP4";
 	}
 
 	/** Req Info  -- System **/
 	public static final int REQ_SUCCESS = 10;
 	public static final int REQ_FAIL = 19;


 	/** RusultPageNumber Info -- System **/
 	public class HttpStatus {
 		public static final int RESULT_CODE_200 = 200;
 		public static final int RESULT_CODE_400 = 400;
 		public static final int RESULT_CODE_404 = 404;
 		public static final int RESULT_CODE_500 = 500;
 	}
 }

CheckInService

 public class CheckInService {
 		public static final int CHK_OUT_RESULT_SUCCESS = 40;
 		public static final int CHK_OUT_RESULT_FAIL = 49;
 
 		public static final int CHK_OUT_FAIL_ALREADY_IN = 401;
 		public static final int CHK_OUT_FAIL_NOT_REVERSED = 402;
 		public static final int CHK_OUT_FAIL_DEBT_CHARHED = 403;
 		public static final int CHK_OUT_FAIL_SYSTEM_ERR = 499;
	  
 		// ... 중략
 
 		public boolean checkInServ(UserInfo user) {
 			// ...
 			switch(result) {
 				case CHK_OUT_RESULT_SUCCESS:
 					// ...
 					break;
				case CHK_OUT_RESULT_FAIL:
 					// ...
 					break
 			}
 		}
 }

CheckOutService

 public class CheckOutService {
 		public static final int CHK_IN_RESULT_SUCCESS = 30;
 		public static final int CHK_IN_RESULT_FAIL = 39;
 
 		public static final int CHK_IN_FAIL_ALREADY_IN = 301;
 		public static final int CHK_IN_FAIL_NOT_REVERSED = 302;
 		public static final int CHK_IN_FAIL_DEBT_CHARHED = 303;
 		public static final int CHK_IN_FAIL_SYSTEM_ERR = 399;
 		
 		// ... 중략
 
 		public boolean checOutServ(UserInfo user) {
 			// ...
 			switch(result) {
 				case CHK_IN_RESULT_SUCCESS:
 					// ...
 					break;
				case CHK_IN_RESULT_FAIL:
 					// ...
 					break
 			}
 		}
 }

PayService

 public class PayService {
 		public static final int PAY_RESULT_SUCCESS = 50;
 		public static final int PAY_RESULT_FAIL = 51;
 
 		public static final int PAY_FAIL_DEUBT_CHARGE = 501;
 		public static final int PAY_FAIL_DEUBT_RETURN = 502;
 		public static final int PAY_FAIL_DEUBT_CHARGE = 503;
 		
 		// ... 중략
 
 		public boolean payServ(UserInfo user) {
 			// ...
 			switch(result.getSuccess()) {
 				case PAY_RESULT_SUCCESS:
 					// ...
 					break;
				case PAY_RESULT_FAIL:
 					case(failResult):
 						case PAY_FAIL_DEUBT_CHARGE:
 							// ..
 							break;
 						case PAY_FAIL_DEUBT_RETURN:
 							// ..
 							break;
						case PAY_FAIL_DEUBT_CHARGE:
 							// ..
 							break;
 					break;
 			}
 		}
 }

요약 및 정리

ConstValue에 있는 상수 중 일부가 이동되어 추가되었고, 각 로직에서만 사용되던 상수들은 그에 맞는 Service 클래스로 이동하여 ConstValue 클래스에 대한 의존성이 낮아지거나, 없어졌다.
또한, 각 로직에 필요한 상수는 로직과 관련된 클래스에서 찾고 추가할 수 있어 유지보수성도 높아졌다.

상수를 관리하는 클래스를 만드는 것은 설계 초기 매우 유용한 방법이지만, 상수가 많아지고, 상수들의 사용 범위가 모호해지면 관리를 위한 상수 지합이 원하는 코드를 찾는 순간부터 독이 된다.
따라서 초기 좋은 모습을 유지하려면 무조건적인 사용을 막고, 목적에 맞게 상수를 명확히 구분짓는 습관이 필요하다.
특히, 시스템 관련 상수는 로직 상수와 섞이지 않게하고, 로직 관련 상수는, 되도록 로직과 가까운곳에 있는것이 좋다.

profile
노드 리액트 스프링 자바 등 웹개발에 관심이 많은 초보 개발자 입니다

1개의 댓글

comment-user-thumbnail
2022년 12월 30일

내용 좋네요 감사합니다 ^^

답글 달기