מדריך לסגנון הקוד

סגנון הקוד של HIDL דומה לקוד C++‎ ב-Android framework, עם הזחות של 4 רווחים ושמות קבצים באותיות רישיות וקטנות. הצהרות על חבילות, ייבוא ומחרוזות תיעוד דומים לאלה ב-Java, עם שינויים קלים.

בדוגמאות הבאות של IFoo.hal ו-types.hal מוצגים סגנונות קוד של HIDL, ויש בהן קישורים מהירים לפרטים על כל סגנון (הדוגמאות של IFooClientCallback.hal, IBar.hal ו-IBaz.hal הושמטו).

hardware/interfaces/foo/1.0/IFoo.hal
/*
 * (License Notice)
 */

package android.hardware.foo@1.0;

import android.hardware.bar@1.0::IBar;

import IBaz;
import IFooClientCallback;

/**
 * IFoo is an interface that*/
interface IFoo {

    /**
     * This is a multiline docstring.
     *
     * @return result 0 if successful, nonzero otherwise.
     */
     foo() generates (FooStatus result);

    /**
     * Restart controller by power cycle.
     *
     * @param bar callback interface that* @return result 0 if successful, nonzero otherwise.
     */
    powerCycle(IBar bar) generates (FooStatus result);

    /** Single line docstring. */
    baz();


    /**
     * The bar function.
     *
     * @param clientCallback callback after function is called
     * @param baz related baz object
     * @param data input data blob
     */
    bar(IFooClientCallback clientCallback,
        IBaz baz,
        FooData data);

};
hardware/interfaces/foo/1.0/types.hal
/*
 * (License Notice)
 */

package android.hardware.foo@1.0;

/** Replied status. */
enum Status : int32_t {
    OK,
    /* invalid arguments */
    ERR_ARG,
    /* note, no transport related errors */
    ERR_UNKNOWN = -1,
};

struct ArgData {
    int32_t[20]  someArray;
    vec<uint8_t> data;
};

מוסכמות למתן שמות

השמות של הפונקציות, המשתנים ושמות הקבצים צריכים להיות תיאוריים. כדאי להימנע מקיצורים מוגזמים. מתייחסים לראשי תיבות כמו למילים (לדוגמה, משתמשים ב-INfc במקום ב-INFC).

מבנה הספריות ומתן שמות לקבצים

מבנה הספריות צריך להיראות כך:

  • ROOT-DIRECTORY
    • MODULE
      • SUBMODULE (אופציונלי, יכול להיות יותר מרמה אחת)
        • VERSION
          • Android.mk
          • IINTERFACE_1.hal
          • IINTERFACE_2.hal
          • IINTERFACE_N.hal
          • types.hal (אופציונלי)

איפה:

  • ROOT-DIRECTORY הוא:
    • hardware/interfaces לחבילות HIDL ליבה.
    • vendor/VENDOR/interfaces לחבילות של ספקים, כאשר VENDOR מתייחס לספק SoC או ל-OEM/ODM.
  • MODULE צריך להיות מילה אחת באותיות קטנות שמתארת את מערכת המשנה (לדוגמה, nfc). אם צריך יותר ממילה אחת, צריך להשתמש ב-SUBMODULE מקונן. יכולות להיות כמה רמות של קינון.
  • VERSION צריכה להיות בדיוק אותה גרסה (major.minor) כמו שמתואר בקטע גרסאות.
  • IINTERFACE_X צריך להיות שם הממשק עם UpperCamelCase/PascalCase (לדוגמה, INfc) כמו שמתואר במאמר שמות של ממשקים.

דוגמה:

  • hardware/interfaces
    • nfc
      • 1.0
        • Android.mk
        • INfc.hal
        • INfcClientCallback.hal
        • types.hal

הערה: לכל הקבצים צריכות להיות הרשאות שאינן הרשאות הפעלה (ב-Git).

שמות של חבילות

שמות החבילות חייבים להיות בפורמט שם מוגדר במלואו (FQN) הבא (שנקרא PACKAGE-NAME):

PACKAGE.MODULE[.SUBMODULE[.SUBMODULE[]]]@VERSION

איפה:

  • PACKAGE הוא החבילה שממופה ל-ROOT-DIRECTORY. בפרט, הערך של: PACKAGE הוא:
    • android.hardware לחבילות ליבה של HIDL (מיפוי ל-hardware/interfaces).
    • vendor.VENDOR.hardware לחבילות של ספקים, כאשר VENDOR מתייחס לספק SoC או ל-OEM/ODM (מיפוי ל-vendor/VENDOR/interfaces).
  • MODULE[.SUBMODULE[.SUBMODULE[…]]]@VERSION השמות של התיקיות זהים בדיוק למבנה שמתואר במאמר מבנה הספריות.
  • שמות החבילות צריכים להיות באותיות קטנות. אם השם כולל יותר ממילה אחת, צריך להשתמש במילים כשמות של מודולי משנה או לכתוב אותן ב-snake_case.
  • אסור להשתמש ברווחים.

השם המלא משמש תמיד בהצהרות על חבילות.

גרסאות

הגרסאות צריכות להיות בפורמט הבא:

MAJOR.MINOR

הגרסה של MAJOR ושל MINOR צריכה להיות מספר שלם יחיד. ב-HIDL נעשה שימוש בכללים של semantic versioning.

ייבוא

ייבוא יכול להיות באחד משלושת הפורמטים הבאים:

  • ייבוא של חבילות שלמות: import PACKAGE-NAME;
  • ייבוא חלקי: import PACKAGE-NAME::UDT; (או, אם הסוג שיובא נמצא באותו חבילה,import UDT;
  • ייבוא של סוגים בלבד: import PACKAGE-NAME::types;

הפורמט של PACKAGE-NAME זהה לפורמט שמופיע בשמות החבילות. החבילה הנוכחית types.hal (אם היא קיימת) מיובאת באופן אוטומטי (אין לייבא אותה באופן מפורש).

שמות מלאים (FQNs)

משתמשים בשמות מלאים לייבוא של סוגים שהוגדרו על ידי המשתמש רק כשצריך. לא מציינים את PACKAGE-NAME אם סוג הייבוא נמצא באותו חבילה. אסור שיהיו רווחים ב-FQN. דוגמה לשם שמוגדר במלואו:

android.hardware.nfc@1.0::INfcClientCallback

בקובץ אחר בקטע android.hardware.nfc@1.0, הממשק שלמעלה נקרא INfcClientCallback. אחרת, צריך להשתמש רק בשם המלא.

קיבוץ וסידור של ייבוא

משתמשים בשורה ריקה אחרי הצהרת החבילה (לפני הייבוא). כל פעולת ייבוא צריכה להיות בשורה נפרדת, ללא הזחה. ייבוא קבוצות בסדר הבא:

  1. חבילות אחרות של android.hardware (צריך להשתמש בשמות מלאים).
  2. חבילות אחרות של vendor.VENDOR (צריך להשתמש בשמות מלאים).
    • כל ספק צריך להיות קבוצה.
    • מיון הספקים לפי סדר אלפביתי.
  3. ייבוא מממשקים אחרים באותה חבילה (שימוש בשמות פשוטים).

משתמשים בשורה ריקה בין קבוצות. בתוך כל קבוצה, ממיינים את הייבוא לפי סדר אלפביתי. דוגמה:

import android.hardware.nfc@1.0::INfc;
import android.hardware.nfc@1.0::INfcClientCallback;

/* Importing the whole module. */
import vendor.barvendor.bar@3.1;

import vendor.foovendor.foo@2.2::IFooBar;
import vendor.foovendor.foo@2.2::IFooFoo;

import IBar;
import IFoo;

שמות הממשקים

שמות הממשקים חייבים להתחיל ב-I, ואחריו בשם UpperCamelCase/PascalCase. צריך להגדיר קובץ IFoo.hal עם ממשק בשם IFoo. הקובץ הזה יכול להכיל הגדרות רק לממשק IFoo (הממשק INAME צריך להיות ב-INAME.hal).

פונקציות

לשמות של פונקציות, ארגומנטים ומשתני החזרה, משתמשים ב-lowerCamelCase. דוגמה:

open(INfcClientCallback clientCallback) generates (int32_t retVal);
oneway pingAlive(IFooCallback cb);

שמות של שדות struct ו-union

בשמות של שדות מסוג struct או union, משתמשים ב-lowerCamelCase. דוגמה:

struct FooReply {
    vec<uint8_t> replyData;
}

הקלדת שמות

שמות סוגים מתייחסים להגדרות של מבנים או איגודים, להגדרות של סוגי enum ול-typedef. כדי להשתמש בשם הזה, צריך להשתמש ב-UpperCamelCase/PascalCase. דוגמאות:

enum NfcStatus : int32_t {
    /*...*/
};
struct NfcData {
    /*...*/
};

ערכי enum

ערכי ה-enum צריכים להיות UPPER_CASE_WITH_UNDERSCORES. כשמעבירים ערכי enum כארגומנטים של פונקציה ומחזירים אותם כערכי החזרה של פונקציה, צריך להשתמש בסוג ה-enum בפועל (ולא בסוג המספר השלם הבסיסי). דוגמה:

enum NfcStatus : int32_t {
    HAL_NFC_STATUS_OK               = 0,
    HAL_NFC_STATUS_FAILED           = 1,
    HAL_NFC_STATUS_ERR_TRANSPORT    = 2,
    HAL_NFC_STATUS_ERR_CMD_TIMEOUT  = 3,
    HAL_NFC_STATUS_REFUSED          = 4
};

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

בשמות מלאים של ערכי enum, משתמשים בנקודתיים בין שם סוג ה-enum לבין שם ערך ה-enum:

PACKAGE-NAME::UDT[.UDT[.UDT[…]]:ENUM_VALUE_NAME

אסור שיהיו רווחים בשם שמוגדר במלואו. משתמשים בשם מלא רק כשצריך, ומשמיטים חלקים מיותרים. דוגמה:

android.hardware.foo@1.0::IFoo.IFooInternal.FooEnum:ENUM_OK

תגובות

לתגובה בשורה אחת, אפשר להשתמש ב-//, ב-/* */ וב-/** */.

// This is a single line comment
/* This is also single line comment */
/** This is documentation comment */
  • שימוש ב/* */ לתגובות. ‫HIDL תומך ב-// לתגובות, אבל לא מומלץ להשתמש בהן כי הן לא מופיעות בפלט שנוצר.
  • משתמשים ב-/** */ כדי ליצור תיעוד. אפשר להחיל אותם רק על הצהרות של סוגים, שיטות, שדות וערכי enum. דוגמה:
    /** Replied status */
    enum TeleportStatus {
        /** Object entirely teleported. */
        OK              = 0,
        /** Methods return this if teleportation is not completed. */
        ERROR_TELEPORT  = 1,
        /**
         * Teleportation could not be completed due to an object
         * obstructing the path.
         */
        ERROR_OBJECT    = 2,
        ...
    }
  • כדי להתחיל תגובות עם כמה שורות, צריך להוסיף את התו /** בשורה נפרדת. משתמשים ב-* בתחילת כל שורה. בסוף התגובה, בשורה נפרדת, כותבים */ ומיישרים את הכוכביות. דוגמה:
    /**
     * My multi-line
     * comment
     */
  • הודעת הרישוי ויומני השינויים צריכים להתחיל בשורה חדשה עם /* (כוכבית אחת), להשתמש ב-* בתחילת כל שורה, ולהציב את */ בשורה האחרונה לבד (הכוכביות צריכות להיות מיושרות). דוגמה:
    /*
     * Copyright (C) 2017 The Android Open Source Project
     * ...
     */
    
    /*
     * Changelog:
     * ...
     */

תגובות לקבצים

מתחילים כל קובץ בהודעת הרישוי המתאימה. במקרה של HALs מרכזיים, הערך הזה צריך להיות רישיון Apache של AOSP ב-development/docs/copyright-templates/c.txt. חשוב לזכור לעדכן את השנה ולהשתמש בהערות מרובות שורות בסגנון /* */, כפי שמוסבר למעלה.

אפשר גם להוסיף שורה ריקה אחרי הודעת הרישיון, ואחריה יומן שינויים או פרטים על הגרסה. משתמשים בתגובות מרובות שורות בסגנון /* */ כמו שמוסבר למעלה, מציבים את השורה הריקה אחרי יומן השינויים, ואז ממשיכים עם הצהרת החבילה.

תגובות TODO

הערות TODO צריכות לכלול את המחרוזת TODO באותיות גדולות, ואחריה נקודתיים. דוגמה:

// TODO: remove this code before foo is checked in.

מותר להשתמש בתגובות TODO רק במהלך הפיתוח. אסור להשתמש בהן בממשקים שפורסמו.

תגובות על ממשקים ופונקציות (מחרוזות תיעוד)

משתמשים ב-/** */ עבור מחרוזות תיעוד מרובות שורות ומחרוזות תיעוד בשורה אחת. אין להשתמש ב-// עבור מחרוזות תיעוד.

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

/**
 * IFooController is the controller for foos.
 */
interface IFooController {
    /**
     * Opens the controller.
     *
     * @return status HAL_FOO_OK if successful.
     */
    open() generates (FooStatus status);

    /** Close the controller. */
    close();
};

צריך להוסיף @params ו-@returns לכל פרמטר או ערך מוחזר:

  • צריך להוסיף את @param לכל פרמטר. אחריו צריך להזין את שם הפרמטר ואז את מחרוזת התיעוד.
  • צריך להוסיף את @return לכל ערך מוחזר. אחריו צריך להוסיף את השם של הערך המוחזר ואז את מחרוזת התיעוד.

דוגמה:

/**
 * Explain what foo does.
 *
 * @param arg1 explain what arg1 is
 * @param arg2 explain what arg2 is
 * @return ret1 explain what ret1 is
 * @return ret2 explain what ret2 is
 */
foo(T arg1, T arg2) generates (S ret1, S ret2);

כללי עיצוב

כללי עיצוב כלליים:

  • אורך המסילה. כל שורת טקסט צריכה להיות באורך של עד 100 עמודות.
  • רווחים לבנים. אין רווחים לבנים בסוף השורות. שורות ריקות לא יכולות להכיל רווחים לבנים.
  • מרחבים לעומת כרטיסיות. משתמשים רק ברווחים.
  • Indent size (גודל ההזחה). משתמשים ב-4 רווחים לבלוקים וב-8 רווחים לגלישת שורות
  • תמיכה. חוץ מערכי הערות, סוגר מסולסל פתוח מופיע באותה שורה של הקוד שקודם לו, אבל סוגר מסולסל סגור ונקודה ופסיק שאחריו תופסים את כל השורה. דוגמה:
    interface INfc {
        close();
    };

הצהרת חבילה

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

package PACKAGE-NAME;

דוגמה:

package android.hardware.nfc@1.0;

הצהרות על פונקציות

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

interface IFoo {
    /** ... */
    easyMethod(int32_t data) generates (int32_t result);
};

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

interface IFoo {
    suchALongMethodThatCannotFitInOneLine(int32_t theFirstVeryLongParameter,
                                          int32_t anotherVeryLongParameter);
    anEvenLongerMethodThatCannotFitInOneLine(int32_t theFirstLongParameter,
                                             int32_t anotherVeryLongParameter)
                                  generates (int32_t theFirstReturnValue,
                                             int32_t anotherReturnValue);
    superSuperSuperSuperSuperSuperSuperLongMethodThatYouWillHateToType(
            int32_t theFirstVeryLongParameter, // 8 spaces
            int32_t anotherVeryLongParameter
        ) generates (
            int32_t theFirstReturnValue,
            int32_t anotherReturnValue
        );
    /* method name is even shorter than 'generates' */
    foobar(AReallyReallyLongType aReallyReallyLongParameter,
           AReallyReallyLongType anotherReallyReallyLongParameter)
        generates (ASuperLongType aSuperLongReturnValue, // 4 spaces
                   ASuperLongType anotherSuperLongReturnValue);
}

פרטים נוספים:

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

הערות

צריך להשתמש בפורמט הבא להערות:

@annotate(keyword = value, keyword = {value, value, value})

ממיינים את ההערות בסדר אלפביתי ומשתמשים ברווחים מסביב לסימני השוויון. דוגמה:

@callflow(key = value)
@entry
@exit

חשוב לוודא שההערה תופסת את כל השורה. לדוגמה:

/* Good */
@entry
@exit

/* Bad */
@entry @exit

אם ההערות לא נכנסות לאותה שורה, מוסיפים הזחה של 8 רווחים. דוגמה:

@annotate(
        keyword = value,
        keyword = {
                value,
                value
        },
        keyword = value)

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

אם כל מערך הערכים יכול להיכנס לאותה השורה, אל תשתמשו ברווחים אחרי הסוגריים המסולסלים הפותחים ולפני הסוגריים המסולסלים הסוגרים, ותשתמשו ברווח אחד אחרי כל פסיק. לדוגמה:

/* Good */
@callflow(key = {"val", "val"})

/* Bad */
@callflow(key = { "val","val" })

אסור שיהיו שורות ריקות בין ההערות לבין הצהרת הפונקציה. לדוגמה:

/* Good */
@entry
foo();

/* Bad */
@entry

foo();

הצהרות על טיפוסים בני מנייה (enum)

הכללים הבאים חלים על הצהרות enum:

  • אם הצהרות enum משותפות עם חבילה אחרת, צריך להציב את ההצהרות ב-types.hal ולא להטמיע אותן בתוך ממשק.
  • משתמשים ברווח לפני הנקודתיים ואחריהן, וברווח אחרי סוג הנתונים הבסיסי לפני הסוגר הפותח.
  • יכול להיות שלא יהיה פסיק נוסף אחרי הערך האחרון של enum.

הצהרות Struct

הכללים הבאים חלים על הצהרות של מבנים:

  • אם הצהרות על מבנים משותפות עם חבילה אחרת, צריך להציב את ההצהרות ב-types.hal ולא להטמיע אותן בתוך ממשק.
  • משתמשים ברווח אחרי שם סוג המבנה לפני הסוגר הפותח.
  • התאמת שמות השדות (אופציונלי). דוגמה:
    struct MyStruct {
        vec<uint8_t>   data;
        int32_t        someInt;
    }

הצהרות על מערכים

אסור להוסיף רווחים בין:

  • סוג הרכיב וסוגר מרובע פותח.
  • סוגר מרובע שמאלי וגודל המערך.
  • גודל המערך וסוגר מרובע סוגר.
  • סוגרים את הסוגר המרובע ופותחים את הסוגר המרובע הבא, אם יש יותר מממד אחד.

לדוגמה:

/* Good */
int32_t[5] array;

/* Good */
int32_t[5][6] multiDimArray;

/* Bad */
int32_t [ 5 ] [ 6 ] array;

וקטורים

אסור להוסיף רווחים בין:

  • vec וסוגר זוויתי שמאלי.
  • סוגר זוויתי שמאלי וסוג הרכיב (הערה: סוג הרכיב הוא גם a vec).
  • סוג הרכיב וסוגר זוויתי סוגר (יוצא מן הכלל: סוג הרכיב הוא גם vec).

לדוגמה:

/* Good */
vec<int32_t> array;

/* Good */
vec<vec<int32_t>> array;

/* Good */
vec< vec<int32_t> > array;

/* Bad */
vec < int32_t > array;

/* Bad */
vec < vec < int32_t > > array;