정적 팩터리 메서드와 네이밍 컨벤션

후추·2023년 3월 13일
0

들어가기

정적 팩터리 메서드를 공부했다. 그러던 중 궁금한 점이 생겼다.

정적 팩터리 메서드를 다룬 유명한 서적 중 하나인 이펙티브 자바는 정적 팩터리 메서드의 장점 중 하나를 "이름을 가질 수 있는 것"으로 소개한다.

동시에 책 말미에는 정적 팩터리 메서드의 네이밍 컨벤션을 소개한다.

정적 팩터리 메서드의 네이밍 컨벤션

  • from : 하나의 매개 변수를 받아서 객체를 생성하는 메서드
  • of : 여러개의 매개 변수를 받아서 객체를 생성하는 메서드
  • valueOf : from과 of의 자세한 버전
  • getInstance | instance : 인스턴스를 생성하는 메서드. 같은 인스턴스임을 보장하지 않음.
  • newInstance | create : 인스턴스를 생성하는 메서드. 매번 새로운 인스턴스를 생성해 반환.
  • get[OtherType] : 다른 타입의 인스턴스를 생성. 같은 인스턴스임을 보장하지 않음.
  • new[OtherType] : 다른 타입의 새로운 인스턴스를 생성. 매번 새로운 인스턴스를 생성해 반환.

이름을 가질 수 있다는 것이 장점인데 네이밍 컨벤션이 있다니 어찌보면 모순이 아닌가?

팩터리 메서드의 이름이 단지 위 몇 가지로 제한된다면 이름을 갖는 것이 장점이 될 수 없다. new ClassName()(생성자 호출)과 크게 다를 게 없기 때문이다.

그렇다면 네이밍 컨벤션을 무작정 어겨야 하나?

이러한 고민들을 이어가다 이 글에 생각을 정리해 보았다.

네이밍 컨벤션

나는 다음과 같이 생각한다.

  • convention은 말 그대로 관습이다.
  • 관습을 따른 네이밍은 동료 개발자들이 이해하기 쉬운 친절한 코드가 된다.
  • 관습을 따르지 않은 네이밍은 동료 개발자에게 해석을 요구한다.
  • 메서드의 기능을 예상할 수 없는 부적절한 이름은 혼란을 야기할 수 있다.

결국 단순한 생성과 검증 로직 등을 포함하는 메서드에는 친절을 더해 네이밍 컨벤션을 따르는 게 좋다.

반면 메서드가 특정한 기능을 가져 이름으로 의도를 명확히 드러내야 한다면 컨벤션에서 벗어나도 괜찮다.

예시로 보기

1. from - 컨벤션 따르기

아래는 블랙잭 미션에서 사용된 돈 객체를 간단히 표현한 코드이다.

생성자를 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으로 짓는 것이 적절하다.

2. 컨벤션 안 따르기

이번엔 정적 팩터리 메서드에 로직이 추가되었다.

정적 팩터리 메서드가 베팅 금액을 위한 돈 객체를 생성해준다.

생성하는 과정에서 베팅 금액을 검증하는 로직을 갖는다.

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()로 이름을 지었다.

3. 컨벤션을 따르는 듯 안 따르는 듯

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를 이름 앞에 덧붙였다.

정리

  • 단순한 생성 및 검증 로직이라면 컨벤션을 따른다.
  • 특별한 생성 로직이 필요하다면 의미있는 이름을 새로 짓는다.
  • 웬만하면 컨벤션에서 크게 벗어나지 않는다.

0개의 댓글