A carga cognitiva é importante
(github.com/zakirullin)- Este projeto open source organiza de forma sistemática várias formas e casos de reduzir a carga cognitiva no desenvolvimento de software
- Código, estrutura e abstrações desnecessariamente complexos prejudicam a produtividade dos desenvolvedores e aumentam o custo de manutenção
- Em vez de módulos “pequenos e rasos”, o ideal é uma estrutura profunda com interface simples e funcionalidades poderosas
- Abstração excessiva, dependência de frameworks e uso abusivo do princípio DRY acabam aumentando ainda mais a carga cognitiva
- A melhor arquitetura é uma base de código simples, que até novos desenvolvedores consigam entender rapidamente
Resumo do projeto e sua relevância
Este material open source no GitHub foca em “carga cognitiva (cognitive load)” como um dos princípios centrais do desenvolvimento de software. A maior característica deste repositório é organizar, com vários exemplos e soluções, as origens da complexidade que os desenvolvedores realmente enfrentam na prática, independentemente do tamanho da equipe ou das tendências tecnológicas. Diferentemente de outros materiais centrados em best practices, ele também considera a carga psicológica e cognitiva, oferecendo vantagens reais para manutenção e onboarding de novos membros da equipe.
Introdução
- Conceitos da moda e best practices que ouvimos no ambiente de desenvolvimento frequentemente falham na prática
- A principal causa da confusão sentida no trabalho real, e das perdas de tempo/custo que isso provoca, é a alta carga cognitiva
- Desenvolvedores passam mais tempo entendendo e lendo código do que escrevendo
- O texto foca em formas práticas de reduzir a carga cognitiva desnecessária (Extraneous Cognitive Load)
O que é carga cognitiva
- Carga cognitiva é a quantidade de informação que o desenvolvedor precisa manter na cabeça para concluir uma tarefa
- Em média, só é possível manter cerca de 4 “blocos de informação” de uma vez na memória de curto prazo (condições, valores de variáveis etc.)
- Quando a carga cognitiva chega ao limite, há grande queda na compreensão e na velocidade de desenvolvimento
Tipos de carga cognitiva
- Carga cognitiva intrínseca (Intrinsic): vem da dificuldade essencial da própria tarefa. Não pode ser reduzida
- Carga cognitiva desnecessária (Extraneous): vem da forma como a informação é apresentada, do desenho da estrutura e de padrões desnecessários. Pode e deve ser reduzida ativamente
Exemplos práticos e formas de melhoria
Condicionais complexas
- Código com várias condições aninhadas aumenta a quantidade de coisas que precisam ser lembradas em cada etapa, o que eleva a carga cognitiva
- Solução: introduzir variáveis intermediárias com significado claro para separar explicitamente o propósito de cada condição
if aninhado vs Early Return
- Em comparação com
ifaninhado, o padrão de retorno antecipado (Early Return) reduz a necessidade de lembrar pré-condições - Ele libera espaço na memória de trabalho ao permitir que se pense apenas no “caminho feliz” (happy path)
Efeitos colaterais da herança
- Uma hierarquia de herança profunda (ex.: ClasseA → ClasseB → ClasseC ...) exige lembrar muitas camadas de informação ao mesmo tempo para entender o código, fazendo a carga cognitiva disparar
- Solução: priorizar composição, usando composição em vez de herança desnecessária
Módulos/funções rasos demais em excesso
- Em vez de 80 classes pequenas e rasas, alguns módulos profundos com interfaces simples e poderosas são mais fáceis de manter
- O texto usa como exemplo a interface simples de I/O do UNIX (
open,read,write,lseek,close)
Má interpretação do “princípio da responsabilidade única”
- A interpretação ambígua de “faz só uma coisa” acaba produzindo abstrações rasas e pouco claras
- Na prática, deve ser entendido como “responde a um único stakeholder”, o que ajuda a compreender a relação com o negócio e a reduzir a carga cognitiva
Abuso de microsserviços
- Microsserviços rasos demais obrigam a manter na cabeça as relações e integrações entre serviços, aumentando a carga cognitiva e o custo de depuração/lançamento
- No início, uma estrutura monolítica bem construída pode ser mais vantajosa para manutenção
Recursos/opções excessivos da linguagem
- Muitos recursos novos da própria linguagem (especialmente em C++ etc.) fazem com que seja preciso rastrear “por que isso foi implementado assim”, acumulando sobrecarga de memória
- Isso espalha carga cognitiva secundária, sem relação com o negócio
Mapeamento entre códigos de status HTTP e lógica de negócio
- O mapeamento arbitrário entre códigos HTTP (401, 403, 418 etc.) e significados internos de negócio é algo que todos da equipe precisam decorar
- Melhoria: transmitir de forma consistente com códigos em string autoexplicativos (ex.:
"jwt_has_expired")
Uso abusivo do princípio DRY (Do not repeat yourself)
- Em uma base de código complexa, forçar a eliminação de duplicação gera dependências fortes e pode até aumentar a carga cognitiva e o custo de mudança
- O texto cita a famosa frase de Rob Pike de que, localmente, “um pouco de cópia” pode ser melhor
Dependência de framework e prejuízos da Layered Architecture
- Quando se depende profundamente da estrutura “mágica” de um framework, um novo desenvolvedor pode gastar muito tempo para entender a lógica interna
- O acúmulo de camadas de abstração faz a carga cognitiva explodir na hora de rastrear o problema real
- É preciso focar em princípios fundamentais como Dependency Inversion, Info Hiding e controle da carga cognitiva
Má interpretação de DDD (Domain-Driven Design)
- O ponto central de DDD é analisar o domínio do problema; a obsessão por estrutura de pastas/padrões acaba gerando interpretações subjetivas e carga cognitiva
- Frameworks como
Team Topologiespodem ser mais eficazes para dividir a carga cognitiva
Familiaridade vs simplicidade
- Familiaridade não é o mesmo que simplicidade. Algo parece leve porque já é conhecido, não porque sua estrutura seja realmente simples
- Se um novo integrante passa mais de 40 minutos confuso, isso é sinal de que o código precisa ser melhorado
Casos reais de sucesso/fracasso
- Como no Instagram, é possível ter alta escalabilidade e boa manutenção até com uma arquitetura monolítica simples
- Empresas em que “desenvolvedores muito inteligentes” criaram estruturas complexas frequentemente acabaram fracassando
- Uma estrutura que todos os desenvolvedores consigam ler facilmente e na qual possam fazer onboarding rápido tem papel central no aumento de produtividade
Conclusão
- A carga cognitiva desnecessária, além do trabalho essencial, prejudica todos os envolvidos no desenvolvimento
- O melhor código é aquele que outros desenvolvedores no futuro, e você mesmo, consigam entender o mais rápido possível
- Em vez de estruturas que “parecem inteligentes”, soluções comuns e diretas são mais vantajosas no longo prazo para manutenção e produtividade da equipe
Referências/observações
- Também são citadas opiniões de Rob Pike, Andrej Karpathy, Elon Musk, Addy Osmani, antirez (desenvolvedor do Redis) e outros desenvolvedores conhecidos
- O texto apresenta implicações alinhadas com casos reais em larga escala, como Chromium, Redis e Instagram
- Simplicidade, clareza e redução da carga cognitiva são, em última análise, o núcleo da sustentabilidade do software
O valor deste projeto open source
- Material centrado na experiência real do desenvolvedor e em casos práticos, algo que muitos livros de design de software e padrões deixam passar
- Oferece ajuda prática imediata para várias equipes em onboarding de desenvolvedores, revisão de arquitetura e manutenção de longo prazo
- Funciona como um checklist para revisitar o código com a lente clara da “carga cognitiva”
1 comentários
Comentários no Hacker News
Tive meu maior insight com o livro A Philosophy Of Software Design, de John Ousterhout; é o melhor livro sobre esse tema, e eu recomendo fortemente a leitura para qualquer desenvolvedor. A ideia central é que o objetivo do design de software deve ser minimizar a complexidade, e aqui complexidade é definida como “quão difícil é fazer mudanças”, sendo que essa “dificuldade” é determinada pela carga cognitiva necessária para entender o código
O problema é que regra nenhuma consegue substituir julgamento, experiência e intuição. Toda regra pode virar ferramenta de discussão, e você nunca vence debates de arquitetura. A razão de este texto ser bom é que quem não precisa dele já sabe disso, e para quem realmente precisa ele não vai fazer sentido. No fim, isso não é um problema técnico, mas de pessoas e cultura. Arquitetura acaba vindo de pessoas e cultura. Assim como existe o Rob Pike e existe o Google, e por isso surgiu Go, não é lendo um livro que nasce uma linguagem como Go
Acho que o princípio DRY (não se repita) é algo que só deveria ser considerado depois de você entender de verdade a aplicação e ter feito várias versões. No começo, o melhor é até repetir mais para entender primeiro o espaço do problema, e só na segunda versão pensar em manutenção. Só lá pela terceira versão é que talvez faça sentido aplicar DRY
É difícil demais conseguir esse livro sem dar dinheiro para o Jeff Bezos. Se alguém conhecer o autor John, seria ótimo avisar sobre esse problema. Não tinha na livraria do campus, e também não consegui achar em livrarias locais nem na Powell’s
Faz tempo que desisti de procurar a solução perfeita para software. Não parece que exista alguém que tenha organizado isso perfeitamente. No fim, a melhor arma que temos é a sabedoria e a experiência das pessoas. Contexto, setor e time variam demais para isso caber direitinho em números ou leis. Então meu objetivo em design acabou virando encontrar um equilíbrio entre “bagunça” e “beleza”. O mais difícil foi que o negócio é incerto, enquanto o software é determinístico, então os requisitos de negócio estão sempre mudando e é difícil encaixá-los na rigidez de sistemas computacionais. Hoje em dia, eu só tento refatorar quando mudar o código me causa um desconforto real. E mesmo assim organizo só o mínimo. Repetindo esse processo de pequenas refatorações várias vezes, novos padrões acabam surgindo, e às vezes dá para extrair isso em abstrações
Esse livro é justamente o melhor sobre esse assunto. O post também foi inspirado nele. Inclusive conversei uma ou duas vezes com o autor John sobre o conteúdo
A habilidade de escrever código que reduz a carga cognitiva dos outros é extremamente rara e difícil. Mesmo tendo talento, isso exige esforço constante. No fim, o trabalho do desenvolvedor é comprimir as ideias centrais para deixar visível apenas a essência e expor só a complexidade que for realmente necessária. Na prática, quase nunca vejo isso ser feito bem
Código realmente bem feito acaba fazendo as pessoas pensarem: “isso sempre foi um problema fácil assim?”. Já um código visivelmente complicado, tipo um “castelo de cartas”, ganha reconhecimento como prova de esforço e até rende promoção
Isso vale exatamente da mesma forma para interface, UX e design de interação. Desenvolvedores toleram carga cognitiva muito melhor do que pessoas comuns, mas criar algo que funcione bem para quem não é técnico exige sair da própria intuição de desenvolvedor, então o nível de dificuldade é altíssimo. Projetar ferramentas para que usuários comuns resolvam intuitivamente problemas complexos é realmente difícil
A maioria das abstrações não sobrevive bem quando os requisitos mudam. Os frameworks de que gosto sabem que é impossível criar uma camada de abstração perfeita para sempre e, por isso, fornecem deliberadamente “rotas de fuga”. (Ex.: em frameworks de WebUI, oferecer referência direta ao elemento HTML.) É importante ter a sabedoria de reconhecer que não dá para prever perfeitamente o futuro
Na prática, essa habilidade não é essencial em muitas empresas; é mais um bônus. Basta olhar para os principais codebases
Eu me considero um dos “desenvolvedores inteligentes com tendências excêntricas”. Gosto de construir abstrações. Acho curioso e preocupante que a indústria hoje pareça estar voltando para uma arquitetura de “monte de if”. Entendo por que tanta gente gosta disso: parece simples, é fácil de entender e você só precisa fechar o ticket que recebeu. Grande parte dos processos de desenvolvimento depende de um monte de if, e essa abordagem realmente funciona em muitos casos acima de certo nível. Mesmo quando acontece um vazamento massivo de dados pessoais, quase ninguém é responsabilizado. O problema é que eu não sei se existe alternativa melhor. Nem abstrações sofisticadas nem arquiteturas elegantes parecem realmente aumentar a consistência do código. Especialmente no ambiente corporativo, os donos da lógica de negócio quase não prestam atenção na lógica em si, então não dá para construir abstrações bonitas com cuidado. No fim, você tem um requisito como “pedidos só podem ser enviados para um endereço”, e de repente vira “um cliente grande pediu entrega em vários endereços”. No meio desse caos, é difícil imaginar uma abstração sem bugs. Às vezes chego a pensar que, realisticamente, para software corporativo o melhor mesmo é um monte de if
Sobre a pergunta “será que o modelo de monte de if é o melhor?”, recomendo o artigo Big Ball of Mud e este resumo. Sistemas reais estão sempre evoluindo: começam como uma “bola de lama”, depois melhoram em partes, e quando a mudança volta, até abstrações bonitas desmoronam. O ponto central é fazer o sistema crescer com uma combinação de modelagem de domínio, abstrações razoáveis e as mudanças destrutivas necessárias, como uma cidade pequena crescendo até virar uma metrópole
Dá para criar abstrações elegantes mesmo nesse tipo de software, mas, no momento em que a lógica de negócio entra em cena, fica difícil mantê-las. As partes onde abstrações costumam sobreviver melhor são elementos mais “de produto”, como autenticação, autorização, logs, banco de dados, middleware, infraestrutura etc. Quando a lógica interna de negócio começa a influenciar a abstração, tudo acaba virando bagunça de novo. Em alguns lugares, chegaram até a fazer o pessoal de negócio gerenciar a lógica diretamente, e isso virou algo impossível de testar, impossível de entender e até de simular. No fim, ainda precisava de operador, então surgia a situação de um desenvolvedor júnior escrevendo código por uma ferramenta gráfica. Mesmo depois de várias reescritas, em 2 ou 3 anos tudo sempre volta a ficar caótico
É absolutamente impossível para pessoas de negócio explicarem perfeitamente sua lógica de negócio a quem implementa. Mesmo que elas próprias entendam, não conseguem expressar isso na ótica da codificação. Na verdade, pelo menos uma pessoa da implementação precisa vivenciar profundamente a experiência do usuário para realmente entender. Só que, na prática, as empresas forçam a separação entre departamentos, e “lógica de negócio” vira uma área da qual ninguém realmente cuida, então no fim o que se faz é só mexer em if
O ponto principal é que a realidade — isto é, o próprio negócio — já é um monte de if. Se o problema ou domínio for claramente técnico ou passível de generalização, dá para reduzir if com abstrações. Mas, se o próprio domínio é caótico, então a abstração inevitavelmente precisa incorporar “flexibilidade” como base. Contradições podem até virar funcionalidade
Brincando com ferramentas como o Codex CLI, dá para ver que, quando encontra bugs, ele sempre gera patches de if para casos específicos. A menos que você ensine explicitamente os padrões e peça uma nova abstração, ele continua adicionando ifs que chama de “heurísticas”. Para 10 bugs de um certo tipo, ele faz 10 patches; quando aparece o 11º bug parecido, naturalmente já não funciona. A solução que o Codex propõe é um conjunto de repetições de if
Fico pensando o quão comum é, na vida real, pedirem para você mexer em um projeto completamente desconhecido. A menos que você troque muito de projeto, isso acontece talvez uma vez a cada dois anos. Acho melhor focar em problemas realmente difíceis, e não em falta de experiência
Trabalhei 8 anos na organização de desenvolvedores da Microsoft, e no começo foi muito útil modelar tipos de engenheiros. Havia três personas, depois substituídas por um framework mais complexo. Mesmo assim, as personas originais continuam me ajudando muito até hoje a me comunicar melhor com outros engenheiros ao longo da carreira.
Acho que também deveria existir um tipo de engenheira chamada Amanda. Depois de 20 anos revisando o próprio código bagunçado e o dos outros, ela internalizou que código, acima de tudo, é para ser lido por pessoas. Eu montaria um time só de Amandas
Mort é pragmático, Einstein é perfeccionista, e Elvis é... sinceramente, alguém prejudicial para o projeto. Dá um pouco de motivação, mas o time ideal é uma boa mistura de Mort e Einstein. É preciso tornar as coisas tão simples quanto necessário, mas com precisão suficiente para que a manutenção não vire sofrimento. Inclusive, se você deixar um Mort responsável por um projeto de longo prazo, em algum momento ele começa a se preocupar como um Einstein. Aliás, os agentes automáticos de programação recentes são tão Mort que eu mesmo acabo tendo que gerenciá-los como Mort
Personas são uma ferramenta boa. Pela minha experiência, com o tempo elas se degeneram em atalhos ruins. Elvis, na prática, era um termo de política interna e, como na lei de Goodhart, por parecer algo logicamente válido, todo mundo tenta usá-lo em discussões, e isso acaba reduzindo sua utilidade. Alan Cooper introduziu há muito tempo o conceito de personas junto com o desenvolvimento em Visual Basic. É importante entender que perspectivas diferentes da minha — por exemplo, cientistas e desenvolvedores de firmware — têm valores diferentes. Livro relacionado
Acho que os melhores engenheiros são os que conseguem ajustar essas três tendências de acordo com o projeto, a situação e os objetivos pessoais. Cada função exige pesos diferentes. Por exemplo, otimização de compilador precisa mais de Einstein e Mort, enquanto código de engine de jogo tem outra proporção. Dá para discutir se essas características são exatamente corretas, mas o ponto principal é que todas precisam operar de formas diferentes ao longo do tempo
Acho que essa abordagem precisa de limites. Categorizar pessoas de forma simplista demais pode virar rotulação injusta e permanente. Pela minha experiência, aliás, a gestão costuma preferir Mort a Elvis. A solução real está em liderança e gestão. Se você incluir padrões de qualidade de código nos requisitos de um Mort, ele consegue entregar algo razoável, ainda que um pouco mais devagar. Elvis precisa de restrições, e os Einsteins precisam de critérios de prazo definidos de maneira prática. Esse método tem a limitação de ignorar a complexidade humana
Tenho receio de que a IA faça mal à indústria de software. Como a IA não tem limitações humanas, ela pode escrever código extremamente complexo e difícil de ler, mas que funciona; e depois, quando aquilo inevitavelmente quebrar, ninguém vai conseguir consertar. Por isso recomendo aos engenheiros juniores que não dependam só de IA e aprendam as características do codebase. Senão, eles acabam perdendo a capacidade de escrever código por conta própria
Acho esse argumento pouco convincente, porque a IA também pode consertar de novo quando parar de funcionar
Pela minha experiência, eu sempre peço que a IA continue simplificando o código até eu conseguir entendê-lo completamente. Se a solução estiver complexa demais, peço algo mais simples; se usar bibliotecas externas desnecessárias, peço para removê-las; tento resolver com a stdlib ou apenas com dependências já existentes. A IA escreve código no meu lugar, não para ela mesma
Pelo contrário, acho até que a IA pode melhorar a qualidade do software, porque reduz a carga cognitiva do design de baixo nível e permite focar mais na arquitetura de alto nível
Sobre o “hábito típico de desenvolvedores inteligentes” mencionado no texto, parece que o autor quer dizer que só consegue entender código escrito por ele mesmo ou no estilo dele. Mas o verdadeiro segredo para reduzir carga cognitiva está em minimizar a quantidade de código que você precisa ler. Fronteiras fortes e APIs claras tornam mudanças mais fáceis; o importante não é o código inteiro, mas apenas a complexidade derivada de interfaces bem definidas. A “singularidade” do “desenvolvedor inteligente” na verdade é um problema superficial
Essa é justamente a maior vantagem do conceito de microsserviços. Se dois times se comunicam só por API e definem rigidamente as fronteiras com ferramentas como schema, eles se tornam extremamente conservadores com mudanças e passam a pensar muito mais profundamente antes. É como inserir tecnicamente um atrito artificial nos pontos que você não quer que mudem
Algo que percebo muito ao depurar é como é cansativo quando há partes excessivamente otimizadas para o código que precisa ser depurado diretamente no call stack. Por isso, quando extraio partes específicas em funções e separo commits, fica muito mais fácil reverter mudanças indesejadas e manter o foco no código. No PR vai acabar vindo tudo junto, mas, ainda assim, quando as fronteiras são claras, fica muito melhor
Desenvolvedores em geral não pensam tão bem em termos de “contratos e interfaces”. É preciso fazer com que olhem apenas para as promessas expostas externamente, e não para a implementação interna. O objetivo é que seja possível entender sem ler o código. Se para entender meu código a pessoa precisa abri-lo e examiná-lo, então eu falhei
Mesmo que a API seja clara, o código interno pode continuar difícil de entender. Por fora pode parecer que tudo funciona bem, mas muitas vezes é preciso mergulhar por dentro mesmo assim
Gostei muito porque este texto resume muito bem a minha experiência. A maioria das metodologias de programação que aprendi formalmente acaba jogando contra quando outra pessoa precisa ler meu código. Códigos que escondem complexidade como em Rust e C++ são difíceis; quando vejo algo como C, onde não dá para esconder o código atrás de seis camadas de templates, fico até aliviado
Essa ideia de tratar a acessibilidade do código como cidadã de primeira classe realmente me tocou. Regras devem servir como diretrizes, e quem é realmente habilidoso tem a capacidade de julgar, de acordo com o contexto, quando quebrar ou complementar uma regra para manter o código fácil de ler. No fim, o importante é ter sensibilidade para a carga cognitiva do código e seus trade-offs. Seja duplicação ou abstração, você sempre precisa pensar na próxima pessoa — incluindo você mesmo daqui a 6 meses. Quando alguém pede para criar mais uma regra, isso em si já é repetir o próprio problema. Depois de diretrizes básicas, o que conta é o tacit knowledge, ou seja, o conhecimento tácito que vem da experiência. Com o tempo, você passa a sentir sozinho qual código é bom e qual é ruim. No fim, é algo que só dá para aprender sentindo na prática
Sempre se fala em documentar o “porquê”, mas eu tenho dificuldade para separar o “porquê” do “o quê”, então acabo registrando os dois. O pior tipo de comentário é
algo assim. Misturar código e comentário dificulta a leitura e aumenta muito a troca de contexto. Em vez disso,
desse jeito, separando claramente comentário e código, fica melhor. Ainda assim, mesmo escrever o “o quê” já é muito melhor do que não escrever nada, e reduz a carga cognitiva
Comentários nesse formato quase sempre são difíceis de manter, e como a implementação muda umas cinco vezes por ano, eles envelhecem rápido e passam a confundir. Em projetos recentes, tenho removido a maioria dos comentários e deixado só a lógica realmente difícil e a documentação de nível mais alto
Acho que há momentos em que os dois (why/what) precisam ser documentados. Às vezes é preciso esclarecer o motivo, às vezes o comportamento. Hoje em dia, acho que o valor dos comentários anda subestimado. Não é preciso comentar cada linha, mas a maior parte do chamado “código autoexplicativo” também não é tão fácil de ler assim
Hoje, para mim, a maior dificuldade é decidir o que deve ficar no código e o que deve ir para documento de design ou documentação técnica. Minha conclusão é que comentários no código servem melhor para explicar intenções não intuitivas ou escondidas. Ex.: “isso é memoizado por causa de X”
Ri do comentário estilo ChatGPT de
x=4. Mas o problema da forma proposta é o risco de o comentário envelhecer e ficar inconsistente com o código. Quando o código mudar, o comentário também precisa ser atualizadoEu não sou programador de formação, mas físico, então quando comento código tento mudar para esse estilo. Quando eu era estudante, por causa das notas, a prática era sempre colocar comentários em abundância