💡 문서화가 잘 됐는지 판단하는 방법은 Javadoc Utility가 생성한 웹페이지를 읽어보는 길 뿐이다.
📕 목차
1. Javadoc
2. 유의 사항
• 공개된 모든 클래스, 인터페이스, 메서드, 필드 선언에 문서화 주석을 달아라
• 메서드용 문서화 주석은 해당 메서드와 클라이언트 사이 규약을 명료화하라
• 제네릭 타입이나 제네릭 메서드는 모든 타입 매개변수에 주석을 달아라
• 열거 타입은 상수들에도 주석을 달아라
• 애너테이션 타입의 멤버들에도 모두 주석을 달아라
• 패키지와 모듈 시스템 주석
• Thread 안전 수준
3. 메서드 계약(contract) : @param, @return, @throw
4. 메타 문자 무시 : @code, @literal
5. 자기사용 패턴(self-use pattern) : @implSpec
6. 요약 설명(summary description) : @summary
• 해당 메서드와 생성자 동작을 설명하는 (주어가 없는) 동사구여야 한다
• 클래스, 인터페이스, 필드의 요약 설명은 대상을 설명하는 명사절이어야 한다
7. 색인 기능 : @index
8. 메서드 주석 상속 : @inheritDoc
1. Javadoc
📌 Javadoc 이란
- Java에서 API 문서화 작업을 돕는 Utility
- 소스 코드 파일에서 문서화 주석(doc comment)을 추려 API 문서로 변환해준다.
- javadoc guide는 Java 4 이후로 갱신되지 않았지만, API 문서 작성법의 정석이라 부를만하다.
📌 사용 방법
$ javadoc -d docs {file_name}
한글 사용 시에는 UTF-8로 인코딩하기 위해 아래 명령어를 사용해야 한다.
$ javadoc -d docs *.java -encoding UTF-8 -charset UTF-8 -docencoding UTF-8
그러면 이렇게 문서를 작성해준다.
예시 코드에 대한 Javadoc를 확인해보았다.
package Chat8.Item56;
/**
* @author qud12
* 게임 캐릭터 정보를 가지는 클래스
*/
public class Character {
private String name;
private int level;
private int hp;
private int mp;
private Job job;
/**
* 게임 캐릭터를 생성합니다.
* <p> 레벨 1, hp 50, mp 10, 기본 직업은 Beginner로 생성됩니다.
* @param name 캐릭터 이름; 3자 이상 10자 이하로 구성되어야 합니다.
* @throws IllegalArgumentException 캐릭터 name이 범위를 벗어나면,
* 즉, ({@code name.length() < 3 || name.length() > 10})이면 발생한다.
*/
public Character(String name) {
if (name.length() < 3 || name.length() > 10)
throw new IllegalArgumentException("이름은 3자 이상 10자 이하로 입력해주세요.");
this.name = name;
this.level = 1;
this.hp = 50;
this.mp = 10;
this.job = Job.BEGINNER;
}
/**
* 캐릭터의 레벨을 반환합니다.
* @return 캐릭터의 레벨
*/
public int getLevel() {
return level;
}
/**
* 캐릭터의 레벨을 올려주는 메서드입니다.
*/
public void levelUp() {
this.level++;
}
/**
* 캐릭터의 직업을 변경합니다.
* @param job 변경할 직업
* @throws IllegalStateException 캐릭터의 레벨이 10보다 작으면 발생한다.
*/
public void changeJob(Job job) {
if (this.level < 10)
throw new IllegalStateException("레벨이 10이 되어야 직업을 변경할 수 있습니다.");
this.job = job;
}
@Override public String toString() {
return String.format("캐릭터 이름: %s, 레벨: %d, 직업: %s", name, level, job);
}
/**
* 캐릭터의 직업을 나타냅니다.
*/
private enum Job {
/** 초보자 */
BEGINNER,
/** 전사 */
WARRIOR,
/** 마법사 */
MAGICIAN,
/** 궁수 */
ARCHER,
/** 도적 */
THIEF
}
}
2. 유의 사항
📌 공개된 모든 클래스, 인터페이스, 메서드, 필드 선언에 문서화 주석을 달아라
- 직렬화할 수 있는 클래스라면 직렬화 형태(Item 87)에 관해서도 적어라.
- 기본 생성자에는 문서화 주석을 달 방법이 없으니, 공개 클래스는 절대 기본 생성자를 사용하지 마라.
- 유지보수까지 고려한다면 대다수의 private 클래스, 인터페이스 등에서 문서화 주석을 달아야 할 것이다.
📌 메서드용 문서화 주석은 해당 메서드와 클라이언트 사이 규약을 명료화하라
- 상속용 클래스의 메서드가 아니라면, 해당 메서드가 어떻게(how) 동작하는지가 아니라 무엇(what)을 하는지 기술하라.
- 클라이언트가 해당 메서드를 호출하기 위한 전제조건(precondition)을 모두 나열해야 한다.
- 일반적으로 @throws 태그로 비검사 예외를 선언하여 암시적으로 기술한다.
- 즉, 비검사 예외 하나가 전제조건 하나와 연결되어 있다.
- @param 태그로 그 조건에 영향을 받는 매개변수에 기술할 수도 있다.
- 메서드가 성공적으로 수행된 후에 만족해야 하는 사후조건(postcondition)도 모두 나열해야 한다.
- 부작용도 문서화하라.
- 사후조건으로 명확히 나타나지는 않지만 시스템 상태에 어떠한 변화를 가져오는 것을 말한다.
- ex. 백그라운드 thread를 시작시키는 메서드라면 그 사실을 밝혀야 한다.
📌 제네릭 타입이나 제네릭 메서드는 모든 타입 매개변수에 주석을 달아라
/**
* An object that maps keys to values. A map cannot contain duplicate keys;
* ...
* @param <K> the type of keys maintained by this map
* @param <V> the type of mapped values
*/
public interface Map<K,V> { ... }
📌 열거 타입은 상수들에도 주석을 달아라
/**
* 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 method 또한 마찬가지다.
- 설명이 짧다면 주석 전체를 한 문장으로 써도 된다.
📌 애너테이션 타입의 멤버들에도 모두 주석을 달아라
/**
* 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();
}
- 필드 설명은 명사구로 한다.
- 애너테이션 타입의 요약 설명은 프로그램 요소에 해당 애너테이션을 다는 것이 어떤 의미인지를 설명하는 동사구로 한다.
- 한글로 쓰면 동사로 끝나는 평범한 문장이 된다.
📌 패키지와 모듈 시스템 주석
- package-info.java : 패키지 설명하는 문서화 주석을 작성하는 파일
- module-info.java : 모듈 시스템 사용 시, 모듈 관련 설명 주석을 작성하는 파일
📌 Thread 안전 수준
💡 클래스 혹은 정적 메서드가 Thread-safe 여부와 상관 없이, Thread 안전 수준을 API 설명에 포함하라.
- Thread 안전성과 직렬화 가능성에 대한 설명이 자주 누락되는데, 꼭 포함하라.
3. 메서드 계약(contract) : @param, @return, @throw
/**
* 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);
- 메서드 계약을 완벽히 기술하려면 다음 3가지를 지켜라
- 모든 매개 변수에 @param
- 반환 타입이 void가 아니라면 @return
- 발생할 가능성이 있는 (비검사/검사) 모든 예외에 @throws
- 관례상 @param과 @return 태그의 설명은 매개변수가 뜻하는 값이나, 반환값을 설명하는 명사구를 쓴다.
- 인스턴스 메서드 문서화 주석에 쓰인 "this"는 호출된 메서드가 자리하는 객체를 가리킨다.
아래는 위의 예시를 한글로 번역한 내용이다.
/**
* 이 리스트에서 지정한 위치의 원소를 반환한다.
*
* <p>이 메서드는 상수 시간에 수행됨을 보장하지 <i>않는다.</i> 구현에 따라
* 원소의 위치에 비례해 시간이 걸릴 수도 있다.
*
* @param index 반환할 원소의 인덱스; 0 이상이고 리스트 크기보다 작아야 한다.
* @return 이 리스트에서 지정한 위치의 원소
* @throws IndexOutOfBoundsException index가 범위를 벗어나면,
* 즉, ({@code index < 0 || index >= this.size()})이면 발생한다.
*/
E get(int index);
4. 메타 문자 무시 : @code, @literal
- Javadoc은 문서화 주석의 HTML 태그를 최종 문서에 반영한다.
- {@code ... }
- 태그로 감싼 내용을 코드용 폰트로 렌더링한다.
- 태그로 감싼 내용에 포함된 HTML 요소나 다른 자바독 태그를 무시한다.
- 여러 줄로 된 코드 예시를 넣고 싶다면 <pre></pre> 태그로 전체를 감싸면 된다.
- {@literal ... }
- {@code} 태그와 비슷하지만 코드 폰트로 렌더링하지는 않는다.
5. 자기사용 패턴(self-use pattern) : @implSpec
/**
* Returns true if this collection is empty.
*
* @implSpec
* This implementations returns {@code this.size() == 0}.
*
* @return true if this collection is empty
*/
public boolean isEmpty() { ... }
/**
* 이 컬렉션이 비었다면 true를 반환한다.
*
* @implSpec
* 이 구현은 {@code this.size() == 0}의 결과를 반환한다.
*
* @return 이 컬렉션이 비었다면 true, 그렇지 않다면 false
*/
public boolean isEmpty() { ... }
- 일반적인 문서화 주석과 달리 해당 메서드와 하위 클래스 사이의 계약을 설명한다.
- 하위 클래스들이 그 메서드를 상속하거나 super 키워드로 호출할 때, 메서드가 어떻게 동작하는지 명확히 인지하고 사용하도록 돕는다.
6. 요약 설명(summary description) : @summary
💡 한 클래스(혹은 인터페이스) 안에서 요약 설명이 똑같은 멤버(혹은 생성자)가 둘 이상이면 안 된다.
- 요약 설명은 반드시 대상의 기능을 고유하게 기술해야 한다.
- 다중 정의된 메서드들의 설명은 같은 문장으로 시작하는 게 자연스럽겠지만 문서화 주석에서는 허용되지 않는다.
- 첫 번째 마침표(.)가 나오는 문장까지만 요약 설명이 되는 것에 주의하라.
- 정확히는 {<마침표> <공백> <다음 문장 시작>}이다. 여기서 다음 문장 시작은 '소문자가 아닌' 문자다.
- 이를 방지하고 싶다면 "{@literal Mrs. Peacock}."처럼 묶어주어야 한다.
- Java 10부터는 요약 설명 전용 태그 @summary로 전체를 감싸면 된다.
📌 해당 메서드와 생성자 동작을 설명하는 (주어가 없는) 동사구여야 한다
- 주석 작성 규약에 따르면 요약 설명은 완전한 문장이 되는 경우가 드물다.
- 2인칭 문장이 아닌 3인칭 문장으로 써야 한다. (한글로 쓰면 차이가 없다.)
📌 클래스, 인터페이스, 필드의 요약 설명은 대상을 설명하는 명사절이어야 한다
- 클래스와 인터페이스의 대상은 그 인스턴스이고, 필드의 대상은 필드 자신이 된다.
7. 색인 기능 : @index
* This method complies with the {@index IEEE 754} standard.
- Java 9부터는 Javadoc이 생성한 HTML 문서에 색인 기능이 추가되어 검색이 수월해졌다.
- 클래스, 메서드, 필드 같은 API 요소의 색인은 자동으로 만들어진다.
- {@index} 태그를 사용해 API에서 중요한 용어를 추가로 색인화 할 수 있다.
8. 메서드 주석 상속 : @inheritDoc
- Javadoc은 메서드 주석을 상속시켜 중복 내용을 줄일 수 있다.
- 문서화 주석이 없는 API 요소를 발견하면 가장 가까운 문서화 주석을 찾는다.
- 상위 '클래스'보다, 해당 클래스가 구현한 '인터페이스'를 먼저 찾는다.
- @inheritDoc을 사용하면 주석 여러 개를 유지보수하는 부담은 줄일 수 있지만, 사용이 까다롭고 제약도 조금 있다.