Przewodnik po stylu kodu

Styl kodu HIDL przypomina kod C++ w środowisku Android, z wcięciami z czterema spacjami i nazwami plików o różnej wielkości liter. Deklaracje pakietów, importy i dokumenty są podobne do tych w Javie, z niewielkimi modyfikacjami.

Poniższe przykłady IFoo.hal i types.hal ilustrują style kodu HIDL i zapewniają szybkie łącza do szczegółów każdego stylu (pominięto IFooClientCallback.hal , IBar.hal i 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;
};

Konwencje nazewnictwa

Nazwy funkcji, nazwy zmiennych i nazwy plików powinny mieć charakter opisowy; unikać nadmiernych skrótów. Traktuj akronimy jak słowa (np. użyj INfc zamiast INFC ).

Struktura katalogów i nazewnictwo plików

Struktura katalogów powinna wyglądać następująco:

  • ROOT-DIRECTORY
    • MODULE
      • SUBMODULE (opcjonalnie, może mieć więcej niż jeden poziom)
        • VERSION
          • Android.mk
          • I INTERFACE_1 .hal
          • I INTERFACE_2 .hal
          • I INTERFACE_N .hal
          • types.hal (opcjonalnie)

Gdzie:

  • ROOT-DIRECTORY to:
    • hardware/interfaces dla podstawowych pakietów HIDL.
    • vendor/ VENDOR /interfaces dla pakietów dostawców, gdzie VENDOR odnosi się do dostawcy SoC lub OEM/ODM.
  • MODULE powinien składać się z jednego słowa z małej litery opisującego podsystem (np. nfc ). Jeśli potrzebne jest więcej niż jedno słowo, użyj zagnieżdżonego SUBMODULE . Może istnieć więcej niż jeden poziom zagnieżdżenia.
  • VERSION powinna być dokładnie tą samą wersją (główną.minor), jak opisano w sekcji Wersje .
  • I INTERFACE_X powinna być nazwą interfejsu zawierającą UpperCamelCase / PascalCase (np. INfc ) zgodnie z opisem w Nazwy interfejsów .

Przykład:

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

Uwaga: Wszystkie pliki muszą mieć uprawnienia niewykonywalne (w Git).

Nazwy pakietów

Nazwy pakietów muszą używać następującego formatu w pełni kwalifikowanej nazwy (FQN) (określanego jako PACKAGE-NAME ):

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

Gdzie:

  • PACKAGE to pakiet mapowany na ROOT-DIRECTORY . W szczególności PACKAGE to:
    • android.hardware dla podstawowych pakietów HIDL (mapowanie na hardware/interfaces ).
    • vendor. VENDOR .hardware dla pakietów dostawców, gdzie VENDOR odnosi się do dostawcy SoC lub OEM/ODM (mapowanie do vendor/ VENDOR /interfaces ).
  • MODULE [. SUBMODULE [. SUBMODULE […]]]@ VERSION to dokładnie te same nazwy folderów w strukturze opisanej w Struktura katalogów .
  • Nazwy pakietów powinny być pisane małymi literami. Jeśli mają więcej niż jedno słowo, należy je albo użyć jako podmodułów, albo zapisać w snake_case .
  • Żadne spacje nie są dozwolone.

FQN jest zawsze używany w deklaracjach pakietów.

Wersje

Wersje powinny mieć następujący format:

MAJOR.MINOR

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

Import

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

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

PACKAGE-NAME ma format zgodny z nazwami pakietów . types.hal bieżącego pakietu (jeśli istnieje) jest importowany automatycznie (nie importuj go jawnie).

W pełni kwalifikowane nazwy (FQN)

W przypadku importu typu zdefiniowanego przez użytkownika należy używać pełnych nazw tylko wtedy, gdy jest to konieczne. Pomiń PACKAGE-NAME , jeśli typ importu znajduje się w tym samym pakiecie. FQN nie może zawierać spacji. Przykład w pełni kwalifikowanej nazwy:

android.hardware.nfc@1.0::INfcClientCallback

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

Grupowanie i porządkowanie importów

Użyj pustej linii po deklaracji pakietu (przed importem). Każdy import powinien zajmować jedną linię i nie powinien być wcięty. Grupuj importy w następującej kolejności:

  1. Inne pakiety android.hardware (użyj w pełni kwalifikowanych nazw).
  2. Inny vendor. VENDOR Pakiety vendor. VENDOR (użyj w pełni kwalifikowanych nazw).
    • Każdy dostawca powinien stanowić grupę.
    • Sortuj dostawców alfabetycznie.
  3. Importy z innych interfejsów w tym samym pakiecie (użyj prostych nazw).

Użyj pustej linii pomiędzy grupami. Wewnątrz każdej grupy 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 I , po którym następuje nazwa UpperCamelCase / PascalCase . W pliku IFoo.hal należy zdefiniować interfejs o nazwie IFoo . Plik ten może zawierać definicje tylko dla interfejsu IFoo (interfejs I NAME powinien znajdować się w I NAME .hal ).

Funkcje

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

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

Nazwy pól strukturalnych/związkowych

W przypadku nazw pól struct/union użyj lowerCamelCase . Przykład:

struct FooReply {
    vec<uint8_t> replyData;
}

Wpisz nazwy

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

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

Wartości wyliczeniowe

Wartości wyliczeniowe powinny wynosić UPPER_CASE_WITH_UNDERSCORES . Podczas przekazywania wartości wyliczeniowych jako argumentów funkcji i zwracania ich jako zwrotów funkcji, należy używać 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. Ponieważ nie jest to zależne od kompilatora, użycie rzeczywistego typu wyliczeniowego jest jaśniejsze.

W przypadku w pełni kwalifikowanych nazw wartości wyliczeniowych między nazwą typu wyliczeniowego a nazwą wartości wyliczeniowej używany jest dwukropek :

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

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

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

Uwagi

W przypadku komentarza jednoliniowego // , /* */ i /** */ są w porządku.

// This is a single line comment
/* This is also single line comment */
/** This is documentation comment */
  • Użyj /* */ w komentarzach. Chociaż HIDL obsługuje // komentarze, jest to odradzane, ponieważ nie pojawiają się w generowanych wynikach.
  • Użyj /** */ dla wygenerowanej dokumentacji. Można je zastosować 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 rozpoczynaj znakiem /** w osobnej linii. Użyj * na początku każdej linii. Zakończ komentarz znakiem */ w osobnej linii, wyrównując gwiazdki. Przykład:
    /**
     * My multi-line
     * comment
     */
    
  • Informacje o licencji i dzienniki zmian powinny rozpoczynać nową linię od /* (pojedynczej gwiazdki), używać * na początku każdej linii i umieszczać */ osobno w ostatniej linii (gwiazdki powinny być wyrównane). Przykład:
    /*
     * Copyright (C) 2017 The Android Open Source Project
     * ...
     */
    
    /*
     * Changelog:
     * ...
     */
    

Komentarze do plików

Rozpocznij każdy plik odpowiednią informacją o licencji. W przypadku podstawowych warstw HAL powinna to być licencja AOSP Apache w development/docs/copyright-templates/c.txt . Pamiętaj, aby zaktualizować rok i użyć komentarzy wielowierszowych w stylu /* */ jak wyjaśniono powyżej.

Opcjonalnie możesz umieścić pustą linię po uwadze licencyjnej, po której nastąpi informacja o dzienniku zmian/wersji. Użyj /* */ stylu komentarzy wielowierszowych, jak wyjaśniono powyżej, umieść pustą linię po dzienniku zmian, a następnie postępuj zgodnie z deklaracją pakietu.

TODO komentarze

Treść TODO powinna zawierać ciąg TODO wielkimi literami, po którym następuje dwukropek. Przykład:

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

Komentarze do TODO są dozwolone tylko w trakcie programowania; nie mogą one istnieć w opublikowanych interfejsach.

Komentarze do interfejsu/funkcji (dokumenty)

Użyj /** */ dla ciągów dokumentów wielowierszowych i jednowierszowych. Nie używaj // dla ciągów dokumentów.

Dokumentacja interfejsów powinna opisywać ogólne mechanizmy interfejsu, uzasadnienie projektu, cel itp. Dokumentacja funkcji powinna być specyficzna dla danej funkcji (dokumentacja na poziomie pakietu znajduje się w pliku README w katalogu pakietów).

/**
 * 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 s i @return s dla każdego parametru/wartości zwracanej:

  • Do każdego parametru należy dodać @param . Po nim powinna następować nazwa parametru, a następnie dokument.
  • Do każdej zwracanej wartości należy dodać @return . Po nim powinna następować nazwa zwracanej wartości, a następnie ciąg dokumentów.

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

Formatowanie

Ogólne zasady formatowania obejmują:

  • Długość linii . Każdy wiersz tekstu powinien mieć maksymalnie 100 kolumn.
  • Białe znaki . Brak końcowych białych znaków w liniach; puste linie nie mogą zawierać białych znaków.
  • Spacje a tabulatory . Używaj tylko spacji.
  • Rozmiar wcięcia . Użyj 4 spacji na bloki i 8 spacji na zawijanie linii
  • Usztywnienie . Z wyjątkiem wartości adnotacji , nawias otwierający znajduje się w tym samym wierszu, co poprzedni kod, ale nawias zamykający , a następujący po nim średnik zajmuje całą linię. Przykład:
    interface INfc {
        close();
    };
    

Deklaracja pakietu

Deklaracja pakietu powinna znajdować się na górze pliku, po informacji licencyjnej, powinna zajmować całą linię i nie powinna być wcięta. Pakiety deklaruje się w następującym formacie (formatowanie nazw znajdziesz w Nazwy pakietów ):

package PACKAGE-NAME;

Przykład:

package android.hardware.nfc@1.0;

Deklaracje funkcji

Nazwa funkcji, parametry, generates i zwracane wartości powinny znajdować się w tym samym wierszu, jeśli pasują. Przykład:

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

Jeśli nie mieszczą się w tej samej linii, spróbuj umieścić parametry i zwracane wartości na tym samym poziomie wcięcia i rozróżnij generate , aby pomóc czytelnikowi szybko zobaczyć parametry i zwrócić wartości. 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 Szczegóły:

  • Otwarty nawias znajduje się zawsze w tym samym wierszu, co nazwa funkcji.
  • Żadnych spacji pomiędzy nazwą funkcji a nawiasem otwierającym.
  • Żadnych spacji pomiędzy nawiasami i parametrami, z wyjątkiem sytuacji, gdy pomiędzy nimi znajduje się znak nowego wiersza.
  • Jeśli generates znajduje się w tym samym wierszu, co poprzedni nawias zamykający, użyj poprzedzającej spacji. Jeśli generates znajduje się w tym samym wierszu, co następny nawias otwierający, należy dodać spację.
  • Wyrównaj wszystkie parametry i zwróć wartości (jeśli to możliwe).
  • Domyślne wcięcie to 4 spacje.
  • Opakowane parametry są wyrównywane do pierwszych parametrów w poprzedniej linii, w przeciwnym razie mają wcięcie 8-spacji.

Adnotacje

Użyj następującego formatu adnotacji:

@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łą linię. Przykłady:

/* Good */
@entry
@exit

/* Bad */
@entry @exit

Jeżeli adnotacje nie mieszczą się w tym samym wierszu, należy zastosować wcięcie 8 spacji. Przykład:

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

Jeśli cała tablica wartości nie mieści się w tym samym wierszu, po nawiasach otwartych { i po każdym przecinku w tablicy wstaw podziały wierszy. Umieść nawias zamykający bezpośrednio po ostatniej wartości. Nie umieszczaj nawiasów klamrowych, jeśli jest tylko jedna wartość.

Jeśli cała tablica wartości mieści się w tym samym wierszu, nie używaj spacji po nawiasach otwartych i przed nawiasami zamykającymi, a po każdym przecinku wstaw jedną spację. Przykłady:

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

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

Pomiędzy adnotacjami i deklaracją funkcji NIE mogą znajdować się puste linie. Przykłady:

/* Good */
@entry
foo();

/* Bad */
@entry

foo();

Deklaracje wyliczeniowe

Użyj następujących reguł dla deklaracji wyliczeniowych:

  • Jeśli deklaracje wyliczeniowe są współdzielone z innym pakietem, umieść deklaracje w types.hal zamiast osadzać je w interfejsie.
  • Użyj spacji przed i po dwukropku oraz spacji po typie bazowym przed nawiasem otwierającym.
  • Ostatnia wartość wyliczeniowa może zawierać dodatkowy przecinek lub nie.

Deklaracje struktur

Użyj następujących reguł dla deklaracji struktur:

  • Jeśli deklaracje struktury są współdzielone z innym pakietem, umieść deklaracje w types.hal zamiast osadzać je w interfejsie.
  • Użyj spacji po nazwie typu struktury przed nawiasem otwierającym.
  • Wyrównaj nazwy pól (opcjonalnie). Przykład:
    struct MyStruct {
        vec<uint8_t>   data;
        int32_t        someInt;
    }
    

Deklaracje tablic

Nie wstawiaj spacji pomiędzy następującymi elementami:

  • Typ elementu i otwarty nawias kwadratowy.
  • Otwórz nawias kwadratowy i rozmiar tablicy.
  • Rozmiar tablicy i zamykający nawias kwadratowy.
  • Zamknij nawias kwadratowy i następny otwarty nawias kwadratowy, 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 pomiędzy następującymi elementami:

  • vec i otwarty nawias kątowy.
  • Otwarty nawias ostry i typ elementu ( Wyjątek: typ elementu to także vec ).
  • Typ elementu i nawias ostry ( Wyjątek: typ elementu to także 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;