Kernel Linux: Padrão de codificação

Padrão de codificação
Este post faz parte da série Kernel Linux. Leia também os outros posts da série:

No post anterior, o processo de desenvolvimento do Kernel Linux foi exposto. Para dar continuidade nesta série de 3 artigos, agora serão apresentados alguns conceitos práticos e ferramentas que podem ser utilizadas para facilitar a vida dos desenvolvedores do kernel Linux. Serão apresentados os padrões de codificação do kernel Linux e um script para agilizar a verificação de códigos com relação a este padrão. No próximo artigo, e final, vou ensinar como enviar um patch para o Kernel Linux.

 

Apesar de serem específicos ao desenvolvimento do Kernel Linux, muitos dos conceitos apresentados aqui podem ser aplicados em projetos de software e firmware no dia a dia.

 

 

O Padrão de Codificação do Kernel Linux

 

A maioria do tempo despendido com atividades de desenvolvimento de software é gasto com leitura de código. Apenas uma pequena porção é gasta efetivamente codificando. Desta forma, a atividade de leitura de código deve ser a mais otimizada possível [1]. Além disso, em grandes projetos, onde várias pessoas fazem contribuições, é interessante que haja uma convenção sobre a codificação, para haver uma concordância sobre o código gerado, facilitando a leitura deste por todos os envolvidos. Faz-se necessário um acordo formal sobre o estilo/padrão de codificação.  Sendo o Kernel Linux, um projeto gigantesco, não poderia ser diferente. Existe um padrão de codificação do kernel linux que deve ser seguido, inclusive para que você seja ouvido caso sugira algum patch.

 

Em muitos casos o estilo de programação é pessoal. O mais importante é escolher um padrão de codificação e segui-lo com afinco. Isto só se reverterá em benefícios para a legibilidade do código. Nesta seção apresentarei de forma bem resumida o padrão de codificação do Kernel do Linux. Esta prática pode ser estendida, modificada e aplicada no dia a dia de cada um.

 

Antes de começarmos, é importante ressaltar que o Kernel do Linux é codificado em linguagem C, com exceção de alguns trechos dependentes de arquitetura, que são codificados em Assembly. O padrão de codificação completo está disponível no diretório de documentação do Kernel.

 

 

Indentação

 

Indentação é o recuo de texto em relação à sua margem. Nas linguagens de programação, a indentação serve para ressaltar blocos de código dentro de um algoritmo, facilitando a leitura.

 

O padrão de codificação do Kernel com relação à indentação é a tabulação (Tab), que equivale a 8 caracteres. Pode parecer muito, mas uma das vantagens de usar tal indentação é para mostrar que o seu código está muito cheio de estruturas de controle aninhadas (if/else  e for) e está na hora de quebrar o seu código em mais funções.

 

 

Limite no número de caracteres

 

Sugere-se fortemente que o número de caracteres em cada linha de código seja inferior a 80 caracteres. Este requisito pode ser uma herança da época dos terminais, quando não havia Windows e Linux em modo gráfico.

 

Deste modo, é sugerido que linhas maiores que 80 caracteres sejam quebradas em blocos menores. Vou dar dois exemplos que acredito que sejam bem usados na prática.

 

Para o caso de strings literais:

char a[] = “String com mais de 80 caracteres”
char b[] = “String com mais “
         
            “de 80 caracteres”;

 

Funções com lista extensa de parâmetros:

int func(int variavel1, int variavel2, int variavel3, int variavel4, int variavel5, int variavel 6)
{
        ...
}

 

int func(int variavel1, int variavel2, int variavel3,
int variavel4, int variavel5, int variavel 6)
{
….
}

 

 

Colchetes

  

As formas de utilizar os colchetes, diferentemente da indentação, tem poucas razões técnicas que justifiquem certas escolhas. O estilo de codificação do Kernel segue os autores do livro “The C Programming Language” [2], de Kernighan and Ritchie (sendo Denis Ritchie o criador da linguagem C). Colocar o colchete de abertura como último caractere, e o caractere de fechamento de colchetes como último caractere em um bloco: 

if (x) {
        y();
        ...
}

 

Isto se aplica a todos os blocos que não sejam funções (if, switchs, for, while e do).

 

Como caso especial, temos as funções: elas têm o colchete de abertura na próxima linha depois da sua declaração:

int function(int x)
{
        ...
}

 

Algumas pessoas discordam desta regra, pois é uma exceção. Entretanto, funções também são especiais, pois não podem ser aninhadas.

 

 

Espaços

 

O padrão de codificação do Kernel para espaços depende uso de funções e palavras chaves. A regra é usar um espaço depois de quase todas palavras chave. As exceções são sizeof, typeof, aligneof, e __attribute__, que se parecem mais como funções, e são usadas geralmente com parênteses no Linux.

 

Desta forma, deve-se usar um espaço depois das palavras chave if, switch, case, for, do, while. Mas não com sizeof, typeof, alignof ou __attribute__. Exemplo:

s = sizeof(struct file);

 

Não adicione espaços entre os parênteses de expressões. Este é um exemplo de forma errada de se usar os espaços neste caso:

s = sizeof( struct file );

 

Quando se declara um ponteiro ou uma função que retorna um ponteiro, a posição do ‘*’ deve ser adjacente ao nome da variável ou nome de função, e não ao tipo. Exemplo:

char *linux_banner;
unsigned long long memparse(char *ptr, char **retptr);
char *match_strdup(substring_t *s);

  

Use um espaço ao redor de cada operador binário ou ternário, tais como:

= + - ; < > * / % | & ^ <= >= == != ? :

 

Mas não após operadores unários:

&  *  +  -  ~  !  sizeof  typeof  alignof  __attribute__  defined

 

Não use espaços após ou depois de operadores unários prefixos e pós fixos respectivamente:

++  --

 

E, por fim, sem espaços ao redor dos operadores de membros de estruturas “.” e “->”, e sem espaços em branco no final de linhas.

 

 

Nomeação de variáveis e funções

 

C é uma linguagem Espartana, e dessa forma deve ser a nomeação das variáveis. Diferente de programadores de linguagens como Pascal (nada contra!), programadores C não usam nomes “bonitinhos” como EstaVariavelEUmContadorTemporario. O padrão de codificação do Kernel Linux sugere que você chame essa variável de “tmp”, que é muito mais fácil de se escrever e ainda assim consegue passar uma ideia de seu uso. Isso é principalmente aplicável em escopos locais, como veremos mais adiante.

 

No entanto, enquanto o uso de “Camel Case” é desaprovado. Nomes bem descritivos para variáveis globais são extremamente aconselháveis, assim como a nomeação de funções globais. A nomeação usando notação Húngara é proibida, pois é muito difícil de dar manutenção nesta prática, caso haja mudança de tipos, o que ocasiona bugs.

 

Nomes de variáveis locais devem ser curtos, e direto ao assunto. Se você usa um contador para um loop, ele provavelmente deve ser chamado de “i” e não “loop_counter”. Similarmente, “tmp” deve ser usado para uma variável, pasmem, temporária!!

 

 

Typedefs

 

Não use coisas como “vps_t”. É considerado um erro usar typedef para estruturas e ponteiros. Por exemplo, quando você vê:

vps_t a;

 

no código, o que isso significa? Em contraste, quando você vê:

struct virtual_container *a;

 

você pode dizer o que exatamente “a” é.

 

Typedef deve ser usado para definição de objetos opacos, embora isso não seja encorajado no Kernel, tipos de inteiros limpos (u32, u16 ..)  e alguns outros casos.

 

 

Funções

 

Funções devem ser curtas e objetivas (conceito de responsabilidade única). Elas devem caber em uma ou duas telas de 80X24, e fazer somente uma coisa, e fazê-la bem.

 

O tamanho máximo de uma função deve ser inversamente proporcional ao nível de indentação e complexidade desta função. Assim, se você tem uma função simples, que nada mais é que um longo “case”, tudo bem. De forma oposta, funções muito complexas devem ser quebradas em funções menores, de forma a melhorar a legibilidade. Se você acha que pode haver perda de performance, use funções in-line. Outra medida para funções é o número de variáveis locais, que deve ficar entre 5 e 10. Caso esse número seja muito excedido, repense sua função.

 

 

Retorno de funções centralizado

 

Apesar de ser considerado obsoleto por algumas pessoas, o comando “goto” é muito útil quando uma função tem retorno em múltiplos lugares, e que antes do retorno, deve-se fazer algum trabalho de limpeza. No entanto, se não houver trabalho de limpeza, somente use o “return”. Isto é muito útil em device drivers, onde as funções fazem a inicialização de recursos, reserva de memória de IO, etc. Um exemplo de uso é dado a seguir: 

int fun(int a)
{
        int result = 0;
        char *buffer = kmalloc(SIZE);

        if (buffer == NULL)
                return -ENOMEM;

        if (condition1) {
                while (loop1) {
                        ...
                }

                result = 1;
                goto out;
        }

        ...

out:
        kfree(buffer);
        return result;
}

 

 

Comentários

 

Comentários são bons, mas existe um perigo em comentar demais. NUNCA tente explicar como o código funciona nos comentários. É muito melhor escrever um código extremamente legível e elegante, de modo que o funcionamento fique óbvio. Além disso é um desperdício de tempo explicar como um código ruim funciona.

 

Geralmente deve-se explicar o que o código faz, mas não como ele faz. Deve-se evitar colocar comentários dentro do corpo de funções. Pode-se colocar pequenos comentários para alertar sobre alguma coisa particularmente inteligente ou feia, mas deve-se evitar excessos. É melhor colocar os comentários no cabeçalho da função, informando o que a função faz e o porquê.  Também deve-se tomar cuidado, caso seja o comentário de uma função da API do Kernel Linux.

 

Também é importante comentar os dados, sejam eles tipos básicos ou derivados. Deste modo, use somente a declaração de um dado por linha (sem vírgulas para múltiplos dados declarados de um mesmo tipo). Isto deixa espaço para um pequeno comentário em cada item, explicando o que ele faz.

 

 

Checando o padrão de codificação de forma automática

 

Assim como o Kernel Linux, muitas empresas possuem um manual para o estilo de codificação que deve ser usado em seus projetos. O código é escrito por um programador e, ao finalizar a funcionalidade que este está desenvolvendo, envia o código para outro programador para que possa ser revisado num processo conhecido como peer review. Acontece que além do estilo de codificação, geralmente checa-se outros itens, como a alocação correta de memória ou até mesmo o design implementado (arquitetura).

 

Somos programadores destemidos com tempo precioso em mãos que deve ser direcionado para desenvolver a parte crítica de nossos projetos, e não ficar checando minúcias no código.

 

Pensando nisso, foi desenvolvida a ferramenta checkpatch, que é um script feito em Perl que verifica a conformidade de um código com relação ao padrão do Kernel.

 

A ferramenta checkpatch deve ser utilizada para verificar seu código antes que ele seja submetido para algum mantenedor de subsistema e sua respectiva lista de e-email.

 

Como exemplo temos o seguinte código: 

#include <stdio.h>

int main() {
        int a, b, c;

        a = b + c;

        printf("Value a: %d but this is a long long line so that the checkpatch will warn about it \n", a);

        return 0;
}

 

O script fica na pasta /scripts do código fonte do Kernel Linux. Aplicando a ferramenta checkpatch neste arquivo:

$ ~/linux/scripts/checkpatch.pl -f art2.c

ERROR: open brace '{' following function declarations go on the next line

#3: FILE: art2.c:3:

+int main() {

WARNING: line over 80 characters

#7: FILE: art2.c:7:

+ printf("Value a: %d but this is a long long line so that the checkpatch will warn about it \n", a);

WARNING: unnecessary whitespace before a quoted newline

#7: FILE: art2.c:7:

+ printf("Value a: %d but this is a long long line so that the checkpatch will warn about it \n", a);

ERROR: code indent should use tabs where possible

#9: FILE: art2.c:9:

+ return 0;$

WARNING: please, no spaces at the start of a line

#9: FILE: art2.c:9:

+ return 0;$

total: 2 errors, 3 warnings, 10 lines checked

NOTE: whitespace errors detected, you may wish to use scripts/cleanpatch or scripts/cleanfile

art2.c has style problems, please review.

If any of these errors are false positives, please report them to the maintainer, see CHECKPATCH in MAINTAINERS.

 

Fazendo as correções sugeridas no código: 

#include <stdio.h>

int main()
{
        int a, b, c;

        a = b + c;

        printf("Value a: %d\n", a);

        return 0;
}

  

A seguinte saída é obtida do checkpatch: 

$ ~/linux/scripts/checkpatch.pl -f art2.c

total: 0 errors, 0 warnings, 11 lines checked

art2.c has no obvious style problems and is ready for submission.

 

É importante ressaltar que a ferramenta checkpatch pode ser alterada para se adequar à suas necessidades. Além disso, exitem outras ferramentas que podem ser utilizadas para este fim, como:

  • Gnu Indent;
  • Eclipse por meio de plugins.

 

Existem várias outras ferramentas disponíveis. Basta saber qual a necessidade. Eu, particularmente sou contra code beautifiers (ferramentas que arrumam o código em um padrão de codificação automaticamente), pois elas nos mantêm no vício de não escrever o código diretamente no padrão.

 

 

Conclusões

 

Neste artigo foi apresentado o padrão de codificação do Kernel Linux e evidenciados a necessidade e os benefícios da adoção de um padrão. Também foi apresentada a ferramenta checkpatch que é utilizada para automatizar o processo de verificação do código. No próximo artigo irei mostrar como enviar um patch para o Kernel Linux e quais ferramentas devem ser utilizadas.

 

 

Referências

 

[1] Robert C. Martin , “Clean Code: A Handbook of Agile Software Craftsmanship”

[2]  Brian W. Kernighan , Dennis M. Ritchie, "The C Programming Language", 2nd Edition

 

Outros artigos da série

<< Processo de desenvolvimento do Kernel LinuxSubmetendo um patch para o Kernel do Linux >>
Este post faz da série Kernel Linux. Leia também os outros posts da série:
NEWSLETTER

Receba os melhores conteúdos sobre sistemas eletrônicos embarcados, dicas, tutoriais e promoções.

Obrigado! Sua inscrição foi um sucesso.

Ops, algo deu errado. Por favor tente novamente.

Licença Creative Commons Esta obra está licenciada com uma Licença Creative Commons Atribuição-CompartilhaIgual 4.0 Internacional.

Raphael Philipe Mendes da Silva
Raphael Silva é Engenheiro de Computação formado pela USP. Obteve o título de Mestre em Engenharia Elétrica na mesma Instituição. Atualmente integra o time do Linux Technology Center da IBM como Engenheiro de Software.

1
Deixe um comentário

avatar
 
1 Comment threads
0 Thread replies
0 Followers
 
Most reacted comment
Hottest comment thread
1 Comment authors
Cesar Junior Recent comment authors
  Notificações  
recentes antigos mais votados
Notificar
Cesar Junior
Visitante
Cesar Junior

Muito bom! Parabens pelo artigo