Przewodnik po stylu kodu

Styl kodu HIDL przypomina kod C++ na platformie Androida, zawierający 4 spacje i nazwy plików z różną wielkością liter. Deklaracje pakietów, importy i ciągi dokumentacyjne są podobne do tych w Javie, ale z drobnymi modyfikacjami.

Poniższe przykłady dla IFoo.hal i types.hal ilustrować style kodu HIDL i udostępniać szybkie linki do szczegółowych informacji o każdym z nich (IFooClientCallback.hal, IBar.hal i IBaz.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. unikać stosowanie zbyt krótkich skrótów. Traktuj akronimy jako słowa (na przykład używaj zamiast nich INfc) z INFC).

Struktura katalogów i nazewnictwo plików

Struktura katalogów powinna wyglądać tak:

  • ROOT-DIRECTORY
    • MODULE
      • SUBMODULE (opcjonalny; może być więcej niż 1) poziomu)
        • VERSION
          • Android.mk
          • IINTERFACE_1.hal
          • IINTERFACE_2.hal
          • IINTERFACE_N.hal
          • types.hal (opcjonalnie)

Gdzie:

  • ROOT-DIRECTORY to:
    • hardware/interfaces za podstawowe pakiety HIDL.
    • vendor/VENDOR/interfaces w przypadku pakietów dostawców, gdzie VENDOR odnosi się do dostawcy układów SOC OEM/ODM,
  • Pole MODULE powinno zawierać jedno małe słowo, które opisuje podsystem (np. nfc). Jeśli potrzebne jest więcej niż jedno słowo, użyj Zagnieżdżone: SUBMODULE. Może być więcej niż jeden poziom zagnieżdżania.
  • Wartość VERSION powinna być dokładnie taka sama (duża.podrzędna) zgodnie z opisem 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ą zawierać plik (w Git).

Nazwy pakietów

Nazwy pakietów muszą zawierać tę pełną i jednoznaczną nazwę Format (FQN) (nazywany PACKAGE-NAME):

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

Gdzie:

  • PACKAGE to pakiet, który mapuje na ROOT-DIRECTORY W szczególności PACKAGE to:
    • android.hardware za podstawowe pakiety HIDL (mapowanie na hardware/interfaces).
    • vendor.VENDOR.hardware w przypadku pakietów dostawców, gdzie VENDOR oznacza dostawcę układów SOC, OEM/ODM (mapowanie) do vendor/VENDOR/interfaces).
  • MODULE[.SUBMODULE[.SUBMODULE[…]]]@VERSION mają dokładnie te same nazwy folderów w strukturze opisanej w Struktura katalogu.
  • Nazwy pakietów powinny być zapisane małymi literami. Jeśli zawierają one więcej niż jedno słowo, słowa powinny być używane jako moduły podrzędne lub zapisane w języku snake_case.
  • Spacje nie są dozwolone.

Pełna i jednoznaczna nazwa jest zawsze używana w deklaracjach pakietów.

Wersje

Wersje powinny mieć następujący format:

MAJOR.MINOR

Wersja MAJOR i MINOR powinna być jedną liczba całkowita. HIDL używa semantyki obsługi wersji.

Importy

Import może mieć jeden z trzech następujących formatów:

  • Importy całego pakietu: import PACKAGE-NAME;
  • Importy częściowe: import PACKAGE-NAME::UDT; (lub, jeśli zaimportowano typ jest w tym samym pakiecie,import UDT;
  • Importy tylko z typami: import PACKAGE-NAME::types;

PACKAGE-NAME ma format z Nazwy pakietów. Obecny pakiet Plik types.hal (jeśli istnieje) zostanie zaimportowany automatycznie (nie importuj bezpośrednio).

Pełne i jednoznaczne nazwy

W przypadku importu typu zdefiniowanego przez użytkownika używaj pełnych i jednoznacznych nazw tylko wtedy, gdy jest to konieczne. Pomiń PACKAGE-NAME, jeśli typ importu jest taki sam pakietu SDK. Pełna i jednoznaczna 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 zapoznaj się z powyżej interfejsu jako INfcClientCallback. W przeciwnym razie używaj tylko atrybutu pełną i jednoznaczną nazwę.

Grupowanie i sortowanie importów

Użyj pustego wiersza po deklaracji pakietu (przed importem). Każdy import powinny zajmować jeden wiersz i nie powinny mieć wcięcia. Importy grup w w kolejności:

  1. Inne pakiety android.hardware (użyj w pełni kwalifikowanych nazw).
  2. Inne pakiety vendor.VENDOR (użyj w pełni kwalifikowanych nazwy).
    • Każdy dostawca powinien być grupą.
    • Porządkuj dostawców alfabetycznie.
  3. Importy z innych interfejsów w tym samym pakiecie (użyj prostych nazw).

Między grupami używaj pustego wiersza. W każdej grupie posortuj importy w kolejności alfabetycznej. 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, po którym następuje znak Nazwa UpperCamelCase/PascalCase. Interfejs o nazwie Pole IFoo musi być zdefiniowane w pliku IFoo.hal. Ten plik może zawierać wyłącznie definicje interfejsu IFoo (interfejs Wartość INAME powinna mieć wartość INAME.hal).

Funkcje

W przypadku nazw funkcji i argumentów oraz zwracających nazwy zmiennych użyj lowerCamelCase Przykład:

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

Nazwy pól typu struct i union

W przypadku nazw pól typu struct lub Union użyj lowerCamelCase. Przykład:

struct FooReply {
    vec<uint8_t> replyData;
}

Wpisz nazwy

Nazwy typów odnoszą się do definicji struktury lub sumy, definicji typów wyliczeniowych oraz typedef W przypadku tych nazw użyj funkcji UpperCamelCase/PascalCase. Przykłady:

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

Wartości typu wyliczeniowego

Wartości typu enum powinny wynosić UPPER_CASE_WITH_UNDERSCORES. Po pomyślnym zakończeniu wartości wyliczeniowych jako argumentów funkcji i zwracających je jako zwroty funkcji, użyj funkcji rzeczywisty typ wyliczenia (nie bazowy typ 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: typ podstawowy to wyliczenie: jawnie zadeklarowanej po dwukropku. Nie jest on zależny od kompilatora, więc użycie tagu typ rzeczywistej wartości wyliczeniowej jest wyraźniejszy.

W przypadku pełnych i jednoznacznych nazw wartości wyliczeniowych używany jest dwukropek między nazwą typu wyliczenia a nazwą wartości wyliczenia:

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

W pełnej i jednoznacznej nazwie nie mogą znajdować się spacje. Użyj w pełni kwalifikowanego tylko wtedy, gdy jest to konieczne. Pomiń niepotrzebne części. Przykład:

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

Komentarze

W przypadku komentarza w jednym wierszu: //, /* */ i /** */ są w porządku.

// This is a single line comment
/* This is also single line comment */
/** This is documentation comment */
  • Używaj konta /* */ do komentowania. HIDL obsługuje komentarze w języku //, odradzamy ich, ponieważ nie pojawiają się w wygenerowanych danych wyjściowych.
  • Aby wygenerować dokumentację, użyj /** */. Można je stosować tylko na deklaracje typu, metody, pól i wartości wyliczeniowych. 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 tworzenie komentarzy wielowierszowych (/**) w osobnym wierszu. Dodaj * na początku każdego wiersza. Zakończ komentarz ciągiem */ w osobnym wierszu i wyrównaj gwiazdki. Przykład:
    /**
     * My multi-line
     * comment
     */
    
  • Powiadomienia dotyczące licencji i dzienniki zmian powinny zaczynać się od nowego wiersza (/*) (pojedynczą gwiazdka), użyj * na początku każdego wiersza, a następnie umieść */ w ostatnim wierszu (gwiazdki powinny być wyrównane). Przykład:
    /*
     * Copyright (C) 2017 The Android Open Source Project
     * ...
     */
    
    /*
     * Changelog:
     * ...
     */
    

Komentarze do plików

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

Opcjonalnie możesz umieścić pusty wiersz po powiadomieniu o licencji, a następnie dziennika zmian lub informacji o wersji. Użyj stylu /* */ wielowierszowych komentarzy (w sposób opisany powyżej) umieść pusty wiersz po dziennik zmian, a następnie deklarację pakietu.

Komentarze TODO

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

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

Komentarze do zadań są dozwolone tylko na etapie tworzenia aplikacji. muszą nie występują w opublikowanych interfejsach.

Komentarze do interfejsu i funkcji (ciągi znaków doc)

/** */ w przypadku wielowierszowych i jednowierszowych ciągów dokumentacyjnych. Nie używaj // na potrzeby ciągów dokumentacyjnych.

Ciągi dokumentów dotyczące interfejsów powinny opisywać ogólne mechanizmy interfejsu, uzasadnienia projektu, przeznaczenia itp. Ciągi znaków dla funkcji powinny być właściwych 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();
};

Do każdej z nich musisz dodać komponenty typu @param i @return parametr/wartość zwrotna:

  • Do każdego parametru należy dodać @param. Powinna być z nazwą parametru i ciągiem docstring.
  • Dla każdej zwracanej wartości należy dodać atrybut @return. it po nim powinien znajdować się nazwa zwracanej wartości, a następnie ciąg znaków 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);

Reguły formatowania

Ogólne reguły formatowania obejmują:

  • Długość wiersza. Każdy wiersz tekstu powinien składać się z maksymalnie 100 kolumn.
  • Odstępy. Bez spacji na końcu wierszy. puste wiersze nie może zawierać odstępów.
  • Pokoje a karty. Używaj tylko spacji.
  • Rozmiar wcięcia. Użyj 4 spacji jako bloków i 8 spacji na zawijanie wierszy
  • Wyprzedzanie. Z wyjątkiem adnotacji , nawias klamrowy otwarty idzie w tym samym wierszu co poprzedzający ale znajduje się w nim nawias klamrowy close, a następujący średnik cały wiersz. Przykład:
    interface INfc {
        close();
    };
    

Deklaracja dotycząca pakietu

Deklaracja pakietu powinna znajdować się na początku pliku po licencji powinien zająć cały wiersz i nie powinien mieć wcięcia. Przesyłki są zadeklarowano w tym formacie (formatowanie nazw znajdziesz w sekcji Nazwy pakietów):

package PACKAGE-NAME;

Przykład:

package android.hardware.nfc@1.0;

Deklaracje funkcji

Nazwa funkcji, parametry oraz generates i zwracane wartości powinny znajdować się w tej samej linii, 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 podać parametry i zwrócić na tym samym poziomie wcięcia i rozróżnić generate, aby pomóc czytelnik może szybko sprawdzić 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 informacje:

  • Otwarte nawiasy zawsze znajdują się w tym samym wierszu co nazwa funkcji.
  • Nie ma spacji między nazwą funkcji a nawiasem otwierającym.
  • Nie ma spacji między nawiasami i parametrami, z wyjątkiem sytuacji, w których wyświetlają się między nimi.
  • Jeśli generates znajduje się w tym samym wierszu co poprzedni zamykający wiersz nawias klamrowy, użyj poprzedzającej spacji. Jeśli generates jest na tym samym poziomie jako następnego otwierającego nawias, po którym następuje spacja.
  • Wyrównaj wszystkie parametry i zwracaj wartości (jeśli to możliwe).
  • Domyślne wcięcie to 4 spacje.
  • Parametry opakowane są wyrównane do pierwszych parametrów w poprzednim wierszu w przeciwnym razie ma wcięcie wynoszące 8 spacji.

Adnotacje

Zastosuj następujący format adnotacji:

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

Posortuj adnotacje w kolejności alfabetycznej, a wokół znaków równości wstaw spacje. Przykład:

@callflow(key = value)
@entry
@exit

Sprawdź, czy adnotacja zajmuje cały wiersz. Przykłady:

/* Good */
@entry
@exit

/* Bad */
@entry @exit

Jeśli adnotacje nie mieszczą się w tym samym wierszu, zrób wcięcie wynoszące 8 spacji. Przykład:

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

Jeśli cała tablica wartości nie może się zmieścić w tym samym wierszu, znaki podziału wiersza umieść po otwierające nawiasy klamrowe { i po każdym przecinku w tablicy. Zamknięcie miejsca nawias umieszczony bezpośrednio po ostatniej wartości. Nie używaj nawiasów, jeśli są: tylko jedną wartość.

Jeśli cała tablica wartości mieści się w tym samym wierszu, nie używaj spacji po nawiasów klamrowych otwartych i przed zamknięciem – po każdym przecinku zostaw jedną spację. Przykłady:

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

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

Między adnotacjami a funkcją NIE może być pustych wierszy tej deklaracji. Przykłady:

/* Good */
@entry
foo();

/* Bad */
@entry

foo();

Deklaracje typu wyliczeniowego

Deklaracja wyliczeniowa powinna być zgodna z tymi regułami:

  • Jeśli deklaracje typu enum są udostępniane innemu pakietowi, umieść deklaracje w types.hal zamiast umieszczania w interfejsie.
  • Użyj spacji przed i po dwukropku oraz spacji po typie bazowym przed otwartym nawiasem klamrowym.
  • Ostatnia wartość wyliczenia może nie zawierać dodatkowego przecinka.

Deklaracje strukturalne

W przypadku deklaracji struct możesz stosować te reguły:

  • Jeśli deklaracje struct są współdzielone z innym pakietem, umieść deklaracje w types.hal zamiast umieszczania w interfejsie.
  • Wstaw spację 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 tablicowe

Nie wstawiaj spacji między:

  • 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, jeśli jest ich więcej niż 1 .

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:

  • vec i otwarty nawias trójkątny.
  • Otwierający nawias kątowy i typ elementu (wyjątek: typ elementu to także vec).
  • Typ elementu i zamykający nawias kątowy (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;