Documentarea JavaScript cu YUIDoc

Documentarea codului dvs. este oarecum asemănătoare testării; știm cu toții că ar trebui să o facem, nu suntem foarte siguri cum, și cei mai mulți oameni, dacă suntem cinstiți, nu pur și simplu, dar cei care sunt suporterii uriași ai acesteia. Acest tutorial vă va ajuta să accelerați pe una din cele mai bune căi de abordare: YUIDoc.


Ce este YUIDoc?

YUIDoc va genera documentația API pe baza comentariilor pe care le scrieți.

YUIDoc este o aplicație NodeJS care va genera documentația API (sub formă de HTML), pe baza comentariilor pe care le scrieți în codul sursă JavaScript. De fapt, nu este vorba doar de JavaScript: orice limbaj de programare care acceptă comentariile de bloc delimitate de / * * / lucrează pentru YUIDoc. Așa cum ați putea ghici, YUIDoc este unul dintre instrumentele pe care Yahoo! publică împreună cu Biblioteca YUI.

Pentru a instala YUIDoc, veți avea nevoie mai întâi de NodeJS și de managerul de pachete Node (npm). Apoi, puteți instala YUIDoc via npm -g instala yuidocjs. Veți utiliza-o executând yuidoc ; mai multe despre asta mai târziu.


Este vorba despre etichete

Deci, știți că YUIDoc obține documentația din comentariile multiline din fișierul sursă. Desigur, s-ar putea să aveți comentarii care nu fac parte din documentație. Pentru ca YUIDoc să recunoască un comentariu ca fiind semnificativ, trebuie să înceapă cu un dublu început: / **. Asa de:

 / ** YUIDoc va procesa acest * / / * Dar nu acest * /

Desigur, este ceea ce este în interior care contează (în interiorul blocurilor de comentariu, care este). Fiecare trebuie să includă o singură etichetă principală; acesta poate include și zero sau mai multe etichete secundare. Într-adevăr, YUIDoc este atât de simplu: adăugați comentarii cu etichetele potrivite la codul dvs. și presto: documentație! Deci, să învățăm câteva etichete. Iată cum vom face acest lucru: vom trece peste etichetele și unde sunt folosite, cu exemple simple de folosire a acestora; atunci vom scrie și documenta un cod astfel încât să aveți o idee mai bună despre cum etichetele colaborează.


Etichete primare

Înainte de a intra în etichetele primare, rețineți că fiecare bloc de comentarii poate avea doar o singură etichetă principală. Acestea descriu ce este o anumită bucată de cod.

@modul

@modul eticheta descrie un grup de clase de relații. (Da, da, JavaScript nu are clase: YUIDoc se referă la funcțiile constructorului.) Dacă ați folosit YUIDoc pentru a documenta BackboneJS, șira spinării obiect ar fi un modul, pentru că deține Model, Colectie, Vedere, și alte clase. Imediat după etichetă, introduceți numele modulului.

 / ** @module Backbone * / var Backbone = Spine || ;

@clasă

@clasă eticheta descrie cu ușurință o singură clasă. În biblioteca YUI, aceasta înseamnă, de obicei, o funcție a constructorului, dar dacă preferați să folosiți un model diferit și să apelați clasa voastră, puteți face acest lucru. Fiecare comentariu cu @clasă tag-ul ar trebui să aibă, de asemenea, o @static sau @constructor tag (etichete secundare pe care le vom discuta în scurt timp).

 / ** Modelul @class * / funcția Model () 

Dacă clasa ta face parte dintr-un modul, nu trebuie să faci nimic în cadrul @clasă comentariu pentru a desemna că: asigurați-vă că există o @modul comentariu bloc în partea de sus a acelui fișier.

@metodă

Desigur, fiecare clasă va avea cel puțin câteva metode și veți folosi @metodă eticheta pentru a le descrie. Numele metodei va merge după etichetă și veți utiliza etichetele secundare @întoarcere și @params pentru a descrie metoda.

 / ** @method render * / View.prototype.render = funcția (date) 

@proprietate

@proprietate tag-ul este folosit pentru a eticheta proprietățile unei clase. Veți dori să utilizați @tip și @Mod implicit etichete secundare cu aceasta, sigur.

 / ** @problema șablonString * / this.templateString = "div";

@eveniment

Dacă aveți evenimente personalizate speciale pe care le poate declanșa o clasă, veți dori să le utilizați @eveniment eticheta pentru a le descrie. Iată ce trebuie să spună documentația YUIDoc:

Un @eveniment bloc este oarecum similar cu a @metodă bloc, cu excepția asta @întoarcere este irelevant și @param este folosit pentru a descrie proprietățile care atârnă de pe obiectul evenimentului și care asculta apelurile pentru primirea evenimentului.


Etichete secundare

Blocurile de comentarii pot avea mai mult de o etichetă secundară; ei vor avea adesea o mână și, uneori, mai mult decât una de același tip. Să ne uităm la câteva dintre cele pe care le veți folosi adesea.

@submodule

Dacă vă împărțiți modulele în submodule (poate un submodul pe fișier, poate nu), @submodule eticheta este la dispoziția dumneavoastră.

 / ** @module Util @submodule matrice * / Util.array = ;

@extends

@extends tag-ul este util atunci când aveți relații superclass / subclass. Puteți pretinde ce clasă este părintele clasei documentate în prezent:

 / ** @class AppView @ extinde Backbone.View * / var AppView = Backbone.View.extend ();

@constructor

Dacă o clasă poate fi instanțiată, înseamnă că are nevoie de o funcție a constructorului. Dacă utilizați modelul prototyp standard în JavaScript, declarația de clasă este, de asemenea, constructorul. Asta înseamnă că veți vedea adesea ceva de genul:

 / ** @class Rețetă @constructor * / funcția Rețetă () 

De fapt, probabil îmi amintești să spun că fiecare @clasă eticheta ar trebui să aibă fie a @constructor sau @static etichetă secundară.

@static

Vorbind despre @static, aici este. O clasă este considerată statică atunci când nu puteți crea o instanță a acesteia. Un bun exemplu în acest sens este construit Math obiect: nu creați niciodată o instanță a acestuia (nou Math ()), numiți metodele sale din clasa însăși.

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

O metodă poate fi, de asemenea, statică: dacă o clasă poate fi instanțiată, dar are și unele metode la nivel de clasă, aceste metode sunt considerate statice (ele sunt numite în clasă, nu în instanță).

 / ** @class Persoana @constructor * / functie Persoana ()  / ** @method all @static * / Person.all = function () ;

În acest exemplu, puteți crea un Persoană exemplu, dar toate metoda este statică.

@final

Această etichetă este utilizată pentru proprietăți sau atribute și marchează proprietatea menționată ca o constantă: nu trebuie modificată. În timp ce JavaScript nu are constante reale în starea sa actuală, modelul de codare sau ghidul de stil le-ar putea folosi în principiu, deci acest lucru va fi util pentru asta.

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

@param

Iată un exemplu important: @param tag-ul este folosit pentru a defini parametrii a @metodă (inclusiv a @constructor) sau an @eveniment. Există trei biți de informații care merg după @param etichetă: numele parametrului, tipul (care este opțional) și descrierea. Acestea pot fi fie în ordine descriere tip de nume sau introduceți descrierea numelui; dar în ambele cazuri, tipul trebuie să fie înconjurat de bretele curbate.

 / ** @method greet @param person string Numele persoanei de întâmpinare * / funcția greet (persoană) 

Există câteva moduri de a personaliza Nume parte, de asemenea. Punerea în paranteze pătrate o marchează ca opțională, în timp ce puneți = someVal după ce arată ce este valoarea implicită (evident, numai parametrii opționali au o valoare implicită). Apoi, dacă este un substituent pentru mai multe argumente, adăugați * pentru a arăta asta. (Evident, Nume* este un substituent pentru unul sau mai multe argumente, în timp ce [Nume]* este un substituent pentru 0 sau mai multe).

 / ** @class Template @constructor @param template String Șirul de șablon @param [data = ] Object Obiectul a cărui proprietăți vor fi redate în șablon * / funcție Șablon (șablon, date)

@întoarcere

Majoritatea metodelor dvs. vor dori să returneze o valoare, deci acesta este eticheta care descrie acea valoare. Nu uitați să spuneți ce tip este valoarea și dați-i o descriere.

 / ** @method toHTML @param [template = Recipe.defaultTemplate] Template Un obiect șablon @return String Conținutul rețetelor formatat în HTML cu șablonul implicit sau trecut. * / Recipe.prototype.toHTML = funcție (șablon) return "whatever"; ;

@tip

Amintiți-vă @proprietate eticheta principală? Veți dori să definiți ce tip de proprietăți sunt, nu? Ei bine, @tip eticheta este exact ceea ce aveți nevoie. Specificați tipul după etichetă; puteți oferi, de asemenea, mai multe tipuri prin separarea lor cu bare verticale:

 / ** @property URL @type String * / URL: "http://net.tutsplus.com", / ** @property person @type String | Persoană | Obiect * / this.person = persoană nouă ();

@privat / @protejat

Limbile tradiționale de programare oferă proprietăți sau metode private: acestea nu sunt accesibile din afara instanței. La fel ca constantele, JavaScript le are doar prin practică, dar le puteți folosi @privat pentru a le eticheta dacă le folosiți. Rețineți că YUIDoc nu afișează proprietăți private în documentele pe care le generează (ceea ce are sens), astfel încât acest lucru vă permite să documentați o caracteristică pentru propriul dvs. beneficiu și să nu o afișați în docs.

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

Proprietățile protejate și metodele sunt la jumătatea drumului dintre public și privat: ele sunt accesibile numai din interiorul instanțelor și instanțelor de subclase. Dacă acest lucru este un lucru pe care îl faci în JavaScript, iată eticheta ta: @protejat.

@requires

Dacă un modul depinde de unul sau mai multe alte module, puteți utiliza @requires pentru a marca că:

 / ** @module MyFramework.localstorage @ solicită MyFramework * /

Rețineți că @requires ar putea lua, de asemenea, o listă a dependențelor, separate prin virgule.

@Mod implicit

Când se declară a @proprietate, s-ar putea să fie util să-i dați o a @Mod implicit valoare. @Mod implicit ar trebui să fie întotdeauna folosit cu @tip.

 / ** @ elementul de proprietate @type String @default "div" * / element: "div",

@uses

Asa cum am spus, JavaScript nu are intr-adevar clase, dar este suficient de flexibil pentru a crea iluzia de clase si chiar de sub-clase. Ce este chiar mai cool este faptul că este suficient de flexibil pentru a avea mixuri sau module: în acest caz, o clasă "împrumută" proprietăți sau metode dintr-o altă clasă. Și nu este nici moștenire, pentru că vă puteți amesteca în mai multe clase (bineînțeles, YUI are capacitatea de a face acest lucru, dar și Dojo și alte biblioteci). Dacă faci asta, vei găsi @uses foarte util: vă permite să declarați ce clase este clasificată într-o anumită clasă.

 / ** @class ModalWindow @ Utilizați Window @uses DragDroppable * / var ModalWindow = clasă nouă (mixes: [Window, DragDroppable], ...);

Notă: Am făcut doar acea sintaxă mixin, dar sunt destul de sigur că am văzut ceva asemănător undeva.

@exemplu

Doriți să includeți un exemplu de utilizare a unei anumite bucăți de cod? Folosește @exemplu și apoi scrieți exemplul de mai jos, indentându-l cu un singur nivel. Puteți adăuga cât mai multe exemple pe cât doriți.

 / ** @method salut @example person.greet ("Jane"); * / Person.prototype.greet = funcție (nume) ;

@chainable

Probabil sunteți familiarizați cu metode lanțabile de la jQuery. Știi, unde poți apela o metodă de pe un apel de metodă, deoarece metodele returnează obiectul? Marcați metodele dvs. ca atare @chainable.

 / ** @method addClass @chainable * / jQuery.prototype.addClass = funcție (clasă) // stuff; returnați acest lucru; 

@deprecated / @de cand / @beta

Aceste trei etichete se referă la suportul pentru cod (și ar putea fi orice cod: modul, clasă, metodă sau altceva). Utilizare @deprecated pentru a marca anumite funcționalități ca fiind cel mai bun mod de a face acest lucru (funcționalitatea depreciată va fi probabil eliminată într-o versiune viitoare a codului). Opțional, puteți include un mesaj care explică modul în care este prezent modul curent.

 / ** @method toJSON @deprecated Transmite obiectul la 'JSON.parse' în schimb * / Something.toJSON = function () ;

@de cand eticheta doar spune cititorilor ce versiune a codului dat ce a adăugat @beta marchează codul beta: YUI sugerează acest lucru @beta codul ar putea "suferi modificări incompatibile în viitorul apropiat".

 / ** @class Tooltip @ din 1.2.3 @constructor * / function Tooltip () 

@extensie / @extensionfor / extension_for

@extensie tag-ul (și pseudonimele sale) este aproape opusul @uses. Utilizați-l pentru a marca ce clase poate fi amestecată. Desigur, dați seama că acest lucru nu înseamnă că este mereu amestecat, doar că poate fi.

 / ** @class Draggable @extension pentru ModalWindow * /

Comentarii și Markdown

Înainte de a examina un exemplu real, permiteți-mi să subliniez încă două lucruri despre blocurile de comentarii de documentare.

În primul rând, veți dori adesea să adăugați mai multe informații despre codul dvs. decât cele oferite de etichete. Poate doriți să descrieți scopul metodelor sau modul în care o clasă se încadrează în imaginea de ansamblu. Adăugați aceste comentarii în partea de sus a blocului de comentarii, deasupra oricăreia dintre etichete. YUIDoc le va observa și le va include în documentație.

 / ** Clasa "Router" este folosită pentru ... @class Router @static * / var Router = ;

În al doilea rând, veți fi încântați să știți că aceste comentarii, precum și orice descrieri sau mesaje scrise după etichete, pot fi scrise în Markdown, iar YUIDoc îl va converti în codul HTML corect. Puteți chiar să indentați exemple de blocuri de cod în comentariile dvs. și să obțineți evidențierea sintaxei!


Un exemplu

Acum că ați învățat etichetele, să scriem un cod și să-l documentăm. Să creăm a Magazin modul, care deține două clase: Articol și Cart. Fiecare Articol instanța va fi un tip de element din inventarul magazinului: va avea un nume, un preț și o cantitate. A Cart instanța poate adăuga articole în coș și poate calcula prețul total al articolelor din coș (inclusiv taxa). Este destul de simplu, dar ne oferă suficientă varietate de funcționalități pentru a folosi multe dintre etichetele pe care le-am discutat. Am pus tot codul următor store.js.

Începem prin crearea modulului:

 / ** * Acest modul conține clase pentru rularea unui magazin. * @module Store * / var Store = Magazin || ;

Acum, să creăm o "constantă": rata de impozitare.

 / ** * 'TAX_RATE' este stocat ca procent. Valoarea este 13. * @property TAX_RATE * @ static * @final * @type Number * / Store.TAX_RATE = 13;

Aceasta este o constantă (@final) @proprietate de @tip Număr. Observați că am inclus @static: acest lucru se datorează faptului că, dintr-un motiv oarecare, atunci când generăm documentația pentru acest fișier, YUIDoc va afișa acest lucru ca proprietate a Articol clasa: se pare că YUIDoc nu suportă existența unei proprietăți pe un modul. Cred că aș putea crea o clasă statică pentru a ține această constantă (și alte constante care s-ar putea să vină dacă am continuat să dezvoltăm acest lucru), dar l-am lăsat în acest fel ca un memento: să folosiți un instrument ca YUIDoc la potențialul său maxim ar putea să trebuiască să schimbe modul în care codificați. Va trebui să decideți dacă asta doriți să faceți.

Acum, pentru Articol clasă:

 / ** * Articol @class * @constructor * @param name String Nume articol * @param price Număr Pret articol * @param cantitate Număr Cantitate produs (disponibil pentru cumpărare) * / Store.Item = funcție (nume, preț, cantitate) / ** * @ nume de proprietate * @type String * / this.name = nume; / ** * @prețul de preț * @type String * / this.price = prețul * 100; / ** * @ cantitate de produs * @ număr de tip * / this.quantity = quantity; / ** * @ id de proprietate * @type Number * / this.id = Store.Item._id ++; Store.Item.list [this.id] = aceasta; ;

După cum puteți vedea, acest constructor are trei parametri. Apoi, există trei proprietăți în interiorul constructorului pe care le descriem și noi. Deoarece vrem să dăm fiecare Articol un ID unic, trebuie să stocăm o proprietate statică (la nivel de clasă) pentru a crește ID-ul și o altă proprietate statică, un obiect care urmărește Articolprin ID-ul lor.

 / ** * '_id' este incrementat atunci când este creat un element nou, astfel încât fiecare element are un ID unic * @ id de proprietate * @ număr de tip * @ static * @ privat * / Store.Item._id = 1; / ** * @ listă de proprietăți * @ static * @type Object * / Store.Item.list = ;

Ce zici de Cart clasă?

 / ** * @class Cart * @constructor * @ param name String Nume client * / Store.Cart = functie (nume) / ** * @property name * @type String * / this.name = name; / ** * @ elemente de proprietate * @type Object * @default  * / this.items = ; ;

Nu este nimic nou aici: observați că declarăm că starea implicită (sau inițială) a articole proprietatea este un obiect gol.

Acum, metodele. Pentru adaugare element, unul dintre parametri este opțional, deci îl declarăm ca atare și îi dăm o valoare implicită de 1. De asemenea, observați că facem metoda @chainable.

 / ** * Se adaugă 1 sau mai mult dintr-un anumit element în coș, dacă cantitatea aleasă * este disponibilă. Dacă nu, nu se adaugă niciunul. * * @method addItem * @param item Object Obiect 'Item' * @param [quantity = 1] Number Numarul de articole adaugate in cos * @chainable * / Store.Cart.prototype.addItem = funcție (element, cantitate) cantitate = cantitate || 1; dacă (element.quantity> = cantitate) this.items [item.id] = this.items [item.id] || 0; this.items [item.id] + = cantitate; element.quantity - = cantitate;  returnați acest lucru; ;

În cele din urmă, dorim să putem returna prețul total, inclusiv taxele. Observați că facem matematica prețurilor în cenți și apoi convertim în dolari și rotunjim la două zecimale.

 / ** * @method total * @return Număr taxa inclusă valoarea totală a conținutului coșului de cumpărături * / Store.Cart.prototype.total = funcția () var subtotal, id; subtotal = 0; pentru (id în acest.items) if (this.items.hasOwnProperty (id)) subtotal + = Store.Item.list [id] .valoarea * this.items [id];  întoarcere parseFloat (((subtotal * (1 + Store.TAX_RATE / 100)) / 100) .toFixed (2)); ;

Dacă doriți să testați acest cod, faceți un test simplu:

 var app, pere, carte, birou, assertEquals; assertEquals = funcția (una, două, msg) console.log (((una === două) "PASS:": "FAIL:") + msg); ; Apple = noul Store.Item ("Granny Smith Apple", 1,00, 5); pere = noul Store.Item ("Barlett Pear", 2.00, 3); book = new Store.Item ("Pe scris bine", 15.99, 2); birou = noul Store.Item ("IKEA Gallant", 123,45, 1); cart = nou Magazin ("Andrew '); cart.addItem (apple, 1) .addItem (carte, 3) .addItem (birou, 1); assertEquals (cantitate măr, 4, "adăugând 1 măr elimină 1 din cantitatea de articol"); assertEquals (book.quantity, 2, "încercarea de a adăuga mai multe cărți decât există mijloace nu sunt adăugate"); assertEquals (cart.total (), 140.63, "prețul total pentru 1 măr și 1 birou este 140.63");

Generarea documentației

Acum, când am scris codurile și blocurile de comentarii, este timpul să generăm documentația.

Dacă l-ați instalat la nivel mondial prin intermediul npm, veți putea rula pur și simplu yuidoc cale spre js. În cazul meu, asta e

 yuidoc .

Acum, veți vedea că aveți unul afară directorul din dosarul respectiv; deschis out / index.html, și veți vedea documentația. Iată ce parte din Cart clasa de documentare va arata ca:


Configurarea ieșirii

Există mai multe opțiuni de configurare pe care le puteți seta când utilizați YUIDoc. Sigur, le puteți seta ca steaguri de linie de comandă, dar aș prefera să le configurați într-un fișier de configurare JSON. În directorul de proiect, creați un fișier numit yuidoc.json. În primul rând, există o grămadă de informații generale despre proiecte pe care le puteți seta; acest lucru nu influențează prea mult producția, dar este bine să le documentăm:

 "nume": "Documentarea JavaScript cu YUIDoc", "descriere": "Un tutorial despre YUIDoc, pentru Nettuts +", "versiune": "1.0.0", "url": "http://net.tutsplus.com "

Apoi, există o serie de opțiuni reale pe care le puteți seta. Iată câteva interesante;

  • linkNatives: setați această opțiune la "true" pentru a conecta tipurile native ca String sau Number la documentele MDN.
  • outdir: folosiți-l pentru a redenumi afară director
  • căi: utilizați această opțiune pentru a stabili ce căi YUIDoc caută fișiere JavaScript.
  • exclude: setați-l la o listă de fișiere separate de virgule pe care doriți să o ignorați.

Atâta timp cât setați căi opțiuni, puteți rula yuidoc-yuidoc.json și YUIDoc va rula. Chiar dacă nu setați căi și fugi yuidoc ., YUIDoc va vedea fișierul de configurare și îl va aplica.

Iată fișierul meu de configurare total pentru acest proiect:

 "nume": "Documentarea JavaScript cu YUIDoc", "descriere": "Un tutorial despre YUIDoc, pentru Nettuts +", "versiune": "1.0.0", "url": "http://net.tutsplus.com "," opțiuni ": " linkNatives ":" true "," outdir ":" ./docs "," paths ":" 

Evaluare

Pe baza etichetelor pe care YUIDoc le oferă, puteți vedea că a fost făcut pentru JavaScript scris în stil tradițional OOP, precum și în special pentru widgeturile YUI și altele asemenea (de fapt, am lăsat mai multe etichete care erau specifice YUI). Din cauza tuturor acestor lucruri, s-ar putea să constatați că mai multe etichete nu sunt atât de utile pentru dvs. Apoi, trebuie să vă întrebați dacă sunteți dispus să vă schimbați stilul de codificare pentru a se potrivi mai bine cu modul în care YUIDoc "crede". Dar chiar dacă nu veți schimba, cred că veți găsi că majoritatea etichetelor YUIDoc se vor potrivi în regulă.

Întrebarea mai mare pentru mine este dacă vă place să aveți documentația în linie cu codul dvs..

Codul exemplu pe care l-am scris mai sus este 120 de linii cu comentarii, 40 de linii fără. Evident, este un cod foarte simplu și aproape orice exemplu din lumea reală ar fi mai echilibrat; totuși, citirea unui astfel de cod intercalat ar putea fi dificilă. Personal, cred că o să-i dau lui Yuidoc un proces echitabil: o să-mi documentez JavaScript-ul când îl scriu (sau cel puțin, de-a lungul acestuia) pentru următoarele câteva săptămâni. Voi fi interesat să văd dacă sau cum afectează stilul meu de codificare și fluxul de lucru.

Cunoașteți rutina: iubiți-o sau urăște-o, spuneți-mi în comentariile!


Pentru mai mult

  • YUIDoc 0.3.0 Release Blog Post
  • Pagina principală YUIDoc
  • Utilizând YUIDoc
  • Referința sintaxei YUIDoc
  • Teme YUIDoc
Cod