정적 팩터리 메서드를 공부했다. 그러던 중 궁금한 점이 생겼다.
정적 팩터리 메서드를 다룬 유명한 서적 중 하나인 이펙티브 자바는 정적 팩터리 메서드의 장점 중 하나를 "이름을 가질 수 있는 것"으로 소개한다.
동시에 책 말미에는 정적 팩터리 메서드의 네이밍 컨벤션을 소개한다.
이름을 가질 수 있다는 것이 장점인데 네이밍 컨벤션이 있다니 어찌보면 모순이 아닌가?
팩터리 메서드의 이름이 단지 위 몇 가지로 제한된다면 이름을 갖는 것이 장점이 될 수 없다. new ClassName()
(생성자 호출)과 크게 다를 게 없기 때문이다.
그렇다면 네이밍 컨벤션을 무작정 어겨야 하나?
이러한 고민들을 이어가다 이 글에 생각을 정리해 보았다.
나는 다음과 같이 생각한다.
친절한
코드가 된다.해석
을 요구한다.결국 단순한 생성과 검증 로직 등을 포함하는 메서드에는 친절
을 더해 네이밍 컨벤션을 따르는 게 좋다.
반면 메서드가 특정한 기능을 가져 이름으로 의도를 명확히 드러내야 한다면 컨벤션에서 벗어나도 괜찮다.
아래는 블랙잭 미션에서 사용된 돈 객체를 간단히 표현한 코드이다.
생성자를 private로 막아두고 정적 팩터리 메서드 Money.from()을 사용하였다.
public class Money {
private final int amount;
private Money(final int amount) {
this.amount = amount;
}
public static Money from(final int amount) {
return new Money(amount);
}
//...
}
현재 정적 팩터리 메서드는 별다른 로직을 갖지 않는다.
생성자 대신 정적 팩터리 메서드를 사용했다면 코드를 사용하는 클라이언트에게 정적 팩터리 메서드의 존재를 알리는 것이 가장 중요하다.
따라서 네이밍 컨벤션을 따라 이름을 from으로 짓는 것이 적절하다.
이번엔 정적 팩터리 메서드에 로직이 추가되었다.
정적 팩터리 메서드가 베팅 금액을 위한 돈 객체를 생성해준다.
생성하는 과정에서 베팅 금액을 검증하는 로직을 갖는다.
public class Money {
private final int amount;
private Money(final int amount) {
this.amount = amount;
}
public static Money moneyForBetting(final int amount) {
validateBettingAmountRange(amount);
return new Money(amount);
}
private static void validateBettingAmountRange(final int amount) {
if (amount < 1_000 || amount > 100_000_000) {
throw new IllegalArgumentException("배팅 금액은 1,000 이상 100,000,000 이하여야 합니다");
}
}
//...
}
컨벤션을 따랐던 이전의 정적 팩터리 메서드와 새로운 로직이 추가된 메서드는 전혀 다른 기능을 한다.
베팅 금액을 검증하는 로직은 클라이언트가 쉽게 예상할 수 없는 기능이다.
따라서 메서드 이름을 적절히 지어 이러한 기능을 예상할 수 있게 해야한다.
베팅을 위한 돈 객체를 생성하는 것을 분명히 알 수 있게 Money.moneyForBetting()로 이름을 지었다.
Money.moneyForBetting()은 클라이언트에게 코드의 기능을 분명히 암시한다.
하지만 이 메서드가 정적 팩터리 메서드임을 클라이언트가 쉽게 알아차릴 수 있을까?
메서드의 이름만 보고서는 이 메서드가 생성
의 기능을 하는지 쉽게 알아차리기 힘들 수 있다.
따라서 정적 팩터리 메서드 네이밍 컨벤션을 어느정도 지켜주는 편이 좋다.
public class Money {
private final int amount;
private Money(final int amount) {
this.amount = amount;
}
public static Money createMoneyForBetting(final int amount) {
validateBettingAmountRange(amount);
return new Money(amount);
}
private static void validateBettingAmountRange(final int amount) {
if (amount < 1_000 || amount > 100_000_000) {
throw new IllegalArgumentException("배팅 금액은 1,000 이상 100,000,000 이하여야 합니다");
}
}
//...
}
Money.createMoneyForBetting() 메서드는 기능을 예상할 수 있으면서도 생성 기능까지 암시하도록 create를 이름 앞에 덧붙였다.