תקציר:

איך ולמה לתעד את הקוד שלכם בעזרת הכלי החינמי Doxygen, להפוך את התיעוד לזמין באינטרנט, וכיצד להפוך את התהליך לאוטומטי בעזרת כלים חינמיים.

Best practices for writing code comments

“Comments often are used as a deodorant.”

— Martin Fowler and Kent Beck, Refactoring, page 87

הקדמה

המניפסט של פיתוח התוכנה הזריז (Agile) קובע:

אנשים ואינטראקציות על פני תהליכים וכלים

תוכנה עובדת על פני תיעוד מקיף

שיתוף פעולה עם הלקוח על פני משא ומתן חוזי

היענות לשינוי על פני הליכה אחר תוכנית

הוא מעריך את הפריטים שמשמאל יותר (ההדגשה שלי) מאשר את הפריטים שמימין. מבין ארבע ההצהרות האוניברסליות, אני מאמין שהשנייה היא המובנת ביותר לא נכון. ללא תוכנה עובדת, תיעוד מקיף הוא חסר משמעות. המילה המכרעת כאן היא “מקיף”. יצירת תיעוד מקיף דורשת זמן ומאמץ. אבל זה לעולם לא צריך להיות תירוץ שלא ליצור תיעוד קוד.

מה התועלת בתיעוד קוד?

אני מאמין שתיעוד הקוד שונה מסתם הוספת הערות לקוד. אם אתם מנסים להפחית את ה”ריח” של הקוד באמצעות הערות, עדיף שתבצעו refactoring לקוד כדי להפוך אותו למובן יותר. אני מאמין שתיעוד קוד צריך:

לספק מידע שלא ניתן לבטא באמצעות הקוד עצמו

אז איזה סוג של מידע מספק תיעוד קוד שלא ניתן לבטא באמצעות קוד?

  • עקיבות לדרישות (Requirement traceability): יחידת קוד – בין אם פונקציה, מחלקה, מודול או רכיב – צריכה להיות ניתנת למעקב אל דרישה אחת או יותר. עקיבות לדרישות נחוצה כדי לענות על שתי שאלות יסודיות: האם אנחנו בונים את המוצר הנכון? האם אנחנו בונים את המוצר בצורה נכונה? תיעוד קוד יכול להטמיע מזהי דרישות שמאפשרים מעקב קדימה ואחורה אל הקוד.
  • תכנון לפי חוזה (Design by contract): שיטת פיתוח תוכנה זו שורשיה באימות פורמלי של תוכנה. חוזה הוא יחידת קוד. ישנן שלוש השאלות הבאות שעל החוזה לענות עליהן:
  • מה החוזה מצפה לו?
  • מה החוזה מבטיח?
  • מה החוזה משמר?

כללים מחמירים אלה מבטיחים את נכונות התוכנה וניתן לכתוב אותם באמצעות הערות קוד – במיוחד בשפות שאין להן תמיכה מובנית בחוזים.

  • מודל 4+1 של תצוגות ארכיטקטוניות: במודל 4+1 של התצוגות הארכיטקטוניות, תצוגת הפיתוח עוסקת בקוד. זוהי תצוגת המימוש. אין שום סיבה שתיעוד הקוד לא יוכל לרמוז על תצוגות אחרות – במיוחד התצוגה הדינמית ותצוגת הפריסה.
  • כתזכורת למתחזקים: אינני יכול לתאר זאת טוב יותר מהתמונה שמצאתי באינטרנט:

  • דרישה רגולטורית: בתחומים מסוימים כמו מכשור רפואי, תיעוד קוד הוא חובה מכיוון שהוא דרישה לעמידה ברגולציה של מנהל המזון והתרופות (FDA). במצבים כאלה ייאכף המדד של יחס קוד להערות.
  • תיעוד API: מערכות שחושפות שירותים באמצעות ממשקי API (מבוססי REST) נדרשות לספק תיעוד API. תיעוד זה נחשף ללקוחות בתור תיעוד API. הוא עוזר ללקוחות להבין את הפונקציונליות של ממשקי ה-API החשופים ולצרוך אותם בהתאם למפרט. כמו כן, מכיוון שממשקי API משתנים עם הזמן (עם הוספת פונקציונליות חדשה והוצאה משימוש של ישנות), תיעוד ה-API צריך לציין את מידע הגרסאות.

Code Documentation Failure

Doxygen – תיעוד בקלות

בפרק הקודם, אני מאמין שהבהרתי היטב את הנקודה שתיעוד קוד הוא חשוב. למרות שגם הקוד וגם תיעוד הקוד נכנסים לקובץ קוד המקור, יהיה זה לא חכם לקרוא את הקובץ לצורך תיעוד. הפרדת תחומי אחריות (Separation of concerns) היא מה שאנחנו צריכים:

Doxygen הוא כלי ידוע ליצירת תיעוד מתוך קוד מקור מתועד עבור שפות תכנות שונות כמו:

  • C ו-C++‎
  • Objective C
  • C#
  • PHP
  • Java
  • Python
  • Fortran
  • ..וכו’

Doxygen יכול ליצור גם תיעוד מקוון (HTML המתארח בשרת וניתן לצפייה דרך דפדפן, למשל תיעוד D-Bus https://dbus.freedesktop.org/doc/api/html/index.html) וגם תיעוד לא מקוון (בפורמטים RTF, PDF, PostScript או LaTeX).

Doxygen הוא פרויקט קוד פתוח בוגר שקיים כבר יותר מ-20 שנה. הוא מתוחזק היטב מכיוון שהוא פותר בעיה חיונית של יצירת תיעוד קוד מתוך קבצים מתועדים ובכך מספק ערך.

דוגמת Doxygen

לצורך הדגמה זו, אני יוצר מערכת בלוג בשפת C++‎ כדי להציג מעט קוד. הנה כותרת אחת (header):

BlogEntry.h:

#pragma once

/*! \file BlogEntry.h
* \brief The BlogEntry class is defined in this file
*/

#include <chrono>
#include <string>

/*! \namespace BlogChrono
* \brief Currently based on std::chrono, but can be changed
* later to other non-standard library if the need arises. \n Code based on this
* namespace should not change.
*/

namespace BlogChrono = std::chrono;
/*! \class BlogEntry
* \brief The BlogEntry class
* BlogEntry is a domain object under the bounded context
* of our blogging system's domain driven design. Every BlogEntry has the
* following characterstics:
* Title: The title of the blog \n
* Author: The author of the blog \n
* Published date: Date of publication of blog \n
* Keywords: SEO keywords in the blog \n
* See also : Blog.h \n
*/

class BlogEntry
{
    private:
        std::wstring m_Title;
        std::wstring m_Author;
        const BlogChrono::time_point<BlogChrono::system_clock> m_PublishedDate;
    public:
        //! Blog published date in a nice format so as to embed in a page
        std::wstring GetPublishedDateInHumenReadableForm();
        ///...showoff our expertise in domain driven design...///
        // ...
};

BlogEntry.cpp:

#include "BlogEntry.h"

/**Create a wide char string that is human readable to embed into a
* blog post.
@return: Empty string if conversion fails, else a valid string.
*/
std::wstring BlogEntry::GetPublishedDateInHumenReadableForm()
{
    return std::wstring();
}

כפי שניתן לראות, תיעדתי את הקוד שלי באמצעות תגיות (markups) של Doxygen. כדי ליצור תיעוד מבוסס HTML שאוכל לארח באינטרנט קיימות כמה אפשרויות:

  • אפשרות 1: להוריד ולהתקין את Doxygen בעצמי (כולל את הכלי graphviz ליצירת גרפים בתוך Doxygen), להגדיר הכול בעצמי, להריץ בעצמי ולהעלות את התיעוד לענן לשירות אחסון בעצמי.
  • אפשרות 2: להשתמש ב-CodeDocs שדואג לכל זה ועושה זאת באופן אוטומטי (אך הוא מיועד רק למאגרים ציבוריים ב-GitHub, והוא אינו יוצר גרפים בתיעוד שלכם ואינו מנהל בקרת גרסאות של התיעוד שלכם).
  • אפשרות 3: להשתמש ב-GitHub action שמייצר את התיעוד באופן אוטומטי ומעלה אותו ל-GitHub pages שלכם (אך זה אינו מנהל בקרת גרסאות של התיעוד שלכם). ישנם מספר GitHub actions זמינים ב-GitHub marketplace.

הנה החוויה עם אפשרות 1:

  1. הורידו והתקינו את Doxygen. לאחר סיום ההתקנה, אתם אמורים להיות מסוגלים להדפיס את גרסת Doxygen משורת הפקודה באמצעות doxygen -v

  1. צרו קובץ תצורה (configuration file) באותה תיקייה של קוד המקור שלכם באמצעות הפקודה doxygen -g

  1. קובץ ה-democonfig שנוצר זקוק למספר עריכות. הנה מה שעליכם לעשות:

האם לא יהיה נחמד אם ניתן יהיה להפוך זאת לאוטומטי?

נערך

  1. כעת הריצו את הפקודה: doxygen democonfig

תראו את Doxygen מבצע עיבוד מקדים ומנתח את ה-headers וקבצי המקור של הפרויקט שלכם. לאחר שזה מסתיים, תוכלו לבדוק את התיעוד שנוצר באמצעות דפדפן אינטרנט בכך שתנווטו לעמוד ‎.\html\index.html.

בואו ניתן מספר פרויקט ונראה מה משתנה.

PROJECT_NUMBER = 3.14

ונייצר מחדש את התיעוד.

הגרסה הופכת מפורשת:

  1. כך ייראה התיעוד עבור המחלקה:

כך ייראה התיעוד עבור ה-header:

הוספת ניהול גרסאות לתיעוד חשובה מאוד – חשובה כמו ניהול הגרסאות של קוד המקור עצמו. שמירה ידנית על סנכרון בין גרסאות התיעוד והקוד היא כאב ראש. לכן מומלץ מאוד להפוך את התהליך הזה לאוטומטי ולבצע אותו בסנכרון עם בקרת הגרסאות שלכם ב-Git.

כיצד להגדיר אוטומציה לסנכרון תיעוד Doxygen שלכם עם הקוד שלכם?

לשימוש באוטומציה צריכות להיות שתי מטרות עיקריות:

  1. שמירה על בקרת גרסאות של התיעוד שלכם בסנכרון עם בקרת הגרסאות של הקוד שלכם
  2. הפיכת התהליך לאוטומטי כדי לחסוך לכם זמן

איך עושים זאת?

ניתן לממש זאת בדרכים רבות באמצעות שרת אוטומציה כגון Jenkins או באמצעות GitHub Actions וכו’. אבל מימוש מסוג זה דורש מעט יותר כישורים טכניים ועקומת למידה ארוכה יותר. אם אינכם בקיאים בשרתי אוטומציה ועדיין הייתם רוצים להפוך זאת לאוטומטי מבלי ללמוד יותר מדי, אז הדרך הפשוטה והישירה ביותר לממש זאת היא באמצעות pre-commit hook. זה אידיאלי אם אתם צוות ממש קטן או עובדים לבד על פרויקט.

בעת שימוש ב-pre-commit hook תוכלו לכתוב סקריפט שיוצר עבורכם את התיעוד באופן אוטומטי ושומר אותו במאגר שלכם (יחד עם הקוד שלכם) לפני ששינויי הקוד שלכם בוצעו (committed). כך הקוד והתיעוד שלכם תמיד מסונכרנים. אם אתם משתמשים ב-GitHub Pages תוכלו אפילו להגדיר את GitHub pages כך שיציג תמיד את הגרסה העדכנית ביותר של התיעוד שלכם, וכך תקבלו את התיעוד שלכם באינטרנט. אם, לעומת זאת, אתם צריכים לראות גרסה קודמת של התיעוד, היא לא תהיה גלויה ב-GitHub pages, אבל תמיד תוכלו לבצע checkout ל-commit הרלוונטי למחשב שלכם ולפתוח אותו מקומית. כמו כן, GitHub Pages יציג את התיעוד שלכם לציבור גם אם המאגר שלכם פרטי. תוכלו לשלוט בנראות של GitHub Pages שלכם רק אם תשדרגו ל-GitHub Enterprise (שעולה לא מעט).

החיסרון בשימוש ב-pre-commit hook הוא שהסקריפט שלכם מסתמך על ההתקנה והתצורה של Doxygen במחשב המקומי שלכם. אם חבר צוות אחר רוצה להשתמש באותו סקריפט במחשב שלו, עליו להגדיר את הדברים בדיוק באותו אופן, אחרת עלולים להיווצר הבדלים בתוצאות התיעוד שלכם. אם הייתם משתמשים בשרת אוטומציה, אז הכול מוגדר בשרת הזה, ואז תקבלו תוצאות עקביות עבור כל המשתמשים בסקריפט. לכן אני ממליץ להשתמש ב-pre-commit hook רק אם אתם צוות קטן או מפתחים בודדים.

סיכום

הערות בהחלט לא צריכות לשמש כדאודורנט. קוד לעולם לא צריך להסריח. אם בפרויקט יש “ריחות” של עיצוב או של קוד, השתמשו בזה כעיקרון מנחה לבצע refactoring לקוד שלכם. תחזקו את תיעוד הקוד. יש לכך השפעה ישירה על האיכות הפנימית של המערכת שלכם. עליכם לתחזק את הקוד שלכם וכן את התיעוד שלכם תוך הקפדה שהם מסונכרנים. כהמשך, אני ממליץ לעיין בפוסט הנוסף שלנו על כיצד תיעוד Doxygen יכול לעזור לכם לכתוב קוד טוב יותר ב-C וב-C++‎.