우테코 미션 진행 중 다음과 같은 요구사항이 있었습니다.
모든 원시 값과 문자열을 포장한다.
저는 이 문구를 보고 모든 원시 값을 포장하라는 말의 범위는 어디까지일지 의문이 생겼습니다.
먼저 원시 값을 왜 포장해야 하는지 알아야 이에 대해 대답할 수 있을 것 같았기에 이에 대해 생각해 보았습니다.
간단하게 정리하면 원시 값은 기본형 집착(Primitive Obsession) 이라는 안티패턴을 피하기 위해 필요합니다.
기본형 집착이란 도메인을 표현하기 위해 객체를 만들지 않고 프로그래밍 언어에서 제공하는 기본형을 사용하는 나쁜 습관을 말합니다.
도메인을 표시하는 데 원시 값을 사용하는 게 왜 나쁜 습관이지?
코드를 보면 쉽게 와닿으실 것이라고 생각하기 때문에 코드로 예를 들어보겠습니다.
우리가 만든 게임이 있고, 이 게임에서는 유저 별로 아이템을 구매할 수 있는 게임 머니를 다르게 가질 수 있습니다.
이를 관리하는 User 클래스를 만들었다고 생각해 봅시다.
public class User {
private int money;
public void buyItem(Item item) {
...
this.money = this.money - item.price();
}
...
}
여기서 우리는 어렵지 않게 money가 User가 가진 게임 머니를 나타내는 필드라고 유추할 수 있습니다.
그런데 어떤 사람들은 저 int 타입의 필드를 보고 불안함이 생길 수 있습니다.
아이템을 계속 구매하다가 money가 음수가 되면 어떡하지?
그래서 이를 위해 검증하는 로직을 넣었습니다.
public class User {
private int money;
public void buyItem(Item item) {
...
if (money < item.price()) {
System.out.println("게임 머니가 부족합니다");
return;
}
money = money - item.price();
}
...
}
이제 안심할 수 있습니다.
그런데 만약 게임머니가 Item 구매뿐 아니라, 특정한 퀘스트를 완료하는데, 어떤 지역으로 이동하는데도 사용된다면 어떡하죠?
모든 함수에 검증 로직을 넣어주면 되겠지만, 실수로 이를 빼먹을 수도 있겠죠.
또 더 나아가 과연 유저가 어떤 행동을 할 때 돈이 부족한 것을 체크하는 것이 진짜 User라는 클래스에서 가져야 하는 책임인지 의문이 생길 수도 있습니다.
이러한 걱정을 원시 값 포장으로 해결할 수 있습니다.
public Class User{
private Money money;
public void buyItem(Item item) {
...
money.use(item.price());
}
public void doQuest(Quest quest) {
...
money.use(quest.cost());
}
public void entryNewArea(Area area) {
...
money.use(area.entryCost());
}
}
public Class Money{
private int money;
...
public void use(int price) {
validate(price)
money = money - price;
}
private void validate(price) {
if (money < price) {
throw new IllegalStateException("게임 머니가 부족합니다.");
}
}
}
이처럼 코드를 개선하였을 때 생기는 장점들이 보이시나요?
User 클래스에서 money에 대한 검증 코드를 일일이 넣어주는 작업이 필요 없어졌습니다.
User가 money 에 대한 책임을 가지는 것보다 더 어색하지 않게 로직을 구성할 수 있게 되었습니다. (각 클래스가 가지는 책임이 더 확실해졌습니다.)
User 클래스 안에서 money - price 라는 코드를 보지 않아도 money.use 라는 메서드 이름을 통해, 이 경우에는 게임 머니를 사용하는구나 하고 직관적으로 알 수 있게 되었습니다.
User를 생성하는 클래스에서 원시 값을 이용해 이를 생성할 때 보다 명확하게 어떤 정보를 넘겨주는지 표시할 수 있게 되었습니다.
//before
User user1 = new User("포이", 10000);
User user2 = new User("글렌", 30);
//after
User user1 = new User(new Name("포이"), new Money(10000));
User user2 = new User(new Name("글렌"), new Money(30));
앞서 살펴 본 장점들을 통해 어디까지 원시 값을 포장해야 하는지 결론을 내릴 수 있었습니다.
위의 예시에서 볼 수 있듯, 우리가 프로그래밍을 할 때 어떤 변수는 단순히 프로그래밍 언어적인 자료라는 의미를 넘어 비즈니스상의 어떤 상태, 정보를 나타냅니다.
이러한 변수를 원시 값으로 놔두기 보단 명확한 의미를 갖는 객체로 포장해 준다면 이 의미와 책임이 더 잘 표현될 수 있을 것입니다.