Effective Dart: 문서화(Documentation)
Effective Dart: 문서화(Documentation)
명확하고 도움이 되는 주석과 문서를 작성해볼게요.
출처: 원문
본문
오늘 여러분의 코드가 명확하다고 생각하기 쉽지만, 사실 얼마나 많은 맥락을 머릿속에 이미 담고 있는지 깨닫지 못하기 쉬워요. 여러분의 코드를 처음 접하는 사람이나, 심지어 미래의 건망증 있는 여러분 자신도 그 맥락을 갖고 있지 않아요. 간결하고 정확한 주석은 쓰는 데 몇 초밖에 걸리지 않지만, 그런 사람들의 시간을 몇 시간이나 아껴줄 수 있어요.
우리 모두 코드가 스스로 문서화되어야 하고 모든 주석이 도움이 되는 것은 아니라는 걸 알고 있어요. 하지만 현실은 대부분의 사람들이 필요한 만큼 주석을 쓰지 않는다는 거예요. 운동과 같아요. 기술적으로 너무 많이 할 수도 있지만, 너무 적게 하고 있을 가능성이 훨씬 높죠. 조금 더 신경 써 보도록 해요.
주석(Comments)
다음 팁은 생성된 문서에 포함하고 싶지 않은 주석에 적용돼요.
DO: 주석을 문장처럼 형식화해요
// Not if anything comes before it.
if (_chunks.isNotEmpty) return false;
대소문자를 구분하는 식별자가 아니라면 첫 단어를 대문자로 써 주세요. 마침표(또는 "!"나 "?", 아마도)로 끝내 주세요. 이것은 모든 주석, 즉 문서 주석, 인라인, 심지어 TODO에도 해당돼요. 문장 조각이라도 마찬가지예요.
DON'T: 문서화에 블록 주석을 사용하지 마세요
// 좋음
void greet(String name) {
// Assume we have a valid name.
print('Hi, $name!');
}
// 나쁨
void greet(String name) {
/* Assume we have a valid name. */
print('Hi, $name!');
}
코드 섹션을 일시적으로 주석 처리할 때는 블록 주석(/* ... */)을 사용할 수 있지만, 그 외 모든 주석은 //를 사용해야 해요.
문서 주석(Doc comments)
문서 주석은 dart doc이 파싱해서 보기 좋은 문서 페이지를 생성해 주기 때문에 특히 유용해요. 문서 주석은 선언 앞에 나타나며 dart doc이 찾는 특별한 /// 구문을 사용하는 모든 주석이에요.
DO: 멤버와 타입을 문서화할 때 /// 문서 주석을 사용해요
린터 규칙: slash_for_doc_comments
일반 주석 대신 문서 주석을 사용하면 dart doc이 그것을 찾아 문서를 생성할 수 있어요.
// 좋음
/// The number of characters in this chunk when unsplit.
int get length => ...
// 나쁨
// The number of characters in this chunk when unsplit.
int get length => ...
역사적 이유로 dart doc은 두 가지 문서 주석 구문을 지원해요. ///("C# 스타일")와 /** ... */("JavaDoc 스타일")가 그것이에요. 우리는 더 간결하기 때문에 ///를 선호해요. /**와 */는 여러 줄 문서 주석에 내용이 없는 두 줄을 추가해요. /// 구문은 문서 주석이 목록 항목을 표시하는 데 *를 사용하는 글머리 기호 목록을 포함할 때처럼 어떤 상황에서는 읽기도 더 쉬워요.
여전히 JavaDoc 스타일을 사용하는 코드를 만난다면 정리하는 것을 고려해 보세요.
PREFER: 공개 API에 문서 주석을 작성해요
린터 규칙: public_member_api_docs
모든 라이브러리, 최상위 변수, 타입, 멤버에 문서를 작성할 필요는 없지만, 대부분에는 작성을 해야 해요.
CONSIDER: 라이브러리 수준 문서 주석 작성도 고려해요
Java처럼 클래스가 프로그램 구성의 유일한 단위인 언어와 달리, Dart에서 라이브러리는 그 자체로 사용자가 직접 다루고 import하며 생각하는 개체예요. 그 때문에 library 지시문은 독자에게 제공되는 핵심 개념과 기능을 소개하는 문서를 두기에 좋은 자리예요. 다음을 포함하는 것을 고려해 보세요.
- 라이브러리가 무엇을 위한 것인지 한 문장으로 된 요약
- 라이브러리 전반에서 쓰이는 용어에 대한 설명
- API 사용을 안내하는 완전한 코드 샘플 두어 개
- 가장 중요하거나 자주 사용되는 클래스와 함수에 대한 링크
- 라이브러리가 다루는 도메인에 대한 외부 참고 자료 링크
라이브러리를 문서화하려면 library 지시문과 파일 시작 부분에 붙는 애너테이션 앞에 문서 주석을 배치해 주세요.
// 좋음
/// A really great test library.
@TestOn('browser')
library;
CONSIDER: 비공개 API에 문서 주석 작성도 고려해요
문서 주석은 단지 라이브러리 공개 API의 외부 소비자를 위한 것만은 아니에요. 라이브러리의 다른 부분에서 호출되는 비공개 멤버를 이해하는 데도 도움이 될 수 있어요.
DO: 문서 주석을 한 문장 요약으로 시작해요
마침표로 끝나는 간결하고 사용자 중심적인 설명으로 문서 주석을 시작해 주세요. 문장 조각으로도 충분한 경우가 많아요. 독자가 방향을 잡고 계속 읽을지, 아니면 문제 해결책을 다른 곳에서 찾을지 결정할 수 있는 정도의 맥락만 제공해 주세요.
// 좋음
/// Deletes the file at [path] from the file system.
void delete(String path) { ... }
// 나쁨
/// Depending on the state of the file system and the user's permissions,
/// certain operations may or may not be possible. If there is no file at
/// [path] or it can't be accessed, this function throws either [IOError]
/// or [PermissionError], respectively. Otherwise, this deletes the file.
void delete(String path) { ... }
DO: 문서 주석의 첫 문장을 별도의 문단으로 분리해요
첫 문장 뒤에 빈 줄을 추가해 그것을 별도의 문단으로 나눠 주세요. 한 문장보다 더 많은 설명이 유용하다면 나머지는 이후 문단에 넣어 주세요.
이렇게 하면 문서를 요약하는 타이트한 첫 문장을 쓰는 데 도움이 돼요. 또한 dart doc 같은 도구는 클래스와 멤버 목록 같은 곳에서 첫 문단을 짧은 요약으로 사용해요.
// 좋음
/// Deletes the file at [path].
///
/// Throws an [IOError] if the file could not be found. Throws a
/// [PermissionError] if the file is present but could not be deleted.
void delete(String path) { ... }
// 나쁨
/// Deletes the file at [path]. Throws an [IOError] if the file could not
/// be found. Throws a [PermissionError] if the file is present but could
/// not be deleted.
void delete(String path) { ... }
AVOID: 주변 맥락과의 중복을 피해요
클래스의 문서 주석을 읽는 사람은 클래스 이름, 구현하는 인터페이스 등을 분명히 볼 수 있어요. 멤버 문서를 읽을 때는 시그니처가 바로 거기 있고, 둘러싼 클래스도 명확해요. 그런 것들은 문서 주석에 다시 적을 필요가 없어요. 대신 독자가 아직 모르는 것을 설명하는 데 집중해 주세요.
// 좋음
class RadioButtonWidget extends Widget {
/// Sets the tooltip to [lines].
///
/// The lines should be word wrapped using the current font.
void tooltip(List<String> lines) { ... }
}
// 나쁨
class RadioButtonWidget extends Widget {
/// Sets the tooltip for this radio button widget to the list of strings in
/// [lines].
void tooltip(List<String> lines) { ... }
}
주변 맥락에서 금방 드러나지 않는 맥락, 주의 사항, 또는 사용 세부 사항을 제공할 때만 문서 주석을 추가해 주세요.
PREFER: 함수나 메서드의 주요 목적이 부수 효과(side effect)라면 3인칭 동사로 시작해요
문서 주석은 코드가 무엇을 하는지에 집중해야 해요.
// 좋음
/// Connects to the server and fetches the query results.
Stream<QueryResult> fetchResults(Query query) => ...
/// Starts the stopwatch if not already running.
void start() => ...
PREFER: 불리언이 아닌 변수나 프로퍼티 주석은 명사구로 시작해요
문서 주석은 프로퍼티가 무엇인지 강조해야 해요. 계산이나 다른 작업을 수행할 수 있는 게터에도 마찬가지예요. 호출자가 신경 쓰는 것은 그 작업의 결과이지, 작업 자체가 아니에요.
// 좋음
/// The current day of the week, where `0` is Sunday.
int weekday;
/// The number of checked buttons on the page.
int get checkedCount => ...
PREFER: 불리언 변수나 프로퍼티 주석은 "Whether" 뒤에 명사구나 동명사구로 시작해요
문서 주석은 이 변수가 나타내는 상태를 명확히 해야 해요. 계산이나 다른 작업을 수행할 수 있는 게터에도 마찬가지예요. 호출자가 신경 쓰는 것은 그 작업의 결과이지, 작업 자체가 아니에요.
// 좋음
/// Whether the modal is currently displayed to the user.
bool isVisible;
/// Whether the modal should confirm the user's intent on navigation.
bool get shouldConfirm => ...
/// Whether resizing the current browser window will also resize the modal.
bool get canResize => ...
참고: 이 지침은 의도적으로 "Whether or not"을 포함하지 않아요. 많은 경우 "whether"와 함께 "or not"을 쓰는 것은 불필요하며, 특히 이 맥락에서 생략할 수 있어요.
PREFER: 값 반환이 주요 목적이라면 함수나 메서드에 명사구나 비명령형 동사구를 사용해요
메서드가 구문상으로는 메서드지만 개념적으로는 프로퍼티라서 명사구나 비명령형 동사구로 이름 지어졌다면, 그것도 그렇게 문서화해야 해요. 이런 불리언이 아닌 함수에는 명사구를, 불리언 함수에는 구문상 프로퍼티나 변수처럼 "Whether"로 시작하는 구를 사용해 주세요.
// 좋음
/// The [index]th element of this iterable in iteration order.
E elementAt(int index);
/// Whether this iterable contains an element equal to [element].
bool contains(Object? element);
참고: 이 지침은 선언이 개념적으로 프로퍼티로 보이는지 여부에 따라 적용해야 해요. 부수 효과가 없어서 개념적으로 프로퍼티로 보일 수 있는 메서드도,
list.take()처럼 동사구로 이름 짓는 것이 여전히 더 단순할 수 있어요. 그때도 명사구로 문서화해야 해요. 예를 들어Iterable.take는 "The first [count] elements of ..."로 설명할 수 있어요.
DON'T: 프로퍼티의 게터와 세터 둘 다에 문서를 작성하지 마세요
프로퍼티에 게터와 세터가 둘 다 있다면 둘 중 하나에만 문서 주석을 만들어 주세요. dart doc은 게터와 세터를 하나의 필드처럼 취급하며, 게터와 세터 둘 다 문서 주석이 있으면 dart doc은 세터의 문서 주석을 버려요.
// 좋음
/// The pH level of the water in the pool.
///
/// Ranges from 0-14, representing acidic to basic, with 7 being neutral.
int get phLevel => ...
set phLevel(int level) => ...
// 나쁨
/// The depth of the water in the pool, in meters.
int get waterDepth => ...
/// Updates the water depth to a total of [meters] in height.
set waterDepth(int meters) => ...
PREFER: 라이브러리나 타입 주석은 명사구로 시작해요
클래스에 대한 문서 주석은 프로그램에서 가장 중요한 문서인 경우가 많아요. 타입의 불변식(invariants)을 설명하고, 사용하는 용어를 정립하며, 클래스 멤버를 위한 다른 문서 주석에 맥락을 제공해요. 여기에 조금만 더 신경 쓰면 다른 모든 멤버들을 문서화하기 더 쉬워져요.
문서는 타입의 인스턴스 한 개를 설명해야 해요.
// 좋음
/// A chunk of non-breaking output text terminated by a hard or soft newline.
///
/// ...
class Chunk { ... }
CONSIDER: 문서 주석에 코드 샘플을 포함하는 것을 고려해요
// 좋음
/// The lesser of two numbers.
///
/// ```dart
/// min(5, 3) == 3
/// ```
num min(num a, num b) => ...
사람들은 예제에서 일반화하는 데 뛰어나기 때문에, 코드 샘플 하나만으로도 API를 배우기가 훨씬 쉬워져요.
DO: 문서 주석에서 대괄호를 사용해 범위 안의 식별자를 가리켜요
린터 규칙: comment_references
변수, 메서드, 타입 이름 같은 것을 대괄호로 감싸면 dart doc이 이름을 조회해 관련 API 문서에 연결해요. 괄호는 선택 사항이지만 함수나 생성자를 가리키고 있음을 명확히 할 수 있어요. 다음 부분 문서 주석들은 이런 주석 참조가 유용한 몇 가지 경우를 보여줘요.
// 좋음
/// Throws a [StateError] if ...
///
/// Similar to [anotherMethod()], but ...
특정 클래스의 멤버에 연결하려면 클래스 이름과 멤버 이름을 점으로 구분해 사용해 주세요.
// 좋음
/// Similar to [Duration.inDays], but handles fractional days.
점 구문은 이름 붙은 생성자를 가리키는 데도 사용할 수 있어요. 이름 없는 생성자의 경우 클래스 이름 뒤에 .new를 사용해 주세요.
// 좋음
/// To create a point, call [Point.new] or use [Point.polar] to ...
분석기와 dart doc이 문서 주석에서 지원하는 참조에 대해 더 알아보려면 문서 주석 참조(Documentation comment references)를 확인해 주세요.
DO: 매개변수, 반환 값, 예외를 산문(prose)으로 설명해요
다른 언어들은 메서드의 매개변수와 반환이 무엇인지 설명하기 위해 장황한 태그와 섹션을 사용해요.
// 나쁨
/// Defines a flag with the given name and abbreviation.
///
/// @param name The name of the flag.
/// @param abbr The abbreviation for the flag.
/// @returns The new flag.
/// @throws ArgumentError If there is already an option with
/// the given name or abbreviation.
Flag addFlag(String name, String abbreviation) => ...
Dart에서의 관례는 그것을 메서드 설명에 통합하고 대괄호를 사용해 매개변수를 강조하는 거예요. 매개변수를 설명하는 "The [parameter]"(으)로 시작하는 섹션과, 반환 값에는 "Returns", 예외에는 "Throws" 섹션을 두는 것을 고려해 보세요. 오류는 예외와 같은 방식으로 문서화하거나, 던져질 정확한 오류를 문서화하지 않고 충족해야 하는 요구 사항으로만 문서화할 수 있어요.
// 좋음
/// Defines a flag with the given [name] and [abbreviation].
///
/// The [name] and [abbreviation] strings must not be empty.
///
/// Returns a new flag.
///
/// Throws a [DuplicateFlagException] if there is already an option named
/// [name] or there is already an option using the [abbreviation].
Flag addFlag(String name, String abbreviation) => ...
DO: 메타데이터 애너테이션 앞에 문서 주석을 배치해요
// 좋음
/// A button that can be flipped on and off.
@Component(selector: 'toggle')
class ToggleComponent {}
// 나쁨
@Component(selector: 'toggle')
/// A button that can be flipped on and off.
class ToggleComponent {}
마크다운(Markdown)
문서 주석에서 대부분의 마크다운 형식을 사용할 수 있으며 dart doc은 markdown 패키지를 사용해 그에 따라 처리해요.
마크다운을 소개하는 가이드는 이미 아주 많아요. 보편적인 인기가 우리가 그것을 선택한 이유예요. 여기 지원되는 형식의 맛을 보여주는 간단한 예제가 있어요.
/// This is a paragraph of regular text.
///
/// This sentence has *two* _emphasized_ words (italics) and **two**
/// __strong__ ones (bold).
///
/// A blank line creates a separate paragraph. It has some `inline code`
/// delimited using backticks.
///
/// * Unordered lists.
/// * Look like ASCII bullet lists.
/// * You can also use `-` or `+`.
///
/// 1. Numbered lists.
/// 2. Are, well, numbered.
/// 1. But the values don't matter.
///
/// * You can nest lists too.
/// * They must be indented at least 4 spaces.
/// * (Well, 5 including the space after `///`.)
///
/// Code blocks are fenced in triple backticks:
///
/// ```dart
/// this.code
/// .will
/// .retain(its, formatting);
/// ```
///
/// The code language (for syntax highlighting) defaults to Dart. You can
/// specify it by putting the name of the language after the opening backticks:
///
/// ```html
/// <h1>HTML is magical!</h1>
/// ```
///
/// Links can be:
///
/// * https://www.just-a-bare-url.com
/// * [with the URL inline](https://google.com)
/// * [or separated out][ref link]
///
/// [ref link]: https://google.com
///
/// # A Header
///
/// ## A subheader
///
/// ### A subsubheader
///
/// #### If you need this many levels of headers, you're doing it wrong
AVOID: 마크다운을 과하게 사용하지 마세요
의심스러우면 덜 형식화해 주세요. 형식은 콘텐츠를 밝히기 위해 존재하지, 콘텐츠를 대체하기 위해 존재하는 게 아니에요. 중요한 것은 단어예요.
AVOID: 형식화에 HTML을 사용하지 마세요
표 같은 것에는 드물게 유용할 수 있지만, 거의 모든 경우에 마크다운으로 표현하기에 너무 복잡하다면 표현하지 않는 편이 나아요.
PREFER: 코드 블록에는 백틱 fence를 사용해요
마크다운에는 코드 블록을 나타내는 두 가지 방법이 있어요. 각 줄을 네 칸 들여쓰기 하거나, 삼중 백틱 "fence" 줄 쌍으로 감싸는 방법이에요. 전자 구문은 들여쓰기가 이미 의미 있는 마크다운 목록 안에서나 코드 블록 자체가 들여쓰기된 코드를 포함할 때 취약해요.
백틱 구문은 그런 들여쓰기 문제를 피하고, 코드의 언어를 지정할 수 있으며, 인라인 코드에 백틱을 사용하는 것과 일관돼요.
// 좋음
/// You can use [CodeBlockExample] like this:
///
/// ```dart
/// var example = CodeBlockExample();
/// print(example.isItGreat); // "Yes."
/// ```
// 나쁨
/// You can use [CodeBlockExample] like this:
///
/// var example = CodeBlockExample();
/// print(example.isItGreat); // "Yes."
작문(Writing)
우리는 스스로를 프로그래머라고 생각하지만, 소스 파일에 있는 대부분의 문자는 주로 사람이 읽기 위한 것이에요. 영어는 우리가 동료들의 머리를 바꾸기 위해 코딩하는 언어예요. 어떤 프로그래밍 언어와 마찬가지로, 실력을 향상시키는 데 노력을 기울일 가치가 있어요.
이 섹션은 우리 문서에 대한 몇 가지 지침을 나열해요. 일반적인 기술 문서 작성 모범 사례에 대해 더 알아보려면 Technical writing style 같은 글을 참고할 수 있어요.
PREFER: 간결함을 선호해요
명확하고 정확하되, 또한 간결하게 작성해 주세요.
AVOID: 명백하지 않은 한 약어와 준말을 사용하지 마세요
많은 사람들이 "i.e.", "e.g.", "et al."이 무엇을 뜻하는지 모르요. 여러분 분야의 모든 사람이 안다고 확신하는 그 약어도 생각만큼 널리 알려지지 않았을 수 있어요.
PREFER: 멤버의 인스턴스를 가리킬 때 "the" 대신 "this"를 사용해요
클래스의 멤버를 문서화할 때, 멤버가 호출되는 객체를 다시 가리켜야 하는 경우가 많아요. "the"를 사용하면 모호할 수 있어요. "this" 뒤에 한정어를 붙이는 것을 선호해요. 홀로 쓰인 "this"도 모호할 수 있어요.
class Box {
/// The value this box wraps.
Object? _value;
/// Whether this box contains a value.
bool get hasValue => _value != null;
}