Poradnik dotyczący stylu kodu

Styl kodu HIDL przypomina kod C++ w platformie Android z 4-znakowymi wcięciami i nazwami plików z wielkimi i małymi literami. Deklaracje pakietów, instrukcje importu i ciągi dokumentujące są podobne do tych w Javie, ale z niewielkimi modyfikacjami.

Poniższe przykłady dla IFoo.haltypes.hal ilustrują style kodu HIDL i zawierają szybkie linki do szczegółów każdego stylu (IFooClientCallback.hal, IBar.halIBaz.hal zostały pominięte).

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;
};

Konwencje nazewnictwa

Nazwy funkcji, zmiennych i plików powinny być opisowe. Unikaj nadmiernych skrótów. Traktuj akronimy jak słowa (np. używaj INfc zamiast INFC).

Struktura katalogów i nazewnictwo plików

Struktura katalogu powinna wyglądać tak:

  • ROOT-DIRECTORY
    • MODULE
      • SUBMODULE (opcjonalnie, może być więcej niż jeden poziom)
        • VERSION
          • Android.mk
          • IINTERFACE_1.hal
          • IINTERFACE_2.hal
          • IINTERFACE_N.hal
          • types.hal (opcjonalnie)

Gdzie:

  • ROOT-DIRECTORY to:
    • hardware/interfaces w przypadku podstawowych pakietów HIDL.
    • vendor/VENDOR/interfaces w przypadku pakietów dostawców, gdzie VENDOR oznacza dostawcę układu SOC lub producenta OEM/ODM.
  • MODULE powinno być jednym słowem zapisanym małymi literami, które opisuje podsystem (np. nfc). Jeśli potrzebujesz więcej niż jednego słowa, użyj zagnieżdżonego elementu SUBMODULE. Może być więcej niż 1 poziom zagnieżdżenia.
  • VERSION powinna być dokładnie tą samą wersją (główną.podrzędną), która została opisana w sekcji Wersje.
  • IINTERFACE_X powinna być nazwą interfejsu z UpperCamelCase/PascalCase (np. INfc), zgodnie z opisem w sekcji Nazwy interfejsów.

Przykład:

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

Uwaga: wszystkie pliki muszą mieć uprawnienia inne niż wykonywalne (w Git).

Nazwy pakietów

Nazwy pakietów muszą mieć format pełnej nazwy (FQDN) (określanej jako PACKAGE-NAME):

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

Gdzie:

  • PACKAGE to pakiet, który jest zmapowany na ROOT-DIRECTORY. W szczególności: PACKAGE to:
    • android.hardware w przypadku podstawowych pakietów HIDL (mapowanie na hardware/interfaces).
    • vendor.VENDOR.hardware w przypadku pakietów dostawców, gdzie VENDOR odnosi się do dostawcy układu SoC lub producenta OEM/ODM (mapowanie na vendor/VENDOR/interfaces).
  • MODULE[.SUBMODULE[.SUBMODULE[…]]]@VERSION są dokładnie takie same jak nazwy folderów w strukturze opisanej w sekcji Struktura katalogów.
  • Nazwy pakietów powinny być pisane małymi literami. Jeśli nazwy składają się z więcej niż jednego słowa, powinny być używane jako podmoduły lub zapisywane w formacie snake_case.
  • Spacje są niedozwolone.

W deklaracjach pakietów zawsze używana jest pełna nazwa.

Wersje

Wersje powinny mieć format:

MAJOR.MINOR

Zarówno wersja MAJOR, jak i MINOR powinna być pojedynczą liczbą całkowitą. HIDL używa reguł wersjonowania semantycznego.

Import

Import może mieć jeden z tych 3 formatów:

  • Importy całych pakietów: import PACKAGE-NAME;
  • Importy częściowe: import PACKAGE-NAME::UDT; (lub, jeśli importowany typ znajduje się w tym samym pakiecie,import UDT;
  • Importowanie tylko typów: import PACKAGE-NAME::types;

Symbol PACKAGE-NAME jest zgodny z formatem w sekcji Nazwy pakietów. Bieżący pakiet types.hal (jeśli istnieje) jest importowany automatycznie (nie importuj go jawnie).

Pełne nazwy (FQNs)

Używaj w pełni kwalifikowanych nazw w przypadku importowania typów zdefiniowanych przez użytkownika tylko wtedy, gdy jest to konieczne. Pomiń PACKAGE-NAME, jeśli typ importu znajduje się w tym samym pakiecie. W pełni kwalifikowana nazwa nie może zawierać spacji. Przykład pełnej i jednoznacznej nazwy:

android.hardware.nfc@1.0::INfcClientCallback

W innym pliku w sekcji android.hardware.nfc@1.0 odwołuj się do powyższego interfejsu jako INfcClientCallback. W przeciwnym razie użyj tylko pełnej nazwy.

Grupowanie i sortowanie importów

Po deklaracji pakietu (przed importami) użyj pustego wiersza. Każdy import powinien zajmować jeden wiersz i nie powinien być wcięty. Importuj grupy w tej kolejności:

  1. Inne pakiety android.hardware (użyj pełnych i jednoznacznych nazw).
  2. Inne pakiety vendor.VENDOR (używaj pełnych i jednoznacznych nazw).
    • Każdy dostawca powinien być grupą.
    • Uporządkuj dostawców alfabetycznie.
  3. Importowanie z innych interfejsów w tym samym pakiecie (używaj prostych nazw).

Używaj pustego wiersza między grupami. W każdej grupie posortuj importy alfabetycznie. Przykład:

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;

Nazwy interfejsów

Nazwy interfejsów muszą zaczynać się od znaku I, a po nim musi następować nazwa UpperCamelCase/PascalCase. W pliku IFoo.hal musi być zdefiniowany interfejs o nazwie IFoo. Ten plik może zawierać definicje tylko dla interfejsu IFoo (interfejs INAME powinien znajdować się w INAME.hal).

Funkcje

W przypadku nazw funkcji, argumentów i zmiennych zwracanych używaj lowerCamelCase. Przykład:

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

Nazwy pól struktur i unii

W przypadku nazw pól struktur lub unii używaj znaku lowerCamelCase. Przykład:

struct FooReply {
    vec<uint8_t> replyData;
}

Nazwy typów

Nazwy typów odnoszą się do definicji struktur lub unii, definicji typów wyliczeniowych i typedef. W przypadku tych nazw użyj znaku UpperCamelCase/PascalCase. Przykłady:

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

Wartości typu wyliczeniowego

Wartości wyliczeniowe powinny być UPPER_CASE_WITH_UNDERSCORES. Podczas przekazywania wartości wyliczeniowych jako argumentów funkcji i zwracania ich jako wartości zwracanych przez funkcję używaj rzeczywistego typu wyliczeniowego (a nie bazowego typu całkowitego). Przykład:

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
};

Uwaga: typ bazowy typu wyliczeniowego jest jawnie deklarowany po dwukropku. Używanie rzeczywistego typu wyliczeniowego jest bardziej przejrzyste, ponieważ nie zależy od kompilatora.

W przypadku w pełni kwalifikowanych nazw wartości wyliczeniowych używa się dwukropka między nazwą typu wyliczeniowego a nazwą wartości wyliczeniowej:

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

W pełnej nazwie nie może być spacji. Używaj pełnej nazwy tylko wtedy, gdy jest to konieczne, i pomijaj niepotrzebne części. Przykład:

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

Komentarze

W przypadku komentarza w jednym wierszu wystarczą //, /* *//** */.

// This is a single line comment
/* This is also single line comment */
/** This is documentation comment */
  • Używaj /* */ do komentowania. HIDL obsługuje komentarze w formacie //, ale nie są one zalecane, ponieważ nie pojawiają się w wygenerowanych danych wyjściowych.
  • W wygenerowanej dokumentacji używaj /** */. Można je stosować tylko do deklaracji typu, metody, pola i wartości wyliczeniowej. Przykład:
    /** 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,
        ...
    }
  • Komentarze wielowierszowe zaczynaj od znaku /** w osobnym wierszu. Na początku każdego wiersza użyj znaku *. Zakończ komentarz znakiem */ w osobnym wierszu, wyrównując gwiazdki. Przykład:
    /**
     * My multi-line
     * comment
     */
  • Informacje o licencji i dzienniki zmian powinny zaczynać się w nowym wierszu od znaku /** (pojedyncza gwiazdka), a na początku każdego wiersza powinien znajdować się znak *. Znak */ powinien znajdować się w ostatnim wierszu samodzielnie (gwiazdki powinny być wyrównane). Przykład:
    /*
     * Copyright (C) 2017 The Android Open Source Project
     * ...
     */
    
    /*
     * Changelog:
     * ...
     */

Komentarze do plików

Każdy plik powinien zaczynać się od odpowiedniej informacji o licencji. W przypadku podstawowych interfejsów HAL powinna to być licencja Apache AOSP w development/docs/copyright-templates/c.txt. Pamiętaj, aby zaktualizować rok i użyć komentarzy wielowierszowych w stylu /* */, jak opisano powyżej.

Opcjonalnie możesz umieścić pusty wiersz po informacji o licencji, a następnie informacje o zmianach lub wersjach. Używaj wielowierszowych komentarzy w stylu /* */, jak opisano powyżej. Umieść pusty wiersz po dzienniku zmian, a następnie dodaj deklarację pakietu.

Komentarze TODO

Elementy TODO powinny zawierać ciąg tekstowy TODO pisany wielkimi literami, a po nim dwukropek. Przykład:

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

Komentarze TODO są dozwolone tylko podczas tworzenia. Nie mogą występować w opublikowanych interfejsach.

Komentarze do interfejsu i funkcji (docstringi)

Używaj /** */ w przypadku wielowierszowych i jednowierszowych ciągów dokumentujących. Nie używaj // w ciągach dokumentujących.

Ciągi dokumentujące interfejsy powinny opisywać ogólne mechanizmy interfejsu, uzasadnienie projektu, przeznaczenie itp. Ciągi dokumentujące funkcje powinny być specyficzne dla funkcji (dokumentacja na poziomie pakietu znajduje się w pliku README w katalogu pakietu).

/**
 * 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();
};

Musisz dodać @param i @return dla każdego parametru lub zwracanej wartości:

  • @param musi zostać dodany do każdego parametru. Po nim powinna następować nazwa parametru, a potem ciąg dokumentujący.
  • @return musi być dodany do każdej wartości zwracanej. Po nim powinna nastąpić nazwa zwracanej wartości, a potem ciąg dokumentujący.

Przykład:

/**
 * 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);

Reguły formatowania

Ogólne reguły formatowania:

  • Długość wiersza Każdy wiersz tekstu powinien mieć maksymalnie 100 kolumn.
  • Białe znaki Brak spacji na końcu wierszy; puste wiersze nie mogą zawierać spacji.
  • Spacje a tabulatory Używaj tylko spacji.
  • Rozmiar wcięcia Używaj 4 spacji w blokach i 8 spacji w zawijaniu wierszy.
  • Wzmocnienie Z wyjątkiem wartości adnotacji, otwierający nawias klamrowy znajduje się w tym samym wierszu co poprzedzający kod, ale zamykający nawias klamrowy i następujący po nim średnik zajmują cały wiersz. Przykład:
    interface INfc {
        close();
    };

Deklaracja dotycząca pakietu

Deklaracja pakietu powinna znajdować się u góry pliku po informacji o licencji, zajmować cały wiersz i nie powinna być wcięta. Pakiety są deklarowane w tym formacie (informacje o formatowaniu nazw znajdziesz w sekcji Nazwy pakietów):

package PACKAGE-NAME;

Przykład:

package android.hardware.nfc@1.0;

Deklaracje funkcji

Nazwa funkcji, parametry, generates i wartości zwracane powinny mieścić się w jednym wierszu. Przykład:

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

Jeśli nie mieszczą się w tym samym wierszu, spróbuj umieścić parametry i wartości zwracane na tym samym poziomie wcięcia i wyróżnić je znakiem generate, aby ułatwić czytelnikowi szybkie rozpoznanie parametrów i wartości zwracanych. Przykład:

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);
}

Dodatkowe informacje:

  • Nawias otwierający zawsze znajduje się w tym samym wierszu co nazwa funkcji.
  • Między nazwą funkcji a nawiasem otwierającym nie może być spacji.
  • Między nawiasami a parametrami nie może być spacji, z wyjątkiem sytuacji, gdy między nimi występują znaki końca wiersza.
  • Jeśli znak generates znajduje się w tym samym wierszu co poprzedni nawias zamykający, użyj spacji przed nim. Jeśli znak generates znajduje się w tym samym wierszu co następny nawias otwierający, dodaj po nim spację.
  • Wyrównaj wszystkie parametry i wartości zwracane (jeśli to możliwe).
  • Domyślne wcięcie to 4 spacje.
  • Zawijane parametry są wyrównywane do pierwszych parametrów w poprzednim wierszu, w przeciwnym razie mają wcięcie o 8 spacji.

Adnotacje

W przypadku adnotacji użyj tego formatu:

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

Sortuj adnotacje w kolejności alfabetycznej i używaj spacji wokół znaków równości. Przykład:

@callflow(key = value)
@entry
@exit

Upewnij się, że adnotacja zajmuje cały wiersz. Przykłady:

/* Good */
@entry
@exit

/* Bad */
@entry @exit

Jeśli adnotacje nie mieszczą się w jednym wierszu, wstaw wcięcie o 8 spacji. Przykład:

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

Jeśli cała tablica wartości nie mieści się w jednym wierszu, wstaw znaki końca wiersza po otwierających nawiasach klamrowych { i po każdym przecinku w tablicy. Umieść nawias zamykający bezpośrednio po ostatniej wartości. Jeśli jest tylko jedna wartość, nie umieszczaj nawiasów klamrowych.

Jeśli cała tablica wartości mieści się w tym samym wierszu, nie używaj spacji po otwierającym nawiasie klamrowym ani przed zamykającym nawiasem klamrowym, a po każdym przecinku używaj jednej spacji. Przykłady:

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

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

Między adnotacjami a deklaracją funkcji NIE może być pustych wierszy. Przykłady:

/* Good */
@entry
foo();

/* Bad */
@entry

foo();

Deklaracje wyliczeń

W przypadku deklaracji wyliczeń obowiązują te reguły:

  • Jeśli deklaracje wyliczeń są udostępniane innemu pakietowi, umieść je w types.hal zamiast osadzać w interfejsie.
  • Użyj spacji przed i po dwukropku oraz spacji po typie bazowym przed nawiasem klamrowym otwierającym.
  • Ostatnia wartość wyliczenia może nie mieć dodatkowego przecinka.

Deklaracje struktur

W przypadku deklaracji struktur obowiązują te reguły:

  • Jeśli deklaracje struktur są udostępniane innemu pakietowi, umieść je w types.hal zamiast osadzać w interfejsie.
  • Przed nawiasem otwierającym po nazwie typu struktury wstaw spację.
  • Wyrównaj nazwy pól (opcjonalnie). Przykład:
    struct MyStruct {
        vec<uint8_t>   data;
        int32_t        someInt;
    }

Deklaracje tablic

Nie wstawiaj spacji między tymi elementami:

  • Typ elementu i nawias kwadratowy otwierający.
  • Otwierający nawias kwadratowy i rozmiar tablicy.
  • Rozmiar tablicy i nawias kwadratowy zamykający.
  • Zamknij nawias kwadratowy i otwórz kolejny, jeśli istnieje więcej niż jeden wymiar.

Przykłady:

/* Good */
int32_t[5] array;

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

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

Wektory

Nie wstawiaj spacji między tymi elementami:

  • vec i otwierający nawias trójkątny.
  • Otwierający nawias trójkątny i typ elementu (Wyjątek: typ elementu to też a vec).
  • Typ elementu i zamknięcie nawiasu kątowego (wyjątek: typ elementu jest też vec).

Przykłady:

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