Документування 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 встановити 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 або @strucstrator (додаткові теги, які ми обговоримо найближчим часом).

/**
@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";

@veent

Якщо у вас є спеціальні події, які клас може запустити, ви хочете використати тег @ event, щоб описати їх. Ось що повинна сказати документація ЮДІК:

Блок @ 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 або secondary tag tag @static.

@static

Говорячи про @static, ось воно. Клас вважається статичним, коли ви не можете створити його екземпляр. Хорошим прикладом цього є вбудований об'єкт Math: ви ніколи не створюєте його (новий Math ()), ви називаєте його методами з самого класу.

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

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

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

У цьому прикладі ви можете створити екземпляр особи, але весь метод статичний.

@ 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) {}

Існує кілька способів налаштовування частини назви. Встановлення його в квадратні дужки позначає його як необов'язковий, під час встановлення = someVal після того, як він показує, яке значення за замовчуванням (очевидно, лише додаткові параметри мають значення за замовчуванням). Тоді, якщо це є заповнювач для декількох аргументів, додайте *, щоб показати це. (Очевидно, назва * є місцем для 1 або більше аргументів, тоді як [name] * є заповнювачем для 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 для позначення цього:

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

Зауважте, що @requires також може брати список залежностей, розділених комами.

@default

При оголошенні @property ви можете виявити корисним надання йому значення @default. @default слід завжди використовувати з @type.

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

@uses

Як ми вже говорили, у JavaScript насправді немає класів, але це досить гнучко, щоб створити ілюзію класів і навіть підкласів. Ще більш крутим є те, що він досить гнучкий, щоб мати змішані або модулі: саме там один клас "запозичує" властивості або методи іншого класу. І це також не є успадкуванням, тому що ви можете поєднати їх у частині більш ніж одного класу (Звичайно, YUI має можливість зробити це, але так само роблять Dojo та інші бібліотеки). Якщо ви робите це, ви знайдете @ useful дуже корисно: це дозволяє оголосити, які класи даного класу змішують у частинах.

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

Примітка. Я просто створив цей синтаксис змішувача, але я впевнений, що я десь бачив щось подібне.

@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
*/

Comments and Markdow

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

По-перше, вам часто хочеться додати трохи більше інформації про ваш код, ніж те, що пропонують. Можливо, ви хочете описати мету методів або як клас вписується у більшу картину. Додайте ці коментарі у верхній частині блоку коментарів над будь-яким тегом. YUIDoc помітить їх і включить їх у документацію.

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

По-друге, вам буде приємно дізнатись, що ці коментарі, а також будь-які описи або повідомлення, написані після тегів, можуть бути записані в Markdown, а YUIDoc перетворить їх у правильний HTML. Ви можете навіть відтворити приклади блоків коду у ваших коментарях та отримувати підсвічування синтаксису!


Приклад

Тепер, коли ви вивчили теги, давайте насправді напишемо код та документуємо його. Створимо модуль 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) @ властивість @type Number. Зверніть увагу, що я включив @static: це пов'язано з тим, що з якоїсь причини, коли ми генеруємо документацію для цього файлу, YUIDoc покаже це як властивість класу нашого Предмета: здається, що 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;
};

Як видно, цей конструктор має три параметри. Потім у конструкторі є три властивості, які ми також описуємо. Оскільки ми хочемо надати кожному Пункту унікальний ідентифікатор, нам потрібно зберегти статичне (класне рівня) властивість, щоб збільшити ідентифікатор, а також інше статичне властивість, об'єкт, який відстежує Items за їх ідентифікатором.

/**
 * `_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 = {};

Як щодо класу кошика?

/**
 * @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 .

Тепер ви побачите, що у вашій папці є вихідний каталог; відкрийте index.html, і ви побачите документацію. Ось яка частина документації класу кошика буде виглядати так:


Налаштування виводу

Існує кілька параметрів конфігурації, які можна встановити під час використання 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: використовуйте цей для перейменування шляхів каталогу: використовуйте цей параметр, щоб визначити, які шляхи 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, написаного традиційним стилем OOP, а також спеціально для віджетів YUI та таких (фактично, я залишив кілька тегів, які були YUI-специфічними) Через це ви можете виявити, що кілька тегів просто не є корисними для вас. Потім ви повинні запитати себе, чи хочете ви змінити стиль кодування, щоб краще підійти до того, як YUIDoc "думає". Але навіть якщо ви не збираєтеся змінюватися, я думаю, ви виявите, що більшість тегів YUIDoc підійдуть просто добре.

Більший питання для мене - чи хочете ви, щоб ваша документація була вбудована у ваш код.

Приклад коду, який ми написали вище, становить 120 рядків з коментарями, без 40 рядків. Очевидно, що це суперпростий код, і практично будь-який реальний приклад буде більш збалансованим; однак, читання такого перехресного коду може бути складним. Особисто я думаю, що я збираюся надати ЮІДК справедливий судовий процес: я буду документувати свій JavaScript, як я написав (або, принаймні, поряд з ним) протягом наступних кількох тижнів. Я буду зацікавлений в тому, щоб дізнатись, чи впливає він на моє стиль та робочий процес кодування.

Ви знаєте звичайну програму: любите її або ненавидите її, дайте мені знати в коментарях!


Для більш

YUIDoc 0.3.0 Випуск блогів Публікація домашньої сторінки YUIDoc Використання YUIDoc YUIDoc Синтаксис довідки YUIDoc Теми