O Código Vai Quebrar – E Não do Jeito que Você Espera!
Você já se pegou olhando para seu código e pensando: “Isso aqui tá lindo, não tem como dar erro?”. Pois é, todo mundo já passou por isso. Você rodou os testes, tudo deu verde, a funcionalidade principal flui perfeitamente… e, por um breve momento, você sente aquela confiança de que fez um ótimo trabalho.
Mas aí a realidade vem e dá uma voadora no seu código. Um usuário preenche um campo com um texto que você não esperava, a API que você consome decide dar timeout sem aviso, um banco de dados retorna null em uma consulta onde jamais deveria – e pronto, seu castelo de cartas desmorona.
O problema? Você escreveu código só para o caminho feliz, aquele fluxo ideal onde tudo dá certo. Mas o mundo real não funciona assim. Sistemas vivem em ambientes caóticos, interagem com serviços imprevisíveis e são usados por pessoas que não fazem questão de seguir o manual. E se você não planejar para o inesperado, pode ter certeza: seu código vai quebrar, e você vai ter que correr atrás do prejuízo.
Este artigo é um papo reto sobre isso. Vamos explorar onde a ingenuidade custa caro no desenvolvimento de software.
O caminho do dia a dia…
O processo de desenvolvimento geralmente deveria seguir um roteiro previsível:
1. Coleta de requisitos – O que o sistema precisa fazer?
2. Especificação funcional – Como o sistema deve se comportar?
3. Especificação técnica – Como implementamos isso?
4. Codificação – A funcionalidade começa a tomar forma.
5. Testes e correção de bugs – O código passa por um ciclo de revisão até estar “pronto”.
Até aqui, tudo bem. Mas tem um detalhe: a maioria dos desenvolvedores foca apenas em entregar a funcionalidade pedida. E, para isso, eles seguem um caminho muito específico: o caminho feliz.
O que é o Caminho Feliz?
A definição da Wikipédia resume bem (mas os dicionários também 😅):
“No contexto de software ou modelagem de informações, um caminho feliz é um cenário padrão que não apresenta condições excepcionais ou de erro e compreende a sequência de atividades executadas se tudo ocorrer conforme o esperado.”
Ou seja, o caminho feliz é aquele fluxo perfeito, onde o usuário faz exatamente o que esperamos, os dados chegam formatados corretamente e o sistema funciona como planejado e escrito.
Um exemplo simples: imagine uma função que valida números de cartão de crédito. O caminho feliz é quando o usuário insere um número correto, o formato está certo, e todas as regras passam sem erro. Fácil, né?
Agora, o que acontece quando:
• O usuário digita letras no campo do cartão?
• O número está certo, mas tem espaços extras no meio?
• A API de validação do cartão está fora do ar?
Aí vem o problema. Muitos sistemas não preveem esses cenários, e quando eles acontecem, o código desmorona.
O Caminho Feliz vs. A Realidade Cruel
Quando estamos desenvolvendo, seguimos um fluxo lógico: sabemos quais dados entram, o que o código precisa fazer e o que sai. Mas há um problema: nossos usuários não sabem disso. Eles vão inserir coisas erradas, clicar em botões antes da hora e fazer coisas que nunca cogitamos.
O problema é que muitos desenvolvedores escrevem código assumindo que tudo dará certo. E quando algo sai do esperado, o resultado é um erro inesperado – um crash em produção, uma falha silenciosa ou, pior, um comportamento estranho que ninguém sabe como reproduzir.
Vamos ver isso na prática.
Falha ao lidar com APIs instáveis
Agora, imagine um sistema que busca informações de um cliente em uma API externa. O desenvolvedor escreve um código simples e direto, onde faz uma requisição e retorna o nome do cliente. No caminho feliz, tudo funciona perfeitamente – o cliente existe, a API responde rápido e os dados vêm completos.
Mas e quando algo sai do esperado? Aqui estão alguns cenários problemáticos que ninguém previu:
• O cliente não existe → O código assume que a API sempre retornará um objeto válido com um campo nome, mas se o ID não for encontrado, a resposta pode ser null, um objeto vazio ou um erro HTTP. O resultado? O sistema tenta acessar nome em um dado inexistente e lança uma exceção.
• O ID do cliente não é válido → O código não verifica se o ID é realmente um número válido. Se um valor inesperado (como null, undefined ou uma string) for passado, a requisição pode ser feita para uma URL inválida, causando falha na conexão.
• A API está fora do ar → O desenvolvedor assume que a API sempre responderá corretamente, mas e se o servidor estiver temporariamente indisponível? O código falhará com um erro de conexão, e sem um tratamento adequado, isso pode derrubar toda a aplicação.
• A resposta da API está lenta ou incompleta → Se a API demorar muito para responder, o código pode travar esperando indefinidamente. Além disso, a resposta pode vir truncada ou com um formato inesperado, quebrando o fluxo da aplicação.
O erro aqui? O código foi escrito apenas para o cenário perfeito, onde tudo funciona. Mas no mundo real, sistemas são instáveis, usuários cometem erros e serviços externos podem falhar sem aviso. Se você não planejar para o pior, seu código vai quebrar quando mais precisar dele.
Processando valores de entrada sem validar
Imagine um sistema que recebe a quantidade de produtos em um pedido e calcula o total. O caminho feliz assume que sempre receberemos um número válido.
Código ingênuo:
function calcularTotal(preco: number, quantidade: number): number {
return preco * quantidade;
}
console.log(calcularTotal(10, 2)); // 20 ✅
Parece perfeito, certo? Agora veja o que acontece quando algo sai do caminho feliz.
Testando com valores inesperados:
console.log(calcularTotal(10, "dois" as any));
// NaN 😱 -> Multiplicação de número com string
console.log(calcularTotal(10, undefined as any));
// NaN 😱 -> Multiplicação com undefined
console.log(calcularTotal(10, null as any));
// 0 😱 -> Opa, isso passou "despercebido"?
A questão é: o desenvolvedor assumiu que o input sempre seria válido. Mas em produção, isso nem sempre acontece.
Problema Silencioso ao Acessar Propriedades de Objetos Indefinidos
Esse é um dos erros mais traiçoeiros que um desenvolvedor pode cometer: assumir que um objeto sempre terá os dados esperados.
O problema acontece porque, quando escrevemos código, temos uma visão clara da estrutura que ele deveria receber. Mas no mundo real, isso raramente é garantido.
Pense em um simples objeto que deveria conter informações de um usuário. O desenvolvedor assume que ele sempre terá a propriedade nome, e sem pensar muito, acessa diretamente essa propriedade no código.
Agora, o que acontece quando esse objeto:
• Nunca foi inicializado e está undefined? O código vai tentar acessar algo que nem existe na memória, resultando em um erro.
• Foi declarado, mas não recebeu os valores esperados? Se alguém esqueceu de definir nome, a propriedade simplesmente não está lá. O código não tem o que acessar.
• Recebeu um formato diferente? O objeto pode ter sido estruturado de outra forma, contendo apelido ao invés de nome, por exemplo. Mas o código, cego para essa possibilidade, tentará acessar nome e falhará.
• Veio de um banco de dados incompleto? Se a informação do usuário foi cadastrada parcialmente ou se o dado foi corrompido, algumas propriedades podem estar ausentes sem que ninguém perceba até o erro acontecer.
• Foi sobrescrito por um valor null? Dependendo de como o dado foi manipulado antes de chegar ao código, a variável pode ter sido substituída por null, causando um erro inesperado ao tentar acessar suas propriedades.
O grande perigo desses cenários é que o código quebra sem aviso. Não há um erro de compilação que impeça o deploy, e tudo pode parecer funcional até que, um dia, o dado errado apareça e derrube a aplicação.
Código que só funciona no caminho feliz não está pronto
A ilusão de um código “pronto” acontece porque ele só é testado nos cenários esperados. Mas quando usuários reais ou sistemas instáveis interagem com ele, os problemas aparecem.
• Inputs mal formatados quebram operações matemáticas.
• APIs externas podem falhar ou retornar respostas incompletas.
• Acessar propriedades de objetos indefinidos gera exceções inesperadas.
E isso nos leva ao próximo ponto que quero conversar com vocês.
Por que desenvolvedores não são bons testadores? Podemos melhorar?
Seja sincero: quantas vezes você escreveu código, testou apenas o fluxo principal, viu tudo funcionando e se sentiu satisfeito? Você sabia que existiam caminhos alternativos, mas pensou: “Ah, dificilmente isso vai acontecer.” Ou talvez você simplesmente não quisesse gastar tempo lidando com essas possibilidades porque já estava ansioso para partir para a próxima tarefa.
Esse comportamento é muito comum. Nós, desenvolvedores, gostamos de construir. Criar algo funcional e ver o código rodando é recompensador. Mas testar? Testar significa admitir que o código inicial provavelmente não está pronto, que existem falhas, que teremos que refatorar. E, sejamos honestos, ninguém gosta de ver seu próprio trabalho desmoronando.
Agora, pense comigo: os testes de segurança dos carros funcionam assim? Será que as montadoras simplesmente colocam um carro em linha reta, batem contra uma parede e conferem se os bonecos sobreviveram? Ou será que há muito mais envolvido?
Engenheiros de Colisão: O Que Podemos Aprender Com Eles?
Se tem uma coisa que os engenheiros de segurança veicular fazem bem, é não confiar cegamente no design inicial de um carro. Eles sabem que o projeto no papel pode parecer perfeito, mas a realidade é sempre mais dura.
Eles não olham para um carro e dizem: “Bom, teoricamente a estrutura parece resistente, os airbags estão posicionados corretamente, então acho que ele é seguro!” Não. Eles jogam o carro contra uma parede a 50 km/h, batem nele de lado, simulam atropelamentos e observam como ele falha.
E quando falha – porque sempre falha –, eles voltam para o projeto, fazem ajustes e testam de novo, e de novo, e de novo até que o veículo esteja realmente preparado para as colisões do mundo real.
Agora me diz: por que raios engenheiros de software não pensam da mesma forma?
O Trabalho dos Engenheiros de Testes de Impacto
Esses profissionais não estão só jogando carros contra barreiras por diversão (embora, sejamos sinceros, deva ser um trabalho bem legal). Eles têm um objetivo claro: garantir que o veículo proteja ao máximo seus ocupantes e minimize riscos para pedestres em um acidente real.
Isso significa que eles precisam considerar diversos fatores:
🚗 A estrutura do carro – O metal amassa da forma certa para absorver o impacto? Ou transfere a força para os passageiros?
🦾 Os ocupantes – Como um corpo humano se move em um impacto? O cinto segura a pessoa sem machucar? O airbag protege ou pode causar mais danos?
🚶 Os pedestres – Se um carro atinge alguém, o impacto pode ser reduzido para minimizar ferimentos fatais?
⚖ Normas e regulamentações – O veículo atende às exigências de segurança dos órgãos reguladores como Euro NCAP, ANCAP, NHTSA e IIHS?
Eles trabalham com simulações computacionais para prever como o carro se comportaria em diferentes tipos de colisão, mas também realizam testes físicos, porque simulação não é garantia de que tudo vai funcionar perfeitamente na prática.
Eles sabem que acidentes não acontecem só de um jeito e, por isso, realizam sete testes principais para cobrir os cenários mais comuns e perigosos.
1️⃣ Colisão frontal contra barreira sólida – Simula um carro batendo direto contra um muro.
2️⃣ Impacto lateral (T-bone) – Testa a segurança quando outro veículo bate na lateral do carro.
3️⃣ Impacto lateral distante – Avalia o que acontece quando a batida ocorre no lado oposto ao motorista.
4️⃣ Teste de poste oblíquo – Simula um carro saindo da pista e batendo em um poste ou árvore.
5️⃣ Proteção de pedestres – Mede o impacto do carro sobre uma pessoa em caso de atropelamento.
6️⃣ Proteção contra chicotadas – Avalia o impacto da colisão traseira na cabeça e pescoço dos ocupantes.
7️⃣ Teste de colisão frontal com barreira deformável – Simula o impacto entre dois veículos reais.
Eles não fazem apenas um teste e dizem que o carro está seguro. Eles o jogam contra diferentes obstáculos, em velocidades variadas, sob ângulos inesperados.
E, acima de tudo, eles não assumem que um design perfeito no papel significa um carro seguro na realidade.
Agora Vamos Falar dos Engenheiros de Software
Se você é desenvolvedor, já percebeu onde quero chegar.
Nós, engenheiros de software, muitas vezes não temos essa mentalidade de colisão.
Nós olhamos para nosso código, testamos no caminho feliz e dizemos: “É, parece bom.” E mandamos para produção.
Ou pior: jogamos tudo nas costas do QA e assumimos que testar é problema de outra pessoa.
🚨 Isso é perigoso.
Muitos desenvolvedores, especialmente os mais seniores, caem em uma armadilha perigosa: acham que sua experiência os torna imunes a erros.
Eles escrevem código, rodam um teste básico e, se não deu erro de cara, acreditam que está tudo bem. Se alguém sugere mudanças ou aponta possíveis falhas, a reação muitas vezes é defensiva:
“Meu código está certo.”
“Eu sei o que estou fazendo.”
“Se der problema, o QA pega.”
Agora imagine se os engenheiros de colisão tivessem essa mentalidade:
“Eu calculei a estrutura do carro, então ele é seguro.”
“Eu sei que o airbag funciona, não preciso adicionar ele ao teste de colisão.”
“Se der problema, os clientes que compraram o carro avisam.”
Isso seria irresponsável e criminoso.
Mas porque achamos aceitável fazer isso com software?
O Código Precisa Ser Testado Como Se Fosse um Carro em Um Crash Test
Os testes de colisão automotiva não se resumem a um único impacto frontal. Eles simulam os mais diversos cenários porque sabem que, no mundo real, acidentes não seguem um script. Um carro pode capotar, pode ser atingido de lado, pode colidir com um objeto fixo ou ser atingido por outro veículo em movimento.
Para evitar que vidas sejam perdidas, órgãos como a ANCAP (Australasian New Car Assessment Program) realizam sete testes principais, citados anteriormente, vamos conversar melhor sobre cada um:
1. Teste Frontal de Largura Total
Imagine um motorista distraído que, por um segundo, não vê um muro à sua frente. Esse teste simula exatamente essa situação: um carro indo direto contra uma parede sólida a 50 km/h. Parece um teste simples, certo? Mas o objetivo não é só ver se o carro fica amassado. Eles verificam como os airbags reagem, se os cintos de segurança seguram os passageiros corretamente e se o impacto é distribuído de maneira que proteja os ocupantes.
Agora pense no seu código. Quando você testa, você só verifica se ele retorna a resposta esperada? Ou você analisa como ele lida com situações inesperadas, como falhas em APIs, dados incompletos ou inputs absurdos?
2. Teste de Impacto Lateral
Nem todo acidente é uma batida frontal. Muitos motoristas são atingidos na lateral, onde a proteção estrutural é menor. Para isso, um carrinho motorizado colide na porta do motorista a 60 km/h, simulando um T-bone.
E no software? Você já testou o que acontece se um usuário tentar acessar uma página que não existe? Ou se ele clicar duas vezes no botão de “Confirmar” e enviar a mesma requisição duas vezes? O seu sistema pode lidar com concorrência e acessos simultâneos?
3. Teste de Impacto Lateral Distante
Se um carro for atingido do lado oposto ao motorista, o impacto pode ser menos direto, mas pode lançar passageiros contra a estrutura interna do veículo. Esse teste mede como os ocupantes se movem internamente durante um acidente e se há perigo de ferimentos devido à colisão indireta.
Nos sistemas de software, um erro pode não quebrar imediatamente, mas pode causar efeitos colaterais perigosos. Você já testou se um erro no login pode afetar a sessão de outro usuário? Se um campo que deveria ser obrigatório não está sendo validado corretamente, permitindo que dados inconsistentes entrem no sistema?
4. Teste de Pólo Oblíquo
Este teste simula um carro saindo da pista e batendo em um objeto fixo – um poste ou uma árvore. Diferente da batida frontal tradicional, o impacto ocorre em um ângulo, e a força da colisão pode atingir diretamente a cabeça do motorista.
E no código? Você já considerou que um sistema pode ser usado de maneira inesperada? Um formulário que deveria aceitar apenas números pode ser preenchido com caracteres especiais. Uma API que deveria receber apenas JSON pode ser chamada com um XML mal formatado. Você já testou o seu código contra usos imprevistos?
5. Proteção de Pedestres
Os testes de colisão não consideram apenas os ocupantes do carro. Eles também analisam o impacto sobre pedestres, medindo o risco de ferimentos caso um carro atinja uma pessoa a 40 km/h. Onde o impacto ocorre? A frente do carro absorve o impacto ou transfere toda a força para o corpo da vítima?
Agora, pense no seu sistema. Você já considerou os impactos colaterais de uma falha? Se um usuário tentar recuperar uma senha, o sistema dá uma mensagem genérica do tipo “Usuário não encontrado”, ou ele vaza informações ao dizer “Esse e-mail não está cadastrado” permitindo que atacantes descubram quais e-mails existem na base de dados?
6. Proteção contra chicotadas
A colisão traseira pode não ser letal, mas pode causar danos sérios ao pescoço. Para isso, os testes colocam assentos em uma plataforma móvel que simula o impacto. O objetivo não é apenas ver se os encostos de cabeça estão lá, mas se eles realmente protegem os ocupantes e absorvem o choque da forma correta.
E no software? Será que o código não só lida com erros, mas também se recupera bem deles? Se um serviço externo falhar, o sistema consegue continuar funcionando, ou ele trava completamente? Se um usuário perder a conexão, ele pode retomar o que estava fazendo, ou precisa começar tudo do zero?
7. Teste de Barreira Deformável Progressiva Móvel
Aqui, dois carros colidem um contra o outro a 50 km/h, mas apenas uma parte da frente é impactada. Isso simula colisões comuns em estradas, onde nem sempre um carro bate de frente, mas sim com uma quina da lataria. A estrutura absorve o impacto corretamente ou transfere toda a força para os ocupantes?
Em software temos algo semelhante, um erro pode afetar apenas parte do código. Mas será que ele não pode se propagar? Um simples erro de cálculo pode causar um débito bancário incorreto. Um bug em um sistema de estoque pode gerar pedidos duplicados. Você já testou seu código para entender quais partes podem impactar outras?
Testar Significa Quebrar Antes Que o Usuário Quebre
Os engenheiros automotivos sabem que acidentes vão acontecer, por isso testam exaustivamente todos os cenários possíveis. Mas no software, ainda resistimos. Muitos desenvolvedores tratam testes como um obstáculo, um desperdício de tempo. Mas testar não significa perder tempo. Significa descobrir falhas antes que os usuários as descubram por você.
O código pode estar “funcionando” agora, mas você já testou como ele quebra?
Qual é o meu objetivo com esse artigo? Testar vai além de simplesmente executar um código e ver se o output está de acordo. Um desenvolvedor que realmente se importa precisa enxergar além do óbvio, antecipando problemas antes que eles se tornem desastres. Bora para o próximo tópico e conversar mais sobre isso!
Como um engenheiro de software que se importa busca compreender além do caminho feliz?
Um engenheiro de software que realmente se importa não se limita a escrever código que “funciona” no cenário ideal. Ele sabe que software é uma entidade viva, que interage com sistemas, usuários e condições incontroláveis. O objetivo não é apenas entregar funcionalidade, mas garantir que ela não cause problemas, não exponha falhas críticas e não se torne um risco operacional.
Mas o que esse profissional avalia? Como ele pensa? Como ele age para evitar as brechas que podem colocar um sistema inteiro em risco?
A Diferença Entre “Funciona” e “Está Sólido”
Muitos desenvolvedores cometem um erro comum: testar apenas para confirmar que o código faz o que foi solicitado. Mas um engenheiro de software que se importa sabe que testar vai muito além disso. Ele se pergunta:
✅ “Meu código falha de maneira controlada?”
✅ “Se algo inesperado acontecer, ele se recupera ou desmorona?”
✅ “Existem brechas evidentes que podem ser exploradas?”
✅ “O comportamento do sistema se mantém estável sob carga, erro ou entradas maliciosas?”
A preocupação não é apenas se o código executa corretamente, mas se ele resiste ao uso no mundo real.
Brechas Nunca Podem Passar Despercebidas
Brechas em software não são apenas bugs. Elas podem derrubar sistemas, abrir portas para ataques e comprometer a experiência do usuário de forma irreversível. Nenhum software está livre de falhas, mas um desenvolvedor cuidadoso age para mitigar riscos antes que eles se tornem problemas reais.
Existem três formas principais de encontrar e reduzir brechas:
1. Testes Automatizados
• Testes unitários verificam cada pequeno componente isoladamente.
• Testes de integração garantem que diferentes módulos funcionam juntos.
• Testes end-to-end simulam a jornada do usuário do início ao fim.
• Testes de carga e stress mostram como o sistema responde sob pressão.
Mas um detalhe importante: testes automatizados só encontram aquilo que foram programados para encontrar.
2. Testes Manuais e Exploratórios
• Um engenheiro experiente não confia apenas em testes automatizados. Ele testa manualmente, explorando inputs inesperados (e tenta incluir isso nos testes automatizados se for possível) e verificando se o software realmente se comporta como deveria.
• Ele tenta pensar como um usuário desatento que pode errar preenchendo um formulário.
• Ele simula cenários caóticos, como desligar o Wi-Fi no meio de uma requisição ou enviar valores extremos que ninguém considerou.
3. Revisão da Lógica e Discussão em Equipe
• Code reviews são uma das formas mais eficazes de evitar problemas antes que eles cheguem ao usuário. O olhar de um colega pode enxergar falhas que o próprio autor do código ignorou.
• Pair programming permite que um desenvolvedor questione e refine a abordagem do outro em tempo real.
• Discussões técnicas sobre arquitetura e segurança ajudam a antecipar riscos antes mesmo do código ser escrito.
O Engenheiro Sábio Sabe Que Não Existe Código Perfeito
O erro de muitos desenvolvedores é buscar perfeição técnica, acreditando que o código mais performático e elegante é sempre o melhor. Mas a realidade do software em produção é muito mais cruel:
🚨 Código rápido e otimizado pode ser inútil se não for seguro.
🚨 Código bem estruturado pode ser um desastre se não for resiliente.
🚨 Código que funciona no ambiente de desenvolvimento pode falhar miseravelmente em produção.
O bom engenheiro não busca apenas eficiência e beleza no código. Ele busca robustez, confiabilidade e previsibilidade.
O Que Está Em Jogo?
Quando um software falha, o impacto pode ser enorme. Pense nos seguintes cenários:
• Um bug permite que um cliente veja os dados bancários de outra pessoa.
• Um erro faz com que uma loja online cobre valores errados em transações.
• Um sistema crítico de uma empresa falha e causa prejuízo de milhões por uma indisponibilidade de minutos.
Cada brecha ignorada pode se tornar um desastre operacional, financeiro e reputacional.
Se o desenvolvedor não pensa além do caminho feliz, o risco de que esses problemas aconteçam aumenta exponencialmente.
“Programadores escrevem código. Programadores experientes escrevem código que funciona em determinadas situações. Programadores sábios escrevem testes para provar que seu código se comporta de maneira adequada até nas situações extremas!”
A Responsabilidade do Engenheiro que se Importa
Um engenheiro que se importa não está satisfeito apenas com “funciona na minha máquina”. Ele se faz perguntas como:
🔍 “Eu testei os piores cenários possíveis?”
🔍 “O sistema se recupera bem de falhas?”
🔍 “Posso melhorar a segurança sem comprometer a usabilidade?”
🔍 “Este código pode ser abusado de alguma forma que eu não previ?”
🔍 “Como um usuário mal-intencionado poderia tentar quebrar isso?”
Ele entende que não é só sobre escrever código. É sobre garantir que o software entregue valor de forma confiável, segura e previsível.
Depois de tudo isso, pare e reflita: como você tem escrito e testado seu código?
• Você realmente testa além do caminho feliz ou assume que “dificilmente isso vai acontecer”?
• Você considera os impactos operacionais e de segurança do seu código ou só foca em performance e elegância?
• Você discute riscos com colegas, revisa sua lógica e busca ativamente problemas antes que eles virem um incêndio em produção?
A realidade é: não importa quão bom você seja tecnicamente, se você ignora falhas óbvias, você não é um bom engenheiro.
O verdadeiro engenheiro de software não é aquele que escreve código impecável – porque isso não existe. Ele é aquele que reduz ao máximo os riscos e as incertezas.
Como Evitar o Fracasso
Se você acompanhou essa conversa até aqui, já deve ter percebido um padrão: muitos problemas de software acontecem porque os desenvolvedores não pensam além do caminho feliz. Mas e agora? Como evitar que falhas previsíveis cheguem ao usuário final? Como garantir que o código não se torne uma bomba-relógio esperando para explodir?
A resposta está em testar de forma inteligente e pensar nos erros antes que eles aconteçam.
O Poder de Escrever Testes Antes do Código
Uma das formas mais eficazes de evitar falhas é escrever testes unitários enquanto você desenvolve o código. Parece óbvio, mas aqui está o segredo: testar primeiro faz você pensar melhor antes de codar.
Quando você escreve um teste antes do código, precisa se perguntar:
✅ O que essa funcionalidade realmente deve fazer?
✅ Quais valores são permitidos? Existem limites mínimos e máximos?
✅ Quais valores inesperados o código pode receber?
✅ O que deve acontecer quando ocorre um erro?
Isso força o desenvolvedor a antecipar situações inesperadas e planejar como o código deve reagir. Afinal, é muito mais fácil evitar um problema antes que ele aconteça do que corrigir um sistema quebrado em produção.
O grande problema é que muitos desenvolvedores estão ansiosos para começar a codar e pulam essa etapa. Eles escrevem o código primeiro, verificam se funciona no caminho feliz e só depois – se houver tempo e paciência – pensam em testes.
Mas tem um detalhe importante: depois que o código já está escrito, escrever testes se torna muito menos interessante.
Isso acontece porque o desenvolvedor já acredita que o código está funcionando. Ele já passou mentalmente pelo fluxo e validou que “faz sentido”. Escrever testes depois parece um trabalho extra, chato e desnecessário.
E é aí que as falhas começam a escapar.
Erros São Inevitáveis… Mas Como Você Lida Com Eles?
O código vai encontrar erros em produção. O que diferencia um software confiável de um sistema frágil não é a ausência de falhas, mas sim como ele lida com elas.
Quando um erro acontece, há basicamente três formas de reagir:
1️⃣ Falhar rápido e abortar a ação – Se algo inesperado acontecer, o sistema para imediatamente. Nenhum dado é modificado, e a falha fica explícita para que possa ser corrigida.
2️⃣ Continuar e avisar o usuário – Em alguns casos, o sistema pode tentar seguir em frente, contornando a falha e notificando o usuário de forma clara.
3️⃣ Ignorar o erro e seguir como se nada tivesse acontecido – A pior decisão possível. Isso pode causar efeitos colaterais inesperados e gerar problemas silenciosos que só serão descobertos muito tempo depois.
A pior coisa que pode acontecer não é um erro travar o sistema. É um erro alterar dados críticos sem que ninguém perceba. Um bug que, silenciosamente, duplica pedidos, altera valores errados ou corrompe informações pode custar milhões.
Por isso, a abordagem “errado e visível” sempre será melhor do que “errado e invisível”.
Não Deixe Erros Simples Escaparem Para o Usuário Final
Se há uma regra de ouro no desenvolvimento de software, é esta: o erro mais barato de corrigir é o que nunca chegou ao usuário.
Todo desenvolvedor já sentiu a frustração de encontrar um bug bobo em produção e pensar: “Como isso passou despercebido?”
A resposta é simples: porque ninguém testou direito.
Seja com testes unitários, revisões de código ou simplesmente revisitando a lógica da operação, o ideal é que as falhas sejam descobertas ainda no ambiente de desenvolvimento.
Se você ainda não escreve testes unitários, considere começar. Mas se mesmo assim você não estiver convencido, ao menos faça o seguinte:
✔ Revise seu código com olhar crítico – Assuma que ele vai quebrar e tente descobrir como.
✔ Procure por possíveis brechas – Inputs mal formatados? Valores inesperados? Dados ausentes?
✔ Teste manualmente comportamentos extremos – Insira números absurdamente grandes. Simule quedas de conexão. Faça requisições malformadas.
✔ Tenha um plano para lidar com erros – Como o sistema reage quando algo dá errado?
Quanto mais cedo um erro for detectado, menor será o estrago.
Conclusão
Se há uma lição que podemos tirar disso tudo, é que código que só funciona no caminho feliz não está pronto para o mundo real.
Os engenheiros de colisão não testam carros para confirmar que tudo funciona – eles os destroem para encontrar falhas antes que alguém pague o preço com a própria vida. E nós, engenheiros de software, precisamos pensar da mesma forma.
Nosso trabalho não é apenas escrever código elegante e funcional, mas sim garantir que ele resista ao caos, que não quebre de forma catastrófica, e que não coloque usuários e empresas em risco.
Isso significa: testar além do óbvio, aceitar questionamentos, prever falhas e nunca terceirizar essa responsabilidade para o QA.
Porque no fim das contas, código mal testado sempre vai colidir. A única pergunta é: você prefere que essa colisão aconteça no ambiente de desenvolvimento ou nas mãos dos seus usuários?