Escrito por: Breno Oliveira

Quando uma pessoa desenvolvedora precisa modificar um serviço crítico, a mudança solicitada parece simples, mas entender suas consequências é outra história.

O serviço acumulou anos de lógica de negócio. Nesse contexto, algumas dependências são mal documentadas, certos comportamentos são difíceis de reproduzir localmente e os testes disponíveis oferecem apenas uma segurança parcial. Por isso, uma modificação que parece isolada pode afetar uma parte completamente diferente do sistema.

A pessoa desenvolvedora, então, tem duas opções: investigar mais a fundo, aceitando um prazo de entrega incerto, ou implementar a mudança e torcer para que as salvaguardas existentes detectem quaisquer consequências indesejadas. Dito isso, nenhuma das opções é particularmente atraente.

O que torna essa situação interessante é que o problema pode não ter nada a ver com a cultura do time. A pessoa engenheira pode trabalhar ao lado de colegas que oferecem apoio, sentir-se à vontade para fazer perguntas e ter total liberdade para propor melhorias. Ainda assim, o próprio código continua sendo difícil de confiar.

Essa distinção revela uma dimensão frequentemente negligenciada da experiência de quem desenvolve. A confiança na engenharia depende não apenas das relações entre as pessoas, mas também do grau em que os sistemas de software podem ser compreendidos, testados e modificados com segurança.

E, à medida que os sistemas crescem, essa confiança se torna cada vez mais ligada às decisões de arquitetura tomadas ao longo do caminho.

A incerteza por trás de uma mudança aparentemente simples

A complexidade de software não é, por si só, um problema. Um sistema que lida com milhares de regras de negócio, se integra a serviços externos ou processa transações financeiras críticas naturalmente envolverá complexidade.

A dificuldade começa quando as pessoas engenheiras não conseguem determinar onde essa complexidade reside ou como as diferentes partes do sistema influenciam umas às outras.

Considere um serviço responsável por várias capacidades de negócio. Uma pessoa desenvolvedora que modifica uma dessas capacidades pode precisar entender fluxos não relacionados, porque eles compartilham estado interno, dependências ou premissas.

Mesmo quando a mudança em si exige apenas algumas linhas de código, avaliar seu impacto pode se tornar uma investigação considerável. É aqui que a incerteza técnica começa a influenciar o comportamento da engenharia.

Uma pessoa engenheira que não consegue identificar com confiança as consequências de uma modificação pode adiar uma refatoração, introduzir uma abstração adicional para evitar mexer na lógica existente ou solicitar uma revisão extensa de alguém mais familiarizado com o sistema.

Essas decisões podem ser totalmente racionais. Em um ambiente de produção, a cautela costuma ser preferível a introduzir riscos desnecessários. No entanto, quando esse padrão se torna rotina, uma organização pode perder gradualmente a capacidade de evoluir seu software de forma eficiente.

As estimativas de entrega passam a refletir não apenas o esforço de implementação, mas também o tempo necessário para investigar dependências desconhecidas. O onboarding se torna mais exigente, porque entender o sistema depende de conhecimentos que raramente estão documentados. Pessoas engenheiras seniores podem se tornar pontos recorrentes de consulta, o que limita sua disponibilidade para outros desafios técnicos.

A questão de fundo não é necessariamente que as pessoas engenheiras não têm autonomia. É que exercer essa autonomia envolve incerteza demais.

Check our job opportunies

A arquitetura determina o quanto uma pessoa engenheira precisa saber

Uma das decisões de arquitetura com mais consequências é onde estabelecer as fronteiras entre responsabilidades. Uma fronteira bem definida permite que as pessoas engenheiras raciocinem sobre um componente a partir de seu comportamento documentado e de suas interfaces, sem precisar entender cada detalhe de sua implementação.

Por exemplo, um time que trabalha em um produto relacionado a cartões deveria, idealmente, conseguir consumir as capacidades comuns de cartões sem precisar entender as regras de negócio que regem todos os outros produtos que as utilizam, mas alcançar essa separação raramente é simples.

Capacidades que parecem independentes do ponto de vista do produto podem compartilhar dados, restrições operacionais ou premissas históricas. Extraí-las para componentes reutilizáveis exige entender essas relações e decidir quais responsabilidades realmente pertencem ao mesmo lugar.

Ainda assim, o benefício vai além da organização do código. Quando as fronteiras de arquitetura correspondem a responsabilidades significativas, as pessoas engenheiras conseguem reduzir o escopo de suas investigações. Elas podem identificar quais componentes são afetados por uma mudança, quais contratos precisam permanecer estáveis e quais testes devem fornecer evidências de que a modificação se comporta como esperado.

Isso torna a incerteza mais fácil de localizar e gerenciar, e essa distinção se torna especialmente importante quando uma arquitetura originalmente projetada em torno de um pequeno número de produtos precisa acomodar um negócio em expansão.

O que a evolução da arquitetura do Nubank revela sobre mudança

A história da engenharia do Nubank oferece um exemplo útil de como os requisitos de arquitetura evoluem junto com uma organização. Como descrito no artigo Evoluindo a arquitetura do Nubank rumo a uma plataformização consciente, a arquitetura original da empresa foi projetada para suportar um único produto, um cartão de crédito, em um único mercado, o Brasil.

Essa arquitetura refletia os requisitos de negócio de sua época, mas, à medida que o Nubank se expandiu para outros serviços financeiros e países, as mesmas premissas de arquitetura já não conseguiam acomodar cada novo requisito com a mesma naturalidade.

Diferentes produtos precisavam acessar capacidades comuns, enquanto mercados individuais introduziam suas próprias regras de negócio e considerações regulatórias. O desafio era distinguir o que deveria ser compartilhado do que precisava permanecer específico de um produto ou de uma geografia.

Um exemplo discutido pela Principal Engineer Bel Gonçalves durante o Nubank Engineering Meetup #12 foi a evolução da plataforma de cartões. Anteriormente, as funcionalidades estavam concentradas em serviços como o CreditCardAccounts, cujas responsabilidades eram fortemente associadas a cartões de crédito.

À medida que o negócio se expandiu, o Nubank evoluiu para serviços mais flexíveis, capazes de suportar funcionalidades de débito e crédito em diferentes produtos e mercados. O relato publicado destaca dois contextos distintos: os cartões múltiplos do Brasil e os cartões de débito e crédito separados do México.

Suportar essas diferentes configurações exigiu uma arquitetura capaz de separar as capacidades reutilizáveis de cartões das regras que regem produtos e países específicos. É aqui que a plataformização se torna relevante para a questão mais ampla da confiança na engenharia.

Uma arquitetura organizada principalmente em torno de um produto específico pode exigir que as pessoas engenheiras naveguem por premissas específicas daquele produto ao adaptar funcionalidades para um contexto diferente. Separar as capacidades reutilizáveis da lógica localizada pode tornar essas premissas mais explícitas.

Em princípio, isso permite que os times raciocinem sobre o comportamento compartilhado de forma independente das regras de negócio de cada produto. A importância não está simplesmente no fato de os serviços se tornarem reutilizáveis. Está no fato de suas responsabilidades poderem se tornar mais fáceis de identificar. Para quem desenvolve, essa clareza oferece uma base mais sólida para entender o impacto potencial de uma mudança.

O relato publicado pelo Nubank não afirma que as pessoas engenheiras que trabalhavam no CreditCardAccounts não tinham confiança, nem que a transformação melhorou de forma mensurável a experiência delas. A conquista documentada é de arquitetura: uma mudança em direção a serviços com maior flexibilidade entre produtos e mercados.

Ainda assim, o caso ilustra um princípio importante de engenharia. À medida que as responsabilidades se tornam mais claramente separadas, a quantidade de comportamentos não relacionados do sistema que as pessoas desenvolvedoras precisam entender para fazer uma determinada mudança pode diminuir.

Os trade-offs por trás de uma evolução de arquitetura mais segura

Fronteiras mais claras são valiosas, mas criá-las pode introduzir riscos próprios. A plataformização do Nubank envolveu extrair funcionalidades críticas de serviços legados já em produção, além de reescrever componentes fundamentais. Essas atividades exigem mais do que decidir onde o código deve ficar.

Mover funcionalidades entre serviços pode afetar a propriedade dos dados, as garantias de consistência, as dependências, a coordenação de deploys e o tratamento de falhas. Uma abstração que simplifica o trabalho de um time pode introduzir complexidade adicional para outro.

Há também o risco de generalizar cedo demais. Uma plataforma projetada para acomodar todo produto futuro imaginável pode se tornar difícil de entender justamente por expor opções de configuração e pontos de extensão demais. Nessa situação, a flexibilidade se torna mais uma fonte de incerteza.

O relato de junho de 2025 identifica explicitamente a necessidade de equilibrar generalidade e simplicidade e de evitar complexidade de engenharia desnecessária. Esse equilíbrio importa porque a manutenibilidade não pode ser medida pelo número de serviços criados ou pela quantidade de código extraído.

Uma fronteira de arquitetura útil deve tornar as responsabilidades mais claras sem forçar as pessoas desenvolvedoras a navegar por uma rede de abstrações desnecessariamente complicada.

Às vezes, manter as funcionalidades juntas é a escolha mais simples e segura. Em outros momentos, separá-las cria as condições para uma evolução independente. O desafio da engenharia é saber qual trade-off o sistema realmente exige.

A confiança vem de evidências, não de suposições

A arquitetura determina boa parte do que as pessoas engenheiras precisam entender antes de fazer uma mudança. Mas entender a estrutura de um sistema é apenas parte do problema.

As pessoas engenheiras também precisam de formas de verificar suas suposições. Testes automatizados são particularmente valiosos quando descrevem comportamentos significativos, em vez de apenas exercitar detalhes de implementação.

Um teste que estabelece como um serviço responde a uma condição de negócio específica pode servir a dois propósitos. Ele verifica se o software se comporta corretamente e comunica o que modificações futuras precisam preservar. Isso se torna especialmente importante ao alterar componentes fundamentais que suportam vários produtos.

No entanto, os testes não podem oferecer segurança ilimitada. Uma suíte que passa diz pouco sobre os cenários que ela não cobre, e testes acoplados demais a detalhes de implementação podem tornar refatorações legítimas desnecessariamente difíceis.

A confiança depende de entender tanto o que os testes verificam quanto onde termina sua cobertura. Depuração e observabilidade oferecem outra camada de evidências. Logs, métricas, traces e ambientes de desenvolvimento reproduzíveis podem ajudar as pessoas engenheiras a entender como um sistema se comporta para além do que seu código-fonte sugere.

Ferramentas de desenvolvimento interativo também podem encurtar a distância entre uma hipótese e sua verificação. Em Clojure, por exemplo, o REPL permite que as pessoas desenvolvedoras avaliem expressões e inspecionem comportamentos durante o desenvolvimento.

Essas capacidades são complementares, e não intercambiáveis. Um REPL pode ajudar a explorar o comportamento de uma função, enquanto o tracing distribuído pode ser necessário para entender como uma requisição transita entre serviços em produção.

A documentação resolve um problema diferente: a perda de contexto de arquitetura ao longo do tempo. Uma interface pode explicar como interagir com um componente sem revelar por que ele foi projetado daquela forma. Os Architecture Decision Records (ADRs) podem preservar o raciocínio por trás de decisões importantes, incluindo as alternativas consideradas e as restrições que influenciaram o resultado.

Esses registros são particularmente úteis quando pessoas engenheiras no futuro encontram um design que parece desnecessariamente restritivo até que seu contexto histórico fique claro. Padrões técnicos compartilhados podem reduzir ainda mais o esforço necessário para transitar entre sistemas.

O mesmo artigo do Building Nubank descreve o uso que a empresa faz de Clojure, microsserviços e comunicação assíncrona como uma base comum de engenharia. Ele também aborda a imutabilidade como um princípio de design importante.

Consistência não garante manutenibilidade, mas padrões familiares podem reduzir o número de convenções que as pessoas engenheiras precisam aprender ao trabalhar com diferentes serviços. Juntas, essas práticas tornam as mudanças mais explicáveis e suas consequências mais observáveis. Elas não eliminam o risco operacional, mas podem ajudar os times a tomar decisões com evidências melhores.

As consequências humanas das decisões de arquitetura

Existe uma distinção importante entre sentir-se seguro com os colegas e sentir-se confiante para modificar um sistema. A segurança psicológica, em seu sentido organizacional consolidado, diz respeito a se as pessoas se sentem capazes de se manifestar, reconhecer erros, fazer perguntas e assumir riscos interpessoais sem medo de humilhação ou punição.

A confiança técnica diz respeito a algo diferente: se as pessoas engenheiras têm entendimento, ferramentas e evidências suficientes para fazer mudanças com um nível de risco aceitável.

Um time pode ter uma sem a outra. Uma pessoa engenheira pode reconhecer abertamente sua incerteza durante um code review e, ainda assim, não conseguir determinar se uma modificação afetará um serviço não relacionado. Por outro lado, uma pessoa engenheira pode conhecer profundamente uma base de código, mas trabalhar em um ambiente em que questionar decisões é desencorajado. As duas condições importam, e nenhuma substitui a outra.

A expressão segurança psicológica do código, explorada por Breno Salgado, oferece uma metáfora útil para a relação entre as pessoas desenvolvedoras e os sistemas que elas mantêm. Seu valor está em chamar a atenção para as consequências humanas do design técnico, preservando a distinção em relação à segurança psicológica interpessoal. Essas consequências vão além do conforto individual.

Quando o comportamento de um sistema é difícil de entender, o conhecimento pode ficar concentrado em um pequeno grupo de pessoas engenheiras experientes. O envolvimento delas pode se tornar necessário para mudanças rotineiras, criando dependências que afetam tanto a entrega quanto o desenvolvimento profissional.

Pessoas engenheiras recém-chegadas podem passar mais tempo aprendendo exceções não documentadas do que desenvolvendo um entendimento mais amplo do produto. Pessoas engenheiras experientes podem dedicar uma atenção desproporcional a explicar comportamentos históricos em vez de lidar com problemas emergentes.

Um sistema com boa manutenibilidade não pode eliminar a necessidade de colaboração, nem deveria. O trabalho complexo de engenharia depende de conhecimento compartilhado, mas uma boa arquitetura pode ajudar a fazer da colaboração uma escolha guiada pelo problema, e não uma exigência recorrente imposta por um código opaco.

A verdadeira medida da manutenibilidade

Uma base de código pode ser estável em produção e, ainda assim, ser desnecessariamente difícil de mudar. Essa distinção importa porque a estabilidade operacional descreve como um sistema se comporta hoje, enquanto a manutenibilidade influencia a eficácia com que ele poderá evoluir amanhã.

A evolução da arquitetura do Nubank ilustra a importância de revisitar decisões de design à medida que os requisitos de negócio mudam. Uma arquitetura que atende bem a um único produto pode precisar de fronteiras diferentes quando passa a suportar vários produtos, países e contextos regulatórios.

O objetivo é tornar a complexidade compreensível o suficiente para que as pessoas engenheiras possam trabalhar com ela de forma deliberada. Isso significa projetar fronteiras em torno de responsabilidades significativas, preservar o raciocínio por trás das decisões, investir em mecanismos de feedback confiáveis e reconhecer quando uma abstração cria mais incerteza do que elimina.

No fim das contas, uma organização de engenharia não deveria ter que escolher entre proteger seus sistemas existentes e dar às pessoas desenvolvedoras espaço para melhorá-los. A arquitetura com melhor manutenibilidade não é necessariamente aquela que muda com mais facilidade. É aquela que ajuda as pessoas engenheiras a entender o que uma mudança vai afetar, verificar suas suposições e gerenciar os riscos que permanecem.

No fim do dia, a confiança na engenharia de software vem de ter conhecimento e evidências suficientes para seguir em frente com responsabilidade, mesmo quando algo pode dar errado.

Check our job opportunies