Przewodnik po stylu kodu

Zadbaj o dobrą organizację dzięki kolekcji Zapisuj i kategoryzuj treści zgodnie ze swoimi preferencjami.

Styl kodu HIDL przypomina kod C++ w strukturze systemu Android, z wcięciami na 4 spacje i nazwami plików z mieszanymi wielkościami liter. Deklaracje pakietów, importy i dokumenty są podobne do tych w Javie, z niewielkimi modyfikacjami.

Poniższe przykłady dla IFoo.hal i types.hal ilustrują style kodu HIDL i udostępniają szybkie łącza do szczegółów dotyczących każdego stylu ( 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 być opisowe; unikaj nadmiernego skrótu. Traktuj akronimy jako 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 powinno być jednym małym słowem opisującym 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 taką samą wersją (major.minor), jak opisano w sekcji Wersje .
  • I INTERFACE_X powinien być nazwą interfejsu z 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ą mieć następujący format w pełni kwalifikowanej nazwy (FQN) (nazywany PACKAGE-NAME ):

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

Gdzie:

  • PACKAGE to pakiet, który mapuje do ROOT-DIRECTORY . W szczególności PACKAGE to:
    • android.hardware dla podstawowych pakietów HIDL (mapowanie do 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, powinny być użyte jako podmoduły lub zapisane w snake_case .
  • 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 powinny być jedną liczbą całkowitą. HIDL używa semantycznych reguł wersjonowania .

Import

Import ma jeden z trzech następujących formatów:

  • Import całej paczki: import PACKAGE-NAME ;
  • Import częściowy: import PACKAGE-NAME :: UDT ; (lub, jeśli importowany typ znajduje się w tym samym pakiecie, import UDT ;
  • Importy samych typów: import PACKAGE-NAME ::types;

PACKAGE-NAME zgodna z formatem w Nazwy pakietów . Plik types.hal bieżącego pakietu (jeśli istnieje) jest importowany automatycznie (nie importuj go jawnie).

W pełni kwalifikowane nazwy (FQN)

Używaj w pełni kwalifikowanych nazw dla importu typu zdefiniowanego przez użytkownika 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 , odnieś się do powyższego interfejsu jako INfcClientCallback . W przeciwnym razie używaj tylko w pełni kwalifikowanej nazwy.

Grupowanie i zamawianie importów

Użyj pustego wiersza po deklaracji paczki (przed importem). Każdy import powinien zajmować jedną linię i nie powinien być wcięty. Importy grupowe 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 sprzedawca powinien być grupą.
    • Zamawiaj dostawców alfabetycznie.
  3. Importy z innych interfejsów w tym samym pakiecie (użyj prostych nazw).

Użyj pustej linii mię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 . Interfejs o nazwie IFoo musi być zdefiniowany w pliku IFoo.hal . Ten plik może zawierać definicje tylko dla interfejsu IFoo (interfejs I NAME powinien być 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 struktury/związków

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

struct FooReply {
    vec<uint8_t> replyData;
}

Wpisz nazwy

Nazwy typów odwołują się do definicji struktur/unii, definicji typu enum i typedef . Dla tych nazw użyj UpperCamelCase / PascalCase . Przykłady:

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

Wartości wyliczeniowe

Wartości wyliczenia powinny być UPPER_CASE_WITH_UNDERSCORES . Podczas przekazywania wartości wyliczenia jako argumentów funkcji i zwracania ich jako zwracanych funkcji użyj rzeczywistego typu wyliczenia (nie podstawowego typu liczby całkowitej). 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: Podstawowy typ typu wyliczenia jest jawnie zadeklarowany po dwukropku. Ponieważ nie jest zależny od kompilatora, użycie rzeczywistego typu wyliczenia jest jaśniejsze.

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

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

Wewnątrz w pełni kwalifikowanej nazwy 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

Dla komentarza jednowierszowego // , /* */ i /** */ są poprawne.

// This is a single line comment
/* This is also single line comment */
/** This is documentation comment */
  • Użyj /* */ dla komentarzy. Chociaż HIDL obsługuje // dla komentarzy, są one odradzane, ponieważ nie pojawiają się w generowanych danych wyjściowych.
  • Użyj /** */ dla wygenerowanej dokumentacji. Można je zastosować tylko do deklaracji typu, metody, pola i wartości wyliczenia. 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,
        ...
    }
    
  • Rozpocznij komentarze wielowierszowe od /** w osobnym wierszu. Użyj * na początku każdej linii. Zakończ komentarz znakiem */ w osobnym wierszu, wyrównując gwiazdki. Przykład:
    /**
     * My multi-line
     * comment
     */
    
  • Powiadomienie o licencjonowaniu i dzienniki zmian powinny rozpoczynać nową linię od /* (pojedyncza gwiazdka), używać * na początku każdej linii i umieszczać */ na ostatniej linii, wszystkie osobno (gwiazdki powinny się wyrównać). Przykład:
    /*
     * Copyright (C) 2017 The Android Open Source Project
     * ...
     */
    
    /*
     * Changelog:
     * ...
     */
    

Komentarze do plików

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

Możesz opcjonalnie umieścić pustą linię po powiadomieniu o licencji, po której znajduje się dziennik zmian/informacje o wersji. Użyj /* */ stylu komentarzy wieloliniowych, jak wyjaśniono powyżej, umieść pustą linię po dzienniku zmian, a następnie podaj deklarację pakietu.

TODO komentarze

TODO powinny zawierać ciąg TODO we wszystkich wersalikach, po którym następuje dwukropek. Przykład:

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

Komentarze TODO są dozwolone tylko podczas tworzenia; nie mogą istnieć w opublikowanych interfejsach.

Komentarze interfejsu/funkcji (docstrings)

Użyj /** */ dla wieloliniowych i jednowierszowych łańcuchów dokumentacyjnych. Nie używaj // dla dokumentów.

Dokumenty dla interfejsów powinny opisywać ogólne mechanizmy interfejsu, uzasadnienie projektu, cel, itp. Dokumenty dla funkcji 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 s i @return s dla każdego parametru/wartości zwracanej:

  • @param należy dodać dla każdego parametru. Po nim powinna następować nazwa parametru, a następnie docstring.
  • @return należy dodać dla każdej zwracanej wartości. Po nim powinna następować nazwa zwracanej wartości, a następnie docstring.

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.
  • Spacje . Brak końcowych spacji na liniach; puste wiersze nie mogą zawierać spacji.
  • Spacje a tabulatory . Używaj tylko spacji.
  • Rozmiar wcięcia . Użyj 4 spacji na bloki i 8 spacji na zawijanie linii
  • Stężenie . Z wyjątkiem wartości adnotacji , otwarty nawias klamrowy znajduje się w tym samym wierszu co poprzedni kod, ale zamknięty nawias klamrowy, a następujący średnik zajmuje cały wiersz. Przykład:
    interface INfc {
        close();
    };
    

Deklaracja paczki

Deklaracja pakietu powinna znajdować się na początku pliku za informacją o licencji, powinna zajmować całą linię i nie powinna być wcięta. Pakiety są deklarowane przy użyciu następującego formatu (formatowanie nazw, patrz 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 tym samym wierszu, spróbuj umieścić parametry i zwracane wartości na tym samym poziomie wcięcia i rozróżnij generate , aby ułatwić czytelnikowi szybkie zobaczenie parametrów i zwracanych 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 zawsze znajduje się w tym samym wierszu co nazwa funkcji.
  • Brak spacji między nazwą funkcji a otwartym nawiasem.
  • Brak spacji między nawiasami i parametrami, z wyjątkiem sytuacji, gdy między nimi znajdują się znaki końca linii.
  • 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 otwarty nawias, po nim należy umieścić spację.
  • Wyrównaj wszystkie parametry i zwróć wartości (jeśli to możliwe).
  • Wcięcie domyślne to 4 spacje.
  • Opakowane parametry są wyrównywane do pierwszych parametrów w poprzednim wierszu, w przeciwnym razie mają wcięcie z 8 miejscami.

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śli adnotacje nie mieszczą się w tym samym wierszu, dodaj wcięcie 8 spacjami. 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, wstaw podziały wierszy po otwartych nawiasach klamrowych { i po każdym przecinku wewnątrz tablicy. 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 może zmieścić się w tym samym wierszu, nie używaj spacji po otwartych nawiasach klamrowych i przed zamknięciem nawiasów klamrowych i użyj jednej spacji po każdym przecinku. 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 wyliczenia

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

  • Jeśli deklaracje enum są współdzielone z innym pakietem, umieść deklaracje w types.hal zamiast osadzania wewnątrz interfejsu.
  • Użyj spacji przed i po dwukropku oraz spacji po typie podstawowym przed otwartym nawiasem klamrowym.
  • Ostatnia wartość wyliczenia może mieć dodatkowy przecinek lub nie.

Deklaracje struktury

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

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

Deklaracje tablicy

Nie umieszczaj spacji między następującymi elementami:

  • Typ elementu i otwarty nawias kwadratowy.
  • Otwórz nawias kwadratowy i rozmiar tablicy.
  • Rozmiar tablicy i zamknij 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 umieszczaj spacji między następującymi elementami:

  • vec i otwarty wspornik kątowy.
  • Otwarty nawias kątowy i typ elementu ( Wyjątek: typ elementu to również vec ).
  • Typ elementu i nawias kątowy zamykający ( wyjątek: typ elementu to również 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;