Seu frontend não quebra por acaso, ele quebra por suposição

Recentemente, lendo Software Engineering at Google, me deparei com um conceito que me fez pensar bastante sobre como escrevemos código: a Lei de Hyrum. Ela diz algo mais ou menos assim: Quanto mais sistemas consumirem sua API, qualquer comportamento observável de um sistema será dependido por alguém, mesmo que não seja algo explicitamente documentado.
No livro, o foco é mostrar como o Google evita problemas ao criar contratos claros entre equipes e APIs. Mas, como um bom pintor de botões (ou desenvolvedor front-end, se preferir), caiu a ficha sobre a importância de sempre escrever código que dependa de contratos explícitos, e não de suposições sobre como APIs, bibliotecas ou quaisquer componentes de terceiros vão se comportar.
Reuni aqui algumas das armadilhas mais comuns ao codificar UIs complexas, pode soar como bom senso pra quem já tem uns bugs de experiência, mas é sempre bom relembrar.
1 - Utilizar dados diretamente de uma camada externa (e confiar demais nisso)
❌ Bomba relógio
const fetchData = async () => {
const res = await fetch("/api/products");
return res.json();
};
const data = await fetchData();
console.log(data.price); // Pode quebrar se a API mudar (spoiler: ela vai!)
Aqui o código assume implicitamente que a API sempre retornará um objeto com price.
Se a resposta mudar de formato, o frontend quebra silenciosamente, seu usuário vai pro site do concorrente e seu chefe te manda um whats sábado a noite.
✅ Forma explícita
A boa prática é criar uma camada intermediária de adaptação (parser, adapter ou normalizer), um módulo responsável por traduzir a resposta da API para o formato que o frontend espera.
Assim, o contrato entre “API externa” e “UI” fica claro e controlado.
// Define o formato que o frontend espera
interface Product {
id: string;
name: string;
price: number;
}
// Função que lida com a incerteza da API
const parseProducts = (data: unknown): Product[] => {
if (!Array.isArray(data)) {
throw new Error("Formato inesperado da resposta da API");
}
// Valida e adapta se necessário
return data.map((item) => ({
id: String(item.id),
name: String(item.name ?? "Sem nome"),
price: Number(item.price ?? 0),
}));
};
// Função responsável por buscar e normalizar os dados
const fetchProducts = async (): Promise<Product[]> => {
const res = await fetch("/api/products");
const raw = await res.json();
return parseProducts(raw);
};
A função parseProducts traduz o comportamento externo para o interno, ela isola possíveis mudanças da API e garante que o resto do front sempre receba dados no formato esperado.
Essa camada é a garantia de uma noite de sono tranquila, ela converte o mundo incerto da API em algo previsível, seguro e documentado.
2 - Tratar apenas o cenário feliz
❌ Bomba relógio
Supõe que algo sempre retorne um produto válido
const ProductPrice = () => {
const product = useProduct();
// Uso direto sem checar
return <div>Preço: R$ {product.price}</div>;
};
Se useProduct() mudar de comportamento (por exemplo, passar a retornar null enquanto carrega ou undefined em erro), o componente quebra silenciosamente em produção.
Esse é o tipo de dependência implícita que parece inofensiva, até que uma atualização de biblioteca ou API mude um detalhe.
✅ Forma explícita (sem confiar nem na própria sombra)
const ProductPrice = () => {
const { product, loading } = useProduct();
if (loading) return <p>Carregando...</p>;
if (!product) return <p>Produto não encontrado</p>;
return <div>Preço: R$ {product.price.toFixed(2)}</div>;
};
Mesmo que a biblioteca mude internamente, o front sabe exatamente o que esperar e pode tratar cada caso com segurança.
Ao montar a camada de visualização, nunca presuma que os dados de uma API serão sempre válidos. Se esforce pra identificar os possíveis ponto de falha e, caso necessário, falhe seu front graciosamente.
3 - Encapsular componentes de bibliotecas externas
❌ Bomba relógio
import { Button } from "biblioteca-da-moda-2025";
export const ProductActions = () => (
<div>
<Button onClick={handleBuy}>Comprar</Button>
<Button onClick={handleAddToCart}>Adicionar ao carrinho</Button>
</div>
);
Aqui, o frontend depende diretamente da biblioteca biblioteca-da-moda-2025.
Se amanhã a biblioteca mudar a API (por exemplo, trocar onClick por onPress ou alterar o estilo padrão), você precisará alterar dezenas de arquivos espalhados pelo código.
Além disso, o contrato visual e funcional do botão não está sob seu controle, pertence à lib.
✅ Forma explícita
Encapsule o comportamento e a aparência esperada no seu próprio componente, e use apenas ele em toda sua aplicaçâo.
import { Button as LibButton } from "biblioteca-da-moda-2025";
interface ButtonProps {
label: string;
onClick: () => void;
variant?: "primary" | "secondary";
}
export const Button = ({ label, onClick, variant = "primary" }: ButtonProps) => {
return (
<LibButton
onClick={onClick}
className={`my-button ${variant}`}
>
{label}
</LibButton>
);
};
Agora, até um trabalho moroso como passar a usar a biblioteca-da-moda-2026, basta alterar um único componente.
Quando uma biblioteca oferece um componente para resolver um problema (botão, modal, input etc.), use-a como dependência interna, não como parte pública da sua aplicação.
Crie o seu próprio componente que encapsula o comportamento, isso reduz o acoplamento, facilita a manutenção e torna futuras substituições quase triviais.
Nenhum outro arquivo do projeto precisa saber que existe uma dependência externa.
Conclusão
Claro que o front não pode (nem deve) absorver toda a responsabilidade do mundo, ele é só uma peça do sistema.
Mas pode ser defensivo: validar o que recebe, explicitar o que espera e nunca confiar cegamente em nada que venha de fora.
No fim das contas, quanto menos o seu código depender do que parece funcionar, e mais do que foi claramente acordado, mais livre você é pra evoluir o sistema sem medo de quebrar o resto.
No fim das contas, explícito pode dar trabalho, mas implícito dá plantão.