문서 작성하기
문서 작성하기 (Writing documentation)
Elixir는 문서를 일급 시민(first-class citizen) 으로 취급해요. 문서는 쓰기 쉽고 읽기 쉬워야 한다는 게 핵심이에요. 이 가이드에서는 모듈 속성, 스타일 관례, 독테스트(doctest) 같은 구성을 다루면서 Elixir에서 문서를 어떻게 작성하는지 배워 보게 돼요.
본문
마크다운 (Markdown)
Elixir 문서는 마크다운으로 작성돼요. 온라인에 마크다운 가이드가 많지만, GitHub의 Basic writing and formatting syntax 문서를 출발점으로 추천해요.
모듈 속성 (Module Attributes)
Elixir의 문서는 보통 모듈 속성에 붙여요. 예시를 볼게요.
defmodule MyApp.Hello do
@moduledoc """
This is the Hello module.
"""
@moduledoc since: "1.0.0"
@doc """
Says hello to the given `name`.
Returns `:ok`.
## Examples
iex> MyApp.Hello.world(:john)
:ok
"""
@doc since: "1.3.0"
def world(name) do
IO.puts("hello #{name}")
end
end
@moduledoc 속성은 모듈에 문서를 추가하는 데 사용돼요. @doc는 함수 앞에 붙여 그 함수에 대한 문서를 제공해요. 위 속성들 외에도 @typedoc를 사용하면 typespec의 일부로 정의한 타입에 문서를 붙일 수 있어요.
함수 인자 (Function arguments)
함수를 문서화할 때 인자 이름은 컴파일러가 추론해요. 예를 들어:
def size(%{size: size}) do
size
end
컴파일러는 이 인자를 map으로 추론할 거예요. 때로는 추론이 비효율적일 수 있는데, 특히 함수가 여러 절을 가지면서 각 절이 서로 다른 값에 매칭되는 경우가 그래요. 구현보다 앞선 어느 지점에서든 함수 헤드만 먼저 선언하면 문서화를 위한 올바른 이름을 지정할 수 있어요.
def size(map_with_size)
def size(%{size: size}) do
size
end
문서 메타데이터 (Documentation metadata)
Elixir는 개발자가 문서에 임의의 메타데이터를 붙일 수 있게 해요. 관련 속성(@moduledoc, @typedoc, @doc)에 키워드 리스트를 넘기면 돼요.
메타데이터는 어떤 키든 가질 수 있어요. 문서화 도구들은 메타데이터를 활용해 독자에게 더 많은 데이터를 제공하고 사용자 경험을 풍부하게 만들곤 해요. 다음 키들은 이미 도구에서 사용하는 미리 정의된 의미를 가져요.
:deprecated
흔히 쓰는 메타데이터로 :deprecated가 있어요. 문서에 사용이 권장되지 않는다는 경고를 표시해요.
@doc deprecated: "Use Foo.bar/2 instead"
@doc deprecated: ...는 개발자가 함수를 호출할 때 경고를 내지는 않아요. 코드에서도 경고를 내고 싶다면 @deprecated 속성을 사용하면 돼요.
@deprecated "Use Foo.bar/2 instead"
:group
함수, 콜백, 타입이 속하는 그룹이에요. iex에서 자동 완성에 사용되고, ExDoc가 사이드바에서 항목을 그룹화하는 데도 자동으로 사용돼요.
@doc group: "Query"
def all(query)
@doc group: "Schema"
def insert(schema)
:since
해당 모듈, 함수, 타입, 콜백이 추가된 버전을 표시해요.
@doc since: "1.3.0"
def world(name) do
IO.puts("hello #{name}")
end
권장 사항 (Recommendations)
문서를 작성할 때는 다음을 지켜 보세요.
- 문서의 첫 문단은 간결하고 단순하게, 보통 한 줄로 유지해요. ExDoc 같은 도구는 첫 줄을 사용해 요약을 만들어요.
- 모듈은 전체 이름으로 참조해요. 마크다운은 코드를 인용할 때 백틱(
`)을 사용해요. Elixir는 이를 확장해서, 모듈이나 함수 이름이 참조되면 자동으로 링크를 생성해요. 그래서 항상 전체 모듈 이름을 사용해야 해요.MyApp.Hello라는 모듈이 있다면 항상`MyApp.Hello`로 참조하고, 절대`Hello`로는 참조하지 마세요. - 로컬 함수는 이름과 아리티로
`world/1`처럼 참조하고, 외부 모듈을 가리킨다면 모듈·이름·아리티로`MyApp.Hello.world/1`처럼 참조해요. @callback은`c:world/1`처럼c:를 앞에 붙여 참조해요.@type은`t:values/0`처럼t:를 앞에 붙여 참조해요.- 새 섹션은 2단계 마크다운 헤더
##로 시작해요. 1단계 헤더는 모듈·함수 이름을 위해 예약돼 있어요. - 다중 절 함수에서는 첫 번째 절 앞에 문서를 배치해요. 문서는 항상 함수·아리티 단위로 붙고 절 단위로 붙지 않아요.
- 새 함수나 모듈을 API에 추가할 때는 문서 메타데이터의
:since키를 사용해 표시해요.
독테스트 (Doctests)
문서에 예시를 포함하도록 권장하는데, 보통 별도의 ## Examples 헤더 아래에 두는 형태예요. 예시가 낡는 일을 막기 위해, Elixir의 테스트 프레임워크(ExUnit)는 독테스트(doctest) 라는 기능을 제공해요. 이 기능을 쓰면 문서의 예시를 테스트할 수 있어요. 독테스트는 문서에서 iex>로 시작하는 코드 샘플을 파싱해서 동작해요. 자세한 내용은 ExUnit.DocTest에서 확인할 수 있어요.
문서와 코드 주석은 다르다 (Documentation != Code comments)
Elixir는 문서와 코드 주석을 서로 다른 개념으로 취급해요.
- 문서(documentation) 는 API를 사용하는 사람(서드파티 개발자, 동료, 미래의 나)과의 명시적인 계약이에요. API의 일부라면 모듈과 함수는 항상 문서화되어야 해요.
- 코드 주석(code comments) 은 코드를 읽는 개발자를 위한 거예요. 개선할 점을 표시하거나, 메모를 남기거나(예: 라이브러리의 버그 때문에 우회 방법을 쓸 수밖에 없었던 이유), 그런 용도로 유용해요. 주석은 소스 코드에 묶여 있어요. 함수를 완전히 다시 쓰고 기존 코드 주석을 모두 지워도, 동작이나 문서에는 변화 없이 똑같이 동작해요.
private 함수는 외부에서 접근할 수 없기 때문에, Elixir는 private 함수에 @doc 속성이 있으면 경고를 내고 그 내용을 버려요. 하지만 다른 코드와 마찬가지로 private 함수에도 코드 주석을 달 수 있고, 해당 코드를 읽고 유지하는 사람에게 유용한 정보를 더해 준다고 생각된다면 그렇게 하길 권장해요.
요약하면, 문서는 소스 코드를 꼭 보지 못할 수도 있는 API 사용자와의 계약이고, 코드 주석은 소스 코드와 직접 상호작용하는 사람을 위한 것이에요. 이 두 개념을 분리함으로써 소프트웨어에 대해 서로 다른 보증을 배우고 표현할 수 있어요.
내부 모듈과 함수 숨기기 (Hiding internal modules and functions)
라이브러리는 공개 인터페이스의 일부로 제공하는 모듈·함수 외에도, API의 일부는 아니지만 중요한 기능을 구현하기도 해요. 이런 모듈·함수는 접근 가능하지만 라이브러리 내부용이므로 최종 사용자를 위한 문서는 없어야 해요.
다행히 Elixir는 개발자가 문서에서 모듈·함수를 숨길 수 있게 해요. 특정 함수를 숨기려면 @doc false를, 모듈 전체를 숨기려면 @moduledoc false를 설정하면 돼요. 모듈이 숨겨져 있어도 모듈 안의 함수는 문서화할 수 있지만, 모듈 자체는 문서에 나열되지 않아요.
defmodule MyApp.Hidden do
@moduledoc false
@doc """
This function won't be listed in docs.
"""
def function_that_wont_be_listed_in_docs do
# ...
end
end
모듈 전체를 숨기고 싶지 않다면 함수를 개별적으로 숨길 수 있어요.
defmodule MyApp.Sample do
@doc false
def add(a, b), do: a + b
end
다만 @moduledoc false나 @doc false가 함수를 private으로 만들지는 않는다는 점을 기억하세요. 위 함수는 여전히 MyApp.Sample.add(1, 2)처럼 호출할 수 있어요. 뿐만 아니라 MyApp.Sample이 import되면 add/2 함수도 호출자에게 import돼요. 그런 이유로 @doc false를 함수에 추가할 때는 주의해야 해요. 대신 다음 두 가지 방법 중 하나를 사용하면 돼요.
- 문서화하지 않은 함수를
@moduledoc false인 모듈로 옮기기 — 예를 들어MyApp.Hidden같은 모듈로요. 그러면 함수가 실수로 노출되거나 import되는 일을 막을 수 있어요.@moduledoc false로 모듈 전체를 숨겨도 각 함수는@doc으로 문서화할 수 있어요. 도구는 여전히 그 모듈을 무시해요. - 함수 이름을 밑줄 하나 또는 둘로 시작하기 — 예를 들어
__add__/2처럼요. 밑줄로 시작하는 함수는 자동으로 숨겨진 것으로 처리돼요. 물론@doc false를 명시적으로 추가할 수도 있어요. 컴파일러는 앞에 밑줄이 있는 함수를 import하지 않으며, 코드를 읽는 사람에게 의도된 private 용도를 암시해 줘요.
Code.fetch_docs/1
Elixir는 문서를 바이트코드 안의 미리 정의된 청크(chunk) 에 저장해요. 문서는 모듈이 로드될 때 메모리에 올라오지 않고, 대신 Code.fetch_docs/1 함수로 디스크의 바이트코드에서 읽을 수 있어요. 단점은 IEx에서 정의된 모듈처럼 메모리 안에서 정의된 모듈은 바이트코드를 디스크에 쓰지 않으므로 문서에 접근할 수 없다는 거예요.