Документирование JavaScript с помощью YUIDoc

30 июля 2017

Документирование вашего кода несколько напоминает тестирование; мы все знаем, что мы должны это делать, мы не совсем уверены, как и большинство людей, если мы честны, просто нет, но те, кто это делает, являются огромными сторонниками этого. Этот учебник поможет вам справиться с одним из лучших способов его решения: YUIDoc.


Что такое YUIDoc?

YUIDoc будет генерировать документацию API на основе комментариев, которые вы пишете.

YUIDoc - это приложение NodeJS, которое будет генерировать документацию API (в виде HTML) на основе комментариев, которые вы пишете в исходном коде JavaScript. На самом деле, это не только для JavaScript: любой язык программирования, поддерживающий комментарии к блокам, ограниченные * *, работает для YUIDoc. Как вы могли догадаться, YUIDoc является одним из инструментов, которые Yahoo! публикует вместе с их библиотекой YUI.

Чтобы установить YUIDoc, вам понадобится сначала NodeJS и менеджер пакетов Node (npm). Затем вы можете установить YUIDoc через npm -g install yuidocjs. Вы будете использовать его, запустив yuidoc; подробнее об этом позже.


Это все о тегах

Итак, вы знаете, что YUIDoc получает свою документацию из многострочных комментариев в исходном файле. Конечно, у вас могут быть комментарии, которые не являются частью документации. Чтобы YUIDoc распознал комментарий как значительный, он должен начинаться с двойного старта: **. Так:

/**
YUIDoc will process this
*/
/*
But not this
*/

Конечно, это то, что внутри этого подсчета (внутри блоков комментариев, то есть). Каждый должен включать один и только один основной тег; он также может включать в себя ноль или более вторичных тегов. На самом деле YUIDoc так просто: добавьте комментарии с правильными тегами к вашему коду, а presto: документация! Итак, давайте изучим некоторые теги. Вот как мы это сделаем: мы рассмотрим теги и где они используются, с простыми примерами их использования; то мы напишем и документируем некоторый код, чтобы вы лучше поняли, как эти теги работают вместе.


Первичные теги

Прежде чем входить в основные теги, помните, что в каждом блоке комментариев может быть только один первичный тег. Они описывают, что такое кусок кода.

@module

Тег @module описывает группу родственных классов. (Да, да, у JavaScript нет классов: YUIDoc ссылается на функции конструктора.) Если вы использовали YUIDoc для документирования BackboneJS, объект Backbone был бы модулем, поскольку он содержит модели, коллекцию, просмотр и другие классы, Сразу после тега вы помещаете имя модуля.

/**
@module Backbone
 */
 var Backbone = Backbone || {};

@class

Тег @class точно описывает один класс. В библиотеке YUI это обычно означает функцию конструктора, но если вы предпочитаете использовать другой шаблон и называть его своим классом, вы тоже можете это сделать. Каждый комментарий с тегом @class также должен иметь тег @static или @constructor (вторичные теги, которые мы обсудим в ближайшее время).

/**
@class Model
 */
 function Model () {}

Если ваш класс является частью модуля, вам не нужно ничего делать в комментарии @class, чтобы обозначить это: просто убедитесь, что в этом файле есть блок комментариев @module.

@method

Конечно, каждый класс будет иметь как минимум несколько методов, и вы будете использовать тег @method для их описания. Имя метода будет идти после тега, и вы будете использовать вторичные теги @return и @params для описания метода.

/**
@method render
*/
View.prototype.render = function (data) {}

@property

Тег @property используется для тега свойств класса. Вы обязательно захотите использовать вторичные теги @type и @default с этим.

/**
@property templateString
*/
this.templateString = "div";

@event

Если у вас есть специальные пользовательские события, которые может запускать класс, вам нужно использовать тег @event для их описания. Вот что должна сказать документация YUIDoc:

Блок @event несколько похож на блок @method, за исключением того, что @return не имеет значения, а @param используется для описания свойств, зависающих от объекта события, которые обращаются к прослушиванию события Получать.

Вторичные теги

Блоки комментариев могут содержать более одного вторичного тега; у них часто будет горстка, а иногда и более одного типа. Давайте рассмотрим некоторые из тех, которые вы часто будете использовать.

@submodule

Если вы разделите свои модули на подмодули (возможно, подмодуль на файл, а может и нет), тег @submodule к вашим услугам.

/**
@module Util
@submodule array
*/
Util.array = {};

@extends

Тег @extends полезен, если у вас есть отношения подкласса суперкласса. Вы можете утверждать, какой класс является родителем текущего документального класса:

/**
@class AppView
@extends Backbone.View
*/
var AppView = Backbone.View.extend({});

@constructor

Если экземпляр класса может быть создан, это означает, что ему нужна функция-конструктор. Если вы используете стандартный шаблон прототипа в JavaScript, объявление класса также является конструктором. Это означает, что вы часто увидите что-то вроде этого:

/**
@class Recipe
@constructor
*/
function Recipe () {}

На самом деле, вероятно, вы, наверное, помните, что каждый тег @class должен иметь либо атрибут @constructor, либо дополнительный атрибут @static.

@static

Говоря о @static, вот оно. Класс считается статическим, если вы не можете создать его экземпляр. Хорошим примером этого является встроенный объект Math: вы никогда не создаете его экземпляр (новый Math ()), вы вызываете его методы из самого класса.

/**
@class MathHelpers
@static
*/
var MathHelpers = {};

Метод также может быть статическим: если класс может быть создан, но также имеет некоторые методы на уровне класса, эти методы считаются статическими (они вызываются в классе, а не в экземпляре).

/**
@class Perso
@constructor
*/
function Person () {}
/**
@method all
@static
*/
Person.all = function () {};

В этом примере вы можете создать экземпляр Person, но весь метод статический.

@final

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

/**
@property DATE_FORMAT
@final
*/
var DATE_FORMAT = "%B %d, %Y";

@param

Вот важный: тег @param используется для определения параметров @method (включая @constructor) или @event. Есть три бита информации, которые идут после тега @param: имя параметра, тип (который является необязательным) и описание. Они могут быть либо в описании типа имени заказа, либо в описании имени типа; но в любом случае тип должен быть окружен фигурными фигурными скобками.

/**
@method greet
@param person {string} The name of the person to greet
*/
function greet (person) {}

Существует несколько способов настройки части имени. Помещение в квадратные скобки обозначает его как необязательное, а put = someVal после того, как он показывает, что такое значение по умолчанию (очевидно, только параметры, имеющие значение по умолчанию, имеют значение по умолчанию). Затем, если это заполнитель для более чем одного аргумента, добавьте *, чтобы показать это. (Очевидно, имя * является заполнителем для 1 или более аргументов, а [имя] * является заполнителем для 0 или более).

/**
@class Template
@constructor
@param template {String} The template string
@param [data={}] {Object} The object whose properties will be rendered in the template
*/
function Template (template, data) {}

@retur

Большинство ваших методов захотят вернуть значение, поэтому это тег, который описывает это значение. Не забудьте сказать, какой тип это значение, и дать ему описание.

/**
@method toHTML
@param [template=Recipe.defaultTemplate] {Template} A template object
@return {String} The recipe contents formatted in HTML with the default or passed-in template.
*/
Recipe.prototype.toHTML = function (template) {
    return "whatever";
};

@type

Помните основной тег @property? Вы хотите определить, какой тип эти свойства, не так ли? Ну, тег @type - это именно то, что вам нужно. Укажите тип после тега; вы также можете предложить несколько типов, разделив их на вертикальные полосы:

/**
@property URL
@type String
*/
URL: "http://net.tutsplus.com",
/**
@property perso
@type String|Person|Object
*/
this.person = new Person();

@private @protected

Традиционные языки программирования предлагают частные свойства или методы: они недоступны извне экземпляра. Как и константы, JavaScript использует их только на практике, но вы можете использовать @private, чтобы пометить их, если вы их используете. Обратите внимание, что YUIDoc не показывает частные свойства в документах, которые он генерирует (что имеет смысл), поэтому это позволяет вам документировать функцию для вашей собственной выгоды и не показывать ее в документах.

/**
@method _toString
@private
*/
var _toString = Object.prototype.toString.call;

Защищенные свойства и методы находятся на полпути между публичными и частными: они доступны только из экземпляров и экземпляров подклассов. Если это то, что вы делаете в JavaScript, вот твой тег: @protected.

@requires

Если модуль зависит от одного или нескольких других модулей, вы можете использовать @requires, чтобы отметить это:

/**
@module MyFramework.localstorage
@requires MyFramework
*/

Обратите внимание, что @requires также может принимать список зависимостей, разделенных запятыми.

@default

При объявлении @property вы можете счесть полезным присвоить ему значение @default. @default всегда следует использовать с @type.

/**
@property element
@type String
@default "div"
*/
element: "div",

@uses

Как мы уже говорили, JavaScript не имеет классов, но он достаточно гибкий, чтобы создать иллюзию классов и даже подклассов. Что еще более круто, так это то, что он достаточно гибкий, чтобы иметь микшины или модули: здесь один класс «заимствует» свойства или методы из другого класса. И это тоже не наследование, потому что вы можете смешивать в нескольких классах (конечно, YUI имеет возможность сделать это, но Dojo и другие библиотеки). Если вы это сделаете, вы найдете @uses очень полезным: он позволяет вам объявлять классы, которые данный класс смешивает в некоторых частях.

/**
@class ModalWindow
@uses Window
@uses DragDroppable
*/
var ModalWindow = new Class({
    mixes: [Window, DragDroppable],
    ...
});

Примечание. Я только что составил этот синтаксис mixin, но я уверен, что где-то видел нечто подобное.

@example

Хотите включить пример того, как использовать конкретный фрагмент кода? Используйте тег @example, а затем напишите пример ниже, отделив его на один уровень. Вы можете добавить столько примеров, сколько захотите.

/**
@method greet
@example
    person.greet("Jane");
*/
Person.prototype.greet = function (name) {};

@chainable

Вероятно, вы знакомы с цепными методами из jQuery. Вы знаете, где вы можете вызвать метод при вызове метода, потому что методы возвращают объект? Отметьте свои методы как таковые с помощью @chainable.

/**
@method addClass
@chainable
*/
jQuery.prototype.addClass = function (class) {
    // stuff;
    return this;
}

@deprecated @since @beta

Эти три тега касаются поддержки кода (и это может быть любой код: модуль, класс, метод или что-то еще). Используйте @deprecated, чтобы отметить некоторые функции, поскольку это не лучший способ сделать это (устаревшие функции, вероятно, будут удалены в будущей версии кода). При желании вы можете включить сообщение, объясняющее, каков его текущий способ.

/**
@method toJSO
@deprecated Pass the object to `JSON.parse` instead
*/
Something.toJSON = function () {};

Тег @since просто сообщает читателям, какая версия данного кода добавлена. И @beta отмечает бета-код: YUI предлагает, чтобы код @beta мог «претерпеть обратно-несовместимые изменения в ближайшем будущем».

/**
@class Tooltip
@since 1.2.3
@constructor
*/
function Tooltip () {}

@extension @extensionfor extension_for

Тег @extension (и его псевдонимы) в значительной степени противоположен @uses. Используйте его, чтобы отметить, к каким классам может быть добавлен класс расширения. Конечно, понимайте, что это не означает, что он всегда смешивается, просто так бывает.

/**
@class Draggable
@extensionfor ModalWindow
*/

Комментарии и Markdow

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

Во-первых, вы часто захотите добавить немного больше информации о своем коде, чем те, что предлагают теги. Возможно, вы хотите описать цель методов или как класс вписывается в большую картину. Добавьте эти комментарии вверху блока комментариев, над любым из тегов. YUIDoc заметит их и включит их в документацию.

/**
The `Router` class is used for . . .
@class Router
@static
*/
var Router = {};

Во-вторых, вам будет приятно узнать, что эти комментарии, а также любые описания или сообщения, написанные после тегов, могут быть записаны в Markdown, а YUIDoc преобразует их в правильный HTML. Вы можете даже отступать от блоков кода примера в своих комментариях и получать подсветку синтаксиса!


Пример

Теперь, когда вы узнали теги, давайте на самом деле написать код и документировать его. Давайте создадим модуль Store, который содержит два класса: Item и Cart. Каждый экземпляр элемента будет типом элемента в инвентаре магазина: он будет иметь имя, цену и количество. Экземпляр Cart может добавлять предметы в корзину и рассчитать общую стоимость предметов в корзине (включая налог). Это довольно просто, но дает нам достаточно разнообразную функциональность, чтобы использовать многие теги, которые мы обсуждали. Я поместил весь следующий код в store.js.

Начнем с создания модуля:

/**
* This module contains classes for ruing a store.
* @module Store
*/
var Store = Store || {};

Теперь давайте создадим «постоянную»: ставку налога.

/**
* `TAX_RATE` is stored as a percentage. Value is 13.
    * @property TAX_RATE
    * @static
    * @final
    * @type Number
*/
Store.TAX_RATE = 13;

Это константа (@final) @property из номера @type. Заметьте, что я включил @static: это потому, что по какой-то причине, когда мы создаем документацию для этого файла, YUIDoc будет отображать это как свойство нашего класса Item: кажется, что YUIDoc не поддерживает наличие свойства на модуль. Я думаю, я мог бы создать статический класс для хранения этой константы (и других констант, которые могли бы возникнуть, если мы еще больше ее разработали), но я оставил ее таким образом для напоминания: использовать такой инструмент, как YUIDoc, в максимально возможной степени, вы возможно, придется изменить способ кодирования. Вам придется решить, хотите ли вы это сделать.

Теперь для класса Item:

/**
 * @class Item
 * @constructor
 * @param name {String} Item name
 * @param price {Number} Item price
 * @param quantity {Number} Item quantity (the number available to buy)
 */
Store.Item = function (name, price, quantity) {
    /**
     * @property name
     * @type String
     */
    this.name = name;
    /**
     * @property price
     * @type String
     */
    this.price = price * 100;
    /**
     * @property quantity
     * @type Number
     */
    this.quantity = quantity;
    /**
     * @property id
     * @type Number
     */
    this.id = Store.Item._id++;
    Store.Item.list[this.id] = this;
};

Как вы можете видеть, этот конструктор имеет три параметра. Затем внутри конструктора есть три свойства, которые мы также описываем. Поскольку мы хотим дать каждому элементу уникальный идентификатор, нам нужно сохранить свойство static (class-level), чтобы увеличить ID и другое статическое свойство, объект, который отслеживает элементы по их идентификатору.

/**
 * `_id` is incremented when a new item is created, so every item has a unique ID
 * @property id
 * @type Number
 * @static
 * @private
 */
Store.Item._id = 1;
/**
 * @property list
 * @static
 * @type Object
 */
Store.Item.list = {};

Как насчет класса Cart?

/**
 * @class Cart
 * @constructor
 * @param name {String} Customer name
 */
Store.Cart = function (name) {
    /**
     * @property name
     * @type String
     */
    this.name = name;
    /**
     * @property items
     * @type Object
     * @default {}
     */
    this.items = {};
};

Здесь нет ничего нового: обратите внимание, что мы объявляем, что стандартное (или начальное) состояние свойства items является пустым.

Теперь методы. Для addItem один из параметров является необязательным, поэтому мы объявляем его как таковое и присваиваем ему значение по умолчанию 1. Также обратите внимание, что мы делаем метод @chainable.

/**
 * Adds 1 or more of a given item to the cart, if the chosen quantity
 * is available. If not, none are added.
 *
 * @method addItem
 * @param item {Object} An `Item` Object
 * @param [quantity=1] {Number} The number of items to add to the cart
 * @chainable
 */
Store.Cart.prototype.addItem = function (item, quantity) {
    quantity = quantity || 1;
    if (item.quantity >= quantity) {
        this.items[item.id] = this.items[item.id] || 0;
        this.items[item.id] += quantity;
        item.quantity -= quantity;
    }
    return this;
};

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

/**
 * @method total
 * @return {Number} tax-included total value of cart contents
 */
Store.Cart.prototype.total = function () {
    var subtotal, id;
    subtotal = 0;
    for (id in this.items) {
        if(this.items.hasOwnProperty(id)) {
            subtotal += Store.Item.list[id].price * this.items[id];
        }
    }
    return parseFloat(((subtotal * (1 + Store.TAX_RATE / 100)) / 100).toFixed(2));
};

Если вы хотите протестировать этот код, вот простые тесты:

var apple, pear, book, desk, assertEquals;
assertEquals = function (one, two, msg) {
    console.log(((one === two) ? "PASS : " : "FAIL : ") + msg);
};
apple = new Store.Item('Gray Smith Apple', 1.00, 5);
pear  = new Store.Item('Barlett Pear', 2.00, 3);
book  = new Store.Item('On Writing Well', 15.99, 2);
desk  = new Store.Item('IKEA Gallant', 123.45, 1);
cart  = new Store.Cart('Andrew');
cart.addItem(apple, 1).addItem(book, 3).addItem(desk, 1);
assertEquals(apple.quantity, 4, "adding 1 apple removes 1 from the item quantity");
assertEquals(book.quantity, 2, "trying to add more books than there are means none are added");
assertEquals(cart.total(), 140.63, "total price for 1 apple and 1 desk is 140.63");

Создание документации

Теперь, когда мы написали блоки кода и комментариев, пришло время создать документацию.

Если вы установили его глобально через npm, вы сможете просто запустить yuidoc {path to js}. В моем случае это

yuidoc .

Теперь вы увидите, что у вас есть каталог out в этой папке; откройте index.html, и вы увидите документацию. Вот какая часть документации класса Cart будет выглядеть так:


Конфигурирование вывода

Существует несколько параметров конфигурации, которые вы можете установить при использовании YUIDoc. Конечно, вы можете установить их как флаги командной строки, но я предпочел бы установить их в файле конфигурации JSON. В каталоге проекта создайте файл с именем yuidoc.json. Во-первых, есть куча общей информации о проекте, которую вы можете установить; это не влияет на результат слишком сильно, но хорошо документировать их:

{
    "name": "Documenting JavaScript with YUIDoc",
    "description": "A tutorial about YUIDoc, for Nettuts+",
    "version": "1.0.0",
    "url": "http://net.tutsplus.com"
}

Затем есть ряд фактических параметров, которые вы можете установить. Вот пара интересных;

linkNatives: установите для этого параметра значение "true", чтобы связать родные типы, такие как String или Number, с документами MDN. outdir: используйте это, чтобы переименовать пути out out: используйте это, чтобы указать, какие пути YUIDoc ищет файлы JavaScript. exclude: установите это в список файлов, которые вы хотите, чтобы игнорировать YUIDoc.

Пока вы задаете параметры путей, вы можете запустить yuidoc -c yuidoc.json и запустить YUIDoc. Даже если вы не задаете пути и просто запустите yuidoc., YUIDoc увидит этот файл конфигурации и применит его.

Вот мой общий файл конфигурации для этого проекта:

{
    "name": "Documenting JavaScript with YUIDoc",
    "description": "A tutorial about YUIDoc, for Nettuts+",
    "version": "1.0.0",
    "url": "http://net.tutsplus.com",
    "options": {
        "linkNatives": "true",
        "outdir": "./docs",
        "paths": "."
    }
}

Оценка

Основываясь на тегах, которые предлагает YUIDoc, вы можете увидеть, что это было сделано для JavaScript, написанных в традиционном стиле ООП, а также специально для виджетов YUI и т. Д. (На самом деле, я оставил несколько тегов, которые были специфичными для YUI). Из-за всего этого вы можете обнаружить, что несколько тегов просто не так полезны для вас. Затем вы должны спросить себя, хотите ли вы изменить свой стиль кодирования, чтобы лучше соответствовать тому, как YUIDoc «думает». Но даже если вы не собираетесь меняться, я думаю, вы обнаружите, что большинство тегов YUIDoc будут соответствовать в просто отлично.

Больший вопрос для меня заключается в том, хотите ли вы, чтобы ваша документация была встроена в ваш код.

Пример кода, который мы написали выше, - это 120 строк с комментариями, из которых 40 строк. Очевидно, что это супер простой код, и почти любой пример реального мира будет более сбалансированным; однако чтение такого перемешанного кода может быть затруднено. Лично я думаю, что я собираюсь дать YUIDoc справедливое судебное разбирательство: я буду документировать свой JavaScript, поскольку я пишу его (или, по крайней мере, рядом с ним) в течение следующих нескольких недель. Мне будет интересно узнать, влияет ли это на мой стиль кодирования и рабочий процесс.

Вы знаете рутину: любите ее или ненавидите, дайте мне знать в комментариях!


Для большего количества

YUIDoc 0.3.0 выпуск Блог Post YUIDoc Домашняя страница Использование YUIDoc YUIDoc Справочник по синтаксису Темы YUIDoc