이펙티브 자바 -7-
메서드
메서드를 설계할 때 주의 할점
매개변수가 유효한지 검사하라
오류는 가능한 빨리(발생한 곳에서) 잡아야 한다. 오류를 즉시 잡지 못하면 해당 오류를 감지하기 어려워지고, 감지하더라도 오류의 발생 지점을 찾기 어려워 진다. 메서드 몸체가 실행되기 전에 매개변수를 확인한다면 잘못된 값이 넘어왔을 때 즉각적으로 예외를 던질 수 있다.
매서드가 직접 사용하지는 않으나 나중에 쓰기 위해 저장하는 매개 변수는 특히 더 신경 써서 검사해야 한다
정리
매서드나 생성자를 작성할 때면 그 매개변수들에 어떤 제약이 있을 지 생각해야 한다. 그 제약들을 문서화하고 메서드 시작 부분에서 명시적으로 검사해야 한다.
적시에 방어적 복사본을 만들라
클라이언트가 우리의 불변식을 깨뜨리려 혈안이 되어 있다고 가정하고 방어적으로 프로그래밍해야 한다. 어떤 객체든 그 객체의 허락 없이는 외부에서 내부를 수정하는 일은 불가능해야 한다.
Date는 불변 객체가 아니므로 불변 객체인 LocalDateTime, ZondedDateTime등을 사용하자
외부 공격으로 부터 인스턴스의 내부를 보호하려면 생성자에서 받은 가변 매개변수 각각을 방어적으로 복사 해야 한다.(깊은 복사). 그 후 인스턴스는 원본이 아닌 복사본을 사용한다.
매개변수의 유효성을 검사 하기 전에 방어적 복사본을 만들고, 이 복사본으로 유효성을 검사한다. 멀티 스레딩 환경이라면 원본 객체가 유효성을 검사한 후 복사본을 만드는 찰나의 취약한 순간에 다른 스레드가 원본 객체를 수정할 위험이 있기 때문이다.
매개 변수가 제 3자에 의해 확장 될수 있는 타입이라면 방어적 복사본을 만들 때 clone을 사용해서는 안된다.
인스턴스의 가변 필드를 사용자가 접근할 때 가변 필드의 방어적 복사본을 반환하라(실제 가변 필드의 레퍼런스가 아닌 새로운 가변 필드 인스턴스를 만들어서 반환해라)
정리
클래스가 클라이언트로부터 받는 혹은 클라이언트로 반환화는 구성요소가 가변이라면 그 요소는 반드시 방어적으로 복사해야 한다. 복사 비용이 너무 크거나 클라이언트가 그 요소를 잘못 수정할 일이 없음을 신뢰한다면 해당 요소를 수정하였을 때 책임이 클라이언트에 있음을 문서에 명시하자
메서드 시그니처를 신중히 설계하라
API 설계 요령이다.
메서드 이름을 신중히 짓자
항상 표준 명명 규칙을 따라야 한다. 이해할 수 있고. 같은 패키지에 속한 다른 이름들과 일관되게 짓는 게 최우선 목표다. 그 다음 목표는 개발자 커뮤니티에서 널리 받아들여지는 이름을 사용하는 것이다. 애매하다면 자바 라이브러리의 API 가이드를 참조하라.
공개된 편의 메서드를 너무 많이 만들지 말자
모든 메서드는 각각 자신의 소임을 다해야 한다. 메서드가 너무 많은 클래스는 익히고, 사용하고, 문서화하고, 테스트하고, 유지보수하기 어렵다. 인터페이스도 마찬가지다. 메서드가 너무 많으면 이를 구현하는 사람과 사용하는 사람 모두를 고통스럽게 한다. 클래스나 인터페이스는 자신의 기능을 완벽히 수행하는 메서드로 제공해야 한다. 확신이 서지 않으면 만들지 말자
매개변수 목록은 짧게 유지하자
4개 이하가 좋다. 4개가 넘어가면 매개변수를 전부 기억하기가 쉽지 않다. 같은 타입의 매개변수 여러 개가 연달아 나오는 경우가 특히 해롭다. 사용자가 매개변수 순서를 기억하기 여려울 뿐더러 실수로 순서를 바꿔 입력해도 컴파일되고 실행된다.
과하게 긴 매개변수 목록을 짧게 줄여주는 기술
- 여러 메서드로 쪼갠다. 쪼개진 매서드 각각은 원래 매개변수 목록의 부분집합을 받는다
- 매개변수 여러개를 묶어주는 도우미 클래스를 만든다. 잇따른 매개변수 몇 개를 독립된 하나의 개념으로 볼 수 있을 때 추천하는 방법이다. 예를 들어 카드 게임을 클래스로 만든다고 가정한다. 메서드를 호출할 때 카드의 숫자와 무늬를 뜻하는 두 매개변수는 항상 같은 순서로 전달된다면, 이 둘을 묶는 도우미 클래스를 만들어 하나의 매개변수로 주고 받을 수 있다
- 객체 생성에 사용한 빌더 패턴을 메서드 호출에 응용한다. 이 기법은 매개변수가 많을 때, 특히 그중 일부는 생각해도 괜찮을 때 도움이 된다. 먼저 모든 매개변수를 하나로 추상화한 객체를 정의하고, 클라이언트는 이 객체의 세터 메서드를 호출해 필요한 값을 설정하게 하는 것이다. 그 다음 execute 메서드를 호출해 앞서 설정한 매개 변수의 유효성을 검증한다. 마지막으로, 설정이 완료된 객체를 넘겨 원하는 계산을 수행한다
매개변수의 타입으로는 클래스보다는 인터페이스가 더 낫다
매개변수로 적합한 인터페이스가 있다면 그 인터페이스를 직접 사용하자, 다형성 측면에서 좋다
열거타입을 사용하라
열거 타입을 사용하면 코드를 일고 쓰기가 더 쉬워진다.
다중정의는 신중히 사용하라
예제
public class CollectionClassifier {
public static String classify(Set<?> s) {
return "집합";
}
public static String classify(List<?> lst) {
return "리스트";
}
public static String classify(Collection<?> c) {
return "그 외";
}
public static void main(String[] args) {
Collection<?>[] collections = {
new HashSet<String>(),
new ArrayList<BigInteger>(),
new HashMap<String, String>().values()
};
for (Collection<?> c : collections)
System.out.println(classify(c));
}
}
//결과 "그 외" 만 세번 출력된다.
//그 외
//그 외
//그 외
다중정의(overloading, 오버로딩)는 어느 메서드를 호출할지 컴파일 타임에 정해진다. 컴파일타임에는 for 문 안의 c는 항상 Collection<?> 타입이다. 런타임에는 타입이 매번 달라지지만, 호출할 매서드를 선택하는 데는 영향을 주지 못한다. 따라서 컴파일타임 매개변수 타입을 기준으로 항상 세 번째 메서드인 classify(Collection<?> c)만 호출된다.
재정의한 메서드(오버라이딩)는 동적으로 선택되고, 다중정의한 메서드(오버로딩)는 정적으로 선택된다.
다중정의가 혼동을 일으키는 상황을 피해야 한다. 안전적이고 보수적으로 가려면 매개변수가 같은 다중정의는 만들지 말자. 위의 예제 처럼 혼란을 일으키는 상황에는 다중정의하는 대신 이름을 다르게 지어주는 것이 좋다.
다중정의된 메서드들이 함수형 인터페이스를 인수로 받을 때, 비록 서로 다른 함수형 인터페이스라도 인수 위치가 같으면 혼란이 생긴다. 따라서 메서드를 다중정의할 때, 서로 다른 함수형 인터페이스라도 같은 위치의 인수로 받아서는 안 된다.
Object 외의 클래스 타입과 배열 타입은 근본적으로 다르다. Serializable 과 Cloneable 외의 인터페이스 타입과 배열 타입도 근본적으로 다르다. 상위/하위 관계가 아닌 두 클래스는 관련이 없으므로 근본적으로 다르다
정리
프로그래밍 언어가 다중정의를 허용한다고 해서 다중정의를 꼭 활용하란 뜻은 아니다. 일반적으로 매개변수 수가 같을 때 다중정의를 피하는 게 좋다.
가변인수는 신중히 사용하라
가변인수 메서드는 명시한 타입의 인수를 0개 이상 받아 인수의 개수와 길이가 같은 배열을 만들고 인수들을 배열에 저장하여 가변인수 메서드에 건네준다
성능에 민감한 상황이라면 가변인수가 걸림돌이 될수 있다. 가변인수 메서드는 호출될 때마다 배열을 새로 하나 할당하고 초기화 한다. 효율을 위해 가변인수를 다중정의 하자
정리
인수 개수가 일정하지 않은 메서드를 정의해야 한다면 가변인수가 필요하다. 메서드를 정의할 때 필수 매개변수는 가변인수 앞에 두고, 가변 인수를 사용할 때는 다중정의를 통해 성능 문제를 고려하자
null이 아닌, 빈 컬렉션이나 배열을 반환하라
nulll이 아닌, 빈 배열이나 컬렉션을 반환하라. null을 반환하는 API는 사용하기 어렵고 오류 처리 코드도 늘어난다.
Optional 반환은 신중히 하라
옵셔널을 반환하는 메서드에서는 절대 null을 반환하지 말자(return null 금지, 옵셔널로 감싸서 return). 옵셔널은 반환값이 없을 수도 있음을 API 사용자에게 명확히 알려준다. 따라서 클라이언트가 대처하는 코드를 만들도록 유도할 수 있다.
반환값을 옵셔널을 사용한다고 해서 무조건 득이 되는 것은 아니다. 컬랙션, 스트림, 배열, 옵셔널 같은 컨테이너 타입은 옵셔널로 감싸면 안된다. 빈 리스트나 빈 배열로 반환하는 것이 좋다.
Optional 사용 기본 규칙은 다음과 같다. 결과가 없을 수 있으며, 클라이언트가 이 상황을 특별하게 처리해야 한다면 Optional<T>를 반환한라
정리
값을 반환하지 못할 가능성이 있고, 호출할 때마다 반환값이 없을 가능성을 염두에 둬야 하는 메서드라면 옵셔널을 반환해야 할 상황일 수 있다. 하지만 옵셔널 반환에는 성능 저하가 뒤따르니, 성능에 민감한 메서드라면 null을 반환하거나 예외를 던지는 편이 나을 수 있다.
공개된 API 요소에는 항상 문서화 주석을 작성하라
우리의 API를 올바른 문서화하려면 공개된 모든 클래스, 인터페이스, 메서드, 필드 선언에 문서화 주석을 달아야 한다.
문서화 주석은 우리 API를 문서화하는 가장 훌륭하고 효과적인 방법이다. 공개 API라면 빠짐없이 설명을 달아야 한다. 표준 규약을 일관되게 지키자. (자세한 지침은 문서화 주석 작성법[Javadoc-guide] 참고)