Programando em C: Boas Práticas

The C Programming Language

Em 1972 o funcionário da AT&T Dennis Ritchie iniciou o desenvolvimento de uma linguagem de programação que simplificasse a sua tarefa diária de programador nos laboratórios Bell. Chamou-a simplesmente de “C”, em referência clara à uma linguagem anterior, escrita por Ken Thompson, conhecida como “B”. Após estar madura o suficiente para substituir o assembly no kernel do sistema Unix em 1973, a linguagem ganhou o mundo, evoluindo com o tempo e tornando-se referência, mas sem perder as principais características que a fizeram ocupar uma posição de destaque.

Este artigo pretende conversar diretamente com o programador, revelando algumas boas práticas que podem auxiliá-los no desenvolvimento e manutenção do código. Apesar de genérico à linguagem C, os tópicos abordados são frutos da experiência como desenvolvedor de sistemas embarcados, sendo estes o nosso foco principal.

 

Mantendo Bonito

A linguagem C, por definição, não exige que você escreva o programa seguindo regras que o tornem esteticamente agradável de ver (e ler). Nada impede você de escrever algo como:

/*#define ppp xp
 * /## define */#define x =
 #define zim void
 /*\**\*\***//*/ #denife $/*/
 #define p ()
 #define iu for(
 /*\**\*\***//*/ #defie $##def x^3printdf/*/
 #define xyz <
 /*/*  8/ *******/ #define o ;}
 #define v "\na l o   m u n d o "
 int s (zim) { char b x 1; iu b x 9;\
 b xyz 21; b++){ printf( v ) o /*}}}*/}
 zim main( zim ) { s p o

Exceto, claro, o bom senso. De uma maneira geral, o código bonito e organizado é sempre mais fácil de ler.

 

Indentação & Espaços

Utilizar indentação no código é algo essencial para a legibilidade. O tamanho do espaçamento pode variar, mas o importante é sempre manter blocos aninhados respeitando os níveis de indentação das estruturas condicionais e laços de repetição. De uma forma geral, o nível de indentação muda sempre após o início de uma função, estrutura condicional ou laço:

void exemplo ( void ) {
    if ( a > 5 ) {
        b += 3;
        for ( i=0; i<5; i++ ) {
          z++;
          x--;
        }
     }
 }

No exemplo acima foram utilizados quatro espaços entres os blocos. Você pode configurar o seu editor de texto preferido para que a tecla TAB substitua o caracter invisível de tabulação por estes espaços, é bastante útil. Evite espaçamentos muito curtos, como apenas um espaço, ou muito longos.

Além da indentação, que você já deve estar habituado, outro recurso que facilita a legibilidade é a utilização de espaços horizontais e verticais. Aumentar o número de linhas e espaços não vai, obviamente, aumentar o tamanho do binário compilado. Ao invés de escrever uma sentença condicional como:

if (a<20 &&(c==5||d==6)) { }

prefira algo assim:

if (  (a < 20)   &&   ( (c==5) || (d==6) )  ) { }

que é visivelmente mais agradável de avaliar. Separar as sentenças por parênteses também evita algum erro de precedência que você possa cometer ao digitar as expressões, reduzindo o tempo de depuração no futuro.

Laços complexos também podem ser quebrados em várias linhas, com o mesmo objetivo:

for ( cont = 0, aux = 3;
   cont < 100;
   cont++, aux+=2 ) {
     a = c + b;
     d = e - f;
 }

Quando falo em legibilidade do código, lembro-me que existe um operador na linguagem C, o ternário, que utilizado em excesso pode tornar as expressões pouco legíveis aos olhos menos treinados. De uma forma geral eu evito utilizar o operador ternário, mesmo em sentenças simples. A operação

aux = ( cont < aux ) ? cont : cont * -1;

poderia ser escrita, sem nenhum prejuízo, como:

if ( cont < aux )
     aux = cont;
 else
     aux = cont * -1;

Mas se você gosta do operador ternário, uma opção para melhorar a legibilidade é separar as sentenças por linhas indentadas. A mesma expressão poderia ser escrita assim:

aux = ( cont < aux )
 ? cont
 : cont * -1;
Isto será especialmente útil caso você utilize o operador ternário de forma encadeada. Este código:
a = ( b < z )
 ? x
 : ( i > x )
     ? i
     : ++i;

é certamente mais legível que este:

a = ( b < z ) ? x : ( i > x ) ? i : ++i;

– ++ e —

Evite agregar mais de um ++ ou — na mesma sentença. Com o mesmo intuito, evite atribuir ao resultado da operação operadores que utilizaram o incremento ou decremento na expressão.

Portando, expressões como:

z = a++ + ++b;

ou

a[b] = b++;

devem ser evitadas e substituidas por cláusulas que inibam, ou pelo menos minimizem, interpretações errôneas.

 

Números Mágicos

As constantes são suas amigas, e os “#define” uma das mais ricas formas de melhorar a legibilidade do código. “Números Mágicos” são valores, inteiros ou não, que aparecem no código e que são constantes, mas não foram declarados como tais. Fazer isso prejudica a organização e dificulta a manutenção.

Ao invés de ter condições como:

while ( tensao_bateria < 30 ) {
    if ( potencia_transmissor > 50 )
        diminua_potencia();
    else {
        desliga_radio();
        while ( tensao_bateria < 30 ) { /* espera bateria carregar */ }
        liga_radio();
    }
 }

substitua os inteiros por constantes com nomes adequados, como LIMITE_BATERIA_CRITICA e POTENCIA_MINIMA_TRANSMISSOR. Além de melhorar a organização facilita a manutenção, em caso de alterações nestas constantes.

 

Loops “Parados”

Utilizando o exemplo anterior, observe que escrevemos

while ( tensao_bateria < 30 ) { /* espera bateria carregar */ }

ao invés de simplesmente

while ( tensao_bateria < 30 );

A primeira forma deixa explícita a nossa intenção, ao contrário da segunda, que poderia ser confundida com um erro do programador ou causar problemas posteriormente, caso o “;” fosse erroneamente removido (possivelmente sem erro na compilação).

 

Convenções

Não importa exatamente quais convenções você utiliza, o importante é que você defina uma (própria ou não) e siga-a da forma mais rigorosa possível. Preferencialmente estas convenções devem ser descritas em um documento simples, que permita consultas rápidas sempre que necessário. Exemplificarei algumas abaixo que são bastante populares, mas você deve adaptá-las às suas necessidades.

- Constantes: para as constantes utilize sempre letras maiúsculas. Desta forma será fácil distingui-la entre as variáveis.
 Ex.:
 #define LIMITE_TEMPERATURA 40
 #define BATERIA_BAIXA 30

– Variáveis: mais importante que as convenções, uma regra básica: utilize nomes claros para as variáveis, abreviando apenas o que é bastante conhecido ou óbvio. Programadores são, em sua maioria, ágeis no teclado. Portanto, abreviar pressaoMotorEsquerdo para preMotE provavemente não vai economizar muito do seu tempo durante o desenvolvimento e certamente dificultará a leitura do código.

Existem algumas convenções mais antigas como a Notação Húngara ou mais modernas, como a Notação Camelo, muito utilizada na programação orientada a objetos. Usualmente, em C, é bastante comum todas as variáveis serem escritas com letras minúsculas e/ou com o “_” como separador de palavras. Sendo assim, variáveis como velocidademaxima ou velocidade_maxima são bastante comuns, sendo esta última forma mais freqüente.

– Tipos de Variáveis: uma das grandes vantagens da linguagem C é a portabilidade entre arquiteturas. No entanto, alguns tipos de variáveis possuem comprimento variável dependendo da arquitetura e compilador, o que pode ocasionar problemas no software, como overflow ou erros de conversões. Por exemplo, o tipo inteiro (int) normalmente representa o tamanho da palavra do processador, nos processadores modernos de 32 e 64 bit e compiladores como o GCC. Mas isso não é necessariamente uma regra; no compilador HITECH para microcontroladores PIC de 8 bit, por exemplo, o inteiro possui 16 bit.
Sendo assim, defina tipos claros e utilize-os, como uint8 para inteiros não sinalizados de 8 bit ou int16 para inteiros sinalizados de 16 bit.

Também seja atencioso ao declarar ponteiros. Uma declaração como:

char*   s, t, u;

não está errada, mas provavelmente não é o que você deseja, já que t e u não serão declarados como ponteiros. Sendo assim, prefira esta forma para declarar os ponteiros sem margem para erros:

char   *s, *t, *u;

Conclusão

Nossa intenção neste artigo foi abordar algumas técnicas que podem ajudá-lo a melhorar a legibilidade do seu código. Apesar de existirem vários outros pontos que podem ser abordados neste tópico, de uma forma geral, escrevendo um código organizado, bonito e bem comentado existe uma grande probabilidade do seu código tornar-se bastante legível, quesito que traduz-se em agilidade e produtividade em todas as fases do ciclo de vida do software.

 

Roberto Alcântara
roberto@eletronica.org