Como configurar pre-commit hooks para garantir qualidade de código

1. Introdução aos pre-commit hooks e seu papel na qualidade

Pre-commit hooks são scripts executados automaticamente pelo Git antes que um commit seja efetivado. Eles funcionam como um gatekeeper: se qualquer hook falhar, o commit é bloqueado até que o problema seja corrigido. Esse mecanismo permite capturar erros antes mesmo de o código entrar no repositório.

A principal vantagem da automação via hooks é a prevenção de bugs e a padronização do código. Em vez de depender de checklists manuais ou da boa vontade de cada desenvolvedor, você define regras que são aplicadas automaticamente a cada commit. Isso reduz drasticamente o retrabalho em code reviews e acelera o onboarding de novos membros no time.

O ecossistema de pre-commit hooks é variado. O pre-commit framework (para Python e multilinguagem) é o mais flexível, enquanto o Husky é popular em projetos Node.js. Há também hooks nativos do Git, mas o framework oferece gerenciamento centralizado e atualizações automáticas.

2. Instalação e configuração básica do pre-commit framework

A instalação é simples com pip:

pip install pre-commit

Em seguida, crie o arquivo de configuração .pre-commit-config.yaml na raiz do repositório:

repos:
  - repo: https://github.com/pre-commit/pre-commit-hooks
    rev: v4.6.0
    hooks:
      - id: trailing-whitespace
      - id: end-of-file-fixer
      - id: check-yaml

Para ativar os hooks no repositório local:

pre-commit install

Comandos essenciais para o dia a dia:

# Executar hooks em todos os arquivos (útil na primeira configuração)
pre-commit run --all-files

# Atualizar versões dos hooks para as releases mais recentes
pre-commit autoupdate

3. Hooks para formatação e estilo de código

A formatação consistente é o primeiro passo para qualidade. Exemplos de hooks populares:

repos:
  - repo: https://github.com/psf/black
    rev: 24.4.2
    hooks:
      - id: black
        language_version: python3.12

  - repo: https://github.com/pre-commit/mirrors-prettier
    rev: v3.1.0
    hooks:
      - id: prettier
        types_or: [javascript, typescript, css, markdown]

  - repo: https://github.com/charliermarsh/ruff-pre-commit
    rev: v0.5.0
    hooks:
      - id: ruff
        args: [--fix]

Para evitar conflitos entre formatadores e linters, configure a ordem correta — geralmente o formatador deve ser executado antes do linter. Use args para definir opções específicas, como --fix no Ruff para correção automática de problemas.

4. Hooks para análise estática e prevenção de bugs

Além de estilo, é crucial detectar bugs potenciais e vulnerabilidades:

repos:
  - repo: https://github.com/pre-commit/mirrors-mypy
    rev: v1.10.0
    hooks:
      - id: mypy
        args: [--strict]

  - repo: https://github.com/PyCQA/bandit
    rev: 1.7.9
    hooks:
      - id: bandit
        args: [-ll, -c, pyproject.toml]

  - repo: https://github.com/pre-commit/pre-commit-hooks
    rev: v4.6.0
    hooks:
      - id: check-added-large-files
        args: [--maxkb=500]
      - id: detect-private-key

O check-added-large-files impede que arquivos grandes (como datasets ou binários) sejam commitados acidentalmente. O detect-private-key varre o código em busca de chaves SSH ou tokens expostos — uma camada extra de segurança.

5. Hooks para validação de testes e cobertura

Para garantir que testes passem antes de cada commit, configure um hook personalizado:

repos:
  - repo: local
    hooks:
      - id: pytest
        name: pytest
        entry: pytest --cov --cov-fail-under=80
        language: system
        types: [python]
        stages: [pre-push]

Observe o uso de stages: [pre-push] — isso evita que testes lentos sejam executados a cada commit, rodando apenas antes do push. Para hooks mais rápidos, use always_run: false e defina types específicos. Estratégias como essa mantêm o fluxo de trabalho ágil sem sacrificar a qualidade.

6. Hooks personalizados e scripts locais

Quando as ferramentas existentes não atendem, crie hooks locais:

repos:
  - repo: local
    hooks:
      - id: commit-msg-validator
        name: Validar mensagem de commit
        entry: scripts/validate_commit_msg.sh
        language: script
        stages: [commit-msg]

      - id: file-naming-convention
        name: Verificar nomes de arquivos
        entry: python scripts/check_filenames.py
        language: python
        types: [python]

Boas práticas para hooks locais:
- Idempotência: executar várias vezes deve produzir o mesmo resultado.
- Performance: evite scripts que demorem mais que alguns segundos.
- Logs claros: mensagens de erro devem indicar exatamente o que corrigir.

Exemplo de script validate_commit_msg.sh:

#!/bin/bash
# Valida se a mensagem de commit segue o padrão Conventional Commits
commit_msg=$(cat "$1")
pattern="^(feat|fix|docs|style|refactor|perf|test|chore)(\(.+\))?: .{1,50}"
if ! [[ $commit_msg =~ $pattern ]]; then
    echo "ERRO: Mensagem de commit inválida. Use: tipo(escopo): descrição"
    exit 1
fi

7. Integração contínua e manutenção dos hooks

Para garantir que todos os desenvolvedores e a CI rodem os mesmos hooks, adicione um passo no pipeline. Exemplo para GitHub Actions:

name: Pre-commit Checks
on: [push, pull_request]
jobs:
  pre-commit:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-python@v5
      - run: pip install pre-commit
      - run: pre-commit run --all-files

A manutenção contínua é facilitada pelo pre-commit autoupdate, que atualiza as versões dos hooks. Para automação total, configure o Dependabot para criar PRs de atualização:

version: 2
updates:
  - package-ecosystem: "pip"
    directory: "/"
    schedule:
      interval: "weekly"

Para lidar com falsos positivos, use args para excluir arquivos ou padrões específicos. Em casos urgentes, o desenvolvedor pode pular hooks com git commit --no-verify, mas isso deve ser exceção, não regra.

8. Conclusão e próximos passos

Configurar pre-commit hooks é um investimento que paga dividendos rapidamente. Os benefícios incluem:
- Consistência: todo commit segue o mesmo padrão de formatação e estilo.
- Redução de retrabalho: bugs e más práticas são capturados antes do review.
- Onboarding mais rápido: novos desenvolvedores aprendem as convenções automaticamente.

Checklist final para adoção no time:
1. Documente os hooks escolhidos e como executá-los localmente.
2. Faça um teste piloto com um pequeno grupo antes de aplicar a todos.
3. Revise periodicamente os hooks — remova os que geram muitos falsos positivos.

Lembre-se: hooks são ferramentas, não prisões. Ajuste as configurações conforme o amadurecimento do projeto. O objetivo é tornar a qualidade um subproduto natural do fluxo de trabalho, não um obstáculo.

Referências