openjdk.ruOpenJDK на русском

JEP draft: javadoc tags to distinguish API, implementation, specification, and notes

Теги javadoc для разделения API, реализации, спецификации и примечаний

ОтветственныйStuart Marks
ТипInformational
ОбластьJDK
СтатусDraft
Обсуждениеjdk9 dash dev at openjdk dot java dot net
ТрудоёмкостьS
ДлительностьS
РецензентыAlex Buckley, Chris Hegarty, Jonathan Gibbons
Создан2015/01/06 23:58
Обновлён2021/03/26 17:40
Задача8068562

Аннотация

Этот информационный JEP описывает некоторые специфичные для JDK теги javadoc, которые появились в JDK 8, чтобы сделать спецификацию и сопутствующую документацию понятнее, а также улучшить наследование документации подклассами.

Цели

До JDK 8 «javadoc» состоял в основном из спецификации API Java SE, в которую была вкраплена документация о реализации и информационные примечания. Эти разные виды документации не всегда были чётко обозначены, и нормативная сила утверждений о реализации тоже часто была неясной. Этот JEP описывает теги javadoc, которые позволяют разнести эти виды документации по отдельным разделам документации, а подклассам — наследовать их выборочно.

Кроме того, изначально этот JEP предназначен для документирования существующего использования этих тегов в JDK 8. Разработка новых тегов javadoc или других улучшений в его задачи не входит.

Что не является целью

Новые теги специфичны для JDK, и в настоящее время не предполагается, что они станут стандартными тегами javadoc.

Этот JEP не предлагает разработать новые теги javadoc: его область ограничена документированием тегов, появившихся в JDK 8.

Мотивация

То, что многие называют «javadoc», официально называется «Java(tm) Platform, Standard Edition N API Specification». Спецификация в первую очередь описывает, что должна делать та или иная часть API (часто в терминах предусловий и постусловий), а не как API реализован. Поэтому легко прийти к выводу, что спецификация относится только к API. Однако это не так. Абстрактный класс содержит реализацию, и подклассы должны иметь возможность полагаться на определённое поведение этой реализации (например, на то, когда вызывается super.method()), а также определять, следует ли им унаследовать реализацию или переопределить её. Из этого следует, что спецификация должна включать и требования к реализациям. С появлением методов по умолчанию в интерфейсах в Java SE 8 интерфейсы теперь тоже могут содержать реализации. Мы ожидаем, что методы по умолчанию будут использоваться довольно часто, поэтому в Java SE 8 потребность в спецификации реализаций гораздо выше, чем в предыдущих выпусках.

Например, рассмотрим спецификацию AbstractMap.putAll. В ней, в частности, сказано:

Эта реализация перебирает коллекцию entrySet() указанного отображения и вызывает операцию put этого отображения один раз для каждого элемента, возвращённого при переборе.

Это важнейшая информация для тех, кто пишет подклассы. Она указывает, что putAll не обходит метод put, поэтому, чтобы реализовать какое-то особое поведение, подклассам достаточно переопределить put, а переопределять putAll не нужно. Однако если они всё же переопределяют putAll, переопределяющий метод может вызвать super.putAll() и полагаться на специфицированное поведение AbstractMap.putAll. С методами по умолчанию ситуация аналогична. Классам, реализующим интерфейсы с методом по умолчанию, нужно знать, могут ли они полагаться на реализацию метода по умолчанию или его полезно либо необходимо переопределить.

Обратите внимание на разницу между спецификацией AbstractMap.putAll и спецификацией Map.putAll:

Результат этого вызова эквивалентен вызову put(k, v) для этого отображения один раз для каждого соответствия ключа k значению v в указанном отображении.

Слова результат и эквивалентен ясно показывают, что спецификация Map.putAll описывает результаты или постусловия вызова метода, но не то, как метод выполняет свою работу. (В других спецификациях могут использоваться другие слова с похожим значением, например «Результат такой, как если бы ....») Спецификация AbstractMap.putAll, напротив, прямо говорит, что метод вызывает операцию put: никаких слов «как если бы» или «эквивалентно» здесь нет. Это явно спецификация того, как AbstractMap.putAll должен выполнять свою работу.

В приведённом выше фрагменте спецификации AbstractMap использовалась фраза «эта реализация». В этом контексте она означает «реализация метода putAll, находящаяся в классе AbstractMap». Хотя здесь описывается реализация, это в той же мере часть спецификации класса AbstractMap, что и спецификации API. Метод AbstractMap.putAll в каждом выпуске каждого продукта Java SE должен вести себя одинаково. Мы ожидаем, что набор тестов на соответствие будет проверять, что метод putAll действительно вызывает метод put для каждого элемента.

Для сравнения рассмотрим следующий фрагмент из метода Runtime.loadLibrary:

Детали этого процесса зависят от реализации.

В этом контексте «реализация» означает конкретную реализацию Java SE, например OpenJDK, Oracle JDK, независимую реализацию Java SE или даже «ту же самую» версию JDK, работающую в другой операционной системе. Например, не должно вызывать удивления, если поведение Runtime.loadLibrary будет различаться у OpenJDK в Linux и OpenJDK в Windows. В отличие от примера с AbstractMap, поведение Runtime.loadLibrary может различаться в разных продуктах и на разных платформах. Поэтому утверждения о реализации Runtime.loadLibrary не имеют той силы спецификации, которую имеют утверждения о AbstractMap.

Можно возразить, что приведённый выше пример спецификации реализации AbstractMap.putAll на самом деле является спецификацией API с необычно высокой степенью детализации. На самом деле это не так, и это становится видно, если рассмотреть наследование.

Спецификация API для putAll определена в интерфейсе Map. Она относится ко всем подтипам Map. Обычно считается ошибкой проектирования, если у какого-либо потомка Map есть метод putAll, нарушающий утверждения, сделанные в спецификации Map.putAll.

С другой стороны, спецификация реализации AbstractMap.putAll относится только к самому AbstractMap и тем его потомкам, которые решили унаследовать putAll. Вполне возможно, что у потомка AbstractMap будет собственная реализация putAll, которая обходит метод put. Разумеется, эта реализация всё равно должна соответствовать требованиям, изложенным в спецификации Map.putAll. Поэтому такому классу-потомку нужно выборочно наследовать части документации своих предков. Ему нужно унаследовать спецификацию API от Map.putAll, но не наследовать спецификацию реализации от AbstractMap.putAll.

Спецификации обычно состоят из утверждений, предусловий, постусловий и тому подобного. Однако документация содержит и другую информацию, например предысторию, обоснование, соображения по использованию и тому подобное. В документах стандартов утверждения спецификации называются «нормативными», а остальная документация — «ненормативной» (иногда «информативной»). Для краткости в этом документе для этих понятий используются термины «spec» (спецификация) и «notes» (примечания).

Мы выделили два противопоставления: между API и реализацией и между спецификациями и примечаниями. Таким образом, в «javadoc» встречаются четыре разные категории документации. Кроме того, мы выявили потребность подтипов выборочно наследовать части документации от своих супертипов. Поэтому мы ввели несколько новых тегов javadoc, с помощью которых документацию можно разделить на разделы, соответствующие этим категориям. Мы также изменили поведение тега @inheritDoc, чтобы он наследовал только тот раздел документации, в котором находится тег.

Описание

Есть четыре категории документации, которые нужно различать с помощью тегов javadoc:

  1. Спецификация API – (без тега)
  2. Примечания к API – @apiNote
  3. Спецификация реализации – @implSpec
  4. Примечания к реализации – @implNote

Начальный текст комментария javadoc относится к категории «Спецификация API». Если используется один из этих тегов, последующий текст относится к соответствующей категории. Все эти теги необязательны. Если ни один из тегов не указан, вся документация считается спецификацией API.

Определения категорий:

Спецификация API. Это самая привычная категория, так как большая часть javadoc уже является спецификацией API. Сюда входят спецификации, которые относятся ко всем допустимым реализациям, включая предусловия, постусловия и т. д. По умолчанию документация считается спецификацией API, поэтому тега для этой категории нет.

Примечания к API. Эта категория состоит из комментариев, обоснований или примеров, относящихся к API.

Спецификация реализации. Здесь специфицируется реализация по умолчанию (или переопределяемая реализация в классе). Те, кто реализует интерфейс или пишет подкласс, используют эту информацию, чтобы решить, разумно или необходимо ли переопределить конкретный метод и на какое поведение можно полагаться при вызове метода через super.

Примечания к реализации. Этот раздел содержит информационные примечания о реализации, например советы разработчикам реализаций или характеристики производительности, специфичные для реализации в этом классе в данной версии JDK. Информация в этом разделе может меняться от выпуска к выпуску. Эти характеристики также могут различаться на разных платформах, у разных поставщиков и в разных версиях.

Поведение тега @inheritDoc изменено: теперь он наследует документацию только для того раздела, в котором находится. Это даёт более тонкий контроль над тем, какие части документации наследуются. Например, метод по умолчанию обычно содержит и спецификацию API (которая должна относиться ко всем реализациям), и спецификацию реализации (которая относится только к реализации метода по умолчанию). Если реализующий класс укажет @inheritDoc без каких-либо тегов категорий, он унаследует только спецификацию API, но не спецификацию реализации метода по умолчанию. Обычно именно такое поведение нужно реализующим классам, которые переопределяют метод по умолчанию. Если реализующий класс хочет унаследовать и спецификацию реализации, ему следует начать раздел @implSpec и поместить в него ещё один тег @inheritDoc.

Эти теги наиболее полезны в документации методов, но могут встречаться и в документации классов и полей. Типичный документирующий комментарий с этими тегами выглядит так:

/**
 * ... API specifications ...
 *
 * @apiNote
 * ... API notes ...
 *
 * @implSpec
 * ... implementation specification ...
 *
 * @implNote
 * ... implementation notes ...
 *
 * @param ...
 * @return ...
 * @throws ...
 */

Реализация

Поддержка этих тегов не добавлялась ни в сам javadoc, ни в стандартный doclet (который отвечает за формирование обычного HTML-вывода). Вместо этого стандартный doclet поддерживает возможность пользовательских тегов, которая настраивается с помощью параметра командной строки -tag. Новые теги были реализованы добавлением соответствующих параметров -tag в вызов javadoc из командной строки в make-файлах JDK. См. JDK-8008632. Пример командной строки javadoc с поддержкой этих тегов выглядит примерно так:

javadoc -tag 'apiNote:a:API Note:' \
        -tag 'implSpec:a:Implementation Requirements:' \
        -tag 'implNote:a:Implementation Note:'

Стандартный doclet потребовалось доработать, чтобы он поддерживал тег @inheritDoc нужным образом. См. JDK-8008768.

Примеры

У класса WombatFactory может быть метод, документированный следующим образом:

/**
 * Returns a list of Wombat instances retrieved from the platform's
 * Wombat repository. The returned list will not contain any duplicates;
 * that is, the result of calling w1.equals(w2) will always be false, where
 * w1 and w2 are any two different elements of the returned list. The
 * returned Wombat instances match the name given as an argument.
 * Whether name matching is case-sensitive or -insensitive is
 * platform-specific behavior. 
 *
 * @apiNote
 * This method returns a List instead of a Collection or Stream,
 * because processing of multiple Wombats usually involves traversing
 * the list in alternating forward and reverse directions.
 *
 * @implSpec
 * The implementation in this class returns a List where
 * access to elements by index is O(1). The returned List is
 * serializable, but it is not guaranteed to be thread-safe.
 *
 * @implNote
 * The JDK Reference Implementation returns the list of wombats sorted
 * by weight, although this is not required of all implementations.
 */
public List<Wombat> getWombats(String name) { ... }

Может существовать подкласс LazyWombatFactory, который выборочно наследует спецификацию API, но не спецификацию реализации:

/**
 * {@inheritDoc}
 *
 * @implSpec
 * The implementation in this class returns a List that is populated
 * with Wombat instances that might not be fully initialized. If
 * get() is called on a not-initialized Wombat instance, the call
 * blocks until the instance has been initialized; otherwise, it
 * returns immediately. The returned List is not serializable, but
 * it is thread-safe.
 */
@Override
public List<Wombat> getWombats(String name) { ... }

Ссылки

Первоначальное предложение обсуждалось в рассылке экспертной группы JSR-335 (Lambda Libraries). Обсуждение продолжилось до февраля в сообщениях с темой «Javadoc conventions in the presence of default methods».

Изменения в реализации для поддержки этих новых пользовательских тегов описаны в задаче JDK-8008632.

Изменения в реализации, необходимые для поддержки новой модели @inheritDoc, описаны в задаче JDK-8008768.

Альтернативы

В одном из ранних предложений рассматривалась доработка стандартного doclet или самого javadoc для поддержки этих тегов. Было сочтено преждевременным «встраивать» эти теги непосредственно в эти компоненты, не имея никакого опыта их использования. Вместо этого теги были реализованы с помощью возможности пользовательских тегов стандартного doclet.

Альтернативный вариант — ничего не делать и продолжать пользоваться существующими механизмами. Описанные выше проблемы можно было бы просто излагать обычным текстом, без изменений в форматировании или разметке. В принципе это так, однако до Java SE 8 вопрос спецификации реализации возникал сравнительно редко (в абстрактных классах, которые предоставляли реализации для поддержки своих подклассов), поэтому платформа могла кое-как обходиться без специальной поддержки в разметке. Ожидается, что появление методов по умолчанию в интерфейсах в Java SE 8 сделает такие проблемы спецификации более частыми, и потребность в том, чтобы инструменты поддерживали эти случаи более формально, возрастёт.