Guia de estilo do Google em C ++. Parte 10

Parte 1. Introdução
...
Parte 9. Comentários
Parte 10. Formatação
...


Este artigo é uma tradução de parte do guia de estilo do Google em C ++ para o russo.
Artigo original (fork no github), tradução atualizada .

Formatação


O estilo de codificação e formatação é arbitrário, mas o projeto é muito mais fácil de gerenciar se todos seguirem o mesmo estilo. Embora alguém possa discordar de todas as regras (ou usar o que está acostumado), é muito importante que todos sigam as mesmas regras para ler e entender facilmente o código de outra pessoa.
Para formatação correta, criamos um arquivo de configurações para o emacs .

Comprimento da linha


É aconselhável limitar o comprimento das linhas de código a 80 caracteres.
Essa regra é um pouco controversa, mas a maior parte do código existente adere a esse princípio, e também o apoiamos.

Para os
adeptos da regra, eles dizem que linhas mais longas não são necessárias, e ajustar constantemente o tamanho das janelas é cansativo. Além disso, algumas colocam janelas com código próximas umas das outras e não podem aumentar arbitrariamente a largura das janelas. Ao mesmo tempo, uma largura de 80 caracteres é um padrão histórico, por que alterá-lo? ...

Contra O

outro lado afirma que as linhas longas podem melhorar a legibilidade do código. 80 caracteres é uma relíquia do mainframe da década de 1960. Telas modernas podem muito bem mostrar linhas mais longas.

Um veredicto de

80 caracteres é o máximo.

Uma cadeia pode exceder um limite de 80 caracteres se:

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

-ASCII


Caracteres não ASCII devem ser usados ​​o mais raramente possível, a codificação deve ser UTF-8.
Você não precisa codificar seqüências de caracteres para mostrar ao usuário (mesmo em inglês), portanto, caracteres não ASCII devem ser raros. No entanto, em alguns casos, é permitido incluir essas palavras no código. Por exemplo, se o código analisar arquivos de dados (com codificação que não seja em inglês), é possível incluir palavras delimitadoras nacionais no código. Em um caso mais geral, o código do teste de unidade pode conter seqüências nacionais. Nesses casos, a codificação UTF-8 deve ser usada, como é entendido pela maioria dos utilitários (que entendem não apenas ASCII).

Hex também é válido, especialmente se melhora a legibilidade. Por exemplo, "\ xEF \ xBB \ xBF" ou u8 "\ uFEFF"- Um espaço inextricável de tamanho zero em Unicode e que não deve ser exibido no texto UTF-8 correto.

Use o prefixo u8 para que literais como \ uXXXX sejam codificados em UTF-8. Não o utilize para linhas que contenham caracteres não ASCII já codificados em UTF-8 - você pode obter um texto desajeitado se o compilador não reconhecer o código-fonte como UTF-8.

Evitar o uso de C ++ 11 char16_t e char32_t personagens desde eles são necessários para linhas não UTF-8. Pelas mesmas razões, não use wchar_t (exceto ao trabalhar com a API do Windows usando wchar_t ).

Espaços vs. Guias


Use apenas espaços para recuo. 2 espaços para um recuo.
Usamos espaços para recuo. Não use tabulações no seu código - configure seu editor para inserir espaços ao pressionar Tab.

Declarações e definições de funções


Tente colocar o tipo do valor de retorno, o nome da função e seus parâmetros em uma linha (se tudo couber). Divida uma lista de parâmetros por muito tempo em linhas, assim como argumentos em uma chamada de função.

Um exemplo do design de função correto:

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

Se uma linha não for suficiente:

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

ou, se o primeiro parâmetro também não se encaixar:

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

Algumas notas:

  • Escolha bons nomes para as opções.
  • Você pode omitir o nome do parâmetro se ele não for usado na definição da função.
  • , , . .
  • .
  • .
  • .
  • . .
  • , , .
  • .
  • .
  • — 2 .
  • Ao transferir parâmetros para outra linha, indente 4 espaços.

Você pode omitir o nome de parâmetros não utilizados se isso for óbvio no contexto:

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

Parâmetros não utilizados com contexto não óbvio devem ser comentados na definição da função:

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

Tente usar atributos e macros no início de uma definição de anúncio ou função,
até o tipo do valor de retorno:
ABSL_MUST_USE_RESULT bool IsOk();

Lambdas


Formate os parâmetros e o corpo da expressão da mesma maneira que uma função regular; a lista de variáveis ​​capturadas é como uma lista normal.

Para capturar variáveis ​​por referência, não coloque um espaço entre oe comercial (&) e o nome da variável.

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

Lambdas curtas podem ser usadas diretamente como argumento para uma função.

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

Números de ponto flutuante


Os números de ponto flutuante devem sempre estar com um ponto decimal e números em ambos os lados (mesmo no caso de notação exponencial). Essa abordagem melhorará a legibilidade: todos os números de ponto flutuante estarão no mesmo formato, você não o confundirá com um número inteiro, e os caracteres E e da notação exponencial não podem ser utilizados para dígitos hexadecimais. Lembre-se de que um número na notação exponencial não é um número inteiro.

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;

Chamada de função


Escreva a chamada de função inteira em uma linha ou coloque argumentos em uma nova linha. E o recuo pode estar no primeiro argumento ou em 4 espaços. Tente minimizar o número de linhas, coloque alguns argumentos em cada linha.

Formato da chamada de função:

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

Se os argumentos não couberem em uma linha, dividimos-os em várias linhas e cada linha subseqüente será alinhada com o primeiro argumento. Não adicione espaços entre parênteses e argumentos:

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


É permitido colocar argumentos em várias linhas com um recuo de 4 espaços:
if (...) {
  ...
  ...
  if (...) {
    bool result = DoSomething(
        argument1, argument2,  //  4 
        argument3, argument4);
    ...
  }

Tente colocar vários argumentos por linha, reduzindo o número de linhas por chamada de função (se isso não prejudicar a legibilidade). Algumas pessoas pensam que a formatação estrita de um argumento por linha é mais legível e facilita a edição de argumentos. No entanto, focamos principalmente nos leitores de código (não na edição), por isso oferecemos várias abordagens para melhorar a legibilidade.

Se vários argumentos na mesma linha degradam a legibilidade (devido à complexidade ou complexidade das expressões), tente criar variáveis ​​"falantes" para os argumentos:

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

Ou coloque o argumento complexo em uma linha separada e adicione um comentário explicativo:

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

Se a chamada de função ainda tiver argumentos que é desejável colocar em uma linha separada - coloque-a. Uma solução deve ser baseada na melhoria da legibilidade do código.

Os argumentos às vezes formam uma estrutura. Nesse caso, formate os argumentos de acordo com a estrutura necessária:

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


Formatando uma lista de inicialização


Formate a lista de inicialização da mesma maneira que uma chamada de função.

Se a lista entre parênteses seguir o nome (por exemplo, o nome de um tipo ou variável), formate {} como se fosse uma chamada de função com esse nome. Mesmo se não houver nome, considere que ele está vazio.

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

Condições


Tente não inserir espaços no interior dos colchetes. Coloque if e else em linhas diferentes.

Existem duas abordagens para formatar condições. Um permite espaços entre parênteses e uma condição, o outro não.

A opção preferida sem espaços. Outra opção também é válida, mas seja consistente . Se você modificar o código existente, use o formato que já está no código. Se você estiver escrevendo um novo código, use o formato como os arquivos localizados no mesmo diretório ou use o formato do projeto. Se não tiver certeza, não adicione espaços.

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

Se estiver usando um formato de espaço:

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

Observe que, em qualquer caso, deve haver um espaço entre if e o suporte de abertura. Você também precisa de um espaço entre o suporte de fechamento e o suporte encaracolado (se houver).

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

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

Condições curtas podem ser escritas em uma linha se isso melhorar a legibilidade. Use esta opção apenas se a linha for curta e a condição não contiver uma seção else .

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

Não use a versão reduzida se houver uma seção else :

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

Normalmente, os aparelhos não são necessários para uma condição curta, mas são aceitáveis. Condições ou códigos complicados também são melhor lidos com chaves. Muitas vezes, é necessário que qualquer se seja com parênteses.

if (condition)
  DoSomething();  //  2 

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

E se uma parte da condição usa chaves, também emita a segunda com elas:

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

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


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

Loops e interruptores


A construção de switch pode usar parênteses para blocos. Descreva transições não triviais entre opções. Os colchetes são opcionais para loops de expressão única. Um loop vazio deve usar um corpo vazio entre colchetes ou continuar .

Os blocos de caixas no comutador podem ser com chaves, ou sem eles (de sua escolha). Se parênteses forem usados, use o formato descrito abaixo.

É recomendável alternar para a seção padrão no switch . Isso não é necessário ao usar uma enumeração e o compilador pode emitir um aviso se nem todos os valores forem processados. Se a seção padrão não deve ser executada, configure-a como um erro. Por exemplo:

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

A transição de um rótulo para o próximo deve ser marcada com a macro ABSL_FALLTHROUGH_INTENDED; (definido em absl / base / macros.h ).
Coloque ABSL_FALLTHROUGH_INTENDED; no ponto em que a transição será. Uma exceção a essa regra são rótulos consecutivos sem código; nesse caso, nada precisa ser marcado.

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

Os colchetes são opcionais para loops de operação única.

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

Um loop vazio deve ser denominado como par de colchetes ou como continuar sem colchetes. Não use um único ponto e vírgula.

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

while (condition);  //  -     do/while

Ponteiros e links


Por aí '.' e '->' não colocam espaços. O operador de desreferenciamento ou captura deve estar sem espaços.

A seguir, exemplos de formatação adequada de expressões com ponteiros e links:

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

Nota:

  • '.' e '->' são usados ​​sem espaços.
  • Os operadores * ou & não são separados por espaços.

Ao declarar uma variável ou argumento, você pode colocar '*' no tipo e no nome:

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

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

Tente usar um estilo único no arquivo de código; ao modificar um arquivo existente, use a formatação usada.

É permitido declarar várias variáveis ​​em uma expressão. No entanto, não use várias declarações com ponteiros ou links - isso pode ser mal interpretado.

//  - 
int x, y;

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

Expressões lógicas


Se a expressão lógica for muito longa (exceder o valor típico), use uma abordagem única para dividir a expressão em linhas.

Por exemplo, aqui ao envolver o operador AND está localizado no final da linha:

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

Observe que o código é dividido (de acordo com o exemplo) para que && e o operador AND concluam a linha. Esse estilo é usado com mais frequência com o código do Google, embora a localização dos operadores no início da linha também seja aceitável. Além disso, você pode adicionar colchetes adicionais para melhorar a legibilidade. Note-se que utilizando os operadores na forma de pontuação (tal como && e ~ ) é preferível à utilização de operadores, sob a forma de palavras e e compl .

Retornar valores


Não coloque declarações de retorno simples entre parênteses.

Use parênteses em retorno expr; somente se você os tiver usado em uma expressão da forma x = expr; .

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

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

Inicializando variáveis ​​e matrizes


O que usar: = , () ou
{} é sua escolha.

Você pode escolher entre as opções = ,
() e {} . Os seguintes exemplos de código estão corretos:

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

Cuidado ao usar a lista de inicialização {...} para um tipo que possui um construtor com std :: initializer_list .

O compilador preferirá usar o construtor std :: initializer_list quando houver uma lista entre chaves . Observe que chaves entre colchetes vazias {} são um caso especial e o construtor padrão será chamado (se disponível). Para usar explicitamente um construtor sem std :: initializer_list, use parênteses em vez de chaves.

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


Além disso, a construção com colchetes proíbe uma série de transformações de tipos inteiros (transformações com precisão decrescente). E você pode obter erros de compilação.

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

Diretivas de pré-processador


O sinal # (sinal da diretiva do pré-processador) deve estar no início da linha.

Mesmo se a diretiva pré-processador se referir ao código incorporado, as diretivas são gravadas desde o início da linha.

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

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

Formatação de classe


Organize as seções na seguinte ordem: pública , protegida e privada . Recuo é um espaço.

O formato básico para a classe é descrito abaixo (com exceção dos comentários, consulte Comentários na descrição da 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_;
};

Observações:

  • O nome da classe base é escrito na mesma linha que o nome da classe herdada (é claro, levando em consideração o limite de 80 caracteres).
  • Palavras-chave do público: , protegidas ,: e privadas: deve ser recuado um espaço.
  • Cada uma dessas palavras-chave deve ser precedida por uma linha em branco (com exceção da primeira menção). Além disso, em classes pequenas, as linhas em branco podem ser omitidas.
  • Não adicione uma linha em branco após essas palavras-chave.
  • A seção pública deve ser a primeira, atrás dela protegida e, no final, a seção privada .
  • Consulte Procedimento de declaração para criar declarações em cada uma dessas seções.

Listas de inicialização do construtor


As listas de inicialização do construtor podem estar em uma linha ou em várias linhas com recuo de 4 espaços.

A seguir estão os formatos corretos para as listas de inicialização:

//    
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) {}

Formatando namespaces


O conteúdo do espaço para nome é recuado.

O espaço para nome não adiciona preenchimento. Por exemplo:

namespace {

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

}  // namespace

Não recue no espaço para nome:

namespace {

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

}  // namespace

Ao declarar espaços para nome aninhados, coloque cada declaração em uma linha separada.

namespace foo {
namespace bar {

Repartição horizontal


Use avarias horizontais, conforme apropriado. Nunca adicione espaços ao final de uma linha.

Princípios gerais


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

Adicionar espaços delimitadores pode interferir na mesclagem de código. Portanto: não adicione espaços de separação ao código existente. Você pode remover espaços se você já tiver modificado esta linha. Ou faça isso como uma operação separada (é preferível que ninguém trabalhe com esse código).

Ciclos e condições


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

Operadores


//     
x = 0;

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

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

Padrões e moldes


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

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

Repartição vertical


Minimize a divisão vertical.

Isso é mais um princípio do que uma regra: não adicione linhas em branco sem necessidade especial. Em particular, coloque não mais que 1-2 linhas vazias entre as funções, não inicie a função com uma linha vazia, não termine a função com uma linha vazia e tente usar menos as linhas vazias. Uma linha vazia em um bloco de código deve funcionar como um parágrafo de um romance: separe visualmente duas idéias.

O princípio básico: quanto mais código couber em uma tela, mais fácil é entender e rastrear a sequência de execução. Use a cadeia vazia apenas para separar visualmente essa sequência.

Algumas notas úteis sobre linhas em branco:

  • Uma linha vazia no início ou no final de uma função não melhorará a legibilidade.
  • Linhas vazias na blockchain if-else podem melhorar a legibilidade.
  • Uma linha vazia antes da linha de comentário geralmente ajuda a legibilidade do código - um novo comentário geralmente envolve a conclusão de um pensamento antigo e o início de uma nova idéia. E a linha vazia está claramente sugerindo isso.

All Articles