Skip to content

Latest commit

 

History

History
740 lines (572 loc) · 27.8 KB

8장 - 메서드.md

File metadata and controls

740 lines (572 loc) · 27.8 KB

8장 - 메서드


💡 매개변수가 유효한지 검사하라

“매개변수의 제약들을 문서화하고 메서드 코드 시작 부분에서 명시적으로 검사하자”

1. 매개변수 검사

  • 매개변수 검사를 수행하지 않았을 때 문제점
    1. 메서드가 수행되는 중간에 모호한 예외를 던지며 실패할 수 있음.
    2. 메서드가 잘 수행되지만 잘못된 결과를 반환할 수 있음
    3. 메서드는 문제없이 수행됐지만, 어떤 객체를 이상한 상태로 만들어서 미래의 알 수 없는 시점에 이 메서드와는 관련 없는 오류를 낼 수 있음.

→ 매개변수 검사에 실패하면 실패 원자성(failure atomicity)을 어기는 결과를 낳을 수 있음.

  • 매개변수 검사는 메서드 몸체가 실행되기 전에 진행해야 함.
  • 매개변수의 제약을 문서화한다면 그 제약을 어겼을 때 발생하는 예외도 함께 기술해야 함.

2. 단언문(assert)

  • 공개되지 않은 메서드라면 패키지 제작자가 메서드가 호출되는 상황을 통제할 수 있음.
    • 따라서 오직 유효한 값만이 메서드에 넘겨지리라는 것을 보증할 수 있고, 그렇게 해야 함.

public이 아닌 메서드라면 단언문(assert)을 사용해 매개변수 유효성을 검증할 수 있음.

  • 단언문 설명
    1. 실패하면 AssertionError 를 던진다.
    2. 런타임에 아무런 효과도, 아무런 성능 저하도 없다.


💡 적시에 방어적 복사본을 만들라

“클래스가 클라이언트로부터 받는 혹은 클라이언트로 반환하는 구성요소가 가변이라면 그 요소는 반드시 방어적으로 복사해야 한다”

1. 방어적 복사 예시

// 기간을 표현하는 클래스 - 불변식을 지키지 못했다.
public final class Period {
    private final Date start;
    private final Date end;

    /**
     * @param  start 시작 시각
     * @param  end 종료 시각. 시작 시각보다 뒤여야 한다.
     * @throws IllegalArgumentException 시작 시각이 종료 시각보다 늦을 때 발생한다.
     * @throws NullPointerException start나 end가 null이면 발생한다.
     */
    public Period(Date start, Date end) {
        if (start.compareTo(end) > 0)
            throw new IllegalArgumentException(
                    start + "가 " + end + "보다 늦다.");
        this.start = start;
        this.end   = end;
    }

    public Date start() {
        return start;
    }
    public Date end() {
        return end;
    }

    public String toString() {
        return start + " - " + end;
    }
    // 나머지 코드 생략
}
  • 위의 코드에서 Date가 가변이라는 사실을 이용하면 쉽게 불변식을 깨뜨릴 수 있음
// Period 인스턴스의 불변식을 깨뜨리는 예시
Date start = new Date();
Date end = new Date();
Period p = new Period(start, end);
end.setYear(78); // p의 내부를 쉽게 수정.
  • [참고] Date 객체는 낡은 API이니 새로운 코드를 작성할 때는 더 이상 사용하면 안 됨
    • LocalDateTime이나 ZonedDateTime 혹은 Instant를 사용하자
  • 그렇다면 Period불변으로 만드는 방법은 무엇일까?

→ 생성자에서 받은 가변 매개변수를 각각 **방어적으로 복사(defensive copy)**하자

// 수정한 생성자 - 매개변수의 방어적 복사본을 만든다.
public Period(Date start, Date end) {
    this.start = new Date(start.getTime());
    this.end   = new Date(end.getTime());

		// 매개변수 유효성 검사
    if (this.start.compareTo(this.end) > 0)
        throw new IllegalArgumentException(
                this.start + "가 " + this.end + "보다 늦다.");
}
  • 매개변수의 유효성을 검사하기 전에 방어적 복사본을 만들고, 이 복사본으로 유효성을 검사한 점에 주목하자.
    • 반드시 이렇게 작성해야 함.
    • Why? 멀티스레딩 환경이라면 원본 객체의 유효성을 검사한 후 복사본을 만드는 그 찰나의 취약한 순간에 다른 스레드가 원본 객체를 수정할 위험이 있기 때문.
      • 이러한 공격을 검사시점/사용시점(time-of-check/time-of-use), TOCTOU 공격이라고 함.
  • 아직 Period 클래스는 취약하다. 접근자가 내부의 가변 정보를 직접 드러내고 있음. 아래와 같은 공격이 가능함
Date start = new Date();
Date end = new Date();
Period p = new Period(start, end);
p.end().setYear(78); // p의 내부를 쉽게 변경
  • 이 공격을 막으려면 아래와 같이 단순히 접근자가 가변 필드의 방어적 복사본을 반환하면 됨.
// 수정한 접근자 - 필드의 방어적 복사본을 반환한다.
public Date start() {
    return new Date(start.getTime());
}

public Date end() {
    return new Date(end.getTime()) ;
}

→ 이제 Period 자신 말고는 가변 필드에 접근할 방법이 없다. (모든 필드가 객체 안에 완벽히 캡슐화)

  • 교훈 : “되도록 불변 객체들을 조합해 객체를 구성해야 방어적 복사를 할 일이 줄어든다

2. 방어적 복사의 수행

  • 가변 객체라면 그 객체가 클래스에 넘겨진 뒤 임의로 변경되어도 그 클래스가 문제없이 동작할지를 따져보자

확신할 수 없다면 방어적 복사본을 저장하자

  • 방어적 복사를 수행하지 않아도 되는 경우
    1. (같은 패키지에 속하는 등의 이유로) 클라이언트가 컴포넌트 내부를 수정하지 않으리라 확신할 수 있을 때
    2. 방어적 복사 비용이 너무 클 때.

→ 이런 경우에 방어적 복사를 수행하는 대신 해당 구성요소를 수정했을 때의 책임이 클라이언트에 있음을 문서에 명시하자.



💡 메서드 시그니처를 신중히 설계하라

“API 설계 요령”

1. 메서드 이름을 신중히 짓자

  • 항상 표준 명명 규칙을 따라야 함.
  • 같은 패키지에 속한 다른 이름들과 일관되게 짓는 게 최우선 목표
  • 개발자 커뮤니티에서 널리 받아들여지는 이름을 사용하는 것이 그 다음 목표
  • 애매하면 자바 라이브러리 API 가이드를 참조하자.

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

  • 메서드가 너무 많은 클래스(혹은 인터페이스)는 익히고, 사용하고, 문서화하고, 테스트하고, 유지보수하기 어려움.

→ 확신이 서지 않으면 만들지 말자


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

  • 4개 이하가 좋음
  • 같은 타입의 매개변수가 연달아 나오는 것은 해롭다..
    • 실수로 순서를 바꿔 입력해도 그대로 컴파일되고 실행 됨. 단지 의도와 다르게 동작할 뿐..
  • 과하게 긴 매개변수 목록을 짧게 줄여주는 기술 3가지
    1. 여러 메서드로 쪼갠다.
    2. 매개변수 여러 개를 묶어주는 도우미 클래스를 만든다.
      • 일반적으로 이런 도우미 클래스는 정적 멤버 클래스로 둠
    3. 빌더 패턴을 메서드 호출에 응용한다.
      • 매개변수를 하나로 추상화한 객체를 정의하고, 클라이언트에서 이 객체의 세터(setter) 메서드를 호출해 필요한 값을 설정하게 하는 방식.
      • 유효성 검증 및 설정이 완료된 객체를 메서드에 넘기면 됨.
      • 매개변수가 많고 그 중 일부는 생략해도 괜찮을 때 도움이 되는 방식

4. 매개변수의 타입으로는 클래스보다는 인터페이스가 더 낫다

  • 인터페이스 대신 클래스를 사용하면 클라이언트에게 특정 구현체만 사용하도록 제한하는 꼴.
    • 혹시라도 입력 데이터가 다른 형태로 존재한다면 명시한 특정 구현체로 옮겨 담느라 비싼 복사 비용을 치러야 함.

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

  • 메서드 이름상 boolean을 받아야 의미가 더 명확할 경우는 예외임.
  • Thermometer.newInstance(true) 보다는 Thermometer.newInstance(TemperatureScale.CELSIUS) 가 하는 일을 훨씬 명확히 알려줌.


💡 다중정의는 신중히 사용하라

“프로그래밍 언어가 다중정의를 허용한다고 해서 다중정의를 꼭 활용하라는 뜻은 아니다”

1. 다중정의 메서드 선택 시점

// 컬렉션 분류기 - 오류! 이 프로그램은 무엇을 출력할까?
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));
    }
}
  • 위 프로그램은 무엇을 출력할까?
    • “집합”, “리스트”, “그 외”를 출력할 것으로 예상.
  • 이상하게도 위 프로그램은 “그외”만 세번 연달아 출력한다.
  • 이유 : 다중정의 메서드의 선택은 컴파일타임에 오직 매개변수의 컴파일타임 타입에 의해 이뤄지기 때문
  • 즉, main메서드에서 for문 안의 c는 항상 Collection<?>타입이므로 항상 세 번째 classify(Collection<?> c)가 호출된 것.
  • 의도대로 동작시키려면 아래 코드처럼 classify 메서드를 하나로 합친 후 instanceof로 검사하면 됨
public static String classify(Collection<?> c) {
    return c instanceof Set  ? "집합" :
            c instanceof List ? "리스트" : "그 외";
}

2. 다중정의 주의점

  • 다중정의가 혼동을 일으키는 상황을 피해야 한다.
    • API 사용자가 매개변수를 넘기면서 어떤 다중정의 메서드가 호출될지를 모른다면 프로그램이 오동작하기 쉽다.
  • 안전하고 보수적으로 가려면 매개변수 수가 같은 다중정의는 만들지 말자
    • 다중정의하는 대신 메서드 이름을 다르게 지어주는 길도 항상 열려 있음.
  • 메서드를 다중정의할 때, 서로 다른 함수형 인터페이스라도 같은 위치의 인수로 받아서는 안 된다.
    • 서로 다른 함수형 인터페이스라도 서로 근본적으로 다르지 않음.


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

“메서드 정의 시 필수 매개변수는 가변인수 앞에 두고, 가변인수를 사용할 때는 성능 문제까지 고려하자”

1. 간단한 가변인수 활용 예시

// 간단한 가변인수 활용 예
static int sum(int... args) {
    int sum = 0;
    for (int arg : args)
        sum += arg;
    return sum;
}
  • 위 코드는 입력받은 int 인수들의 합을 계산해주는 가변인수 메서드임.(인수가 0개여도 됨)

2. 인수가 1개 이상일 때

  • 잘못된 예시
// 인수가 1개 이상이어야 하는 가변인수 메서드 - 잘못 구현한 예!
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;
}
  • 위 코드의 문제점
    1. 인수를 0개만 넣어 호출하면 (컴파일타임이 아닌) 런타임에 실패 함.
    2. 코드도 지저분함. args 유효성 검사를 명시적으로 해야함.
  • 올바른 예시
// 인수가 1개 이상이어야 할 때 가변인수를 제대로 사용하는 방법
static int min(int firstArg, int... remainingArgs) {
    int min = firstArg;
    for (int arg : remainingArgs)
        if (arg < min)
            min = arg;
    return min;
}
  • 첫 번째로는 평범한 매개변수를 받고, 두 번째로 가변인수를 받으면 잘못된 예시의 문제점들이 말끔히 해결됨.

3. 성능 최적화

  • 예시) 메서드 호출의 95%가 인수를 3개 이하로 사용하는 상황

→ 아래와 같이 인수가 0개인 것부터 4개인 것까지, 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) {}
  • 위와 같이 구현하면 마지막 다중정의 메서드가 인수 4개 이상인 5%의 호출을 담당함

메서드 호출 중 단 5%만이 배열을 생성하게 됨

  • 대다수의 성능 최적화와 마찬가지로 이 기법도 보통 때는 별 이득이 없지만, 꼭 필요한 특수한 상황에서 사막의 오아시스가 되어줄 것임.


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

null을 반환하는 API는 사용하기 어렵고 오류처리 코드도 늘어난다”

1. null을 반환

// 재고가 하나도 없을 때 null을 반환하는 예
public List<Cheese> getCheeses() {
		return cheesesInStock.isEmpty() ? null
				: new ArrayList<>(cheesesInStock);
}
  • 이 코드처럼 null을 반환한다면, 클라이언트는 이 null 상황을 처리하는 코드를 아래처럼 추가로 작성해야 함
List<Cheese> cheeses = shop.getCheeses();
if(cheeses != null && cheeses.contains(Cheese.STILTON))
		System.out.println("좋았어, 바로 그거야.");
  • 클라이언트는 cheeses != null 처럼 방어코드를 항상 작성해줘야 함.

2. 빈 컬렉션을 반환

// 빈 컬렉션을 반환하는 올바른 예
public List<Cheese> getCheeses() {
		return new ArrayList<>(cheesesInStock);
}
  • 사용 패턴에 따라 빈 컬렉션 할당이 성능 저하의 주범이 되는 경우는 어떻게 하지?
    • Collections.emptyList , Collections.emptySet 과 같은 빈 불변 컬렉션을 반환하면 됨
  • Collections.emptyList 사용 예시
// 최적화 - 빈 컬렉션을 매번 새로 할당하지 않도록 함.
public List<Cheese> getCheeses() {
		return cheesesInStock.isEmpty() ? Collections.emptyList()
				: new ArrayList<>(cheesesInStock);
}
  • 배열을 쓸 때도 마찬가지임

→ 절대 null을 반환하지 말고 길이가 0인 배열을 반환하라

// 길이가 0일 수도 있는 배열을 반환하는 올바른 방법
public Cheese[] getCheeses() {
		return cheesesInStock.toArray(new Cheeses[0]);
}
  • 이 방식이 성능을 떨어뜨릴 것 같다면 길이 0짜리 배열을 미리 선언해두고 반환하면 됨.


💡 옵셔널 반환은 신중히 하라

“옵셔널 반환에는 성능 저하가 뒤따르니, 성능이 민감한 메서드라면 null 을 반환하거나 예외를 던지는 편이 나을 수 있다”

1. Optional

  • Optional<T>는 null이 아닌 T 타입 참조를 하나 담거나, 혹은 아무것도 담지 않을 수 있음
  • ‘비었다’ : 아무것도 담지 않은 옵셔널
  • ‘비지 않았다’ : 어떤 값을 담은 옵셔널
  • 옵셔널은 원소를 최대 1개 가질 수 있는 불변 컬렉션임
  • 옵셔널을 반환하는 메서드는 예외를 던지는 메서드보다 유연하고 사용하기 쉬우며, null을 반환하는 메서드보다 오류 가능성이 작다

2. 옵셔널 반환 예시

// 컬렉션에서 최댓값을 구해 Optional<E>로 반환한다. 
public static <E extends Comparable<E>>
Optional<E> max(Collection<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);
}
  • Optional.empty() : 빈 옵셔널 생성
  • Optional.of(value) : 값이 든 옵셔널 생성
    • value 파라미터에 null을 넘기면 NullPointerException을 던지니 주의하자
  • Optional.ofNullable(value) : null 값도 허용하는 옵셔널 생성
  • 스트림의 종단 연산 중 상당수가 옵셔널을 반환 함.

→ 위의 메서드를 아래 코드처럼 리팩터링할 수 있음.

// 컬렉션에서 최댓값을 구해 Optional<E>로 반환한다. - 스트림 버전
public static <E extends Comparable<E>>
Optional<E> max(Collection<E> c) {
    return c.stream().max(Comparator.naturalOrder());
}

3. 옵셔널 활용

  • 기본값을 설정하는 방법
// 기본 값을 정해둘 수 있음
String lastWordInLexicon = max(words).orElse("단어 없음...");
  • 원하는 예외를 던지는 방법
Toy myToy = max(toys).orElseThrow(TemperTantrumException::new)
  • 항상 값이 채워져 있다고 가정
Element lastNobleFas = max(Elements.NOBLE_GASES).get();
  • isPresent() : 옵셔널이 채워져 있으면 true, 비어 있으면 false를 반환

4. 옵셔널 사용 규칙

  • 어떤 경우에 메서드 반환 타입 T 대신 Optional<T> 로 선언해야 할까?

→ 기본 규칙 : 결과가 없을 수 있으며, 클라이언트가 이 상황을 특별하게 처리해야 한다면 Optional<T>를 반환

  • 컬렉션, 스트림, 배열 같은 컨테이너 타입은 옵셔널로 감싸면 안 됨.
    • ex) Optional<List<T>>를 반환하지 말고 List<T>를 반환하자
    • 빈 컨테이너를 그대로 반환하면 클라이언트에 옵셔널 처리코드를 넣지 않아도 됨.
  • 박싱된 기본 타입을 담은 옵셔널을 반환하는 일은 없도록 하자.
    • 박싱된 기본 타입을 담는 옵셔널은 기본 타입보다 두 겹이나 값을 감싸서 무거울 수 밖에 없음.
    • 대체채로 int, long, double 전용 옵셔널 클래스들이 있음
      • OptionalInt, OptionalLong, OptionalDouble
  • 옵셔널을 컬렉션의 키, 값, 원소나 배열의 원소로 사용하는 게 적절한 상황은 거의 없다.
    • ex) 맵의 키로 옵셔널을 사용하는 경우, 맵 안에 키가 없다는 사실을 나타내는 방법이 두 가지가 됨. → 쓸데없이 복잡성만 높여서 혼란과 오류 가능성을 키울 뿐임.
  • 옵셔널 반환의 성능 저하
    • Optional 도 엄연히 새로 할당하고 초기화해야 하는 객체이고, 그 안에서 값을 꺼내려면 메서드를 호출해야 함.
    • 성능이 중요한 상황에서는 옵셔널보다는 null을 반환하거나 예외를 던지는 편이 나을 수 있음


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

문서화 주석은 API를 문서화하는 가장 훌륭하고 효과적인 방법

1. 자바독(Javadoc) 유틸리티


2. 문서화 주석 지침

  • API를 올바르게 문서화하려면 공개된 모든 클래스, 인터페이스, 메서드, 필드 선언에 문서화 주석을 달아야 함.
  • 메서드용 문서화 주석에는 해당 메서드와 클라이언트 사이의 규약을 명료하게 기술해야 함.
  • how(메서드가 어떻게 동작하는지)가 아니라 what(무엇을 하는지)를 기술해야 함
  • 메서드를 호출하기 위한 전제조건(precondition)을 모두 나열해야 함.
    • 일반적으로 전제조건은 @throw 태그로 비검사 예외를 선언하여 암시적으로 기술
    • @param 태그를 이용해 그 조건에 영향받는 매개변수에 기술할 수도 있음
  • 메서드가 성공적으로 수행된 후 만족해야 하는 사후조건(postcondition)도 모두 나열해야 함.
  • 부작용도 문서화해야 함.
    • 부작용 : 사후조건으로 명확히 나타나지는 않지만 시스템의 상태에 어떠한 변화를 가져오는 것
    • ex) 백그라운드 스레드를 시작시키는 메서드
  • 메서드의 계약(contract)을 완벽히 기술하려면 모든 매개변수에 @param 태그를, 반환 타입이 void가 아니라면 @return 태그를, 발생할 가능성이 있는 (검사든 비검사든) 모든 예외에 @thorw 태그를 달아야 한다.
  • 태그 사용 관례
    • 관례상 @param 태그와 @return 태그의 설명은 해당 매개변수가 뜻하는 값이나 반환값을 설명하는 명사구를 사용함.
    • 관례상 @param , @return, @throws 태그의 설명에는 마침표를 붙이지 않음
  • {@code} 태그
    1. 태그로 감싼 내용을 코드용 폰트로 렌더링
    2. 태그로 감싼 내용에 포함된 HTML요소나 다른 자바독 태그를 무시함.
    • 여러 줄로 된 코드 예시를 넣으려면 <pre>{@code ... 코드 ... }</pre>처럼 사용
  • 자기사용 패턴은 자바 8에 추가된 @implSpec 태그로 문서화한다.
  • API 설명에 <, >, & 등의 HTML 메타문자를 포함시키려면 {@literal} 태그로 감싸자
  • 한 클래스(혹은 인터페이스) 안에서 요약 설명이 똑같은 멤버(혹은 생성자)가 둘 이상이면 안 됨.
  • 클래스, 인터페이스, 필드의 요약 설명은 대상을 설명하는 명사절이어야 함.
  • 제네릭 타입이나 제네릭 메서드를 문서화 할 때는 모든 타입 매개변수에 주석을 달아야 함.
  • 열거 타입을 문서화할 때는 상수들에도 주석을 달아야 함.
  • 어노테이션 타입을 문서화할 때는 멤버들에도 모두 주석을 달아야 함.
  • 패키지를 설명하는 문서화 주석은 package-info.java 파일에 작성한다.
  • 클래스 혹은 정적 메서드가 스레드 안전하든 그렇지 않든, 스레드 안전 수준을 반드시 API설명에 포함해야 한다.
  • {@inheritDoc} 태그를 사용해 상위 타입의 문서화 주석 일부를 상속할 수 있음.

3. 문서화 주석 사용 예시

// 문서화 주석 예
public class DocExamples<E> {
    // 메서드 주석 
    /**
     * Returns the element at the specified position in this list.
     *
     * <p>This method is <i>not</i> guaranteed to run in constant
     * time. In some implementations it may run in time proportional
     * to the element position.
     *
     * @param  index index of element to return; must be
     *         non-negative and less than the size of this list
     * @return the element at the specified position in this list
     * @throws IndexOutOfBoundsException if the index is out of range
     *         ({@code index < 0 || index >= this.size()})
     */
    E get(int index) {
        return null;
    }
    // 한글 버전 
    // /**
    //  * 이 리스트에서 지정한 위치의 원소를 반환한다.
    //  *
    //  * <p>이 메서드는 상수 시간에 수행됨을 보장하지 <i>않는다</i>. 구현에 따라
    //  * 원소의 위치에 비례해 시간이 걸릴 수도 있다.
    //  *
    //  * @param  index 반환할 원소의 인덱스; 0 이상이고 리스트 크기보다 작아야 한다.
    //  * @return 이 리스트에서 지정한 위치의 원소
    //  * @throws IndexOutOfBoundsException index가 범위를 벗어나면,
    //  * 즉, ({@code index < 0 || index >= this.size()})이면 발생한다.
    //  */
    // E get(int index) {
    //     return null;
    // }

    // 자기사용 패턴 등 내부 구현 방식을 명확히 드러내기 위해 @implSpec 사용 (335쪽)
    /**
     * Returns true if this collection is empty.
     *
     * @implSpec This implementation returns {@code this.size() == 0}.
     *
     * @return true if this collection is empty
     */
    public boolean isEmpty() {
        return false;
    }
    // 한글 버전
    // /**
    //  * 이 컬렉션이 비었다면 true를 반환한다.
    //  *
    //  * @implSpec 이 구현은 {@code this.size() == 0}의 결과를 반환한다.
    //  *
    //  * @return 이 컬렉션이 비었다면 true, 그렇지 않으면 false
    //  */
    // public boolean isEmpty() {
    //     return false;
    // }

    
    // 문서화 주석에 HTML이나 자바독 메타문자를 포함시키기 위해 @literal 태그 사용 (336쪽)
    /**
     * A geometric series converges if {@literal |r| < 1}.
     */
    public void fragment() {
    }
    // 한글 버전 
    // /**
    //  * {@literal |r| < 1}이면 기하 수열이 수렴한다.
    //  */
    // public void fragment() {
    // }

    // 문서화 주석 첫 '문장'에 마침표가 있을 때 요약 설명 처리 (337쪽)
    /**
     * A suspect, such as Colonel Mustard or {@literal Mrs. Peacock}.
     */
    public enum Suspect {
        MISS_SCARLETT, PROFESSOR_PLUM, MRS_PEACOCK, MR_GREEN, COLONEL_MUSTARD, MRS_WHITE
    }
    // 한글 버전
    // /**
    //  * 머스타드 대령이나 {@literal Mrs. 피콕} 같은 용의자.
    //  */
    // public enum Suspect {
    //     MISS_SCARLETT, PROFESSOR_PLUM, MRS_PEACOCK, MR_GREEN, COLONEL_MUSTARD, MRS_WHITE
    // }

    // 자바독 문서에 색인 추가하기 - 자바 9부터 지원 
    /**
     * This method complies with the {@index IEEE 754} standard.
     */
    public void fragment2() {
    }
    // 한글 버전 
    // /**
    //  * 이 메서드는 {@index IEEE 754} 표준을 준수한다.
    //  */
    // public void fragment2() {
    // }

    
    // 열거 상수 문서화 
    /**
     * An instrument section of a symphony orchestra.
     */
    public enum OrchestraSection {
        /** Woodwinds, such as flute, clarinet, and oboe. */
        WOODWIND,

        /** Brass instruments, such as french horn and trumpet. */
        BRASS,

        /** Percussion instruments, such as timpani and cymbals. */
        PERCUSSION,

        /** Stringed instruments, such as violin and cello. */
        STRING;
    }
    // 한글 버전
    // /**
    //  * 심포니 오케스트라의 악기 세션.
    //  */
    // public enum OrchestraSection {
    //     /** 플루트, 클라리넷, 오보 같은 목관악기. */
    //     WOODWIND,
    // 
    //     /** 프렌치 호른, 트럼펫 같은 금관악기. */
    //     BRASS,
    // 
    //     /** 탐파니, 심벌즈 같은 타악기. */
    //     PERCUSSION,
    // 
    //     /** 바이올린, 첼로 같은 현악기. */
    //     STRING;
    // }

    
    // 애너테이션 타입 문서화
    /**
     * Indicates that the annotated method is a test method that
     * must throw the designated exception to pass.
     */
    @Retention(RetentionPolicy.RUNTIME)
    @Target(ElementType.METHOD)
    public @interface ExceptionTest {
        /**
         * The exception that the annotated test method must throw
         * in order to pass. (The test is permitted to throw any
         * subtype of the type described by this class object.)
         */
        Class<? extends Throwable> value();
    }
    // 한글 버전
    // /**
    //  * 이 애너테이션이 달린 메서드는 명시한 예외를 던져야만 성공하는
    //  * 테스트 메서드임을 나타낸다.
    //  */
    // @Retention(RetentionPolicy.RUNTIME)
    // @Target(ElementType.METHOD)
    // public @interface ExceptionTest {
    //     /**
    //      * 이 애너테이션을 단 테스트 메서드가 성공하려면 던져야 하는 예외.
    //      * (이 클래스의 하위 타입 예외는 모두 허용된다.)
    //      */
    //     Class<? extends Throwable> value();
    // }
}