49. 매개 변수가 유효한지 검사하라
•
메서드와 생성자 대부분은 입력 매개변수의 값이 특정 조건을 만족하기를 바란다.
◦
ex) 인덱스 값은 음수면 안됨, 객체 참조는 null이면 안됨
•
이런 제약은 반드시 문서화해야하며 메서드 몸체가 시작되기 전에 검사해야 한다.
•
메서드 몸체가 실행되기 전 매개변수를 확인한다면 잘못된 값이 넘어왔을때 즉각적이고 깜끔한 방식으로 예외를 던질 수 있다.
•
public과 protected 메서드는 매개변수 값이 잘못됐을 때 던지는 예외를 문서화해야 한다.
전형적인 문서화 예시
/**
* (현재 값 mod m) 값을 반환한다. 이 메서드는
* 항상 음이 아닌 BigInteger를 반환한다는 점에서 remainder 메서드와 다르다.
*
* @param m 계수 (양수여야 한다.)
* @return 현재 값 mod m
* @throws ArithmeticException m이 0보다 작거나 같으면 발생한다.
*/
public BigInteger mod(BigInteger m) {
if (m.signum() < 0)
throw new ArithmeticException("계수(m)는 양수여야 합니다. " + m);
...
}
Java
복사
•
m이 null일 때 NullPointerException을 던진다는 설명은 없다.
◦
그 이유는 설명을 개별 메서드가 아닌 BigInteger 클래스 수준에서 기술했기 때문이다.
◦
각 메서드에 일일이 기술하는 것보다 훨씬 깔끔한 방법이다.
자바의 null 검사기
this.startegy = Objects.requireNonNull(strategy, "전략");
Java
복사
•
자바 7에 추가된 java.util.Objects.requireNonNull 메서드는 유연하고 사용하기도 편하니, 더 이상 null 검사를 수동으로 하지 않아도 된다.
◦
원하는 예외 메세지도 지정 가능하다.
◦
값을 사용하는 동시에 null 검사를 수행할 수 있다.
•
반환값은 그냥 무시하고 필요한 곳 어디서든 순수한 null 검사 목적으로 사용해도 된다.
•
자바 9에서는 범위 검사 기능도 더해졌는데, null 검사 메서드 만큼 유연하지는 않다.
◦
리스트와 배열 전용으로 설계되었고, 닫힌범위(양 끝단 값을 포함하는)는 다루지 못한다.
단언문(assert)를 사용한 유효성 검사
private static void sort(long[] a, int offset, int length) {
assert a != null;
assert offset >= 0 && offset <= a.length;
assert length >= 0 && length <= a.length - offset;
...
}
Java
복사
•
공개되지 않은 메서드라면 메서드가 호출되는 상황을 통제할 수 있다.
◦
따라서 오직 유효한 값만이 메서드에 넘겨지리라는 것을 보증할 수 있고, 그렇게 해야한다.
•
단언문은 유효성 감사와 다르다.
◦
실패하면 AssertionError를 던진다.
◦
런타임에 아무런 효과도, 성능 저하도 없다.
나중에 사용하는 매개변수
•
메서드가 직접 사용하지 않으나 나중에 사용하려고 저장하는 매개변수는 특히 더 신경써야 한다.
•
만약 검사를 생략한다면 어느 단계에서 문제가 발생한것인지 추적하기 곤란하다.
•
생성자는 해당 원칙의 특수한 사례로, 매개변수의 유효성 검사는 클래스 불변식을 어기는 객체가 만들어지지 않게 하는데 꼭 필요하다.
예외
•
유효성 검사 비용이 지나치게 높거나 실용적이지 않을 경우
•
계산 과정에서 암묵적으로 검사가 수행되는 경우
◦
암묵적 유효성 검사에 의존하다가는 실패 원자성을 해칠 수 있다.
정리
•
“매개변수에 제약을 두는게 좋다”고 해석해서는 안된다.
•
메서드는 범용적으로 설계해야 한다.
•
메서드가 건내받는 갓으로 무언가 제대로 된 일을할 수 있다면 매개변수 제약은 적을수록 좋다.
•
구현하려는 개념 자체가 특정한 제약을 내재한 경우도 드물지 않다.
50. 적시에 방어적 복사본을 만들라
•
자바는 안전한 언어로, 자바를 쓰는 즐거움 중 하나다.
•
네이티브 메서드를 사용하지 않아 C/C++ 같이 안전하지 않은 언어에서 흔히보는 버퍼/배열 오버런, 와일드 포인터 같은 메모리 충돌에서 안전하다.
•
자바가 다른 클래스로부터의 침범을 아무 노력없이 다 막을 수 있는건 아니다.
◦
클라이언트가 불변식을 깨뜨리려 혈안이 되어 있다고 가정하고 방어적으로 프로그래밍 해야한다.
불변식을 지키지 못하는 경우
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;
}
}
Java
복사
•
엇필 이 클래스는 불변처럼 보이고, 시작 시각이 종료 시작보다 늦을 수 없다는 불변식이 무리없이 지켜질 것 같다.
•
하지만 Date가 가변이라는 사실을 이용하면 어렵지 않게 불변식을 깨뜨릴 수 있다.
Date start = new Date();
Date end = new Date();
Period p = new Period(start, end);
end.setYear(78); // p 내부 수정
Java
복사
•
다행이 자바8 이후로는 Date대신 Instance를 사용해 쉽게 해결할 수 있다.
방어적 복사
•
외부 공격으로부터 Period 인스턴스의 내부를 보호하려면 생성자에서 받은 가변 매개변수 각각을 방어적으로 복사해야 한다.
매개변수의 방어적 복사
public Period(Date start, Date end) {
this.start = new Date(start.getTime());
this.end = new Date(end.getTime());
if (this.start.compareTo(this.end) > )
throw new IllegalArgumentException(this.start + " after " + this.end);
}
Java
복사
•
매개변수의 유효성을 검사하기 전에 방어적 복사본을 만들고, 이 복사본으로 유효성을 검사한 점에 주목하자.
◦
순서가 부자연스러워 보이겠지만 반드시 이렇게 작성해야한다.
◦
멀티스레딩 환경이라면 유효성 검사 후 복사를 진행하는 찰나의 순간에 값이 수정될 위험이 있다.
•
방어적 복사에 clone 메서드를 사용하지 않는 점에도 주목하자.
◦
Date는 final이 아니므로 clone이 Date가 정의한게 아닐 수도 있다.
◦
즉, clone이 악의를 가진 하위 클래스의 인스턴스를 반환할 수도 있다.
◦
매개변수가 제3자에 의해 확장될 수 있는 타입이라면 방어적 복사본을 만들 때 clone을 사용해서는 안된다.
필드의 방어적 복사본 반환
Date start = new Date();
Date end = new Date();
Period p = new Period(start, end);
p.end().setYear(78); // p의 내부를 공격
Java
복사
•
생성자를 수정하면 앞서의 공격은 막아낼 수 있지만, Period 인스턴스는 아직도 변경 가능하다.
◦
접근자 메서드가 내부의 가변 정보를 직접 드러내기 때문이다.
•
두 번째 공격을 막아내려면 단순히 접근자가 가변 필드의 방어적 복사본을 반환하면 된다.
public Date start() {
return new Date(start.getTime());
}
public Date end() {
return new Date(end.getTime());
}
Java
복사
필드의 방어적 복사본 반환
•
새로운 접근자까지 갖추면 Period는 완벽한 불변으로 거듭난다.
•
Period자신 말고는 가변 필드에 접근할 방법이 없으니 모든 필드가 객체 안에서 캡슐화되었다.
•
생성자와 달리 접근자 메서드에서는 방어적 복사에 clone을 사용해도 된다.
◦
그렇더라도 인스턴스를 복사하는 데는 일반적으로 생성자나 정적 팩터리를 쓰는게 좋다.
•
매개변수를 방어적으로 복사하는 목적이 불변 객체를 만들기 위해서만은 아니다.
◦
메서드든 생성자든 클라이언트가 제공한 객체의 참조를 내부의 자료구조에 보관해야 할 때면 항시 그 객체가 잠재적으로 변경될 수 있는지를 생각해야 한다.
◦
확신할 수 없다면 복사본을 만들어 저장해야 한다.
◦
가변인 내부 객체를 클리언트에 반환할 때는 반드시 심사숙고해야 한다.
정리
•
되도록 불견 객체들을 조합해 객체를 구성해야 방어적 복사를 할일이 줄어든다는 교훈을 얻을 수 있다.
•
방어적 복사에는 성능 저하가 따르고, 또 항상 쓸 수 있는것도 아니다.
◦
호출자가 컴포넌트 내부를 수정하지 않으니라 확신하면 방어적 복사를 생략할 수 있다.
•
다른 패키지에서 사용한다고 해서 넘겨받는 가변 매개변수를 항상 방어적으로 복사해 저장해야 하는 것은 아니다.
◦
메서드나 생성자의 매개변수로 넘기는 행위가 그 객체의 통제권을 이전함을 뜻해, 통제권을 이전하는 메서드를 호출하는 클라이언트는 해당 객체를 더 이상 직접 수정하는 일이 없다고 약속해야 한다.
51. 메서드 시그니처를 신중히 설계하라
메서드 이름을 신중히 짓자.
•
항상 표준 명명규칙을 따라야 한다.
•
이해할 수 있고, 같은 패키지에 속한 다른 이름들과 일관되게 짓는게 최우선 목표다.
편의 메서드를 너무 많이 만들지 말자.
•
모든 메서드는 각각 자신의 소임을 다해야 한다.
•
메서드가 너무 많은 클래스는 익히고, 사용하고, 문서화하고, 테스트하고 유지 보수하기 어렵다.
•
아주 자주쓰일 경우에만 약칭 메서드를 두고, 확신이 서질 않는다면 만들지 말자.
매개변수 목록은 짧게 유지하자.
•
4개 이하가 좋다.
•
같은 타입의 매개변수가 연달아 여러 개 나오는 경우가 특히 해롭다.
과하게 긴 매개변수 목록을 짧게 줄이는 법
•
여러 메서드로 쪼갠다.
◦
쪼개진 메서드 각각은 원래 매개변수의 부분집합을 받는다.
•
매개변수 여러 개를 묶어주는 도우미 클래스를 만들자.
◦
일반적으로 이런 도우미 클래스는 정적 멤버 클래스로 둔다.
•
빌더 패턴을 메서드 호출에 응용한다.
◦
특히 일부 매개변수를 생략해도 괜찮을 때 도움이 된다.
◦
모든 매개변수를 하나로 추상화한 객체를 정의하고, 클라이언트에서 이 객체의 세터 메서드를 호출해 필요한 값을 설정하게 한다.
매개변수의 타입으로는 클래스보다는 인터페이스가 낫다.
•
구현체와 무관하게 구현된 모든 인터페이스를 사용할 수 있다.
•
특정 구현체로 명시하게 된다면 비싼 복사 비용을 치뤄야 할 수도 있다.
boolean 보다는 원소 2개짜리 열거타입이 낫다.
•
메서드 이름상 boolean을 받아야 의미가 더 명확할 때는 예외다.
•
열거 타입을 사용하면 코드를 읽고 쓰기가 쉬워진다.
•
나중에 선택지를 추가하기도 쉽다.
52. 다중정의는 신중히 사용하라
다중정의
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));
}
}
Java
복사
오류가 발생하는 다중정의 예제
•
다중정의된 메서드는 어떤 메서드를 호출할지에 대해서 컴파일타임에 정해진다.
•
때문에 "집합", "리스트", "그 외"를 차례로 출력하기를 예상했지만, 실제로 수행해보면 "그 외"만 세 번 출력한다.
•
런타임에는 매번 타입이 달라지지만, 컴파일타입을 기준으로는 항상 Collection<?>만 호출하는 것이기 때문이다.
•
이처럼 직관과 어긋나는 이유는 재정의한 메서드는 동적으로 선택되고, 다중정의한 메서드는 정적으로 선택되기 때문이다.
•
객체의 런타임 타입은 전혀 중요치 않다.
◦
선택은 컴파일타임에 오직 매개변수의 컴파일타임 타입에 의해 이뤄진다.
문제 해결 방법
public static String classify(Collection<?> c) {
return c instanceof Set ? "집합" :
c instanceof List ? "리스트" : "그 외";
}
Java
복사
더 좋은 방법
•
헷갈릴 수 있는 코드는 작성하지 않는게 좋다.
•
사용자가 매개변수를 넘기면서 어떤 다중정의 메서드가 호출될지를 모른다면 프로그램이 오동작하기 쉽다.
•
다중정의가 혼동을 일으키는 상황을 피해야한다.
•
안전하고 보수적으로 가려면 매개변수 수가 같은 다중정의만들지 말자.
•
다중정의 대신 메서드 이름을 다르게 지어주는 방법도 열려있다.
생성자의 경우
•
생성자는 2개 이상의 경우 무조건 다중정의 된다.
•
정적 팩터리라는 대인을 활용할 수 있는 경우가 많다.
•
생성자는 재정의할 수 없으니 다중정의와 재정의가 혼용될 걱정은 안해도 된다.
매개변수 수가 같은 다중정의
•
매개변수 수가 같더라도, 어느 것이 주어진 매개변수 집합을 처리할지가 명확히 구분된다면 헷갈릴 일은 없을것이다.
•
즉, 매개변수 중 하나 이상이 "근본적으로 다르다"면 헷갈릴 일이 없다.
재정의
class Wine {
String name() { return "포도주"; }
}
class SparklingWine extends Wine {
@Override
String name() {
return "발포성 포도주";
}
}
class Champagne extends SparklingWine {
@Override
String name() {
return "샴페인";
}
}
public class Overriding {
public static void main(String[] args) {
List<Wine> wineList = List.of(new Wine(), new SparklingWine(), new Champagne());
for (Wine wine : wineList)
System.out.println(wine.name());
}
}
Java
복사
•
메서드 재정의란 상위 클래스가 정의한 것과 똑같은 시그니처의 메서드를 하위 클래스에서 다시 정의한 것을 말한다.
•
메서드를 재정의한 다음 '하위 클래스의 인스턴스'에서 그 메서드를 호출하면 재정의한 메서드가 실행된다.
•
예상한 것처럼 이 프로그램은 "포도주", "발포성 포도주", "샴페인"을 차례로 출력한다.
•
컴파일타임 타입이 모두 Wine인 것에 무관하게 항상 ‘가장 하위에서 정의한 재정의 메서드’가 실행되는 것이다.
53. 가변인수는 신중히 사용하라
•
가변인수 메서드는 명시한 타입의 인수를 0개 이상 받을 수 있다.
•
가변인수 메서드를 호출하면, 가장 먼저 인수의 개수와 길이가 같은 배열을 만들고 인수의 인수들을 이 배열에 저장하여 가변인수 메서드에 건네준다.
간단한 가변인수 활용
static int sum(int... args) {
int sum = 0;
for (int arg : args)
sum += arg;
return sum;
}
Java
복사
•
인수가 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;
}
Java
복사
•
이 코드의 문제는 인수를 0개만 넣어 호출했을 때 런타임에 실패한다는 것이다.
•
코드도 지저분하다.
•
args 유효성 검사를 명시적으로 해야하고, min의 초깃값을 Integer.MAX_VALUE로 설정하지 않고는 더 명료한 for-each문도 사용할 수 없다.
올바른 구현 예시
static int min(int firstArgs, int... remainingArgs) {
int min = firstArg;
for (int arg : remainingArgs)
if (arg < min)
min = arg;
return min;
}
Java
복사
성능에 민감한 경우
•
성능에 민감한 상황이라면 가변인수가 걸림돌이 될 수 있다.
•
가변인수 메서드는 호출될 때마다 배열을 새로 하나 할당하고 초기화한다.
•
이 비용을 감당할 수는 없지만 유연성이 필요할 때 선택할 수 있는 멋진 패턴이 있다.
다중정의를 통한 성능향상
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) { }
Java
복사
•
만약 95%의 메서드 호출이 인수를 3개 이하로 사용한다고 가정한다면
•
인수가 0개인 것부터 4개인 것까지, 총 5개를 다중정의하자.
•
마지막 다중정의 메서드가 인수 4개 이상인 5%의 호출을 담당하는 것이다.
•
EnumSet의 정적 팩터리도 이 기법을 사용해 열거 타입 집합 생성 비용을 최소화한다.
•
EnumSet은 비트 필드를 대체하면서 성능까지 유지해야 하므로 아주 적절하게 활용한 예시라 할 수 있다.
54. null이 아닌, 빈 컬렉션이나 배열을 반환하라
흔히 볼 수 있는 메서드
private final List<Cheese> cheesesInStock = ... ;
public List<Cheese> getCheeses() {
return cheesesInStock.isEmpty() ? null : new ArrayList<>(cheesesInStock);
}
Java
복사
•
재고가 없다고 해서 특별히 취급할 이유는 없다.
•
그럼에도 이 코드처럼 null을 반환한다면, 클라이언트는 이 null을 처리하는 코드를 추가로 작성해야 한다.
List<Cheese> cheeses = shop.getCheeses();
if (cheeses != null && cheeses.contains(Cheese.STILTON))
System.out.println("좋았어, 바로 그거야.");
Java
복사
null 방어코드
null 대신 빈 컨테이너를 리턴하자.
•
빈 컨테이너를 할당하는 데도 비용이 드니 null을 반환하는 쪽이 낫다는 주장도 있다.
•
하지만 이것은 틀렸다.
◦
성능 분석 결과 이 할당이 설능 저하의 주범이라고 확인되지 않는 한 신경 쓸 수준임 못된다.
◦
빈 컬렉션과 배열은 굳이 할당하지 않고도 반환할 수 있다.
55. 옵셔널 반환은 신중히 하라
•
자바 8이전에는 메서드가 특정 조건을 반환할 수 없을 때 두 가지 선택지가 있었다.
◦
예외 던지기
◦
null 반환
문제점
•
예외는 진짜 예외적 상황에서만 사용해야 한다.
◦
예외를 생성할 때 스택 추적 전체를 캡처하므로 비용이 비싸다.
TMI. fillInStackTrace를 재정의한다면 해당 캡쳐 비용을 없애거나 줄일 수 있다.
•
별도의 null 처리 코드를 추가해야 한다.
◦
그것도 null을 반환하게 한 실제 원인과는 상관 없는 코드에서 처리해야한다.
옵셔널
•
자바 8부터는 Optional<T>이 생기며 하나의 선택지가 늘어났다.
•
옵셔널은 null이 아닌 T타입 참조 하나를 담거나, 혹은 아무것도 담지 않을 수도 있다.
•
옵셔널은 원소를 최대 1개 가질 수 있는 '불변' 컬렉션이다.
•
보통은 T를 반환해야 하지만 특정 조건에서는 아무것도 반환하지 않아야할 때 T 대신 Optional<T>를 반환하도록 선언하면 된다.
•
옵셔널을 반환하는 메서드는 예외를 던지는 메서드보다 유연하고 사용하기 쉬우며, null을 반환하는 메서드보다 오류 가능성이 작다.
옵셔널을 이용한 개선
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) > )
result = Objects.reuqireNonNull(e);
return Optional.of(result);
}
Java
복사
•
컬렉션에서 최대값을 구해 Optional<E>로 반환한다.
◦
빈 옵셔널은 Optional.Empty()로 만들고, 값이 든 옵셔널은 Optional.of(value)로 생성했다.
•
Optional.of(value)에 null을 넣으면 NullPointerException을 던지니 주의하자.
•
null 값도 허용하는 옵셔널을 만들려면 Optional.ofNullable(value)를 사용하면 된다.
◦
옵셔널을 반환하는 메서드에서는 절대 null을 반환하지 말자.
•
옵셔널은 검사 예외와 취지가 비슷하다.
•
즉 반환 값이 없을 수도 있음을 API 사용자에게 명확히 알려준다라는 점이다.
옵셔널을 리턴받았을 경우
•
메서드가 옵셔널을 반환한다면 클라이언트는 값을 받지 못했을 때 취할 행동을 선택해야 한다.
•
기본값 설정
String lastWordInLexicon = max(words).orElse("단어 없음...");
Java
복사
•
예외 던지기
Toy myToy = max(toys).orElseThrow(TemperTantrumException::new);
Java
복사
•
항상 값이 채워져있다고 가정
Element lastNobleGas = max(Elements.NOBLE_GASES).get();
Java
복사
◦
잘못 판단한 것이라면 NoSuchElementException이 발생한다.
옵셔널을 사용하지 말아야 할 경우
•
컬렉션, 스트림, 배열, 옵셔널 같은 컨테이너 타입은 옵셔널로 감싸면 안된다.
◦
Optional<List<T>>를 반환하기보다는 빈 List<T>를 반환하는게 좋다.
•
옵셔널을 반환해야할 경우
•
결과가 없을 수 있으며, 클라이언트가 이 상황을 특별하게 처리해야 한다면 Optional<T>를 반환한다.
옵셔널의 단점
•
Optional도 엄연히 새로 할당하고 초기화해야 하는 객체이고, 그 안에서 값을 꺼내려면 메서드를 호출해야 하니 한 단계를 더 거치는 셈이다.
◦
그래서 성능이 중요한 상황에서는 옵셔널이 맞지 않을 수 있다.
•
박싱된 기본 타입을 담는 옵셔널은 기본 타입 자체보다 무거울 수밖에 없다.
◦
값을 두 겹이나 감싸기 때문이다.
◦
그래서 자바 API 설계자들은 int, long, double 전용 옵셔널 클래스들을 준비해놨다. 바로 OptionalInt, OptionalLong, OptionalDouble이다.
◦
이 옵셔널들도 Optional가 제공하는 메서드를 거의 다 제공한다.
◦
이렇게 대체제까지 있으니 박싱된 기본 타입을 담은 옵셔널을 반환하는 일은 없도록 하자.
▪
덜 중요한 Boolean, Byte, Character, Short, Float은 예외일 수 있다.
절대 금지
•
옵셔널을 맵의 값으로 사용하면 절대 안 된다.
◦
그리 한다면 맵 안에 키가 없다는 사실을 나타내는 방법이 두 가지가 된다.
▪
키 자체가 없는 경우고
▪
다른 하나는 키는 있지만 그 키가 속이 빈 옵셔널인 경우
◦
쓸데없이 복잡성만 높여서 혼란과 오류 가능성을 키울 뿐이다.
56. 공개된 API 요소에는 항상 문서화 주석을 작성하라
•
API를 쓸모있게 하려면 잘 작성된 문서도 곁들여야 한다.
•
API를 올바로 문서화하려면 공개된 모든 클래스, 인터페이스, 메서드, 필드 선언에 문서화 주석을 달아야 한다.
•
기본 생성자에는 문서화 주석을 달 방법이 없으니 공개 클래스는 절대 기본 생성자를 사용하면 안된다.
•
메서드용 문서화 주석에는 해당 메서드와 클라이언트 사이의 규약을 명료하게 기술해야 한다.
◦
how가 아닌 what을 기술해야 한다.
◦
메서드를 호출하기 위한 전제조건(precondition)을 모두 나열해야 한다.
◦
메서드가 성공적으로 수행된 후에 만족해야 하는 사후조건도 모두 나열해야 한다.
◦
일반적으로 전제조건은 @throws 태그로 비검사 예외를 선언하여 암시적으로 기술한다.
◦
@param 태그를 이용해 그 조건에 영향 받는 매개변수에 기술할 수 있다.
•
부작용도 문서화 해야 한다.
◦
부작용이란 사후조건으로 명확히 나타나지는 않지만 시스템의 상태에 어떠한 변화를 가져오는 것을 말한다.
◦
예컨대 백그라운드 스레드를 시작시키는 메서드라면 그 사실을 문서에 밝혀야 한다.
•
관례상 @param 태그와 @return 태그의 설명은 해당 매개변수가 뜻하는 값이나 반환값을 설명하는 명사구를 쓴다.
◦
드물게는 명사구 대신 산술 표현식을 쓰기도 한다.
•
역시 관례상 @param, @return, @throws 태그의 설명에는 마침표를 붙이지 않는다.