Google Style Guide en C ++. Partie 10

Partie 1. Introduction
...
Partie 9. Commentaires
Partie 10. Formatage
...


Cet article est une traduction d'une partie du guide de style Google en C ++ en russe.
Article original (fork sur github), traduction mise à jour .

Mise en page


Le style de codage et de formatage est arbitraire, mais le projet est beaucoup plus facile à gérer si tout le monde suit le même style. Bien que quelqu'un puisse être en désaccord avec toutes les règles (ou utiliser ce à quoi elles sont habituées), il est très important que tout le monde suive les mêmes règles afin de lire et comprendre facilement le code de quelqu'un d'autre.
Pour un formatage correct, nous avons créé un fichier de paramètres pour emacs .

Longueur de la ligne


Il est conseillé de limiter la longueur des lignes de code à 80 caractères.
Cette règle est un peu controversée, mais la majeure partie du code existant adhère à ce principe, et nous le soutenons également.

Pour les
adeptes de la règle, ils disent que des lignes plus longues ne sont pas nécessaires, et ajuster constamment la taille des fenêtres est fastidieux. De plus, certains placent les fenêtres avec du code côte à côte et ne peuvent pas augmenter arbitrairement la largeur des fenêtres. Dans le même temps, une largeur de 80 caractères est un standard historique, pourquoi le changer? ..

Contre L'

autre côté prétend que les longues lignes peuvent améliorer la lisibilité du code. 80 caractères est une relique du mainframe des années 60. Les écrans modernes peuvent très bien afficher des lignes plus longues.

Un verdict de

80 caractères est le maximum.

Une chaîne peut dépasser une limite de 80 caractères si:

  • . , URL-, 80 .
  • /, 80 . , .
  • include.
  • using

-ASCII


Les caractères non ASCII doivent être utilisés aussi rarement que possible, le codage doit être UTF-8.
Vous n'avez pas à coder en dur les chaînes à afficher à l'utilisateur (même en anglais), les caractères non ASCII doivent donc être rares. Cependant, dans certains cas, il est permis d'inclure de tels mots dans le code. Par exemple, si le code analyse les fichiers de données (avec un codage non anglais), il est possible d'inclure des mots délimiteurs nationaux dans le code. Dans un cas plus général, le code de test unitaire peut contenir des chaînes nationales. Dans ces cas, le codage UTF-8 doit être utilisé, comme il est compris par la plupart des utilitaires (qui ne comprennent pas seulement ASCII).

Hex est également valide, surtout s'il améliore la lisibilité. Par exemple, "\ xEF \ xBB \ xBF" ou u8 "\ uFEFF"- Un espace inextricable de longueur nulle en Unicode, et qui ne doit pas être affiché dans le texte UTF-8 correct.

Utilisez le préfixe u8 pour que les littéraux comme \ uXXXX soient encodés en UTF-8. Ne l'utilisez pas pour les lignes contenant des caractères non ASCII déjà encodés en UTF-8 - vous pouvez obtenir un texte maladroit si le compilateur ne reconnaît pas le code source comme UTF-8.

Evitez d'utiliser les caractères C ++ 11 char16_t et char32_t car ils sont nécessaires pour les lignes non UTF-8. Pour les mêmes raisons, n'utilisez pas wchar_t (sauf lorsque vous travaillez avec l'API Windows à l'aide de wchar_t ).

Espaces vs onglets


Utilisez uniquement des espaces pour l'indentation. 2 espaces pour un tiret.
Nous utilisons des espaces pour l'indentation. N'utilisez pas d'onglets dans votre code - configurez votre éditeur pour insérer des espaces lorsque vous appuyez sur Tab.

Déclarations et définitions des fonctions


Essayez de placer le type de la valeur de retour, le nom de la fonction et ses paramètres sur une seule ligne (si tout convient). Divisez une liste de paramètres trop longtemps en lignes, tout comme les arguments d'un appel de fonction.

Un exemple de la conception de fonction correcte:

ReturnType ClassName::FunctionName(Type par_name1, Type par_name2) {
  DoSomething();
  ...
}

Si une ligne ne suffit pas:

ReturnType ClassName::ReallyLongFunctionName(Type par_name1, Type par_name2,
                                             Type par_name3) {
  DoSomething();
  ...
}

ou, si le premier paramètre ne correspond pas non plus:

ReturnType LongClassName::ReallyReallyReallyLongFunctionName(
    Type par_name1,  //  4 
    Type par_name2,
    Type par_name3) {
  DoSomething();  //  2 
  ...
}

Quelques notes:

  • Choisissez de bons noms pour les options.
  • Vous pouvez omettre le nom du paramètre s'il n'est pas utilisé dans la définition de fonction.
  • , , . .
  • .
  • .
  • .
  • . .
  • , , .
  • .
  • .
  • — 2 .
  • Lors du transfert de paramètres vers une autre ligne, indenter 4 espaces.

Vous pouvez omettre le nom des paramètres inutilisés si cela est évident dans le contexte:

class Foo {
 public:
  Foo(const Foo&) = delete;
  Foo& operator=(const Foo&) = delete;
};

Les paramètres inutilisés avec un contexte non évident doivent être commentés dans la définition de la fonction:

class Shape {
 public:
  virtual void Rotate(double radians) = 0;
};

class Circle : public Shape {
 public:
  void Rotate(double radians) override;
};

void Circle::Rotate(double /*radians*/) {}

//   -  -     ,
//    .
void Circle::Rotate(double) {}

Essayez d'utiliser des attributs et des macros au début d'une définition de publicité ou de fonction,
jusqu'au type de la valeur de retour:
ABSL_MUST_USE_RESULT bool IsOk();

Lambdas


Formatez les paramètres et le corps de l'expression de la même manière qu'une fonction régulière, la liste des variables capturées est comme une liste normale.

Pour capturer des variables par référence, ne mettez pas d'espace entre l'esperluette (&) et le nom de la variable.

int x = 0;
auto x_plus_n = [&x](int n) -> int { return x + n; }

Les lambdas courts peuvent être utilisés directement comme argument d'une fonction.

std::set<int> blacklist = {7, 8, 9};
std::vector<int> digits = {3, 9, 1, 8, 4, 7, 1};
digits.erase(std::remove_if(digits.begin(), digits.end(), [&blacklist](int i) {
               return blacklist.find(i) != blacklist.end();
             }),
             digits.end());

Nombres à virgule flottante


Les nombres à virgule flottante doivent toujours être avec un point décimal et des nombres de chaque côté (même dans le cas de la notation exponentielle). Cette approche améliorera la lisibilité: tous les nombres à virgule flottante seront dans le même format, vous ne le confondrez pas avec un entier et les caractères E e de la notation exponentielle ne peuvent pas être pris pour des chiffres hexadécimaux. N'oubliez pas qu'un nombre en notation exponentielle n'est pas un entier.

float f = 1.f;
long double ld = -.5L;
double d = 1248e6;

float f = 1.0f;
float f2 = 1;   //  
long double ld = -0.5L;
double d = 1248.0e6;

Appel de fonction


Vous pouvez soit écrire l'appel de fonction entier sur une seule ligne, soit placer des arguments sur une nouvelle ligne. Et le retrait peut être soit sur le premier argument, soit 4 espaces. Essayez de minimiser le nombre de lignes, placez quelques arguments sur chaque ligne.

Format d'appel de fonction:

bool result = DoSomething(argument1, argument2, argument3);

Si les arguments ne tiennent pas sur une seule ligne, nous les divisons en plusieurs lignes et chaque ligne suivante est alignée avec le premier argument. N'ajoutez pas d'espaces entre parenthèses et arguments:

bool result = DoSomething(averyveryveryverylongargument1,
                          argument2, argument3);


Il est permis de placer des arguments sur plusieurs lignes avec un retrait de 4 espaces:
if (...) {
  ...
  ...
  if (...) {
    bool result = DoSomething(
        argument1, argument2,  //  4 
        argument3, argument4);
    ...
  }

Essayez de placer plusieurs arguments par ligne, en réduisant le nombre de lignes par appel de fonction (si cela ne nuit pas à la lisibilité). Certaines personnes pensent que le formatage strictement sur un seul argument par ligne est plus lisible et facilite la modification des arguments. Cependant, nous nous concentrons principalement sur les lecteurs de code (et non sur l'édition), nous proposons donc un certain nombre d'approches pour améliorer la lisibilité.

Si plusieurs arguments sur la même ligne dégradent la lisibilité (en raison de la complexité ou de la complexité des expressions), essayez de créer des variables «parlantes» pour les arguments:

int my_heuristic = scores[x] * y + bases[x];
bool result = DoSomething(my_heuristic, x, y, z);

Ou placez l'argument complexe sur une ligne distincte et ajoutez un commentaire explicatif:

bool result = DoSomething(scores[x] * y + bases[x],  //  
                          x, y, z);

Si l'appel de fonction contient encore des arguments qu'il est souhaitable de placer sur une ligne distincte - placez-le. Une solution doit être basée sur l'amélioration de la lisibilité du code.

Les arguments forment parfois une structure. Dans ce cas, formatez les arguments selon la structure requise:

//     3x3
my_widget.Transform(x1, x2, x3,
                    y1, y2, y3,
                    z1, z2, z3);


Formatage d'une liste d'initialisation


Formatez la liste d'initialisation de la même manière qu'un appel de fonction.

Si la liste entre parenthèses suit le nom (par exemple, le nom d'un type ou d'une variable), formatez {} comme s'il s'agissait d'un appel de fonction portant ce nom. Même s'il n'y a pas de nom, considérez qu'il est, seulement vide.

//      .
return {foo, bar};
functioncall({foo, bar});
std::pair<int, int> p{foo, bar};

//     .
SomeFunction(
    {"assume a zero-length name before {"},
    some_other_function_parameter);
SomeType variable{
    some, other, values,
    {"assume a zero-length name before {"},
    SomeOtherType{
        "Very long string requiring the surrounding breaks.",
        some, other values},
    SomeOtherType{"Slightly shorter string",
                  some, other, values}};
SomeType variable{
    "This is too long to fit all in one line"};
MyType m = {  // Here, you could also break before {.
    superlongvariablename1,
    superlongvariablename2,
    {short, interior, list},
    {interiorwrappinglist,
     interiorwrappinglist2}};

Conditions


Essayez de ne pas insérer d'espaces à l'intérieur des supports. Placez if et else sur des lignes différentes.

Il existe deux approches pour mettre en forme les conditions. L'un permet des espaces entre parenthèses et une condition, l'autre ne le permet pas.

L'option préférée sans espaces. Une autre option est également valable, mais soyez cohérent . Si vous modifiez le code existant, utilisez le format qui est déjà dans le code. Si vous écrivez du nouveau code, utilisez le format comme les fichiers situés dans le même répertoire ou utilisez le format du projet. En cas de doute, n'ajoutez pas d'espaces.

if (condition) {  //    
  ...  //  2 
} else if (...) {  // 'else'      
  ...
} else {
  ...
}

Si vous utilisez un format d'espace:

if ( condition ) {  //   
  ...  //  2 
} else {  // 'else'      
  ...
}

Notez que dans tous les cas, il doit y avoir un espace entre if et le support d'ouverture. Vous avez également besoin d'un espace entre le crochet de fermeture et le crochet (s'il y en a un).

if(condition) {   //  -    'if'
if (condition){   //  -    {
if(condition){    //  

if (condition) {  //   -     'if'   {

Des conditions courtes peuvent être écrites sur une seule ligne si cela améliore la lisibilité. Utilisez cette option uniquement si la ligne est courte et que la condition ne contient pas de section else .

if (x == kFoo) return new Foo();
if (x == kBar) return new Bar();

N'utilisez pas la version raccourcie s'il y a une section else :

//  -    ,   'else'
if (x) DoThis();
else DoThat();

Habituellement, les appareils orthopédiques ne sont pas nécessaires pour une condition courte, mais ils sont acceptables. Les conditions compliquées ou le code sont également mieux lus avec des accolades. Il est souvent nécessaire que tout se soit avec des parenthèses.

if (condition)
  DoSomething();  //  2 

if (condition) {
  DoSomething();  //  2 
}

Et si une partie de la condition utilise des accolades, émettez également la seconde avec elles:

//  -    'if',  'else' - 
if (condition) {
  foo;
} else
  bar;

//  -    'else',  'if' - 
if (condition)
  foo;
else {
  bar;
}


//  -     'if'   'else'
if (condition) {
  foo;
} else {
  bar;
}

Boucles et interrupteurs


La construction du commutateur peut utiliser des parenthèses pour les blocs. Décrire les transitions non triviales entre les options. Les crochets sont facultatifs pour les boucles d'expression unique. Une boucle vide doit utiliser un corps vide entre crochets ou continuer .

Les blocs de boîtier dans le commutateur peuvent être avec des accolades bouclés, ou sans eux (de votre choix). Si des parenthèses sont utilisées, utilisez le format décrit ci-dessous.

Il est recommandé de basculer vers la section par défaut du commutateur . Cela n'est pas nécessaire lors de l'utilisation d'une énumération et le compilateur peut émettre un avertissement si toutes les valeurs ne sont pas traitées. Si la section par défaut ne doit pas être exécutée, configurez-la comme une erreur. Par exemple:

switch (var) {
  case 0: {  //  2 
    ...      //  4 
    break;
  }
  case 1: {
    ...
    break;
  }
  default: {
    assert(false);
  }
}

La transition d'une étiquette à la suivante doit être marquée avec la macro ABSL_FALLTHROUGH_INTENDED; (défini dans absl / base / macros.h ).
Placez ABSL_FALLTHROUGH_INTENDED; au point où la transition sera. Une exception à cette règle est les étiquettes consécutives sans code, dans ce cas, rien ne doit être marqué.

switch (x) {
  case 41:  //  
  case 43:
    if (dont_be_picky) {
      //    ( )    
      ABSL_FALLTHROUGH_INTENDED;
    } else {
      CloseButNoCigar();
      break;
    }
  case 42:
    DoSomethingSpecial();
    ABSL_FALLTHROUGH_INTENDED;
  default:
    DoSomethingGeneric();
    break;
}

Les supports sont facultatifs pour les boucles à opération unique.

for (int i = 0; i < kSomeNumber; ++i)
  printf("I love you\n");

for (int i = 0; i < kSomeNumber; ++i) {
  printf("I take it back\n");
}

Une boucle vide doit être stylisée soit comme une paire de crochets, soit comme continuer sans crochets. N'utilisez pas un seul point-virgule.

while (condition) {
  //    false
}
for (int i = 0; i < kSomeNumber; ++i) {}  // .      -   
while (condition) continue;  //  - continue     

while (condition);  //  -     do/while

Pointeurs et liens


Environ '.' et '->' ne mettent pas d'espaces. L'opérateur de déréférencement ou de capture doit être sans espace.

Voici des exemples de mise en forme correcte des expressions avec des pointeurs et des liens:

x = *p;
p = &x;
x = r.y;
x = r->y;

Remarque:

  • "." et '->' sont utilisés sans espaces.
  • Les opérateurs * ou & ne sont pas séparés par des espaces.

Lorsque vous déclarez une variable ou un argument, vous pouvez placer '*' à la fois sur le type et sur le nom:

// ,   *, &
char *c;
const std::string &str;

// ,   *, &
char* c;
const std::string& str;

Essayez d'utiliser un seul style dans le fichier de code; lors de la modification d'un fichier existant, utilisez la mise en forme utilisée.

Il est autorisé de déclarer plusieurs variables dans une expression. Cependant, n'utilisez pas de déclarations multiples avec des pointeurs ou des liens - cela peut être mal compris.

//  - 
int x, y;

int x, *y;  //  -      &  *
char * c;  //  -     *
const std::string & str;  //  -     &

Expressions logiques


Si l'expression logique est très longue (dépasse la valeur typique), utilisez une approche unique pour diviser l'expression en lignes.

Par exemple, ici lors de l'encapsulation, l'opérateur AND est situé à la fin de la ligne:

if (this_one_thing > this_other_thing &&
    a_third_thing == a_fourth_thing &&
    yet_another && last_one) {
  ...
}

Notez que le code est divisé (selon l'exemple) de sorte que && et l'opérateur AND complètent la ligne. Ce style est plus souvent utilisé avec le code Google, bien que l'emplacement des opérateurs au début de la ligne soit également acceptable. Vous pouvez également ajouter des supports supplémentaires pour améliorer la lisibilité. Notez que l'utilisation d'opérateurs sous forme de ponctuation (tels que && et ~ ) est préférable à l'utilisation d'opérateurs sous la forme des mots et et compl .

Valeurs de retour


Ne mettez pas de simples déclarations de retour entre parenthèses.

Utilisez des parenthèses en retour expr; uniquement si vous les avez utilisés dans une expression de la forme x = expr; .

return result;                  //   -  
//  - .    
return (some_long_condition &&
        another_condition);

return (value);                // . ,      var = (value);
return(result);                // . return -   !

Initialisation des variables et des tableaux


Quoi utiliser: = , () ou
{} est votre choix.

Vous pouvez choisir entre les options = ,
() et {} . Les exemples de code suivants sont corrects:

int x = 3;
int x(3);
int x{3};
std::string name = "Some Name";
std::string name("Some Name");
std::string name{"Some Name"};

Soyez prudent lorsque vous utilisez la liste d'initialisation {...} pour un type qui a un constructeur avec std :: initializer_list .

Le compilateur préférera utiliser le constructeur std :: initializer_list lorsqu'il y a une liste entre accolades . Notez que les accolades vides {} sont un cas spécial et le constructeur par défaut sera appelé (si disponible). Pour utiliser explicitement un constructeur sans std :: initializer_list, utilisez des parenthèses au lieu d'accolades.

std::vector<int> v(100, 1);  //    
std::vector<int> v{100, 1};  //   2- : 100  1


De plus, la construction avec des accolades interdit une série de transformations de types entiers (transformations avec une diminution de la précision). Et vous pouvez obtenir des erreurs de compilation.

int pi(3.14);  // : pi == 3
int pi{3.14};  //  : "" 

Directives du préprocesseur


Le signe # (signe de la directive du préprocesseur) doit être au début de la ligne.

Même si la directive du préprocesseur fait référence au code incorporé, les directives sont écrites depuis le début de la ligne.

//  -    
  if (lopsided_score) {
#if DISASTER_PENDING      //  -    
    DropEverything();
# if NOTIFY               //   # - ,   
    NotifyClient();
# endif
#endif
    BackToNormal();
  }

//  -   
  if (lopsided_score) {
    #if DISASTER_PENDING  // ! "#if"     
    DropEverything();
    #endif                // !     "#endif"
    BackToNormal();
  }

Formatage des classes


Organisez les sections dans l'ordre suivant: public , protégé et privé . L'indentation est un espace.

Le format de base de la classe est décrit ci-dessous (à l'exception des commentaires, voir le commentaire sur la description de la classe):

class MyClass : public OtherClass {
 public:      //  1 
  MyClass();  //  2-  
  explicit MyClass(int var);
  ~MyClass() {}

  void SomeFunction();
  void SomeFunctionThatDoesNothing() {
  }

  void set_some_var(int var) { some_var_ = var; }
  int some_var() const { return some_var_; }

 private:
  bool SomeInternalFunction();

  int some_var_;
  int some_other_var_;
};

Remarques:

  • Le nom de la classe de base est écrit sur la même ligne que le nom de la classe héritée (bien sûr, en tenant compte de la limite de 80 caractères).
  • Mots clés du publics: , protégés ,: et privés: devraient être en retrait d' un espace.
  • Chacun de ces mots clés doit être précédé d'une ligne vierge (à l'exception de la première mention). De plus, dans les petites classes, les lignes vides peuvent être omises.
  • N'ajoutez pas de ligne vide après ces mots clés.
  • La section publique devrait être la première, derrière elle protégée et à la fin la section privée .
  • Voir Procédure de déclaration pour construire des déclarations dans chacune de ces sections.

Listes d'initialisation du constructeur


Les listes d'initialisation du constructeur peuvent être sur une ligne ou sur plusieurs lignes avec une indentation de 4 espaces.

Voici les formats corrects pour les listes d'initialisation:

//    
MyClass::MyClass(int var) : some_var_(var) {
  DoSomething();
}

//          ,
//           
MyClass::MyClass(int var)
    : some_var_(var), some_other_var_(var + 1) {
  DoSomething();
}

//     ,     
//     
MyClass::MyClass(int var)
    : some_var_(var),             //  4 
      some_other_var_(var + 1) {  //   
  DoSomething();
}

//     ,       
MyClass::MyClass(int var)
    : some_var_(var) {}

Formatage des espaces de noms


Le contenu de l'espace de noms est en retrait.

L'espace de noms n'ajoute pas de remplissage. Par exemple:

namespace {

void foo() {  // .   
  ...
}

}  // namespace

Ne pas mettre en retrait dans l'espace de noms:

namespace {

  // .   ,   
  void foo() {
    ...
  }

}  // namespace

Lorsque vous déclarez des espaces de noms imbriqués, placez chaque déclaration sur une ligne distincte.

namespace foo {
namespace bar {

Répartition horizontale


Utilisez des ventilations horizontales, le cas échéant. N'ajoutez jamais d'espaces à la fin d'une ligne.

Principes généraux


void f(bool b) {  //       
  ...
int i = 0;  //       
//            .
//    ,      
int x[] = { 0 };
int x[] = {0};

//        
class Foo : public Bar {
 public:
  //  inline-  
  //     (  )
  Foo(int b) : Bar(), baz_(b) {}  //    
  void Reset() { baz_ = 0; }  //      
  ...

L'ajout d'espaces de délimitation peut interférer avec la fusion de code. Par conséquent: n'ajoutez pas d'espaces de séparation au code existant. Vous pouvez supprimer des espaces si vous avez déjà modifié cette ligne. Ou faites-le comme une opération distincte (il est préférable que personne ne travaille avec ce code).

Cycles et conditions


if (b) {          //        
} else {          //   else
}
while (test) {}   //       
switch (i) {
for (int i = 0; i < 5; ++i) {
//         .   .
//   ,  
switch ( i ) {
if ( test ) {
for ( int i = 0; i < 5; ++i ) {
//         
//          ,   
for ( ; i < 5 ; ++i) {
  ...

//           
for (auto x : counts) {
  ...
}
switch (i) {
  case 1:         //    case  
    ...
  case 2: break;  //    ,   (   )  

Les opérateurs


//     
x = 0;

//      ,
//   /   .
//          
v = w * x + y / z;
v = w*x + y/z;
v = w * (x + z);

//       
x = -5;
++x;
if (x && !y)
  ...

Motifs et moulages


//       (<  >),
//  <,  >(  
std::vector<std::string> x;
y = static_cast<char*>(x);

//        .       
std::vector<char *> x;

Répartition verticale


Minimisez le fractionnement vertical.

C'est plus un principe qu'une règle: n'ajoutez pas de lignes vierges sans besoin particulier. En particulier, ne mettez pas plus de 1-2 lignes vides entre les fonctions, ne démarrez pas la fonction avec une ligne vide, ne terminez pas la fonction avec une ligne vide et essayez d'utiliser moins de lignes vides. Une ligne vide dans un bloc de code devrait fonctionner comme un paragraphe dans un roman: séparer visuellement deux idées.

Le principe de base: plus le code tient sur un seul écran, plus il est facile de comprendre et de suivre la séquence d'exécution. Utilisez la chaîne vide uniquement pour séparer visuellement cette séquence.

Quelques notes utiles sur les lignes vides:

  • Une ligne vide au début ou à la fin d'une fonction n'améliorera pas la lisibilité.
  • Des lignes vides dans la blockchain if-else peuvent améliorer la lisibilité.
  • Une ligne vide devant la ligne de commentaire aide généralement à la lisibilité du code - un nouveau commentaire implique généralement l'achèvement d'une vieille pensée et le début d'une nouvelle idée. Et la ligne vide l'indique clairement.

All Articles