Backend/Java

[Effective Java : Item 51] 메서드 시그니처를 신중히 설계하라

김세진 2024. 3. 12. 17:22
반응형

 

 

 

개요

 

메서드 시그니처란, 메서드의 이름과 매개변수 타입 리스트의 조합을 말한다. 이름이 같은 메서드라도 매개변수 리스트의 타입 순서나 구성이 다르다면 서로 다른 메서드 시그니처로 판단된다. 아이템 51에서는 메서드 시그니처와 관련된 API 설계 요령들을 간략하게 모아 소개한다.

 

 

API 설계 요령

 

메서드 이름을 신중히 짓자(아이템 68).

- 항상 표준 명명 규칙을 따라야 한다.

- 같은 패키지에 속한 다른 이름들과 일관되게 지어야 한다.

- 개발자 커뮤니티에서 널리 받아들여지는 이름을 사용하자.

- 너무 긴 이름은 피하자.

- 어렵다면 자바 라이브러리 API 가이드를 참조해도 좋다.

 


편의 메서드를 너무 많이 만들지 말자.

- 메서드가 너무 많은 클래스 및 인터페이스는 익히고, 사용하고, 문서화하고, 테스트하고, 유지보수하기 어렵다.

- 아주 자주 쓰이는 경우에만 별도의 약칭 메서드를 두도록 하자.

- 그 메서드가 필요한 이유에 대해 확신이 서지 않는다면 만들지 말자.

 


매개변수 목록은 짧게 유지하자.

- 4개 이하가 좋다.

- 같은 타입의 매개변수 여러 개가 연달아 나오는 경우가 가장 좋지 않다.

 

아래에 과하게 긴 매개변수 목록을 짧게 줄여주는 기술 세 가지를 소개한다.

 

1. 여러 메서드로 쪼갠다.

메서드를 원래 매개변수 목록의 부분집합을 받는 메서드들로 쪼갤 수 있다. 잘못하면 메서드의 수가 너무 많아질 수 있지만, 결합도가 낮고 기능을 원자적으로 제공하는 메서드로 만든다면, 오히려 메서드의 개수가 줄어들 수 있다.

 

java.util.List 인터페이스를 예로 들어, 만약 전체 리스트가 아닌 지정된 범위의 부분리스트에서 특정 원소를 찾는 메서드가 필요하다고 가정해보자. 만약 이 메서드를 구현하려면 '부분리스트의 시작, 부분리스트의 끝, 찾을 원소' 총 3가지의 매개변수를 받는 메서드를 만들어야 한다. 하지만 List에서는 부분리스트를 반환하는 subList와 원소의 인덱스를 반환하는 indexOf 메서드를 제공하기 때문에 이 메서드들을 적절히 조합하면 추가적인 메서드 없이 원하는 목적을 달성할 수 있다.

 

2. 매개변수 여러 개를 묶어주는 도우미 클래스를 만든다.

매개변수 여러 개를 묶어 독립된 개념으로 볼 수 있을 때 추천하는 기법이다. 그러한 매개변수를 묶어 정적 멤버 클래스로 구성한 뒤 하나의 매개변수로 주고받으면 보다 깔끔하게 구현할 수 있다.

 

public class CardGame {

    // 숫자와 무늬를 포함한 정적 멤버 클래스
    public static class Card {
        private int rank;
        private String suit;

        public Card(int rank, String suit) {
            this.rank = rank;
            this.suit = suit;
        }

        // getter 및 setter 생략
    }

    public void playCard(Card card, int players, int turn) {
        // 카드를 놓는 동작을 수행
        // ...
        System.out.println("Playing card: " + card.getRank() + " of " + card.getSuit()
                + " with " + players + " players, turn: " + turn);
    }

    public static void main(String[] args) {
        CardGame cardGame = new CardGame();
        Card card = new Card(7, "Hearts");
        cardGame.playCard(card, 4, 2);
    }
}

 

3. 빌더 패턴을 메서드 호출에 응용한다.

메서드의 매개변수가 많고, 그 중 일부를 생략해도 될 때 더욱 유용하게 쓸 수 있다. 모든 매개변수를 추상화한 객체를 정의하고, 클라이언트에서 이 객체의 setter 메서드를 호출해 필요한 매개변수들을 설정하도록 한다. 이후 필요한 매개변수의 설정이 완료되면 메서드로 객체를 넘겨 원하는 동작을 수행하도록 한다.

 

빌더 패턴을 적용하는 방법은 아이템 2 에서 보다 자세히 확인할 수 있다.

 


매개변수의 타입으로는 클래스보다는 인터페이스가 더 낫다(아이템 64).

매개변수로 적합한 인터페이스가 있다면 인터페이스를 직접 사용하는 것이 낫다. 예를 들어 매개변수로 HashMap을 받도록 설계했다면, 클라이언트는 HashMap을 제외한 다른 Map 구현체는 전달할 수 없다. 대신 Map을 받도록 설계했다면 클라이언트는 HashMap 말고도 TreeMap, ConcurrentHashMap 등 어떠한 Map 구현체도 인수로 전달할 수 있다.

 


매개변수로 boolean보다는 원소 2개짜리 열거 타입이 낫다.

실제로 boolean 타입을 요구하는 매개변수가 아니라, 타입을 구분지을 때 종종 boolean을 사용하곤 한다. 이러한 경우 boolean 대신 열거 타입을 사용한다면 몇 가지 장점을 취할 수 있다. 예를 들어 화씨(Fahrenheit)와 섭씨(Celsius) 온도를 구분하는 매개변수가 있다고 가정하고 아래에서 살펴보도록 하자.

 

1. 확장성이 증가한다.

boolean 타입의 isCelsius 매개변수를 만들어 isCelsius가 true면 섭씨, false면 화씨로 구분할 수 있다. 다만 이런 경우에 만약 캘빈(Kelvin)온도가 추가된다면 뒤늦게 enum 타입 등 적절한 매개변수 타입으로 변경해야 할 것이고, 추가적인 유지보수가 필요하다.

 

public enum TemperatureScale { FAHRENHEIT, CELSIUS }

 

만약 위와 같이 열거 타입으로 선언한다면, 뒤에 KELVIN 타입만 추가하면 되므로 확장성을 가져갈 수 있다.

 

2. 가독성이 좋아진다.

만약 온도계 클래스의 정적 팩터리 메서드가 온도 단위를 건네받아 인스턴스를 생성해야 하는 경우를 가정해보자. boolean을 사용할 경우, enum을 사용할 경우로 나누어 살펴보자. 

 

//boolean을 사용할 경우
Thermometer.newInstance(true)

// 열거 타입을 사용할 경우
Thermometer.newInstance(TemperatureScale.CELSIUS)

 

boolean을 사용할 경우 true가 매개변수로서 정확히 어떤 역할을 하는지 알기 힘든 반면, 열거 타입을 사용할 경우 보다 명확해지는 것을 알 수 있다.

 

3. 각 타입에 대한 의존성을 개별 열거 타입 상수의 메서드 안으로 리팩터링해 넣을 수도 있다(아이템 34).

예를 들어 다음과 같이 double 값을 받아 섭씨 온도로 변환해주는 메서드를 열거 타입 상수 각각에 정의해둘 수 있다.

 

public enum TemperatureScale {
    FAHRENHEIT {
        @Override
        public double toCelsius(double temperature) {
            return (temperature - 32) * 5 / 9;
        }
    },
    CELSIUS {
        @Override
        public double toCelsius(double temperature) {
            return temperature;
        }
    },
    KELVIN {
        @Override
        public double toCelsius(double temperature) {
            return temperature - 273.15;
        }
    };

    // 섭씨로 변환하는 메서드 추상 메서드
    public abstract double toCelsius(double temperature);

    public static void main(String[] args) {
        double temperatureInFahrenheit = 68.0;
        System.out.println("Temperature in Celsius: " + TemperatureScale.FAHRENHEIT.toCelsius(temperatureInFahrenheit));

        double temperatureInCelsius = 20.0;
        System.out.println("Temperature in Fahrenheit: " + TemperatureScale.CELSIUS.toCelsius(temperatureInCelsius));

        double temperatureInKelvin = 293.15;
        System.out.println("Temperature in Celsius: " + TemperatureScale.KELVIN.toCelsius(temperatureInKelvin));
    }
}

 

 

 

 

반응형