[Java] Javadoc 사용하기(feat. 문서화 주석)
Javadoc이 어떤것인지 알고 있었지만, 문서화 주석에 대한 지식은 거의 없었다. 보통 라이브러리나 프레임워크의 내부 코드를 까볼때 문서화 주석의 도움을 많이 받았다. 하지만 항상 정보만 얻었지 문서화 주석을 어떻게 작성하는지에 대해서는 무지했다.
이번에 이펙티브 자바 <아이템56번. 공개된 API 요소에는 항상 문서화 주석을 작성하라> 를 공부하면서 제대로 정리해보자 라는 생각을 했다!
Javadoc을 팀에서 사용하지 않을 수 있지만 문서화에 필요한 정보가 어떤것인지 얻을 수 있을거라 생각한다.
중간중간 예시를 활용해 Javadoc이 만들어주는 HTML을 직접 확인해보고 싶다면 간단하게 생성해볼 수 있다.
Javadoc 문서 생성은 이 글을 참고해주세요!
Javadoc이란?
Java 소스 코드에서 HTML 형식의 API 문서를 생성하기 위해 Sun Microsystems에서 작성한 문서 생성기입니다.
Javadoc은 소스코드 파일에서 문서화 주석이라는 특수한 형태로 기술된 설명을 추출해 API 문서로 변환해준다.
Javadoc의 대상이 되는 문서화 주석은 다음과 같은 주석 형태입니다.
/**
* 문서화 주석 내용
* 문서화 주석 내용
* 문서화 주석 내용
* 문서화 주석 내용
*/
아래의 형태도 똑같이 Javadoc의 대상이다.
/**
문서화 주석 내용
문서화 주석 내용
문서화 주석 내용
*/
문서화 주석은 다음과 같은 형태로 작성합니다.
(예시에는 메서드에 적용했지만 클래스, 인터페이스, 필드 등등 다양한곳에 적용할 수 있습니다.)
/**
* 문서화 주석 대상의 요약 설명이다.
*
* @param a - ~~ 문자열
* @param b - ~~ 문자열
* @return a와 b를 더한 문자열
* @throws 어떤 상황에서 예외가 발생!
*/
public String doSomething(String a, String b) {
return a + b;
}
문서화 주석의 규칙
문서화 주석 작성하는 규칙이 언어 명세에 속하진 않지만 자바 프로그래머라면 알아야 하는 업계 표준 API라 할 수 있다.
(사실 현업에서 Javadoc을 사용하는가는 컨벤션에 따라 다를거라 생각한다. 팀에 맞는 문서화 방법을 선택하자. Javadoc은 많은 문서화 방법 중 하나에 불과하다.)
이 규칙은 문서화 주석 작성법(How to Write Doc Comments) 웹 페이지에 기술되어있다.
이 문서는 자바 4이후로 갱신되지 않은 오래된 페이지지만, 가치는 여전하다.
자바 버전이 올라가며 추가된 자바독 태그
- {@literal}
- {@code}
- @implSpec
- {@index}
다음은 문서화 주석의 규칙들이다.
- API를 올바르게 문서화하려면 모든 클래스, 인터페이스, 메서드, 필드 선언에 문서화 주석 달아야 한다.
- 직렬화 할 수 있는 클래스라면 직렬화 형태에 관해서도 적어야 한다.
- 문서화 주석이 없다면 Javadoc도 그저 공개 API 요소들의 '선언'만 나열해주는게 전부다.
- 문서화 주석 추가로 공개 API 요소들에 대한 설명을 클라이언트에게 명확하게 전달할 수 있다.
- 문서가 잘 갖춰지지 않은 API는 사용하기 헷갈릴 수 있어 오류의 원인이 되기 쉽다. (사용자에게 불편을 준다.)
- 기본 생성자에는 문서화 주석을 달 방법이 없으니 공개 클래스는 절대 기본 생성자를 사용하면 안된다.
(생성자를 따로 선언하지 않으면 기본 생성자를 사용한다.)
// 기본 생성자란?
class Example {
// 생성자를 하나도 선언하지 않으면 기본 생성자를 사용한다.
}
class Example {
// 생성자를 선언했으므로 기본 생성자는 생성되지 않는다.
public Example() {
...
}
}
유지보수까지 고려한다면 대다수의 public 하지 않은 클래스, 인터페이스, 생성자, 메서드, 필드에도 문서화 주석을 달아주자.
나머지 규칙들은 아래에서 자세히 알아보자
메서드용 문서화 주석은 메서드와 클라이언트 사이의 규약을 기술하자
상속용으로 설계된 클래스의 메서드는 문서화 주석에 자기사용 패턴에 대해서도 문서에 남겨 다른 프로그래머에게 그 메서드를 올바로 재정의하는 방법을 알려줘야 한다.
해당 클래스를 상속해서 사용하고자 하는 개발자는 문서화 주석을 참고하여 메서드의 재정의 시 엉뚱한 동작을 하지 않도록 어느정도 예방해줄 것이다.
이런 자기사용 패턴을 문서에 남길때는 @implSpec이라는 태그를 사용할 수 있다. 이는 아래에서 더욱 자세히 설명하겠다.
/**
* @implSpec
* 이 메서드는 List가 가지고 있는 요소의 개수를 반환합니다.
*/
public int size();
상속용 클래스의 메서드가 아니라면 메서드가 무엇인지를 기술하자. 즉, how 보다는 what 을 기술해야한다.
문서화 주석에는 클라이언트가 해당 메서드를 호출하기 위한 전제조건을 모두 나열해야 한다.
또 메서드가 성공적으로 수행된 후에 만족해야 하는 사후조건도 모두 나열해야 한다.
일반적으로 사후조건은 @throws 태그를 통해 기술된다. @throws 태그로 비검사 예외를 선언하고 전제조건을 기술한다.
/**
* ... 생략
* @throws IndexOutOfBoundsException if the index is out of range
*
* ... 생략
*/
public E get(int index);
전제조건과 사후조건뿐만 아니라 부작용도 문서화한다.
부작용이란 사후조건으로 명확히 나타나지는 않지만 시스템의 상태에 어떠한 변화를 가져오는 것을 뜻한다.
예를들어 백그라운드 스레드를 동작시키는 메서드라면 그 사실을 꼭 문서에 기술하자.
메서드의 규약을 완벽하게 기술하려면 다음의 태그를 전부 활용해보자
- 모든 매개변수에 @param 태그
- 반환타입이 void가 아니라면 @return 태그
- 발생할 가능성이 있는 모든 예외에 @throws 태그
- 예시가 궁금하다면 BigInteger API의 문서를 살펴보자
@throws 태그의 설명은 if로 시작해 해당 예외를 던지는 조건을 설명하는 절이 뒤따른다.
관례상 @param, @return, @throws 태그의 설명에는 마침표를 붙이지 않는다.
다음은 이상의 규칙을 모두 반영한 문서화 주석의 예시다.
/**
* 이 리스트에서 지정한 위치의 원소를 반환한다.
*
* <p>이 메서드는 상수 시간에 수행됨을 보장하지 <i>않는다</i>. 구현에 따라 원소의 위치에 비례해 시간이 걸릴 수도 있다.</p>
*
* @param index 반환할 원소의 인덱스; 0 이상이고 리스트 크기보다 작아야 한다
* @return 이 리스트에서 지정한 위치의 원소
* @throws IndexOutOfBoundsException index가 범위를 벗어나면,
* 즉, ({@code index < 0 || index >= this.size()})이면 발생한다
*/
E get(int index);
문서화 주석에 HTML 태그<p>와 <i>를 쓴 점에 주목하자.
자바독 유틸리티는 문서화 주석을 HTML로 변환하므로 문서화 주석 안의 HTML 요소들이 최종 HTML 문서에 반영된다.
(드물게는 HTML table까지 집어넣는 프로그래머도 있다.)
{@code}
위의 예시에서 @throws 태그에서 사용한 '즉, ({@code index < 0 || index >= this.size()})이면 발생한다.' 를 주목하자.
{@code} 에는 두 가지 효과가 있다.
- 태그로 감싼 내용을 코드용 폰트로 렌더링한다.
- 태그로 감싼 내용에 포함된 HTML 요소나 다른 자바독 태그를 무시한다.
두 번째 효과 덕분에 HTML 메타 문자인 '<'기호를 별다른 처리없이 표현했다.
만약 {@code}내부에 여러 줄의 코드를 넣고 싶다면 <pre>{@code}</pre> 형태를 사용하자.
이렇게 하면 '\n' 과 같이 탈출 문자를 사용하지 않고도 줄바꿈을 할 수 있다.
단, @기호에는 무조건 탈출문자를 붙여야 하니 문서화 주석 안의 코드에서 주석 사용한다면 주의하자.
/**
*
* <pre>{@code 예시로
* 사용하는 코드
* 줄바꿈이 자유롭다}</pre>
*/
문서화 주석에서의 this
위에서 한글로 작성했던 문서화 주석을 영어 본문으로 살펴보자.
/**
* 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 time proportional to the element position</p>
*
* @param – index of the 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 (index < 0 || index >= size())
*/
인스턴스 메서드의 문서화 주석에서 쓴 'this'는 관례상 호출된 메서드가 자리하는 객체를 의미한다.
@implSpec
클래스를 상속용으로 설계하면 자기사용 패턴에 대해서도 문서에 남기자.
상속용 클래스의 메서드는 문서화 주석에 자기사용 패턴에 대해서도 문서에 남겨 다른 프로그래머에게 그 메서드를 올바로 재정의하는 방법을 알려줘야 한다.
자기 사용패턴은 자바8에 추가된 @implSpec 태그로 문서화한다.
@implSpec 태그는 해당 메서드와 하위 클래스 사이의 계약을 설명한다.
하위 클래스들이 그 메서드를 상속하거나 super 키워드를 이용해 호출할 때 그 메서드가 어떻게 동작하는지를 명확히 인지하고 사용하도록 하는게 목적이다.
/**
* 이 컬렉션이 비었다면 true를 반환한다.
*
* @implSpec
* 이 구현은 {@code this.size() == 0}의 결과를 반환한다.
*
* @return 이 컬렉션이 비었다면 true, 그렇지 않다면 false
*/
자바독 명령줄에서 -tag "implSpec🅰️Implementation Requirements:" 스위치를 켜주지 않으면 @implSpec 태그를 무시해버린다.
{@literal}
API 설명에 <, >, &등의 HTML 메타문자를 포함시키려면 {@literal} 태그로 해당 메타 문자를 감싸면 된다.
{@literal} 태그는 HTML 마크업이나 자바독 태그를 무시하게 해준다.
(앞서 본 {@code} 태그와 비슷하지만 코드 폰트로 렌더링하지는 않는다.)
문서화 주석의 첫번째 문장은 요약 설명에 해당
각 문서화 주석의 첫 번째 문장은 해당 요소의 요약 설명으로 간주된다.
요약 설명은 반드시 대상의 기능을 고유하게 기술해야 한다.
헷갈리지 않으려면 한 클래스(혹은 인터페이스)안에서 요약 설명이 똑같은 멤버(혹은 생성자)가 둘 이상이면 안 된다.
다중정의된 메서드가 있다면 특히 더 조심하자. 다중 정의된 메서드들의 설명은 같은 문장으로 시작하는 게 자연스럽겠지만 문서화 주석에서는 허용되지 않는다.
요약 설명에는 마침표('.')에 주의해야 한다.
예를들어 문서화 주석의 첫 문장이 "머스터드 대령이나 Mrs. 피콕 같은 용의자."라면 첫 번째 마침표가 나오는 "머스터드 대령이나 Mrs."까지만 요약 설명이 된다.
/**
* 머스터드 대령이나 Mrs. 피콕 같은 용의자.
*
* 최종 HTML에는 '머스터드 대령이나 Mrs.' 까지만 나온다.
*/
요약 설명이 끝나는 판단 기준은 다음과 같다.
처음 발견되는 <마침표><공백><다음 문장 시작> 패턴에서 <마침표>까지가 요약 설명이 된다.
- <공백>은 스페이스, 탭, 줄바꿈이다.
- <다음 문장 시작>은 영어 소문자가 아닌 문자다.
만약 의도치 않게 마침표로 인해 요약 설명이 끝난다면 {@literal}로 감싸주는 것이다.
위의 예시를 개선하면 다음과 같다.
/**
* 머스터드 대령이나 {@literal Mrs. 피콕} 같은 용의자.
*/
요약 설명의 대상에 따라 설명하는 방법이 다르다.
요약 설명의 대상이 메서드나 클래스라면 요약 설명은 동작을 설명하는 (주어가 없는)동사구 여야 한다.
다음 예시를 보면서 느껴보자
ArrayList(int initialCapacity) : 지정한 초기 용량을 갖는 빈 리스트를 생성한다.
Collection.size(): 이 컬렉션 안의 원소 개수를 반환한다.
요약 설명의 대상이 클래스, 인터페이스, 필드라면 대상을 설명하는 명사절 이어야한다.
이번에도 예시를 보자.
Instant: 타임라인상의 특정 순간(지점)
Math.PI: 원주율(pi)에 가장 가까운 double 값
{@index}
자바9부터 자바독이 생성한 HTML 문서에 검색 기능이 추가됐다.
{@index} 태그를 사용하면 색인을 해서 자바독이 생성한 HTML에서 {@index}에서 색인했던 이름으로 검색이 가능하다.
/**
* This method compiles with the {@index IEEE 754} standard
*/
{@Index 색인에 사용할 이름} 형식으로 사용한다.
제네릭, 열거타입, 주석은 문서화 주석에서 특별히 주의해야 한다.
하나씩 예시와 함께 살펴보자
제네릭
제네릭 타입이나 제네릭 메서드를 문서화할 때는 모든 타입 매개변수에 문서화 주석을 기술하자.
/**
* 키와 값을 매핑하는 객체, 맵은 키를 중복해서 가질 수 없다
* 즉, 키 하나가 가리킬 수 있는 값은 최대 1개다.
*
* @param <K> 이 맵이 관리하는 키의 타입
* @param <V> 매핑된 값의 타입
*/
public interface Map<K, V> { ... }
열거 타입
열거 타입을 문서화할 때는 상수들에도 주석을 달아야 한다.
열거 타입 자체와 그 열거 타입의 public 메서드도 물론이다.
/**
* 심포니 오케스트라의 악기 세션
*/
public enum OrchestraSection {
/** 플루트, 클라리넷, 오보 같은 목관악기. */
WOODWIND,
/** 프렌치 호른, 트럼펫 같은 금관악기 */
BRASS,
/** 탐파니, 심벌즈 같은 타악기 */
PERCUSSION,
/** 바이올린, 첼로 같은 현악기 */
STRING
}
애너테이션
애너테이션 타입을 문서화할 때는 멤버들에도 모두 주석을 달아야 한다. 애너테이션 타입 자체도 물론이다.
필드 설명은 명사구로 한다.
애너테이션 타입의 요약 설명은 이 애너테이션을 사용하는 것이 어떤 의미인지를 설명하는 동사구로 한다.
/**
* 이 주석이 달린 메서드는 명시한 예외를 던져야만 성공하는
* 테스트 메서드임을 나타낸다.
*/
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface ExceptionTest {
/**
* 이 주석을 단 테스트 메서드가 성공하려면 던져야 하는 예외
* (이 클래스의 하위 타입 예외는 모두 허용된다.)
*/
Class<? extends Throwable> value();
}
패키지 문서화와 모듈 문서화
패키지를 설명하는 문서화 주석은 package-info.java 파일에 작성한다.
이 파일은 패키지 선언을 반드시 포함해야 하며 패키지 선언 관련 주석을 추가로 포함할 수도 있다.
자바 9부터 지원하는 모듈 시스템도 이와 비슷하다.
모듈 시스템을 사용한다면 모듈 관련 설명은 module-info.java 파일에 작성하면 된다.
스레드 안전성과 직렬화 가능성을 API 문서화에 포함시키자
클래스 혹은 정적 메서드가 스레드 안전하든 그렇지 않든, 스레드 안전 수준을 반드시 API 설명에 포함해야 한다.
직렬화 가능한 클래스라면 직렬화 형태도 API 설명에 기술해야 한다. (@serial 태그를 활용해볼 수 있다.)
Javadoc 상속
자바독은 메서드 주석을 상속 시킬 수 있다.
문서화 주석이 없는 API 요소를 발견하면 Javadoc이 가장 가까운 문서화 주석을 찾아준다.
이때 상위 '클래스'보다 그 클래스가 구현한 '인터페이스'를 우선시 한다.
상세한 검색 알고리즘은 (The Javadoc Reference Guide)를 참고하자.
메소드 주석 상속 알고리즘
1. 메소드에 문서 주석이 없거나 {@inheritDoc} 태그가 있는 경우 Javadoc 도구는 가장 구체적인 적용 가능한 문서 주석을 찾도록 설계된 다음 알고리즘을 사용하여 적용 가능한 주석을 검색합니다. 부모클래스보다 인터페이스를 우선시합니다.
직접 구현된(또는 확장된) 각 인터페이스를 메서드 선언에서 implements(또는 extends)라는 단어 다음에 나타나는 순서대로 살펴봅니다. 이 방법으로 찾은 첫번째 메서드 주석을 사용합니다.
2. 1단계에서 문서화 주석을 찾지 못하면 이 전체 알고리즘을 1단계에서 검사한 것과 동일한 순서로 직접 구현(또는 확장)된 각 인터페이스에 재귀적으로 적용합니다.
3. 2단계에서 문서 주석을 찾지 못하고 이것이 Object(인터페이스가 아님)가 아닌 클래스인 경우:
3-1. 부모클래스에 이 메소드에 대한 문서화 주석이 있으면 이를 사용합니다.
3-2. 3-1단계에서 문서 주석을 찾지 못한 경우 이 전체 알고리즘을 부모클래스에 재귀적으로 적용합니다.
참고 - Oracle Javadoc 문서
또한 {@inheritDoc} 태그를 사용해 상위 타입의 문서화 주석 일부를 상속할 수 있다.
클래스는 자신이 구현한 인터페이스의 문서화 주석을 (복사하지 않고)재사용할 수 있다는 뜻이다.
다만 {@inheritDoc} 태그는 사용하기 까다롭고 제약도 조금 있다.
Javadoc 에서 설명하기 방대한 양은 링크를 적극적으로 활용하자
복잡한 API라면 주석회에도 전체 아키텍쳐를 설명한다던지 내용이 많은 경우가 있다.
이런 설명이 필요하다면 해당 설명이 담긴 문서의 링크를 활용하자. 좋은 대안이 된다.
/**
* ...생략
*
* 해당 부분은 https://parkadd.tistory.com/ 글을 참고해주세요
*
* ... 생략
*/
마무리
잘 쓰인 문서인지 확인하려면 Javadoc이 생성해준 HTML 문서를 직접 읽어보자.
프로그램 코드도 리팩터링이 필요하듯, API 문서또한 지속적인 리팩터링이 필요하다.
다른 사람이 사용할 API, 함께 협업할 사람이 볼 API 문서라면 꼼꼼하게 모든 API 요소를 검토해보자.
API문서를 읽다보면 리팩터링할 부분이 눈에 보이기 마련이다.
Javadoc 요약 정리
- 문서화 주석 작성하는 규칙은 애너테이션 작성법(How to Write Doc Comments)를 참고하자
- API를 올바로 문서화하려면 공개된 모든 클래스, 인터페이스, 메서드, 필드 선언에 문서화 주석 달자
- 기본 생성자는 문서화 주석을 달 방법이 없으니 공개 클래스는 절대 기본 생성자를 사용하면 안된다.
- 상속용 클래스의 메서드는 문서화 애너테이션에 어떻게 동작해야 하는지를 기술한다.
- 상속용 클래스의 메서드가 아니라면 해당 메서드가 무엇인지를 기술한다.
- 메서드를 호출하기 위한 전제조건, 메서드가 성공적으로 수행된 후 만족해야하는 사후조건을 나열한다.
- 발생할 수 있는 부작용도 기술하자. 여기서 말하는 부작용이란 사후조건에 명확히 나타나지 않지만 시스템의 상태에 변화를 주는 것이다.
- 일반적으로 메서드의 제약조건은 @throws 태그로 비검사 예외를 선언하여 기술한다.
- 모든 매개변수에 @param 태그
- 반환타입이 void가 아니라면 @return 태그
- 발생할 가능성이 있는 모든 예외에 @throws 태그, @throws 태크의 설명은 '만약'으로 시작해 해당 예외를 던지는 조건을 설명하는 절이 뒤따른다.
- @param, @return, @throws 태그의 설명에는 마침표를 붙이지 않는다.
- 자바독 유틸리티는 문서화 주석 HTML로 변환하므로 문서화 애너테이션 안의 HTML 요소들이 최종 HTML문서에 반영된다.
- {@code} 는 감싼 내용을 코드용 폰트로 변환해주고, 다른 HTML 요소나 다른 자바독 태그를 무시한다.
- 단 @ 같은 경우는 탈출 문자를 써줘야 한다.
- 문서화 주석에서 "this"는 호출된 메서드가 있는 객체를 가리킨다.
- @implSpec 태그는 해당 메서드와 하위 클래스 사이의 계약을 설명한다.
- 하위 클래스들이 그 메서드를 상속하거나 super 키워드를 이용해 호출할 때 그 메서드가 어떻게 동작하는지를 명확히 인지하고 사용하도록 하는게 목적이다.
- <, >, &등의 HTML 메타문자를 문서화 주석에 포함하려면 {@literal} 태그로 감싸주자. HTML 마크업이나 자바독 태그를 무시한다.
- 문서화 주석은 코드와 변환된 API 문서 모두 읽기 쉬워야 한다는 게 일반 원칙이다.
- 양쪽은 만족하지 못하면 API 문서에서의 가독성을 우선시 하자.
- 각 문서화 주석의 첫 번째 문장은 해당 요소의 요약 설명으로 간주된다. 대상의 기능을 설명한다.
- 대상의 기능을 고유하게 기술하므로 한 클래스안에서 요약 설명이 똑같은 멤버가 둘 이상이면 안 된다.
- <마침표><공백><다음 문자 시작> 의 패턴이 있으면 <마침표>까지가 요약 설명이 된다.
- 요약 설명의 대상이 메서드나 생성자라면 동작을 설명하는 (주어가 없는) 동사구 여야한다.
- 요약 설명의 대상이 클래스, 인터페이스, 필드의 요약 설명이라면 대상을 설명하는 명사절이어야 한다.
- 자바 9부터는 {@index 색인 이름}을 활용해서 HTML에서 검색해 찾을 수 있다.
- 제네릭, 열거타입, 애너테이션은 문서화 주석에서 특별히 주의해야 한다.
- 제네릭 타입이나 제네릭 메서드를 문서화할 때는 모든 타입 매개변수에 주석을 달아야 한다.
- 열거 타입을 문서화할 때는 상수들에도 주석을 달아야 한다.
- 애너테이션 타입을 문서화할 때는 멤버들에도 모두 주석을 달아야 한다. 애너테이션 타입 자체도 물론이다. 필드 설명은 문사구로 한다. 애너테이션 타입의 요약 설명은 이 애너테이션을 사용하는 것이 어떤 의미인지를 설명하는 동사구로 한다.
- 패키지를 설명하는 문서화 주석은 package-info.java 파일에 작성한다.
- (자바 9부터)모듈 시스템을 사용한다면 모듈 관련 설명은 module-info.java 파일에 작성하면 된다.
- 스레드 안전성과 직성화 가능성을 API 문서화에 포함시키자
- 문서화 주석이 없는 API 요소를 발견하면 자바독이 가장 가까운 문서화 주석을 찾아준다.
- 이때 상위 '클래스'보다 그 클래스가 구현한 '인터페이스'를 우선시 한다.
- {@inherit} 태그를 활용해도 상위 클래스의 문서화 주석을 사용할 수 있지만, 사용법이 까다로워 잘 알고 써야한다.
- 자바독만으로 부족하다면 외부 문서의 링크도 활용하자
- 프로그램의 코드도 리팩터링이 필요하듯이, 자바독 문서도 지속적인 리팩터링이 필요하다.