8장 메서드 (2장)


날짜 : 2019-12-19 ~ 2020-01-02


정리

아이템 53. 인수 개수가 일정하지 않은 메서드를정의해야 한다면 가변인수가 반드시 필요하다. 메서드를 정의할 때 필수 매개변수는 가변인수 앞에 두고, 가변인수를 사용할 때는 성능문제까지 고려하자.

아이템 54. null이 아닌, 빈 배열이나 컬렉션을 반환하라. null을 반환하는 API는 상용하기 어렵고 오류 처리 코드도 늘어난다. 하지만 성능이 좋은 것도 아니다.

아이템 55. 값을 반환하지 못할 가능성이 있고, 호출 할 때마다 반환 값이 없을 가능성을 염두에 둬야하는 메서드라면 옵셔널을 반환해야 할 상황일 수 있다. 하지만 옵셔널 반환에는 성능 저하가 뒤따르니, 성능에 민감한 메서드라면 null을 반환하거나 예외를 던지는 편이 나을 수 있다. 옵셔널을 반환값 이외의 용도로 쓰는 경우는 거의 없다.

아이템 56. 문서화 주석은 API를 문서화하는 가장 훌륭하고 효과적인 방법이다. 공개 API라면 빠짐없이 설명을 달아야한다. 표준 규약을 일관되게 지키자. 문서화 주석에 임의의 HTML 태그를 사용할 수 있음을 기억하라. 단, HTML 메타문자는 특별하게 취급해야한다.


내용
아이템 53. 가변인수는 신중히 사용하라
● 가변인수 메서드는 명시한 타입의 인수를 0개 이상 받을 수 있다. 가변인수 메서드를 호출하면, 가장 먼저 인수의 개수와 길이가 같은 배열을 만들고 인수들을 이 배열에 저장하여 가변인수 메서드에 건낸다.

● 가변인수를 받을 때, 인수를 하나 이상 받아야한다면, 첫 번째로는 평범한 매개변수를 받고, 가변인수는 두 번째로 받는 것이 좋다.
// 잘못된 예
static int min(int... args) {
    if (args.length == 0)
        throw new IllegalArgumentException("인수 1개 이상 필요합니다.");
    int min = args[0];
    for (int i = 1; i < args.length; i++)
        if (args[i] < min) min = args[i];
    return min;
}


// 좋은 예
static int min(int firstArg, int... remainingArgs) {
    int min = firstArg;
    for (int arg : remainingArgs)

        if (arg < min) min = args;
    return min;
}
● 성능에는 가변변수가 걸림돌이 될 수 있다. 가변인수 메서드는 호출될 때마다 배열을 새로 하나 할당하고 초기화한다.
만약 인수가 3개 이상으로 사용하는 경우가 5%밖에 안된다면 다음을 고민하는 것이 좋다.

public void foo() {}
public void foo(int a1) {}
public void foo(int a1, int a2) {}
public void foo(int a1, int a2, int... rest) {}
또는 EnmSet의 정적 팩터리도 이 기법을 사용해 열거 타입 집합 생성 비용을 최소화할 수 있다.
아이템 54. null이 아닌, 빈 컬렉션이나 배열을 반환하라
● 컬렉션이 비어있으면 null를 반환한다면, 클라이언트는 이 null의 상황을 처리하는 방어 코드를 추가로 작성해야한다. 클라이언트에서 방어 코드를 빼먹으면 오류가 발생할 수 있다.
이런 경우 오류가 발생하면 나중에 발견될 가능성이 높기 때문에 처리하기 힘들 수 있다.

● 빈 컨테이너를 할당하는 데도 비용이 드니 null을 반환하는 것은 아니다. 성능 저하의 주범이라고 확인되지 않는한, 이정도의 성능 차이는 신경 쓸 수준이 못 된다. 또, 빈 컬렉션과 배열은 굳이 새로 할당하지 않고도 반환할 수 있다.
// 빈 컬렉션과 배열은 굳이 새로 할당하지 않고 반환하는 방법
public List<Cheese> getCheeses() {
    return new ArrayList<>(cheeseInStock);
}
또는 매번 똑같은 빈'불변' 컬렉션을 반환하면 성능 개선이 된다.
public List<Cheese> getCheeses() {
    return cheeseInStock.isEmpty() ? Collections.emptyList() : new ArrayList<>(cheeseInStock);
}

배열의 경우도 길이가 0인 배열을 반환하자. (단순히 성능 개선할 목적이라면 toArray에 넘기는 배열을 미리 할당하는 건 오히려 성능이 않 좋아질 수 있다.)
private static final Cheeses[] EMPTY_CHEESE = new Cheeses[0];
public List<Cheese> getCheeses() {
    return cheeseInStock.toArray(EMPTY_CHEESE);
}

아이템 55. 옵셔널 반환은 신중히 하라
● 자바 8 전에는 메서드가 특정 조건에서 값을 반환할 수 없을 때 취할 수 있는 선택지가 두 가지가 있다. 예외를 던지거나, null을 반환하는 것이다.
두 방법 모두 허점이 있다. 예외는 진짜 예외적인 상황에서만 사용해야 하며, 예외를 생성할 때 스택 추적 전체를 캡쳐하므로 비용도 만만치 않다. null을 반환하면 null을 처리하는 로직을 추가해야한다.

● 자바 8 부터 Optional<T>는 null이 아닌 T타입 참조를 하나 담거나, 혹은 아무것도 담지 않을 수 있다. 옵셔널은 원소를 최대 1개 가질 수 있는 불변 컬렉션이다.
보통은 T를 반환해야 하지만 특정 조건에서는 아무것도 반환하지 않아야 할 때 T 대신 Optional<T>를 반환하도록 선언하면 된다. 그러면 유효한 반환값이 없을 때는 빈 결과를 반환하는 메서드가 만들어진다. 예외를 던지는 메서드보다 유연하고 사용하기 쉽고, 오류 가능성도 줄어든다.

● 옵셔널을 반환하는 메서드에서는 절대 null을 반환하지 말자. 옵셔널은 검사 예외와 취지가 비슷하기 때문이다. 즉, 반환값이 없을 수도 있음을 API 사용자에게 명확히 알려준다.

● 반환값으로 옵셔널을 사용한다고 해서 무조건 득이 되는 것이 아니다. 컬렉션, 스트림, 배열, 옵셔널, 같은 컨테이너 타입은 옵셔널로 감싸면 안 된다. 이럴땐, 빈 List<T>를 반환하는 것이 좋다.

● 결과가 없을 수 있고, 클라이언트가 이 상황을 특별하게 처리해야 한다면 Optional<T>를 반환한다. 

● 박싱된 기본 타입을 담는 옵셔널은 따로 제공하니 이를 이용하는 것이 좋다.

●  옵셔널은 컬렉션의 키, 값, 원소나 배열의 원소로 사용하는 게 적절한 상황은 거의 없다.

● 옵셔널의 활용
// 기본 값을 지정할 수 있다.
String lastword = max(words).orElse("단어 없음");

// 원하는 예외를 던질 수 있다.
Toy myToy = max(words).orElseThrow(TemperTantrumException::new);

// 항상 값이 채워져 있다고 가정한다.
Element lastNoBleGas = max(Elemnts.NOBLE_GASES).get();

아이템 56. 공개된 API 요소에는 항상 문서화 주석을 작성하라
● API를 올바로 문서화하려면 공개된 모든 클래스, 인터페이스, 메서드, 필드 선어네 문서화 주석을 달아야 한다. 직렬화할 수 있는 클래스라면 직렬화 형태에  관해서도 적어야 한다. 문서화 주석이 없다면 자바독도 그저 공개 API 요소들의 선언만 나열해주는 게 전부다.

● 기본 생성자에는 문서화 주석을 달 방법이 없으니 공개 클래스는 절때 기본 생성자를 사용하면 안된다.

● 메서드용 문서화 주석에는 해당 메서드와 클라이언트 사이의 규약을 명료하게 기술해야 한다. 상속용으로 설계된 클래스의 메서드가 아니라면 무엇을 하는지를 기술해야 한다. (what을 기술해야함)

● 작성 방법
- 메서드를 호출하기 위한 전제조건과 메서드가 성공적으로 수행된 후에 만족해야 하는 사후조건도 모두 나열해야한다. 전제조건은 @throws 태그로 비검사 예외를 선언하여 암시적으로 기술 @param태그를 이용해 그 조건에 영향받는 매개변수에 기술할 수 있다.
-모든 매개변수에 @param 태그, 반환 타입이 void가 아니면 @return 태그, 발생할 가능성이 있는 모든 예외에 @throws 태그를 달아야 한다. 관례상 해당 태그에는 마침표를 붙이지 않는다. {@code ...코드...}로 예시를 보여줄수 있다.
-자바 8부턴 @implSpec으로 해당 메서드와 하위 클래스 사이의 계약을 설명하여, 하위 클래스들이 그 메서드를 상속하거나 super 키워드를 이용해 호출할 때 그 메서드가 어떻게 동작하는지를 명확하게 인지하고 사용하도록 해야한다. (자바독 명령줄에 -tag "implSpec:a:Implementation Requirements:" 스위치를 켜야한다.)
- 각 문서화 주석의 첫 번째 문장은 해당 요소의 요약 설명으로 간주된다. 요약설명은 반드시 대상의 기능을 고유하게 기술해야 한다. 즉, 한 클래스(혹은 인터페이스) 안에서 요약 설명이 똑같은 멤버(혹은 생성자)가 둘 이상이면 안된다. 마침표를 사용시 {@literal ...내용...} 을 사용하자
- 제네릭 타입이나 제네릭 메서드를 문서화할 때는 모든 타입 매개변수에 주석을 달아야 한다.
- 열거 타입을 문서화할 때는 상수들에도 주석을 달아야 한다.
- 애너테이션 타입을 문서화할 때는 멤버들에도 모두 주석을 달아야 한다.

● 클래스 혹은 정적 메서드가 스레드 안전하든 그렇지 않든, 스레드 안전 수준을 반드시 API 설명에 포함해야 한다. 또한 직렬화할 수 있는 클래스라면 직렬화 형태도 API 설명에 기술해야 한다.

● 자바독 참조 지침 또는 문서화 주석 작성법을 참조하자.


참고
●  Javadoc-guide(문서화 주석 작성법) : 오라클 홈페이지 자세히 적혀 있다. ( https://www.oracle.com/technetwork/java/javase/documentation/index-137868.html )

댓글

가장 많이 본 글