As Três Leis do TDD

O que é o Desenvolvimento Baseado em Testes ou TDD?

Quais são as Três Leis do TDD?

É isso o que veremos neste post. Veja nesta página os posts da série sobre TDD.

Continue lendo “As Três Leis do TDD”

Minhas Preferências de Formatação

Abaixo estão algumas das minhas preferências e suas razões.

1. Endentação e aspecto geral

  • Endentar com 4 espaços;
  • Limite de 80 caracteres por linha; e
  • Tabulações proibidas.

Para mim 2 espaços é pouco e 8 espaços é muito na endentação. Quatro espaços parecem ser o meio termo adequado.

Linhas longas atrapalham a leitura do código: eu desejo um código tão legível quanto livros e jornais. Além disso, o limite de 80 caracteres permite abrir arquivos lado a lado sem necessitar de rolagem horizontal e ainda facilita revisão do código anterior e atual abertos lado a lado.

Com respeito a proibir tabulações, existe o argumento que tabulações são mais flexíveis, pois o programador pode escolher o tamanho de sua preferência. O argumento é válido, porém somente até se misturarem as tabulações e os espaços: nesse instante, a única pessoa que vê alinhado é o próprio autor do código; e todo o resto do mundo vê uma bagunça desalinhada. Usar apenas um tipo de espaço em branco elimina esse problema.

2. Alinhamento das chaves

Abertura de chaves na linha abaixo.

Isso ajuda a identificar onde acontece a abertura do bloco. Chave abrindo na própria linha permite determinar o início do bloco sem precisar procurar por ela no fim de alguma linha.

unsigned long int funcao_h(
    unsigned long int parametro1,
    unsigned long int parametro2,
    unsigned long int parametro3)
{
    return parametro1 + parametro2 - parametro3;
}

Compare abaixo a mesma função abrindo as chaves na linha. Não é tão óbvio como no caso acima.

unsigned long int funcao_h(
    unsigned long int parametro1,
    unsigned long int parametro2,
    unsigned long int parametro3) {
    return parametro1 + parametro2 - parametro3;
}

Abrindo as chaves na linha abaixo, fica óbvio onde os parâmetros terminam e onde inicia o corpo da função. O mesmo acontece em blocos de controle como if, for, etc.

Mesmo se eliminarmos qualquer informação sobre os caracteres, como é mostrado abaixo, facilmente identificamos a lista de parâmetros e o início do corpo da função.

XXXXXXXX XXXX XXX XXXXXXXXX
    XXXXXXXX XXXX XXX XXXXXXXXXXX
    XXXXXXXX XXXX XXX XXXXXXXXXXX
    XXXXXXXX XXXX XXX XXXXXXXXXXX
X
    XXXXXX XXXXXXXXXX X XXXXXXXXXX X XXXXXXXXXXX
X

XXXXXXXX XXXX XXX XXXXXXXXX
    XXXXXXXX XXXX XXX XXXXXXXXXXX
    XXXXXXXX XXXX XXX XXXXXXXXXXX
    XXXXXXXX XXXX XXX XXXXXXXXXXX X
    XXXXXX XXXXXXXXXX X XXXXXXXXXX X XXXXXXXXXXX
X

A maior parte do tempo que passamos programando é utilizada, na verdade, lendo o código. Esta pequena escolha ajuda a encontrar a parte que importa no momento.

3. Parâmetros de funções

A lista de parâmetros de funções e a lista de argumentos de chamada de uma função devem estar todos em uma linha ou cada um em sua linha.

Dessa forma, a lista de parâmetros (ou argumentos) realmente se parece com uma lista. É fácil de contar os parâmetros e é fácil de encontrá-los.

Nesse ponto, há três possibilidades avaliadas na ordem seguinte:

  • Protótipo todo na mesma linha;
  • Lista de parâmetros toda na segunda linha; e
  • Lista de parâmetros com um parâmetro por linha.

Exemplos abaixo:

/* Protótipo inteiro cabe em uma linha: */

int funcao_f(int parametro1, int parametro2)
{
    return parametro1 + parametro2;
}

/*
 * Protótipo não cabe em uma única linha, porém
 * a lista de parâmetros cabe inteira na segunda linha:
 */

unsigned long int funcao_g(
    unsigned long int parametro1, unsigned long int parametro2)
{
    return parametro1 + parametro2;
}

/*
 * Parâmetros não cabem todos na segunda linha, então
 * coloca-se um parâmetro por linha:
 */

unsigned long int funcao_h(
    unsigned long int parametro1,
    unsigned long int parametro2,
    unsigned long int parametro3)
{
    return parametro1 + parametro2 - parametro3;
}

4. Comentários

  • Usar o mínimo de comentários possível; e
  • Comentários multilinha devem ter um caractere inicial de alinhamento.

Comentários não são analisados pelo compilador e não precisam fazer sentido para o código funcionar. Por isso não são atualizados em conjunto com o código. Consequentemente, com o passar do tempo a semântica (o significado) do código se desvia dos comentários presentes.

Na maioria dos casos é possível fazer um código autoexplicativo através da refatoração e da melhoria de nomes de funções e nomes de variáveis.

/* executa_loop1 */

void executa_loop1(void)
{
    // Lê o valor do sinal do sensor
    int16_t val = ADC0;

    // Calcula tensão do sensor em volts
    // a partir do offset e do ganho
    float valf = (val - 512) * (24.0 / 512.0);

    // Escreve na serial
    Serial_EscreveFloat(valf);
}

/* ---------------------------------------- */

/* executa_loop2 */

#define SENSOR_OFFSET 512
#define SENSOR_GANHO  (24.0 / 512.0)

float TensaoSensor_Volts(void);

void executa_loop2(void)
{
    Serial_EscreveFloat(TensaoSensor_Volts());
}

int16_t SinalSensor_ADC(void)
{
    return ADC0;
}

float TensaoSensor_Volts(void)
{
    return (SinalSensor_ADC() - SENSOR_OFFSET) * SENSOR_GANHO;
}
  • O primeiro comentário é removido com uma chamada da função SinalSensor_ADC().
  • O segundo comentário é removido com uma chamada da função TensaoSensor_Volts() e pelo uso das constantes SENSOR_OFFSET e SENSOR_GANHO.
  • O terceiro comentário é redundante e desnecessário.

Qual versão da função executa_loopX() você prefere ler?

Alguns comentários, por sua vez, são impossíveis de serem transformados em código. Por exemplo, o comentário a seguir mostra um diagrama de um LCD 16×2 que mostra a tensão elétrica e a corrente medida.

/*
 *   0123456789012345
 * 0 Tensão   XX,XX V 0
 * 1 Corrente Y,YYY A 1
 *   0123456789012345
 */

Sobre o caractere inicial dos comentários multilinha, alguns editores e formatadores eliminam espaços em branco iniciais, alinhando o primeiro caractere não branco de cada linha.

Dessa forma, todo o esforço de alinhamento abaixo é perdido em um piscar de olhos:

/*  0123456789012345
  0 Display 16x2     0
  1 Linha 2          1
    0123456789012345 */

O comentário acima acaba por se transformar em alguma dessas atrocidades:

/* 0123456789012345
   0 Display 16x2     0
   1 Linha 2          1
   0123456789012345 */

/* 0123456789012345
0 Display 16x2     0
1 Linha 2          1
0123456789012345 */

Colocando os asteriscos de alinhamento o problema deixa de existir.

/*
 *   0123456789012345
 * 0 Display 16x2     0
 * 1 Linha 2          1
 *   0123456789012345
 */

Considerações finais

Essas são as algumas das minhas preferências, ao menos as que eu acho mais importantes.

Todas essas já mudaram algumas vezes desde que comecei a programar e, com a experiência, são as que me parecem mais naturais e que facilitam a leitura do código.

Além disso, a maioria delas (exceto as sobre comentários), não me dão trabalho algum, pois utilizo um formatador automático. Assim posso focar no código em si e em como melhorá-lo para eliminar comentários.

Formatando o Código

Quando desenvolvido em grupo ou time, a formatação do código de um projeto é um assunto que cria polêmicas com frequência. Cada pessoa tem suas preferências e isso gera conflitos durante as revisões.

Para minimizar esse problema há três pontos que podem ser adotados pelo projeto:

  • Deve haver algum padrão de formatação para o projeto;
  • Dar preferência para formatação automatizada; e
  • Documentar como configurar a formatação automatizada.

Tenha algum padrão de formatação

Para o projeto em si, não importa qual é o padrão estético do código, desde que o código funcione. Os programadores, no entanto, possuem preferências com respeito à estética do código.

Esse primeiro ponto reforça que algum padrão exista. Não importa qual seja o padrão, desde que ele exista e seja seguido. Dessa forma, essas discussões nas revisões deixam de focar em critérios pessoais e passam a usar um critério objetivo: estar ou não de acordo com o padrão.

Com o padrão definido, as discussões sobre preferência pessoal são minimizadas e, caso haja algum ponto adicional que o padrão não abrange, faz-se uma reunião para definir o padrão para tal caso.

Automatize a formatação

O segundo ponto, por sua vez, elimina o trabalho manual de formatar e consequentemente elimina apontamentos de erro nas revisões por conta de erros de formatação.

Também são eliminadas algumas reuniões sobre preferências pessoais não abrangidas pelo padrão, porque as decisões precisam ser mantidas dentro das opções fornecidas pelo formatador automático.

Além disso, são menos regras para se lembrar na hora de escrever código.

Só há uma regra: execute o formatador nos arquivos modificados.

Existe um projeto chamado pre-commit que realiza tarefas logo antes de realizar um commit com o git. O tipo de tarefa mais comum é formatar o código com alguma ferramenta externa, como o clang-format (para C/C++).

Ainda melhor: alguns editores podem ser configurados para formatar o código sempre que o arquivo é salvo.

Documente o processo de automação

É importante documentar como abaixar, instalar e configurar essas ferramentas, de forma que todos possam aderir sem dificuldades.

Crie um link com a informação no LEIAME (README) do próprio projeto ou adicione a informação diretamente nele.

Essa documentação só precisa ser feita uma vez e outros projetos podem simplesmente copiar e adaptar para as suas necessidades.

Links úteis

Considerações finais

Definir um padrão para o código é importante para evitar discussões sem fim sobre a estética do código.

Ferramentas de formatação automática são a melhor opção para eliminar essas discussões e manter o código formatado consistentemente.

Documentar o processo de configuração das ferramentas é importante para os desenvolvedores fazerem uso delas.

Memória de programa AVR/ATmega328P

Sempre que utilizamos strings e arrays constantes em nossos programas, por padrão elas vão parar na memória RAM. Como tempo pouca memória RAM, seria ótimo conseguir colocar isso na memória de programa, liberando a preciosa RAM para as variáveis.

Neste post vemos como fazer isso nos microcontroladores AVR, como o ATmega328P do Arduino Uno.

Continue lendo “Memória de programa AVR/ATmega328P”

Reduzindo o programa AVR/ATmega328P

O seu programa está grande demais? Muito lento para gravar o microcontrolador? Não cabe na memória? Vamos reduzir o programa!

Há alguns truques simples para reduzir o tamanho dos programas e, se não está utilizando todos, as chances são que seu executável está maior do que o necessário.

Vejamos como podemos reduzir o tamanho do programa gerado pelo compilador.

Continue lendo “Reduzindo o programa AVR/ATmega328P”

Compilando código C/C++ com Makefile

Neste post mostramos como criar um Makefile para realizar a compilação de programas C/C++.

Makefiles são muito bons pra automatizar a execução de comandos, evitando ter que digitá-los toda vez.

Também gerenciam dependências entre arquivos, recompilando apenas o que é necessário.

Continue lendo “Compilando código C/C++ com Makefile”

TDD: Testando código C/C++ com Gcov

No post anterior (TDD: Testando código C/C++ com Boost.Test), explicamos como se cria uma suíte de testes com a biblioteca Boost.Test. Muito útil para testar a funcionalidade correta do código.

Neste post mostramos como utilizar o Gcov para verificar se todo o código é executado pelos testes. Criamos uma biblioteca simples e um conjunto de testes para ela.

Ferramentas de code coverage (cobertura de código) como Gcov são muito utilizados para “Desenvolvimento a partir de testes” (TDD – Test Driven Development).

No TDD desenvolvemos os códigos e seus testes em paralelo, para verificar que o código funciona e, no futuro, poder verificar se alguma mudança no código tenha causado um bug.

A análise da cobertura de código auxilia no TDD, identificando partes do código que não estão sendo testadas por completo.

Continue lendo “TDD: Testando código C/C++ com Gcov”

TDD: Testando código C/C++ com Boost.Test

Neste post mostramos como utilizar a biblioteca Boost.Test para testar seus códigos. Podemos compilar a biblioteca junto aos testes, sendo também possível utilizar uma versão pré-compilada para melhorar o tempo de compilação.

Bibliotecas de teste são muito utilizados para “Desenvolvimento a partir de testes” (TDD – Test Driven Development).

No TDD desenvolvemos os códigos e seus testes em paralelo, para verificar que o código funciona e, mais importante, no futuro poder facilmente verificar se alguma mudança no código tenha causado um bug.

Continue lendo “TDD: Testando código C/C++ com Boost.Test”

%d blogueiros gostam disto: