아이템 51. 메소드 시그니처를 신중히 설계하라. - ksw6169/effective-java GitHub Wiki
Goals
- 이번 아이템에서는 API 설계 요령을 다룬다.
- 이 요령들을 잘 활용하면 배우기 쉽고, 쓰기 쉬우며, 오류 가능성이 적은 API를 만들 수 있을 것이다.
1. 메소드 이름을 신중히 짓자.
- 항상 표준 명명 규칙을 따라야 한다.
- 같은 패키지에 속한 다른 이름들과 일관되게 짓는 게 최우선이다.
- 그 다음은 개발자 커뮤니티에서 널리 받아들여지는 이름을 사용하자.
- 긴 이름은 피하자. 애매하면 자바 라이브러리의 API 가이드를 참조하라.
2. 편의 메소드를 너무 많이 만들지 말자.
- 메소드가 너무 많은 클래스는 익히고, 사용하고, 문서화하고, 테스트하고, 유지보수하기 어렵다.
- 마찬가지로 인터페이스도 메소드가 너무 많으면 이를 구현, 사용하기가 어려워진다.
- 클래스나 인터페이스는 자신의 각 기능을 완벽하게 수행하는 메소드로 제공해야 한다.
- 아주 자주 사용되는 경우에만 별도의 약칭 메소드를 둬야 한다. 확신이 서지 않으면 만들지 말자.
3. 매개변수 목록은 짧게 유지하자.
- 4개가 넘어가면 매개변수를 전부 기억하기가 쉽지 않으므로 4개 이하가 좋다.
- 같은 타입의 매개변수 여러 개가 연달아 나오는 경우는 좋지 않다. 사용자가 매개변수 순서를 기억하기 어려울 뿐더러 실수로 순서를 바꿔 입력해도 그대로 컴파일되고 실행된다. 단지 의도와 다르게 동작할 뿐이다.
3-1. 과하게 긴 매개변수 목록을 짧게 줄여주는 방법
1. 여러 메소드로 쪼갠다.
- 이 방식은 하나의 메소드를 여러 개로 분리해 매개변수의 개수를 줄이는 방법이다.
- 쪼개진 메소드 각각은 원래 매개변수 목록의 부분집합을 받는다.
- 잘못하면 메소드가 너무 많아질 수 있지만, 직교성을 높여 오히려 메소드 수를 줄여주는 효과도 있다.
직교성(orthogonality)
기능이 원자적으로 잘 분리되어 있음을 나타내는 성질이다. 직교성이 높은 클래스는 공통점이 없는 기능들이 잘 분리되어 있으며, 기능을 원자적으로 쪼개 제공한다.
본문의 내용 중 "직교성을 높여 오히려 메소드 수를 줄여주는 효과도 있다" 는 말은 기능을 원자적으로 쪼개다 보면, 자연스럽게 중복이 줄고 결합성이 낮아져 작성해야 할 메소드 수가 줄어든다는 것을 의미한다. 일반적으로 직교성이 높은 설계는 가볍고 구현하기 쉽고 유연하고 강력하다.
여기서 주의할 점은 그렇다고 무한정 작게 나누는 게 능사는 아니라는 것이다. API 사용자의 눈높이에 맞게, 즉 API가 다루는 개념의 추상화 수준에 맞게 조절해야 한다. 또한 특정 조합의 패턴이 상당히 자주 사용되거나 최적화하여 성능을 크게 개선할 수 있다면 직교성이 낮아지더라도 편의 기능으로 제공하는 편이 나을 수도 있다.
이처럼 직교성은 절대적인 가치라기보다는 철학과 원칙을 가지고 일관되게 적용해야 하는 설계 특성이다.
2. 매개변수 여러 개를 묶어주는 도우미 클래스를 만든다.
- 매개변수 여러 개를 하나의 클래스로 만들어 메소드 매개변수의 개수를 줄이는 방법이다.
- 일반적으로 도우미 클래스는 정적 멤버 클래스로 둔다.
- 특히 잇따른 매개변수 몇 개를 독립된 하나의 개념으로 볼 수 있을 때 추천하는 기법이다.
- 예를 들어 카드게임을 클래스로 만든다고 하면 메소드 호출 시 카드의 숫자(rank)와 무늬(suit)를 뜻하는 두 매개변수를 항상 같은 순서로 전달할 것이다. 따라서 이 둘을 묶는 도우미 클래스를 만들어 하나의 매개변수로 주고 받으면 API는 물론 내부 구현도 깔끔해질 것이다.
3. 객체 생성에 사용한 빌더 패턴을 메소드 호출에 응용한다.
- 앞서 두 기법을 혼합한 방법으로 매개변수 여러 개를 하나의 클래스로 만들어 매개변수의 개수를 줄이는 방법이다.
- 먼저 모든 매개변수를 추상화한 객체 하나로 정의하고, 클라이언트에서 이 객체의 세터 메소드를 호출해 필요한 값을 설정하게 한다.
- 이 때 각 세터 메소드는 매개변수 하나 혹은 서로 연관된 몇 개만 설정하게 한다.
- 클라이언트는 먼저 필요한 매개변수를 다 설정한 다음, execute 메소드를 호출해 앞서 설정한 매개변수들의 유효성을 검사한다.
- 마지막으로 설정이 완료된 객체를 넘겨 원하는 계산을 수행한다.
- 정리하면 클라이언트에서 메소드로 전달할 값들을 객체에 설정하고 유효성 검사까지 수행한 뒤에 이를 메소드에 넘기는 것이다.
4. 매개변수의 타입으로는 클래스보다는 인터페이스가 낫다.
- 매개변수로 적합한 인터페이스가 있다면 이를 구현한 클래스가 아닌 그 인터페이스를 직접 사용하자.
- Map을 사용하면 HashMap, TreeMap 등 어떤 Map 구현체도 인수로 건넬 수 있다.
- 인터페이스 대신 클래스를 사용하면 클라이언트에게 특정 구현체만 사용하도록 제한하는 꼴이며, 혹시라도 입력 데이터가 다른 형태로 존재한다면 명시한 특정 구현체의 객체로 옮겨 담느라 비싼 복사 비용을 치러야 한다.
5. boolean 보다는 원소 2개짜리 열거 타입이 낫다.
- 단, 메소드 이름상 boolean을 받아야 의미가 더 명확할 때는 예외다.
- 열거 타입을 사용하면 코드를 읽고 쓰기가 더 쉬워지고, 나중에 선택지를 추가하기도 쉽다.
- 예를 들어 화씨온도(Fahrenheit)와 섭씨온도(Celsius)를 원소로 정의한 열거 타입을 매개변수로 사용한다고 해보자.
public enum TemperatureScale { FAHRENHEIT, CELSIUS }
온도계 클래스의 정적 팩토리 메소드가 이 열거 타입을 입력 받아 적합한 온도계 인스턴스를 생성한다고 하면 아래 코드와 같이 열거 타입을 사용하는 것이 사용자 입장에서 훨씬 명확하다.
// boolean 사용
Thermometer.newInstance(true)
// 열거 타입 사용 - 훨씬 명확하다!
Thermometer.newInstance(TemperatureScale.CELSIUS)
또한 나중에 캘빈온도도 지원해야 한다면 Thermometer에 캘빈온도(KELVIN) 만 추가해주면 된다. 이 뿐만 아니라 온도 단위에 대한 의존성을 개별 열거 타입 상수의 메소드 안으로 리팩토링해 넣을 수도 있다. 예컨대 double 값을 받아 섭씨온도로 변환해주는 메소드를 열거 타입 상수 각각에 정의해둘 수도 있다.
참고 자료
- Effective Java 3/E