본문 바로가기
Language/Effective java

메서드

by y.j 2022. 8. 4.
728x90

가변인수는 신중히 사용하라

가변인수는 인수를 0개 이상 받을 수 있도록 도와준다.

static int sum(int... args) {
    int result = 0;
    for(int arg : args)
        result += arg;
    return result;
}

가변인수 메서드를 호출하면, 가장 먼저 인수의 개수와 길이가 같은 배열을 만들고 인수들을 이 배열에 저장하여 가변인수 메서드에 건네준다.

 

신중하게 써야 하는 점은 아래코드를 예시로 들 수 있다. 아래코드는 소스가 지저분해질 뿐 아니라 컴파일타임이 아닌 런타임에 실패한다는 점이다.

static int sum(int... args) {
    if(args.length == 0)
        throw new IllegalArgumentException("인수가 1개 이상 필요합니다.");
    int result = args[0];
    for(int arg : args)
        result = Math.min(arg, result);
    return result;
}

 

이것을 방지하기 위해 첫 번째로는 평점한 매개변수를 받고, 가변인수를 두 번째로 받는 방법이 있다. ( 저자는 항상 컴파일 타임에 에러를 발견하는 것을 중요시 여기는 것으로 보인다. )

static int sum(int firstArg, int... args) {
    int result = firstArg;
    for(int arg : args)
        result = Math.min(arg, result);
    return result;
}

 

성능에 민감한 상황이라면 가변인수가 걸림돌이 된다. 왜냐하면 가변인수 메서드는 호출될 때마다 배열을 새로 하나 할당하고 초기화한다. 아래의 예시 같은 경우는 인수가 3개 이하로 사용될 확률이 95% 나머지 5%는 가변인수로 받는 것이다.

public void foo() { };
public void foo(int a1) { };
public void foo(int a1, int a2) { };
public void foo(int a1, int a2, int a3) { };
public void foo(int a1, int a2, int a3, int... rest) { };

 

null이 아닌, 빈 컬렉션이나 배열을 반환하라

컬렉션을 null처리하게 된다면 따로 null에 대한 상황을 처리해줘야 한다. 하지만 재고가 없다고 해서 특별히 취급할 이유는 없다.

private final List<Cheese> cheeseInStock = ... ;

public List<Cheese> getCheeses() {
    return cheeseInStock.isEmpty() ? null
            : new ArrayList<>(cheeseInStock);
}

위 코드 같이 작성된다면 null코드에 대한 방어적 코딩을 해주어야 하는데 수년 후에 발견 될 수도 있는 것이다.

 

수정 후

public List<Cheese> getCheeses() {
    return cheeseInStock.isEmpty() ? Collections.emptyList()
            : new ArrayList<>(cheeseInStock);
}

 

배열도 똑같이 0인 배열을 리턴해주면 된다.

 

옵셔널 반환은 신중히 하라

메서드가 특정 조건에서 값을 반환할 수 없을 때 취할 수 있는 선택지가 두 가지 있다. 예외를 던지거나 null을 반환하는 것이다. 하지만 두가지 다 허점이 있다. 예외는 정말 예외적인 상황에서 사용해야 하고 null을 반환하면 나름의 문제가 있다(방어적 코딩을 해야하거나..).

 

Optional<T>는 또 다른 대안책으로 나왔다. Optional<T>는 원소를 최대 1개 가질 수 있는 '불변' 컬렉션이다. null이 아닌 T타입 참조를 하나 담거나, 혹은 아무것도 담지 않을 수 있다. 

 

예외 코드

public static <E extends Comparable<E>> E max(Collections<E> c) {
    if(c.isEmpty())
        throw new IllegalArgumentException("빈 컬렉션");
    
    E result = null;
    for(E e : c) 
        if(result == null || e.compareTo(result) > 0)
            result = Objects.requireNonNull(e);
    
    return result;
}

 

Optional<T>를 반환하도록 한 메서드

public static <E extends Comparable<E>> Optional<E> max(Collections<E> c) {
    if(c.isEmpty())
        return Optional.empty();

    E result = null;
    for(E e : c)
        if(result == null || e.compareTo(result) > 0)
            result = Objects.requireNonNull(e);

    return Optional.of(result);
}

 

옵셔널을 반환하는 메서드에서는 절대 null을 반환하지 말자.

 

스트림의 종단 연산 중 상당수가 옵셔널을 반환한다. 반환타입을 Optional<E>로 해준다면 Optional을 생성해 줄 것이다.

public static <E extends Comparable<E>> Optional<E> max(Collection<E> c) {
    return c.stream().max(Comparator.naturalOrder());
}

 

옵셔널은 클라이언트의 값을 받지 못했을 때 취할 행동을 선택해야만 한다.

String lastWordInLexicon = max(words).orElse("단어 없음...");

 

원하는 예외를 던질 수 있다.

Toy myToy = max(Toy).orElseThrow(TemperTantrumException::max);

 

항상 값이 채워져 있다고 가정한다.

Element lastNobleGas = max(Elements.NOBLE_GAS).get();

 

filter, map, flatMap, ifPresent등의 고급 메서드들도 있다.

 

isPresent는 상당수의 상황에 대체 할 수 있는 메서드이며, Optional이 채워져 있으면 true, 아니며 false를 리턴한다.

ptional<ProcessHandle> parentProcess = ph.parent();
System.out.println("부모 PID : " + (parentProcess.isPresent() ?
        String.valueOf(parentProcess.get().pid()) : "N/A"));

 

map으로 사용하는 방법

Optional<ProcessHandle> parentProcess = ph.parent();
System.out.println("부모 PID : " + ph.parent().map(h -> String.valueOf(h.pid())).orElse("N/A"));

 

Stream<Optional<T>>로 받아서 Stream<T>에 건네 담아 처리하는 경우

streamOfOptionals
        .filter(Optional::isPresent)
        .map(Optional::get);
streamOfOptionals
        .flatMap(Optional::stream);

 

반환 값으로 옵셔널을 사용할 때 유의 점

  • 컬렉션, 스트림, 배열, 옵셔널 같은 컨테이너 타입은 옵셔널로 감싸면 안된다. Optional<List<T>>보다는 List<T>를 반환하는게 좋다. null처럼 다른 처리를 해줘야 하는 경우가 발생할 수 있기 때문이다.
  • 결과가 없을 수 있으며, 클라이언트가 이 상황을 특별하게 처리해야 한다면 Optional<T>를 반환한다. Optional<T>의 값을 꺼내려면 메서드를 호출해야 하기 때문에 한 단계를 더 거치게 된다. 성능이 중요한 상황에서는 사용하기 어렵다.
  • 박싱된 기본 타입을 담은 옵셔널을 반환하는 일은 없도록 하자. OptionalInt, OptionalDouble 같은 클래스는 값을 두겹이나 감싸기 때문에 무겁다.
  • Optional을 맵의 값으로써 사용하면 절대 안된다. 그럴 경우 빈Optional과 채워져 있는 Optional에 대해 각각 처리해야 되기 때문에 코드가 나빠지고 적절한 상황은 거의 없다.

 

공개된 API 요소에는 항상 문서화 주석을 작성하라

API를 쓸모 있게 하려면 잘 작성된 문서도 곁들어야 한다. 전통적으로 API문서는 사람이 직접 작성하므로 코드가 변경되면 함께 수정해줘야 한다. 

 

  • API를 올바른 문서화하려면 공개된 모든 클래스, 인터페이스, 메서드, 필드 선언에 문서화 주석을 달아야 한다.

        직렬화 할 수 있는 클래스라면 직렬화 형태에 관해서도 적어야 한다. 

 

  • 메서드용 문서화 주석에는 해당 메서드와 클라이언트 사이의 규약을 명료하게 규약해야 한다.

       how가 아니라 what을 기술해야 한다. 메서드를 호출하기 위한 전제조건을 모두 나열해야 한다.

       또한, 메서드를 사용 후에 만족해야 하는 사후 조건도 적어야 한다.

        @throws : 비검사 예외를 선언하여 암시적으로 기술한다.

        @param : 영향받는 매개변수에 기술 할 수 있다.

        @return : 반환 값을 설명하는 명사구를 쓴다.

        @code   : 태그로 감싼 내용을 코드용 폰트로 렌더링하며, HTML요소나 자바독 태그를 무시한다.

                        <pre>를 통해 여러 줄을 사용 할 수 있다.

/**
 * Translates the sign-magnitude representation of a BigInteger into a
 * BigInteger.  The sign is represented as an integer signum value: -1 for
 * negative, 0 for zero, or 1 for positive.  The magnitude is a byte array
 * in <i>big-endian</i> byte-order: the most significant byte is the
 * zeroth element.  A zero-length magnitude array is permissible, and will
 * result in a BigInteger value of 0, whether signum is -1, 0 or 1.  The
 * {@code magnitude} array is assumed to be unchanged for the duration of
 * the constructor call.
 *
 * @param  signum signum of the number (-1 for negative, 0 for zero, 1
 *         for positive).
 * @param  magnitude big-endian binary representation of the magnitude of
 *         the number.
 * @throws NumberFormatException {@code signum} is not one of the three
 *         legal values (-1, 0, and 1), or {@code signum} is 0 and
 *         {@code magnitude} contains one or more non-zero bytes.
 */

위에 예시에서 <i>태그처럼 html태그가 있는 것이 보인다. 자바독 유틸리티는 문서화 주석을 HTML로 변환하므로 문서화 주석 안의 HTML요소들이 최종 HTML문서에 반영된다.

 

        @implSpec : 해당 메서드와 하위 클래스 사이의 계약을 설명한다.

                             * -tag "implSpec:a:Implemetation Requirements:" 를 켜주어야 함.

/**
 * Return true if this collections is empty.
 * 
 * @implSpec 
 * This implementation returns {@code this.size() == 0}.
 * @return true if this collections is empty.
 */

      @literal    : API설명에 <, >, & 등의 HTML 메타문자를 포함시키려면 특별한 처리를 해준다.

 

  • 제네릭 타입이나 제네릭 메서드를 문서화할 때는 모든 타입 매개변수에 주석을 달아야 한다.

 

  • 열거 타입을 문서화 할 때는 상수들에도 주석을 달아야 한다.

 

  • 에너테이션 타입을 문서화할 때는 멤버들에도 모두 주석을 달아야 한다.
public enum OrchestraSection {
    /** 풀루트, 클라리넷, 오보 같은 목관악기. */
    WOODWIND,

    /** 프렌치 호른, 트럼펫 같은 금관악기. */
    BRASS,

    /** 팀파니, 심벌즈 같은 타악기. */
    PERCUSSION,

    /** 바이올린, 첼로 같은 현악기. */
    STRING;
}

 

  • 클래스 혹은 정적 메서드가 스레드 안전하든 그렇지 않든, 스레드 안전 수준을 반드시 API 설명에 포함해야 한다.

 

 

 

728x90

'Language > Effective java' 카테고리의 다른 글

일반적인 프로그래밍 원칙  (0) 2022.08.10
일반적인 프로그래밍 원칙  (0) 2022.08.08
메서드  (0) 2022.08.01
메서드  (0) 2022.08.01
람다와 스트림  (0) 2022.07.29

댓글