Документуємо код ефективно за допомогою Doxygen



Дана стаття являє собою введення в систему документування вихідних текстів Doxygen, яка на сьогоднішній день, має підстави заявою розробників, стала фактично стандартом для документування програмного забезпечення, написаного на мові C++, а також отримала нехай і менш широке поширення і серед ряду інших мов.

У цій статті ми спочатку познайомимось з самою системою і її можливостями, потім розберемося з її установкою і базовими принципами роботи, і, нарешті, завершимо знайомство розглядом різних прикладів документації, прикладів того, як слід документувати ті чи інші частини коду. Словом, познайомимося з усім тим, що дозволить вам освоїться і почати працювати з цією чудовою системою.

Введення

Найімовірніше, кожен з нас стикався з результатами роботи різних генераторів документації. Загальний принцип їх роботи такий: на вхід такого генератора надходить спеціальним чином коментований вихідний код, а іноді й інші компоненти програми, а на виході створюється готова документація для розповсюдження і використання.

Розглянута система Doxygen як раз і виконує цю задачу: вона дозволяє генерувати на основі початкового коду, що містить коментарі спеціального виду, красиву і зручну документацію, що містить в собі посилання, діаграми класів, викликів і т. п. в різних форматах: HTML, LaTeX, CHM, RTF, PostScript, PDF, man-сторінки.

Для того, щоб скласти загальне враження про систему, нижче представлені приклади різних документацій для API, створених за допомогою Doxygen (слід звернути увагу, що в останні приклади внесені помітні зміни в порівнянні зі стандартною документацією, яку генерує дана система):
  1. Документація щодо API ігрового движка CrystalSpace
  2. Документація до Visualization Toolkit
  3. Документація до исходниками Abiword
  4. Документація щодо API KDE
  5. Документація щодо API Drupal
Уважний читач, напевно, звернув увагу на те, що в більшості прикладів Doxygen використовується для документації програмного забезпечення, написаного на мові C++, проте насправді дана система підтримує набагато велике число інших мов: C, Objective-C, C#, PHP, Java, Python, IDL, Fortran, VHDL, Tcl, і частково D.

Втім, слідуючи традиції, що склалася, в прикладах я буду використовувати C++, проте це не повинно бентежити вас, якщо ви віддаєте перевагу інший підтримуваний мову, оскільки особливої різниці на практиці ви навіть не помітите, і більшість сказаного далі буде справедливо і для вашої мови.

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

Установка і настройка

Завантажити останню версію Doxygen можна на офіційному сайті, дистрибутиви якої доступні для більшості популярних операційних систем, крім того, ви можете скористатися вашим пакетним менеджером. Крім цього для комфортної і повнофункціональної роботи рекомендується встановити Graphviz.

Далі робота з Doxygen досить тривіальна: досить запустити програму, вказавши їй шлях до файлу з налаштуваннями.
doxygen <config_file>

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

В принципі, для редагування цього файлу і, взагалі, роботою з Doxygen, можна скористатися програмою Doxywizard, яка найчастіше йде разом з Doxygen і яка дозволяє трохи зручніше працювати з файлом налаштувань (зліва — Doxywizard; праворуч — відкритий файл у текстовому редакторі):


Отже, приступимо до створення файлу з налаштуваннями. Взагалі, якщо ви використовуєте Doxywizard, то він буде створений автоматично, в іншому разі для створення цього файлу необхідно запустити програму Doxygen з ключем g (від generate):
doxygen-g <config_name>

Розглянемо основні опції, які можуть стати вам у нагоді, щоб створити першу вашу документацію:
Тег Призначення За замовчуванням
DOXYFILE_ENCODING Кодування, яка використовується для всіх символів у файлі налаштувань UTF-8
OUTPUT_LANGUAGE Встановлює мову, на якому буде згенерована документація English
PROJECT_NAME Назва проекту, яка може представляти собою єдине слово або послідовність слів (якщо ви редагуєте поза Doxygen, послідовність слів необхідно помістити в лапки) My Project
PROJECT_NUMBER Цей тег може бути використаний для зазначення номера проекту або його версії
PROJECT_BRIEF Короткий однорядкове опис проекту, яке розміщується зверху кожної сторінки і дає загальне уявлення про призначення проекту
OUTPUT_DIRECTORY Абсолютний або відносний шлях, по якому буде згенерована документація Поточна директорія
INPUT Список файлів і/або папок, розділених пробілом, які містять у собі вихідні коди проекту Поточна директорія
RECURSIVE Використовується в тому випадку, якщо необхідно сканувати вихідні коди в підпапках зазначених директорій NO
Після того, як ми внесли необхідні зміни в файл з налаштуваннями (наприклад, змінили мову, назви проекту тощо) необхідно згенерувати документацію.

Для її генерації можна скористатися Doxywizard (для цього необхідно вказати робочу директорію, з якої будуть братися вихідні коди, перейти на вкладку «Run» і натиснути «Run doxygen») або запустивши програму Doxygen, вказавши їй як параметр шлях до файлу з налаштуваннями:
doxygen <config_file>


Основи документування на Doxygen

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

Документація коду в Doxygen здійснюється за допомогою документирующего блоку. При цьому існує два підходи до його розміщення:
  1. може бути розміщений перед або після оголошення або визначення класу, члена класу, функції, простору імен і т. д.;
  2. Або його можна розташовувати в довільному місці (й навіть іншому файлі), але для цього буде потрібно явно вказати в ньому, до якого елементу коду він відноситься. Ми не буде розглядати цей підхід, оскільки навіть розробники рекомендують його уникати, але якщо цікаво, то детальніше про нього можна прочитати в документації.
Структурно, будь документирующий блок є коментарем, просто оформленим спеціальним чином, тому природно, що його вид залежить від використовуваного мови (докладніше про це можна прочитати в відповідному розділі документації. Тому далі ми зупинимося на розгляді синтаксису для C-подібних мов (C/C++/C#/Objective-C/PHP/Java).

Взагалі, існує два типи документують блоків: багаторядковий блок і однорядковий блок.

Різниця між двома такими типами трохи більш сильна, ніж між однострочным і многострочным коментарем. Справа в тому, що текст, написаний в однорядковому блоці відноситься до короткого опису документируемого елемента (схоже заголовку), а текст, написаний в багаторядковому блоці відноситься до докладного опису. Про цю різницю не слід забувати.

Багаторядковий блок
Ми сказали, що будь-блок — це коментар, оформлений спеціальним чином. Тому необхідно визначити яким таким «відповідним чином». Взагалі, існує цілий ряд способів для опису багаторядкового блоку, і вибір конкретного способу залежить від ваших уподобань:

  1. JavaDoc стиль нагадує звичайний C коментар, але починається з двох зірочок):
    /**
    * ... перша строчка ...
    * ... друга строчка ...
    */
    

    При цьому зірочки не обов'язково ставити на кожному рядку. Такий запис буде еквівалентної:
    /**
    ... перша строчка ...
    ... друга строчка ...
    */
    

  2. Qt стиль, в якому на початку замість другої зірочки ставиться знак оклику:
    /*!
    * ... перша строчка ...
    * ... друга строчка ...
    */
    

    Сказане про необов'язковість проміжних зірочок також залишається справедливим. Крім названих двох стилів є ще кілька, але на них поки ми не будемо зупинятися.
При цьому ще раз зверніть увагу на те, що текст написаний у такому коментарі відноситься до докладного опису.

Для вказівки короткого опису може бути використана команда \brief. Зазначений після команди текст, аж до кінця параграфа буде відноситься до короткого опису, і для відділення докладного опису та короткого опису використовується порожній рядок.
/*!
\brief Короткий опис і
його продовження.

Детальний опис 
*/

Однорядковий блок
Для опису однорядкового блоку знову ж існує цілий ряд способів оформлення, розглянемо два з них:

  1. Можна використовувати спеціальний коментар в C++ стилі:
    /// Короткий опис
    

  2. Можна використовувати аналогічний попередньому коментар, тільки замість додаткового слеша в нього ставиться знак оклику
    //! Короткий опис
    

При цьому хотілося б звернути увагу на два моменти:

  1. Для вказівки докладного опису в однорядковому документирующем блоці може бути використана команда \details:
    /// \details Детальний опис
    

  2. Документирующие блоки, наступні один за одним, об'єднуються в один (причому незалежно від стилю, який використовується і того, є вони або багаторядковими однорядковими).

    Наприклад наступні два способи документування дадуть один і той же результат:
    /// \brief Короткий опис
    /// \details Детальний опис
    

    ///Короткий опис
    /*!
    Детальний опис
    */
    

Так, Doxygen дуже гнучкий в плані способів документування, однак не варто цим зловживати, і в рамках одного проекту завжди дотримуйтеся заздалегідь обумовленого однакового стилю

Розміщення документирующего блоку після елемента
У всіх попередніх прикладах малося на увазі, що документирующий блок передував документований елемент, але іноді бувають ситуації, коли зручніше розмістити його після документируемого елемента. Для цього необхідно в блок додати маркер "<", як у прикладі нижче:
int variable; ///< Короткий опис

Приклад документації
Тепер розглянемо те, як це буде виглядати на практиці. Нижче представлений документований код деякого класу у відповідності з тими правилами, які ми розглядали раніше.
/*!
\brief Батьківський клас, не несе ніякого смислового навантаження

Цей клас має тільки одну просту мету проілюструвати те,
як Doxygen документує спадкування 
*/
class Parent {
public:
Parent();
~Parent();
};

У підсумку Doxygen сформує на основі даних коментарів наступну красиво оформлену сторінку (тут наведено вирізка з неї):

Тепер, коли ми навчилися основам, прийшла пора познайомитися з тим, як можна деталізувати документацію. Інструментом для цього є команди.

Команди

З насколькими з команд в Doxygen ми встигли познайомитися (йдеться про \brief \details), однак насправді їх значно більше. Повний їх список наведено в офіційній документації.

Взагалі, будь-яка команда в Doxygen являє собою слово англійською мовою яка символом "\" або "@" (обидва записи тотожні) і таких команд дуже багато, близько двохсот. Наведемо для прикладу кілька таких команд:
Команда Значення
\authors Вказує автора або авторів
\version Використовується для вказівки версії
\date Призначена для зазначення дати розробки
\bug Перерахування відомих помилок
\warning Попередження для використання
\copyright Використовувана ліцензія
\example Команда, що додається в коментар для зазначення посилання на исходник з прикладом (додається після команди)
Приклад використання деяких команд і результат наведено нижче:
/*!
\brief Дочірній клас
\author Norserium
\версія 1.0
\date Березень 2015 року
\warning Даний клас створений лише в навчальних цілях

Звичайний дочірній клас, який отнаследован від раніше створеного класу Parent
*/
class Son : public Parent {
public:
Son();
~Son();
};



Документування основних елементів вихідного коду

Тепер ми можемо розглянути специфічні особливості документування різних елементів вихідного коду, починаючи від файлів в цілому і закінчуючи класами, структурами, функціями і методами.

Документування файлу
Хорошою практикою є додавання в початок файлу документирующего блоку, описывающегося його призначення. Для того, щоб вказати, що даний блок відноситься до файлу необхідно скористатися командою \file (причому в якості параметра можна вказати шлях до файлу, до якого відноситься даний блок, але за замовчуванням вибирається той файл, в який блок додається, що, як правило, відповідає нашим потребам).
/*!
\файл
\brief Заголовковий файл з описом класів

Даний файл містить у собі визначення основних 
класів, використовуваних в демонстраційній програмі
*/
#ifndef CLASSES_H
#define CLASSES_H

...

#endif // CLASSES_H

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

Параметри
Для вказівки параметрів необхідно використовувати команду \param для кожного з параметрів функції, при цьому синтаксис команди має наступний вигляд:
\param [<направлення>] <ім'я значення> {описание_параметра}

Розглянемо значення компонентів команди:
  1. Ім'я параметра — це ім'я, під яким даний параметр відомий в документируемом коді;
  2. Опис параметра являє собою просте текстове опис використовуваного параметра..
  3. Напрямок — це опціональний атрибут, який показує призначення параметра і може мати три значення "[in]", "[out]", "[in,out]";
Відразу ж перейдемо до прикладу:
/*!
Копіює вміст у вихідній області пам'яті в цільову область пам'ять
\param[out] dest Вихідна область пам'яті
\param[in] src Цільова область пам'яті
\param[in] n Кількість байтів, які необхідно скопіювати
*/
void memcpy(void *dest, const void *src, size_t n);

В результаті ми отримаємо таку ось акуратну документацію функції:


Повертає значення
Для опис повертається використовується команда \return (або її аналог \returns). Її синтаксис має такий вигляд:
\return {описание_возвращаемого_значения}

Розглянемо приклад з описом повертається значення (при цьому зверніть увагу на те, що параметри описуються за допомогою однієї команди і в результаті вони в описі розміщуються разом):
/*!
Знаходить суму двох чисел
\param a,b Складні числа
\return Суму двох чисел, переданих в якості аргументів
*/
double sum(const double a, const double b);

Отримуємо наступний результат:


Виключення
Для вказівки виключення використовується команда \throw (або її синоніми: \throws, \exception), яка має наступний формат:
\throw <об'єкт-виняток> {опис}

Найпростіший приклад наведено нижче:
\throw <std::bad_alloc> У випадку виникнення помилки при виділенні пам'яті

Документування класів
Класи також можуть бути задокументовані просто початком їх документирующим блоком. При цьому величезна кількість інформації Doxygen отримує автоматично, враховуючи синтаксис мови, тому завдання документування класів значно спрощується. Так при документуванні Doxygen автоматичні визначає методи і члени класу, рівні доступу до функцій, дружні функції і т. п.

Якщо ваша мова не підтримує явним чином певні концепції, такі як наприклад рівні доступу або створення методів, але їх наявність мається на увазі і його хотілося б виділити в документації, то існує ряд команд (наприклад, \public, \private, \protected, \memberof), які дозволяють вказати явно про них Doxygen.

Документування перерахувань
Документування перерахувань не сильно відрізняється від документування інших елементів. Розглянемо приклад, який ілюструє те, як можна зручно документувати їх:
/// Набір можливих станів об'єкта
enum States {
Disabled, ///< Вказує, що елемент недоступний для використання
Undefined, ///< Вказує, що стан елемента невизначено
Enabled, ///< Вказує, що елемент доступний для використання
}

Тобто опис станів вказується, власне, після них самих за допомогою короткого або докладного опису (в даному випадку ролі це не грає).

Результат буде мати наступний вигляд:


Оформлення документації

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

Код всередині документації
Часто всередині пояснення до документації необхідно для прикладу додати якийсь код, наприклад, для ілюстрації.

Команди \code \endcode
Один із зручних способів зробити це — команди \code \endcode, загальний вигляд якої наступний:
\code [ {<розширення>}]
...
\endcode

Використовуваний мова визначається автоматично залежно від розширення файлу, в якому розташовується документирующий блок, проте у разі, якщо така поведінка не відповідає очікування розширення можна вказати явно.

Розглянемо приклад використання:
/*!
\brief Алгоритм Евкліда
\param a,b-Два числа, чий найбільший дільник ми хочемо знайти

Ця функція реалізує алгоритм Евкліда, за допомогою якого
знаходиться найбільша спільне кратне двох чисел.

Код функції виглядає наступним чином:
\code
int gcd(int a, int b) {
int r;
while (b) {
r = a % b;
a = b;
b = r;
}
return r;
}
\endcode
*/
int gcd(int a, int b);

Результат буде мати наступний вигляд:


Команда \include
Як альтернатива даного способу існує команда \include, загальний формат якої має наступний вигляд:
\include <ім'я_файлу>

Вона повністю копіює вміст файлу і вставляє його в документацію як блок коду (аналогічно попередньої розглянутої команді.

Команда \snippet
Команда \snippet аналогічна попередній команді, однак вона дозволяє вставляти не весь файл, а певний фрагмент. Не дивно, що її формат дещо інший:
\snippet <ім'я_файлу> ( имя_фрагмента )

Для виділення певного фрагмента коду необхідно на початку і в кінці його розмістити документирующий блок із зазначенням імені фрагмента:
//! [ имя_фрагмента ]
...
//! [ имя_фрагмента ]

Формули з використанням LaTeX
Doxygen дозволяє використовувати TeX формули прямо в документації, це дуже зручно і результат виходить дуже гідним. Проте варто відзначити, що при цьому існують обмеження: на даний момент формули можуть бути вставлені тільки в HTML, LaTeX документацію, але цього, як правило, цілком достатньо.

На даний момент існує два підходи до відображення формул:

  1. Відображення формул за допомогою MathJax, для цього необхідно у файлі налаштувань встановити відповідну опцію:
    USE_MATHJAX = YES
    

  2. Генерація відповідних зображень і вставка їх в документацію. Все це буде зроблено автоматично, але вам буде потрібно наступні інструменти: latex, dvips, gs. За замовчуванням формули відображаються саме цим способом.
Способи додавання формул в документацію
Існують три способи додавання формул в документацію. Послідовно розглянемо кожний із них з прикладами з документації:

  1. Використання малих формул, які обрамляються на початку і наприкінці за допомогою команди "\f$". Приклад наведено нижче:
    відстань між \f$(x_1,y_1)\f$ \f$(x_2,y_2)\f$ одно \f$\sqrt{(x_2-x_1)^2+(y_2-y_1)^2}\f$.
    

    Результатом буде рядок наступного виду: відстань між дорівнює

  2. Використання виносних формул, які починаються на окремому рядку і центруються. На відміну від попередніх формул вони обрамляються на початку командою "\f[", а в кінці командою "\f[". Приклад наведено нижче:
    \f[
    |I_2|=\left| \int_{0}^T \psi(t) 
    \left\{ 
    u(a,t)-
    \int_{\gamma(t)}^a 
    \frac{d\theta}{k(\theta,t)}
    \int_{a}^\theta c(\xi)u_t(\xi,t)\,d\xi
    \right\} dt
    \right|
    \f]
    

    Результатом буде рядок наступного виду:

  3. Існує команда "\f{environment}", де environment — це назва певного оточення в LaTeX. Вона дозволяє використовувати зазначене оточення ніби воно було зазначено в зазвичай LaTeX документі. Приклад наведено нижче:
    \f{eqnarray*}{
    g &=& \frac{Gm_2}{r^2} \\ 
    &=& \frac{(6.673 \times 10^{-11}\,\mbox{m}^3\,\mbox{kg}^{-1}\,
    \mbox{s}^{-2})(5.9736 \times 10^{24}\,\mbox{kg})}{(6371.01\,\mbox{km})^2} \\ 
    &=& 9.82066032\,\mbox{m/s}^2
    \f}
    

    В результаті ми отримаємо наступний результат (зауважимо, що оточення eqnarray* — це ненумеровані оточення для розміщення декількох формул):

Приклад впровадження формул в документацію
Розглянемо конкретний приклад документації з використанням формул LaTeX:
/*!
\brief Обчислення факторіала числа \f$ n \f$
\param n - число, чий необхідно обчислити факторіал
\return \f$ n! \f$

Ця функція обчислює значення факторіала числа \f$ n \f$, визначається за формулою:
\f[
n! = \prod_{i = 1}^n i
\f]
*/
int factorial(int n);

Результат представлений нижче:


Коротко про Markdown
Markdown — це полегшений мова розмітки (почитати про нього можна, наприклад, тут, а також спеціальному розділі в документації). Починаючи з версії 1.8.0. Doxygen забезпечує його поки обмежену підтримку і він служить одним із способів оформити документацію (альтернативою можуть бути, наприклад, команди для оформлення документації або HTML вставки, які, втім, не універсальні).

Не хотілося б зараз розписувати подробиці і принципи цієї мови, тому обмежимося розглядом того, як ця мова дозволяє «прикрасити» нашу документацію:
/*!
Функція генерує число псведослучайное
------------------------------------------
Спочатку планувалося реалізувати в даній функції один з наступних методів генерації випадкових чисел:
- Лінійний конгруэнтный метод;
- Метод Фібоначчі;
- Лінійний регістр зсуву зі зворотнім зв'язком;
- Вихор Мерсенна.

Але розробники згадали про одну чудову цитату:
> Є два способи створення дизайну програми. Один з них, це зробити його настільки простим, що в ньому, очевидно, не буде недоліків. Інший спосіб - зробити його настільки заплутаним, що в ньому не буде очевидних недоліків.
> - C. A. R. Hoare

І обрали перший шлях.
![Опис функції](image.png)
*/
int getRandomNumber();


Результат представлений нижче:


Підбиваючи підсумки

На цій іронічній ноті я вирішив зупинитися. Я прекрасно розумію, що ще багато чого не було описано та порушено, але, сподіваюся, що головні свої цілі стаття виконала: ознайомити з поняттям генератора документації, познайомитися з системою Doxygen, пояснити основні принципи і підходи до документації, а також побіжно торкнутися питання, пов'язані з її оформлення і деталізацією, підготувавши заділ для вашої подальшої роботи.

Спасибі за увагу!

Література та посилання для подальшого вивчення

image1. Основним джерелом, який був використаний при написанні статті було офіційна документація;
2. На велику кількість різноманітних питань, пов'язаних з Doxygen, відповіді були отримані тут.

Джерело: Хабрахабр

0 коментарів

Тільки зареєстровані та авторизовані користувачі можуть залишати коментарі.