Documentation com Doxygen e comentários estruturados

1. Fundamentos da Documentação em C

1.1. Por que documentar código C

Código C, por sua natureza de baixo nível e manipulação direta de memória, exige clareza para ser compreendido e mantido. A documentação adequada transforma código enigmático em ativos reutilizáveis, reduzindo o tempo de onboarding de novos desenvolvedores e prevenindo bugs causados por interpretações equivocadas de APIs. Em projetos colaborativos, documentar não é opcional — é requisito para sustentabilidade.

1.2. Diferença entre comentários de código e documentação gerada

Comentários tradicionais (// ou /* */) explicam como o código funciona internamente. Já a documentação automática (como a gerada pelo Doxygen) descreve o que uma função faz, quais parâmetros espera e o que retorna, servindo como contrato público da API. Enquanto comentários internos são voláteis e raramente consultados fora do código-fonte, a documentação gerada se torna manual técnico navegável.

1.3. Visão geral do Doxygen

Doxygen é uma ferramenta que extrai comentários estruturados do código-fonte e gera documentação em HTML, LaTeX, man pages ou RTF. Ele suporta múltiplas linguagens, mas em C é particularmente útil por permitir documentar funções, structs, enums e macros com sintaxe padronizada, gerando referências cruzadas e diagramas de dependência automaticamente.

2. Instalação e Configuração Básica do Doxygen

2.1. Instalação

  • Linux (Debian/Ubuntu): sudo apt install doxygen graphviz
  • macOS (Homebrew): brew install doxygen graphviz
  • Windows: Baixe o instalador em doxygen.nl/download.html

2.2. Criando o arquivo Doxyfile

Gere um arquivo de configuração inicial:

doxygen -g Doxyfile

Edite as opções essenciais:

PROJECT_NAME           = "Meu Projeto C"
OUTPUT_DIRECTORY       = doc
INPUT                  = src include
RECURSIVE              = YES
EXTRACT_ALL            = YES
GENERATE_LATEX         = NO
GENERATE_MAN           = NO
HAVE_DOT               = YES
CALL_GRAPH             = YES

2.3. Geração de saída

Execute para gerar a documentação:

doxygen Doxyfile

A saída será criada no diretório doc/html. Abra index.html no navegador.

3. Sintaxe de Comentários Estruturados no Doxygen

3.1. Estilos de bloco

O Doxygen reconhece três estilos de comentários especiais:

/**
 * Estilo JavaDoc (recomendado para C)
 */

/*!
 * Estilo Qt
 */

/// Estilo de linha única (C++ style)

3.2. Estrutura básica para funções, tipos e macros

/**
 * @brief Breve descrição da função.
 *
 * Descrição detalhada opcional que pode ocupar
 * múltiplas linhas.
 *
 * @param nome  Descrição do parâmetro
 * @return Descrição do valor retornado
 */
int minha_funcao(int nome);

3.3. Comandos especiais essenciais

Comando Uso
@brief Resumo de uma linha
@param Documenta parâmetro
@return Documenta retorno
@see Referência cruzada
@note Nota importante
@warning Aviso crítico
@deprecated Marca como obsoleto
@code / @endcode Bloco de código literal

4. Documentando Funções e Parâmetros em C

4.1. Exemplo completo com direções de parâmetros

/**
 * @brief Calcula a média de um array de inteiros.
 *
 * A função percorre o array e soma todos os elementos,
 * retornando o valor médio arredondado para inteiro.
 *
 * @param[in]  dados   Ponteiro para array de inteiros (não pode ser NULL)
 * @param[in]  tamanho Número de elementos no array (deve ser > 0)
 * @param[out] soma    Ponteiro para armazenar a soma total (opcional, pode ser NULL)
 *
 * @return Média inteira dos elementos, ou 0 se tamanho == 0.
 *
 * @see maximo_array()
 * @note O array deve estar alocado e inicializado antes da chamada.
 */
int media_array(const int *dados, size_t tamanho, long *soma);

4.2. Documentação com ponteiros e parâmetros opacos

/**
 * @brief Inicializa o contexto do módulo de sensores.
 *
 * @param[in]  ctx      Ponteiro opaco para estrutura de contexto (já alocado)
 * @param[in]  id_sensor Identificador único do sensor (0-255)
 * @param[out] buffer   Buffer de saída com tamanho mínimo de 64 bytes
 * @param[in]  tamanho  Tamanho do buffer fornecido
 *
 * @return 0 em sucesso, -1 em erro (consulte errno para detalhes)
 *
 * @warning O buffer deve ter pelo menos 64 bytes alocados.
 */
int sensor_init(void *ctx, uint8_t id_sensor, char *buffer, size_t tamanho);

4.3. Tratamento de valores de retorno complexos

/**
 * @brief Obtém a configuração atual do dispositivo.
 *
 * @param[out] config  Ponteiro para estrutura que receberá os dados
 *
 * @return Ponteiro para a mesma estrutura em caso de sucesso,
 *         ou NULL em caso de falha. Quando NULL, a estrutura não é modificada.
 *
 * @code
 * config_t cfg;
 * if (obter_configuracao(&cfg) == NULL) {
 *     fprintf(stderr, "Falha ao obter config\n");
 * }
 * @endcode
 */
config_t* obter_configuracao(config_t *config);

5. Documentação de Estruturas, Enumerações e Macros

5.1. Documentação de struct e typedef

/**
 * @brief Representa um ponto no espaço 2D.
 *
 * Utilizado em operações geométricas e renderização.
 */
typedef struct {
    int x; /**< Coordenada X do ponto */
    int y; /**< Coordenada Y do ponto */
} Ponto;

5.2. Documentação de enum com valores individuais

/**
 * @brief Estados possíveis do sistema de alarme.
 */
typedef enum {
    ALARME_DESARMADO,  /**< Sistema inativo, sem alertas */
    ALARME_ARMADO,     /**< Sistema ativo, monitorando sensores */
    ALARME_DISPARADO,  /**< Alerta ativo, sirene ligada */
    ALARME_FALHA       /**< Erro crítico no hardware */
} EstadoAlarme;

5.3. Documentação de macros

/**
 * @brief Calcula o valor absoluto de um inteiro sem branch.
 *
 * Implementação segura para uso em ambientes críticos.
 * Não deve ser usada com INT_MIN.
 *
 * @param x Valor inteiro
 * @return Valor absoluto de x
 *
 * @warning Não funciona para INT_MIN em sistemas complemento-de-dois.
 */
#define ABS(x) ((x) < 0 ? -(x) : (x))

6. Organização do Projeto e Geração de Documentação

6.1. Uso de grupos para modularizar

/**
 * @defgroup sensores Módulo de Sensores
 * @brief Funções para leitura e processamento de sensores.
 *
 * Este módulo gerencia todos os sensores analógicos e digitais.
 * @{
 */

/** @brief Inicializa o barramento I2C para sensores. */
void sensores_init(void);

/** @brief Lê valor de sensor analógico no canal especificado. */
uint16_t sensores_leitura(uint8_t canal);

/** @} */ // fim do grupo sensores

6.2. Páginas de exemplo e arquivos .dox

Crie exemplos.dox para documentação adicional:

/**
@page exemplos Exemplos de Uso

## Exemplo básico de leitura de sensor

@code
#include "sensores.h"

int main() {
    sensores_init();
    uint16_t valor = sensores_leitura(0);
    printf("Leitura: %u\n", valor);
    return 0;
}
@endcode
*/

6.3. Integração com Makefile

# No Makefile
doc:
    doxygen Doxyfile

.PHONY: doc

7. Boas Práticas e Manutenção da Documentação

7.1. Documentação incremental

Atualize a documentação no mesmo commit que altera a API. Use revisão de código para verificar se @param e @return refletem a implementação real.

7.2. Armadilhas comuns

  • Documentação duplicada: Evite descrever o mesmo parâmetro em múltiplos lugares.
  • Desatualização: Comentários que contradizem o código geram mais confusão que ausência deles.
  • Excesso de detalhes: Documente o contrato público, não a implementação interna.

7.3. Uso de @warning e @deprecated

/**
 * @brief Função legada para configuração serial.
 *
 * @deprecated Use serial_configurar() em seu lugar.
 * Esta função será removida na versão 3.0.
 *
 * @warning Não thread-safe. Use serial_configurar() que é reentrante.
 */
void serial_config_antiga(int baud);

8. Exemplo Prático: Documentação de um Módulo C Completo

8.1. Estrutura de diretórios

projeto/
├── src/
│   └── buffer_circular.c
├── include/
│   └── buffer_circular.h
├── test/
│   └── test_buffer.c
└── Doxyfile

8.2. Documentação do cabeçalho público

/**
 * @file buffer_circular.h
 * @brief Buffer circular thread-safe para comunicação entre tarefas.
 *
 * Implementação lock-free para cenários single-producer single-consumer.
 *
 * @author Equipe Firmware
 * @version 1.0
 * @date 2025-01-15
 */

#ifndef BUFFER_CIRCULAR_H
#define BUFFER_CIRCULAR_H

#include <stddef.h>
#include <stdint.h>

/**
 * @brief Estrutura opaca do buffer circular.
 *
 * Os campos internos não devem ser acessados diretamente.
 * Use as funções públicas para manipulação.
 */
typedef struct buffer_circular buffer_circular_t;

/**
 * @brief Cria um novo buffer circular.
 *
 * @param[in] tamanho Número máximo de elementos (deve ser potência de 2)
 * @return Ponteiro para o buffer alocado, ou NULL em falha de memória
 *
 * @note O buffer usa alocação dinâmica. Deve ser destruído com buffer_destroi().
 */
buffer_circular_t* buffer_cria(size_t tamanho);

/**
 * @brief Insere um elemento no buffer.
 *
 * @param[in] buffer Ponteiro para buffer válido
 * @param[in] dado   Valor a ser inserido
 * @return 0 em sucesso, -1 se buffer estiver cheio
 */
int buffer_insere(buffer_circular_t *buffer, uint32_t dado);

/**
 * @brief Remove um elemento do buffer.
 *
 * @param[in]  buffer Ponteiro para buffer válido
 * @param[out] dado   Ponteiro para receber o valor removido
 * @return 0 em sucesso, -1 se buffer estiver vazio
 */
int buffer_remove(buffer_circular_t *buffer, uint32_t *dado);

/**
 * @brief Libera a memória do buffer.
 *
 * @param[in] buffer Ponteiro para buffer (pode ser NULL)
 *
 * @warning Após a chamada, o ponteiro não deve mais ser usado.
 */
void buffer_destroi(buffer_circular_t *buffer);

#endif /* BUFFER_CIRCULAR_H */

8.3. Geração final e visualização

Execute doxygen Doxyfile e abra doc/html/index.html. A documentação gerada incluirá:
- Lista completa de funções com descrições e parâmetros
- Gráficos de chamada (se Graphviz estiver instalado)
- Páginas de exemplo com código formatado
- Referências cruzadas entre structs e funções

Referências