리팩토링 - 9장 데이터 조직화

600g (Kim Dong Geun)·2022년 1월 22일
0

동기

데이터 구조는 프로그램에서 중요한 역할을 한다. 그러나 데이터들이 여러 서비스에서 무분별하게 참조된다면 혼란과 버그를 낳을 수 있다.

목적

따라서 데이터구조를 리팩토링 함에 따라, 데이터 구조로 인한 유지보수 비용을 절약하자.

변수 쪼개기

참조가 불분명하여 변수가 어디서 변경될지 예측하지 못한다던가, 역할이 2개 이상인 변수가 있다면 쪼개야한다.

double temp = 2 * (height + width);
System.out.println(temp);
// 하나의 변수에 2번의 대입이 일어난다.
temp = height * width;
System.out.println(temp);

해당 코드는 다음과 같이 변수쪼개기를 실행할 수 있다.

final double perimeter = 2 * (height + width);
System.out.println(perimeter);
final double area = height * width;
System.out.println(area);
절차
  1. 변수를 선언한 곳과 값을 처음 대입하는 곳에서 변수 이름을 바꾼다.
  2. 가능하면 불변으로 변경한다.
  3. 이 변수에 두번째로 값을 대입하는 곳 앞까지의 모든 참조를 새로운 변수 이름으로 바꾼다.
  4. 두번째 대입시 변수를 원래 이름으로 다시 선언한다.
  5. 테스트 한다.
  6. 반복한다.

구체적 예시는 생략, 대신 개인적 생각을 조금 말해보자면 변수 쪼개기 하나만으로도 다음과 같은 이득을 얻을 수 있다라고 생각한다.
1. 가독성 향상
2. 디버깅 편리
3. Side Effect를 줄인다.

필드 이름 변경하기

데이터 레코드에서의 필드 이름은 프로그램을 이해하는데 아주 큰 역할을 한다. 그래서 직관적인 네이밍 작업은 추후에 프로그램을 이해하는데 있어 중요하다.

  • 변경 전
public class Organization{
	public String name;
}
  • 변경 후
public class Organization {
	@Getter //캡슐화 작업
	@Setter //캡슐화 작업
	private String title
}

절차

  1. 레코드의 유효범위가 제한적이라면 필드에 접근하는 모든 코드를 수정한 후 테스트 한다. 이후단계는 생략한다.
  2. 레코드가 캡슐화되지 않았다면 우선 레코드를 캡슐화한다.
  3. 캡슐화된 객체안의 private 필드명을 변경하고 그에맞게 내부 메서드들을 수정한다.
  4. 테스트 한다.
  5. 생성자의 매개변수중 필드와 이름이 겹치는게 있다면 함수 선언 바꾸기로 변경한다.
  6. 접근자들의 이름도 바꿔준다.

역시 예제 자체는 내가 설명할 필요가 없을 정도로 간단한 예시, 추가로 개인적 견해

네이밍은 굉장히 중요한 작업
변수 이름 추천 사이트 : https://www.curioustore.com/
애용하자.

파생 변수를 질의 함수로 바꾸기

파생변수는 다소 직관적이지 못한 코드흐름을 만들 수 있다. 파생변수가 의미가 매우 직관적이라서 간단하게 사용될 수 있다면 좋겠지만, 우리의 코드들은 그렇지 않다.
그래서 파생 변수가 어디서 참조되어 막기 위해, 이를 불변함수로 변경하여 side effect를 막는다.

  • 변경전
class Billing {
	
	private int discount;
	private int discountedTotal;
	
	public int getDiscountedTotal(){
		return this.discountedTotal;
	}
	
	/*
		뿐만아니라 discountedTotal 이 무엇을 의미하는지 자체가 불분명하다.
		코드가 한번에 읽히지 않았음. < 물론 내 능지 탓도 있겠지만...
		discount가 discountedTotal 자체를 관리할 책임을 지고있는지 다시 생각해보면 좋을문제라고 생각 들었다.
		내가 생각했을때는 discountedTotal이 최종 할인된 가격을 가리킨다면 setDiscount는 2가지 책임을 가지고 있는 것이 된다.
		1. 할인 금액을 설정하는 책임
		2. 최종할인된 할인 금액을 설정하는 책임
	*/
	public void setDiscount(int discount){
		final int old = this.discount;
		this.discount = discount;
		this.discountedTotal = old - discount;
	}
}
  • 변경후
class Billing{
	private int discount;
	private int baseTotal;
	
	//매번 값을 계산하여 리턴함으로써 값이 다른곳에서 참조하지 않도록 막음.
	public int getDiscountedTotal(){
		return this.baseTotal - this.discount;
	}
	
	//또한 setDiscount()가 하나의 책임만 가짐으로써 코드가 직관적이고 유지보수하기 편해짐.
	public void setDiscount(int discount){
		this.discount = discount;
	}
}

baseTotal은 어디서 계산되는지는 생략 되있는듯

과정

  1. 변수 값이 갱신되는 지점을 모두 찾는다. 필요하면 변수 쪼개기를 활용해 각 갱신 지점에서 변수를 분리한다.
  2. 해당 변수의 값을 계산해주는 함수를 찾는다.
  3. 해당 변수가 사용되는 모든 곳에 Assert 를 추가하여 계산결과가 변수의 값과 같은지 확인한다 -> 이게 될까?
    • 적어도 비즈니스 로직에는 Test Code를 짜둬서 코드가 변경이된다면, 컴파일시 에러를 띄워서 추적이 쉽도록 구현해놔야된다는게 내생각.
  4. 테스트한다.
  5. 변수를 읽는 코드를 모두 함수 호출로 대체한다.
  6. 테스트한다.
  7. 변수를 선언하고 갱신하는 코드를 죽은 코드 제거하기로 없앤다.

참조를 값으로 바꾸기

  • 변경전
class Product {

	//애초에 책임 자체가 잘못되어있다. -> 캡슐화를 구데기로 해놓은 케이스.
	//클린코드에서 데메테르 법칙 자체를 지키지 않은코드
	//Product는 Price객체의 프로퍼티를 직접 접근할 권리가 없고,
	//Price 객체에 요청하여 amount에 대해 접근화 해야 한다.
	void applyDiscount(int discount){
		this.price.amount -= discount;
	}
}
  • 변경후
class Product {
	//좀 낫지만 캡슐화가 지켜지지 않음
	void applyDiscount(int discount){
		this.price = new Money(this.price.amount - arg, this.price.currency);
	}
}
  • 추가적인 내생각
class Product {
	
	//이러면 pirce의 가격 측정 정책이 바뀌더라도 side Effect가 발생할 가능성이 굉장히 줄어듬
	//왜냐하면 Product는 Price 내부의 프로퍼티가 어떻게 변경되는지는 관심 X
	void applyDiscount(int discount){
		int amount = this.price.getAmount();
		this.price = new Money(amount, this.price.getCurrency());
	}
}

참조로 처리할 경우 역시 Side Effect가 생길 수 있다. 내부 객체의 값이 얼마든지 바뀔 수 있기 때문에, 프로그램 흐름을 어렵게 하기 때문이다.

그리고 일단 객체를 직접 참조할 경우, 참조한 객체와 강력한 결합이 생기기 때문에 분리하기 어려워 진다.

절차

  1. 후보 클래스가 불변인지 혹은 불변이 될 수 있는지 확인한다.
  2. 각각의 세터를 하나씩 제거한다. -> (내생각엔 이건 개발자 생각에따라)
  3. 이 값 객체의 필드들을 사용하는 동치성 비교 메소드들 만든다.

예시

해당 코드는 예시로 이야기해도 좋을 것 같아서 예를 든다.

  • 후보 클래스가 불변인지 혹은 불변이 될 수 있는지 확인하고 불변 만들기
class Person {
	private Telephone telephone;
	
	//객체를 생성할 때 새로 생성해서 불변을 보장한다.
	// setter를 제거한다 -> 오직 하나의 setter로만 둠.
	void setTelephone(Telephone telephone){
		this.Telephone = new Telephone("010-xxxx-xxxx");
	}
	
	@Override
	public boolean equals(Person otherPerson){
		return otherPerson.getTelephone().getNumber() == telephone.getTelephone();
	}
}

//동치성 추가
class Telephone {
	@Pattern(message ="invalid phone number", regexp = "/^.*(?=^.{8,15}$)(?=.*\d)(?=.*[a-zA-Z])(?=.*[!@#$%^&+=]).*$/  " )
	private String number;

	public String getNumver(){
		return this.number;
	}
}

값을 참조로 바꾸기

값은 불변속성을 띄고 있기 때문에, A에서 변경한 부분이 B화면에도 반영되야 한다면 공유적인 데이터를 사용해야 한다고 한다.

상태관리 플랫폼은 다양하게 사용되고 있다 생각되고, 실제로 프론트나 앱쪽에서는 어떻게 관리 되는지, 스터디 중 물어보기.

  • 변경전
Customer customer = new Customer(customerData);
  • 변경후
Customer customer = customerReository.get(CustomerData.id);

변경전은 객체를 생성하는 거고, 변경후는 이미 생성되어있는 객체를 가져오는예 (영속성을 유지하는 예)
(물론 둘다 객체를 어플리케이션 메모리에 새로 올릴 가능성이 있는 것은 맞다.)
즉 둘다 안좋은 코드가 아니라는 말.

매직 리터럴 바꾸기

  • 변경전
public getPotentialEnergy(double mass, double height){
	return mass * 9.81 * height;
}
  • 변경후

public static double EARTH_GRAVITY = 9.81;

public getPotentialEnergy(double mass, double, height){
	return mass * EARTH_GRAVITY * height;
}

의미전달 면에서 좋다고한다.
9.81이 읽는 사람 측면에서는 어떠한 값을 의미하는지 유추할 수 없기 때문이다.

정리

데이터를 조직화하는데 있어서총 6가지의 예시를 제시해뒀다.
1. 변수 쪼개기 -> Side Effect방지 , 가독성 향상
2. 필드 이름 바꾸기 -> 가독성 향상
3. 파생변수를 질의 함수로 바꾸기 -> Side Effect 방지, 캡슐화
4. 참조를 값으로 바꾸기 -> Side Effect 방지
5. 값을 참조로 바꾸기 -> ???
값을 공유할 때 사용한다는데 잘 모르겠다.
6. 매직리터럴을 함수로 변경하기 -> 가독성향상

결과적으로 데이터 구조를 조직화 한다는 것은 Side Effect방지와 가독성 향상에 중요한 역할을 한다생각하고, 잘 실천해서 좋은 코드를 짜도록 하자.

profile
수동적인 과신과 행운이 아닌, 능동적인 노력과 치열함

0개의 댓글