anúncios

quarta-feira, 2 de setembro de 2026

Entendendo os algoritmos de ordenação clássicos com Big O

Quantas vezes você já precisou ordenar uma lista de elementos em sua aplicação e simplesmente chamou o método nativo .sort() da linguagem de sua preferência? Seja em JavaScript, Python ou Java, é extremamente comum confiarmos cegamente nessas abstrações no nosso dia a dia de desenvolvimento.

No entanto, você já se perguntou qual algoritmo está rodando por trás dessa instrução e até onde ele continua sendo eficiente? Em um cenário real com milhões de registros, confiar em um método sem conhecer sua complexidade computacional pode ser a diferença entre um sistema performático e um colapso total de processamento.

Exploraremos a teoria, a notação Big O e os pseudocódigos dos principais algoritmos de ordenação: Selection Sort, Quick Sort e Bubble Sort.

1. A Armadilha da Abstração e a Notação Big O

Quando trabalhamos com conjuntos pequenos de dados, como 30 ou 100 itens, quase qualquer algoritmo apresentará um tempo de resposta imperceptível. Porém, a engenharia de software exige que pensemos em grande escala.

Para medir o impacto do volume de dados no tempo de execução, utilizamos a Notação Big O. O termo O(n) indica que o tempo de execução cresce proporcionalmente ao número de elementos n da lista.

Regra importante: Ignorando as Constantes

Na análise assintótica do Big O, as constantes matemáticas são desconsideradas. Por exemplo, um algoritmo que faz O(1/2 * n²) operações é simplificado diretamente para O(n²). Isso acontece porque, quando n chega à casa dos milhões ou bilhões, cortar o tempo pela metade continua deixando o número na mesma ordem de grandeza astronômica.

2. Selection Sort (Ordenação por Seleção)

O Selection Sort é um dos algoritmos mais intuitivos e simples. A lógica consiste em percorrer a lista inteira, encontrar o menor (ou maior) elemento, posicioná-lo no início (ou em uma nova lista) e repetir o processo para os elementos restantes.

Complexidade de Tempo

  • Pior caso: O(n²)
  • Caso médio: O(n²)
  • Melhor caso: O(n²)

Como o algoritmo precisa realizar comparações aninhadas para cada item da lista, seu comportamento é quadrático em todos os cenários.

Pseudocódigo: Selection Sort

Veja a estrutura básica que você pode traduzir para a sua linguagem favorita:

funcao selectionSort(lista)
    n = tamanho(lista)
    
    para i de 0 ate n - 1 faca
        indiceMenor = i
        
        para j de i + 1 ate n - 1 faca
            se lista[j] < lista[indiceMenor] entao
                indiceMenor = j
            fimSe
        fimPara
        
        se indiceMenor != i entao
            trocar(lista[i], lista[indiceMenor])
        fimSe
    fimPara
    
    retornar lista
fimFuncao

3. Quick Sort: A Estratégia Dividir para Conquistar

O Quick Sort é um dos algoritmos de ordenação mais eficientes e utilizados na prática (inclusive compondo bibliotecas padrão de linguagens como C e Java). Ele opera através da estratégia de Dividir para Conquistar utilizando a recursão.

Como Funciona o Quick Sort

  • Caso Base: Listas com 0 ou 1 elemento já estão ordenadas por natureza. A recursão para aqui.
  • Escolha do Pivô: Seleciona-se um elemento da lista para ser a referência (pivô).
  • Particionamento: A lista é dividida em duas sublistas: uma com elementos menores que o pivô e outra com elementos maiores.
  • Recursão e Junção: O Quick Sort é chamado recursivamente para as sublistas, e o resultado final é combinado: [menores] + [pivô] + [maiores].

Complexidade de Tempo

  • Caso médio: O(n log n). O fator log n vem da divisão sucessiva do problema ao meio, enquanto o n deriva do loop de particionamento.
  • Pior caso: O(n²). Ocorre quando a escolha do pivô é ruim (por exemplo, escolher sempre o maior ou menor elemento em uma lista que já está ordenada).

Pseudocódigo: Quick Sort

Abaixo está a implementação conceitual recursiva do Quick Sort:

funcao quickSort(lista)
    se tamanho(lista) < 2 entao
        retornar lista // Caso Base
    fimSe

    pivo = lista[0] // Escolhendo o primeiro elemento como pivô
    menores = listaVazia()
    maiores = listaVazia()

    para cada elemento em lista[1..fim] faca
        se elemento <= pivo entao
            adicionar(menores, elemento)
        senao
            adicionar(maiores, elemento)
        fimSe
    fimPara

    retornar concatenar(quickSort(menores), pivo, quickSort(maiores))
fimFuncao

4. Bubble Sort (Ordenação por Bolha)

O Bubble Sort é frequentemente ensinado em cursos introdutórios. Ele percorre a lista repetidamente, comparando pares de elementos adjacentes e trocando-os de lugar se estiverem na ordem errada. Os maiores elementos "flutuam" para o final da lista como bolhas.

Complexidade de Tempo

  • Pior caso: O(n²)
  • Caso médio: O(n²)
  • Melhor caso: O(n) (quando a lista já está ordenada e há uma flag de controle de trocas).

Pseudocódigo: Bubble Sort

funcao bubbleSort(lista)
    n = tamanho(lista)
    
    para i de 0 ate n - 1 faca
        trocou = falso
        
        para j de 0 ate n - i - 2 faca
            se lista[j] > lista[j + 1] entao
                trocar(lista[j], lista[j + 1])
                trocou = verdadeiro
            fimSe
        fimPara
        
        // Se nenhuma troca ocorreu nesta passagem, a lista ja esta ordenada
        se nao trocou entao
            interromper
        fimSe
    fimPara
    
    retornar lista
fimFuncao

5. Tabela Comparativa de Desempenho

Para visualizar a diferença gritante de escala entre as complexidades de tempo, considere o impacto hipotético na execução ao ordenar um conjunto com 1.000 elementos:

Algoritmo Complexidade (Caso Médio) Complexidade (Pior Caso) Desempenho Estimado (1.000 itens)
Selection Sort O(n²) O(n²) Lento (~27 horas em escala comparativa)
Bubble Sort O(n²) O(n²) Muito Lento (Loops Aninhados)
Quick Sort O(n log n) O(n²) Muito Rápido (~996 segundos na mesma escala)

Considerações finais

Conhecer o que está por trás do método .sort() transforma a forma como escrevemos código. Embora na maioria dos casos as funções nativas sejam extremamente otimizadas, compreender o comportamento do Quick Sort, do Selection Sort e a mecânica das partições e da notação Big O fornece a autonomia necessária para diagnosticar gargalos e tomar decisões arquiteturais corretas ao trabalhar com grandes volumes de dados.

Agora que você possui o pseudocódigo em mãos, escolha a sua linguagem de programação principal (seja JavaScript, Python, C#, Java ou Go) e implemente cada um desses algoritmos para fixar o aprendizado na prática!

Feito!

sexta-feira, 28 de agosto de 2026

Como fazer perguntas certas

No universo do desenvolvimento de software, tecnologias mudam, requisitos evoluem, frameworks e linguagens se transformam. No entanto, uma habilidade permanece atemporal e crucial para a eficiência de qualquer profissional: a capacidade de obter informações precisas rapidamente. E o caminho mais curto para isso é saber fazer as perguntas certas.

O Problema da Pergunta Vaga (e por que ela falha sempre)

O exemplo mais clássico e desastroso de comunicação no dia a dia da tecnologia é a famigerada mensagem: "Oi, o aplicativo está dando erro. Você pode me ajudar?".

O exemplo do aplicativo dar erro, sem dizer qual é o problema ou onde ele ocorre, é típico de pessoas que não sabem se comunicar. Esse tipo de abordagem parte da falsa premissa de que quem vai responder sabe tudo de cabeça ou adivinhará o contexto por telepatia. Na realidade, ninguém sabe tudo; o que profissionais experientes possuem é padrão de reconhecimento acumulado. Quando você fornece a mensagem de erro exata e o contexto, fica exponencialmente mais fácil para quem responde apontar a solução mais provável.

Humano vs. Assistente de IA: A Regra é exatamente a mesma

O mesmo se aplica em como fazer perguntas certas para um humano e para um assistente de IA. Se você fizer essa mesma pergunta ruim de exemplo, nenhum humano e nenhum assistente de IA saberá responder. Já o exemplo de pergunta certa facilita tanto para o humano quanto para o assistente de IA.

A comunicação é importante para saber tirar dúvidas com um dev sênior ou com um assistente de IA, pois se não souber se comunicar bem de forma detalhada, fica difícil ajudar. Enquanto o desenvolvedor sênior perde tempo e contexto tentando extrair informações de você, a IA acabará gerando respostas genéricas, códigos irrelevantes ou alucinações. Em ambos os casos, o princípio de Garbage In, Garbage Out (Entrada ruim, saída ruim) se aplica perfeitamente.

Estrutura de uma pergunta eficiente

Uma boa pergunta economiza tempo e demonstra respeito pelo fluxo de trabalho do outro. Ela deve conter obrigatoriamente:

  • Contexto claro: Qual tarefa, ticket ou projeto você está executando.
  • O erro exato: Mensagem de exceção (ex: NullPointerException), logs relevantes ou comportamento retornado.
  • O que já foi tentado: Hipóteses que você já testou ou verificações que realizou no código.
  • Localização: A classe, endpoint ou função onde o problema ocorre.

O ciclo das 2 horas: Equilíbrio entre autonomia e dependência

Ao buscar suporte técnico, a maioria dos devs cai em dois extremos perigosos: ou passam dias travados sem perguntar nada (prejudicando a deadline e as dailies), ou perguntam a cada pequeno obstáculo sem sequer tentar pesquisar (tornando-se um fardo para a equipe).

Para encontrar o ponto de equilíbrio e manter a produtividade alta, adote a regra do Ciclo de 2 Horas antes de solicitar ajuda:

  • 10 a 15 minutos: Busque por um código parecido ou uma funcionalidade similar dentro do próprio projeto para se basear.
  • 5 a 10 minutos: Consulte a documentação interna/da linguagem ou questione seu assistente de IA preferido.
  • 30 a 70 minutos: Tente implementar e debugar a solução na prática.

Se após esse tempo o problema persistir, é o momento exato de pedir ajuda a um colega, trazendo todo o contexto estruturado do que você aprendeu e testou nessas duas horas.

Exceções ao ciclo de 2 Horas

Existem cenários onde você não deve esperar o ciclo terminar para pedir suporte:

  • Tarefas com deadlines urgentes em ambiente de produção.
  • Devs iniciantes ou recém-chegados que receberam uma demanda crítica por engano.
  • Trabalhos que exigem atuação pareada (pair programming) ou em times globais.

Considerações finais: Documente tudo

Por fim, após resolver qualquer dúvida, seja com a ajuda do seu time ou do assistente de IA, documente tudo o que você aprendeu. Manter um registro simples de soluções serve como base de conhecimento para consultas futuras, ajuda seu "eu do futuro" e contribui para a evolução técnica de toda a equipe.

Saber se comunicar com clareza e fazer as perguntas certas é a ferramenta mais poderosa para acelerar sua carreira e se destacar como um desenvolvedor eficiente.

Feito!

quinta-feira, 27 de agosto de 2026

Meu histórico do git parece um desastre, como eu conserto?

O Problema

Você abriu o git log e encontrou algo assim:

fix stuff
wip
tentativa 2
eslint de novo
bug fix
como assim nao funciona
REVERT
feat: adds feature X (nao funciona ainda)
me ajuda por favor
fix: arruma o fix anterior

Seu histórico está bagunçado, commits inúteis poluem o repositório, e o pior: alguém pode ver isso no pull request.

A Causa

Falta de disciplina com mensagens de commit, commits demais para Changes pequenos, e uso incorreto do git add . commitando tudo junto.

A Solução

1. Reescreva os últimos N commits com rebase interativo

git rebase -i HEAD~5

Isso abre um editor com os últimos 5 commits. Você pode:

pick   abc1234 feat: adiciona login
squash def5678 fix: ajusta layout
squash ghi9012 wip: tentativa 2
pick   jkl3456 feat: adiciona logout

squash combina o commit no anterior. fixup faz o mesmo mas descarta a mensagem.

2. Corrija a última mensagem de commit

git commit --amend -m "feat: adiciona autenticação com JWT"

3. Desfaça o último commit mantendo as mudanças

git reset --soft HEAD~1

As mudanças ficam staged para você reorganizar com git add seletivo.

4. Desfaça completamente o último commit

git reset --hard HEAD~1

Cuidado: isso apaga as mudanças permanentemente.

5. Limpe dados sensíveis já commitados

git filter-branch --force --index-filter \
  'git rm --cached --ignore-unmatch .env' \
  --prune-empty --tag-name-filter cat -- --all

Depois force o push:

git push origin --force --all

6. Organize com staging seletivo

git add -p

Isso abre um modo interativo onde você escolhe pedaços específicos do arquivo para commitar.

Antes vs Depois

Antes Depois
fix stuff feat: adiciona autenticação JWT
wip feat: adiciona tela de login
tentativa 2 fix: ajusta validação do formulário
como assim nao funciona test: adiciona testes unitários
REVERT chore: atualiza dependências

Regra de Ouro

Uma mensagem de commit deve explicar o quê mudou e por quê. Se você não consegue explicar em uma linha, talvez deveria ser dois commits.

Nunca force push em branches compartilhadas sem avisar o time.

Feito!

terça-feira, 25 de agosto de 2026

Por que minha query SQL está demorando 30 segundos?

O Problema

Você tem uma tabela pedidos com milhões de registros e precisa buscar pedidos de um cliente específico. A query simples assim demora uma eternidade:

SELECT * FROM pedidos WHERE cliente_id = 12345;

O MySQL retorna 30 segundos de espera. O PostgreSQL idem. O SQLite? Nem fala.

A Causa

Tabela sem índice na coluna cliente_id. O banco faz Full Table Scan — lendo todos os registros para encontrar os que correspondem ao critério.

A Solução

1. Adicione um índice

CREATE INDEX idx_pedidos_cliente_id ON pedidos(cliente_id);

Resultado: de 30s para 0.003s

2. Use EXPLAIN para analisar

EXPLAIN SELECT * FROM pedidos WHERE cliente_id = 12345;

Veja se o plano mostra Using index (bom) ou Using filesort/Using temporary (ruim).

3. Evite SELECT *

Ruim SELECT * FROM pedidos WHERE cliente_id = 12345; Bom SELECT id, valor, data FROM pedidos WHERE cliente_id = 12345;

4. Verifique os índices existentes

SHOW INDEX FROM pedidos;

5. Use composição de índices quando necessário

-- Para queries que filtram por cliente E data CREATE INDEX idx_pedidos_cliente_data ON pedidos(cliente_id, data DESC);

Resultado Final

Métrica Antes Depois
Tempo de resposta 30s 0.003s
Full Table Scan Sim Não
Uso de memória Alto Baixo

Regra de Ouro

Se uma coluna aparece em WHERE, JOIN ou ORDER BY, considere criar um índice nela.

Índices não são mágicos — eles ocupam espaço em disco. Mas na maioria dos casos, o ganho de performance vale muito mais que o custo de armazenamento.

Feito!

sexta-feira, 21 de agosto de 2026

15 anos de blog

O blog está completando hoje, 21 de Agosto de 2026, 15 anos!

Gostaria de agradecer a todos pelo prestígio e a paciência de ler os posts que são publicados. Espero continuar compartilhando conhecimentos no blog para nossos leitores. Continuem prestigiando e divulgando o blog Mundo da Computação Integral, assim aumentamos nossa comunidade.

quinta-feira, 20 de agosto de 2026

Seu Lint não esta configurado (e isso é um risco)

Três revisores de código passaram por este bug. Nenhum viu.

O pull request tinha 200 linhas. Testes passando. Code review aprovado.

Mas escondido na linha 47, um simples == em vez de === estava desabilitando uma feature inteira de pagamento.

O JavaScript, com sua tipagem dinâmica, tratou o valor 0 como false.

E o que deveria ser uma verificação de status virou um gate que bloqueava todo o fluxo de checkout.

O time de suporte começou a receber tickets de "pagamento não processa".

A equipe de pagamentos revisou o gateway. Tudo normal.

O backend revisou a API. Tudo normal.

O frontend revisou o formulário. Tudo normal.

A solução levou 5 minutos. O debug levou 3 dias.

E o pior? Isso poderia ter sido evitado com uma configuração no ESLint:

"eqeqeq": ["error", "always"]

Uma linha no .eslintrc. Uma regra que impede exatamente esse tipo de bug.

Mas a equipe não tinha o lint configurado no CI. O commit passava sem verificação de qualidade.

Adicionamos:

  1. Regra eqeqeq no ESLint, == vira erro, não warning.
  2. Pre-commit hook com husky impede o commit se o lint falhar.
  3. CI pipeline que rejeita PR com lint errors, nenhum merge sem qualidade.

A lição?

Bugs não são sobre código complexo. São sobre código simples que ninguém prestou atenção.

== e === parecem iguais. Mas um aceita 0 == false como true.

O outro te protege.

Qual foi o bug mais "besta" que você já encontrou em produção? Compartilha aí.

Feito!

quarta-feira, 19 de agosto de 2026

Seu container está com o relogio errado (e você não sabe)

Toda segunda-feira às 8h da manhã, a equipe recebia um tsunami de tickets de "não consigo logar".

O sistema de autenticação estava quebrando religiosamente, toda segunda-feira.

Depois de 3 semanas investigando, encontrei o culpado:

O JWT tinha expiresIn: "7d", parecia correto, né?

Mas o relógio do servidor de autenticação estava 3 minutos adiantado.

E o servidor que validava os tokens? Com o relógio correto.

Resultado: o token expirava antes de deveria. E a diferença foi acumulando com o drift do relógio.

Toda segunda-feira, o acumulado de 7 dias de drift ultrapassava o threshold de tolerância.

Usuários bloqueados. Help desk sobrecarregado. Time de DevOps no modo DETETIVE.

A solução foi simples, mas ninguém pensou nela antes:

  1. Sincronizar NTP com chronyd dentro do container, porque container sem NTP é uma bomba relógio
  2. Middleware de refresh com margem de 5 minutos antes da expiração, o token renovava antes de vencer, eliminando a janela de falha.
  3. Monitoramento de drift de relógio entre servidores — se a diferença passar de 1 minuto, alerta automático.

A moral da história?

Às vezes o bug não está no código. Está na infraestrutura que sustenta o código.

Nós revisamos o JWT, testamos a lógica de expiração, validamos o fluxo de refresh. Tudo parecia perfeito.

O problema estava em algo que ninguém olhou: o relógio do container.

Você já perdeu horas debugando algo que parecia código, mas era infraestrutura?

Feito!