12 pontos por GN⁺ 2025-07-19 | 2 comentários | Compartilhar no WhatsApp
  • Dependências parecem recursos gratuitos, mas na prática trazem vários custos e complexidades
  • Uma dependência errada pode gerar diversos riscos, como curva de aprendizado, mudanças repentinas de interface e problemas de implantação/instalação
  • Como exemplo representativo, o TigerBeetle adota uma política de "zero dependências" em busca de segurança, desempenho e simplicidade operacional
  • O autor propõe um framework de avaliação de dependências (ubiquidade, estabilidade, profundidade, ergonomia e completude)
  • É essencial ter pensamento crítico para distinguir boas dependências de más dependências e, ao escolhê-las, considerar não só a produtividade de curto prazo, mas também o custo total e o risco

Uma dependência errada custa mais do que NIH (Not Invented Here)

Desvantagens das dependências e custos ocultos

  • Muitos desenvolvedores tratam adicionar uma dependência como se fosse um "recurso grátis", mas na prática isso traz custos como:
    • Tempo e complexidade para aprender
    • Muitas vezes, até instalar a dependência já é algo difícil
    • Em grandes upgrades de versão, surge o peso de ter que modificar bastante o próprio código
    • A dependência inevitavelmente precisa fazer parte do ambiente de implantação/instalação (contêineres, bundling etc.), gerando mais complexidade
  • A adoção de uma dependência errada pode até criar uma estrutura de implantação complexa que não tem relação com a funcionalidade principal

O princípio de zero dependências do TigerBeetle

  • O TigerBeetle é um banco de dados financeiro baseado em Vanilla Zig e adota uma política de zero dependências
    • É desenvolvido apenas com a linguagem Zig e não possui dependências externas além do toolchain do Zig
    • O objetivo é evitar problemas como risco de ataques à cadeia de suprimentos, queda de desempenho e aumento do tempo de instalação causados por dependências
    • Quanto mais profundamente um software estiver enraizado na infraestrutura, mais os custos causados por dependências se amplificam em toda a stack
    • Usar uma caixa de ferramentas pequena e padronizada favorece a manutenção e a produtividade no desenvolvimento
  • O foco é usar apenas Zig para lidar rapidamente com novos problemas e reduzir a complexidade

Framework de avaliação de dependências

  • Embora reconheça que a ausência total de dependências é impossível para todo desenvolvedor, o autor enfatiza que dependências devem ser avaliadas com cuidado
  • O autor propõe avaliar dependências com base em 5 critérios:
    • Ubiquidade (Ubiquity): quão comum ela é no ambiente de destino? Há necessidade de distribuição/instalação separada?
    • Estabilidade (Stability): com que frequência surgem problemas como compatibilidade retroativa, mudanças de API ou descontinuação de suporte?
    • Profundidade (Depth): quanto de funcionalidade real está escondido sob a API e quão difícil seria substituí-la com implementação própria?
    • Ergonomia (Ergonomics): a API é intuitiva/declarativa e fácil de usar? Como está a documentação?
    • Completude (Watertightness): o quão bem a abstração funciona e o quanto é possível deixar de se preocupar com a tecnologia subjacente?
  • A comunidade de desenvolvimento costuma discutir principalmente usabilidade, enquanto as outras quatro dimensões muitas vezes são ignoradas

Exemplos de boas dependências

  • Chamadas de sistema POSIX

    • Universalidade: podem ser usadas em quase todas as plataformas, como Linux, Android, macOS e BSD
    • Estabilidade: compatibilidade de interface muito alta e quase nenhuma mudança
    • Profundidade: uma única API esconde centenas de milhares de linhas de código do kernel
    • Ergonomia: apesar do estilo C mais tradicional, não há grandes dificuldades de uso
    • Completude: na maioria dos casos não há problemas, embora existam detalhes como persistência de dados em dispositivos de armazenamento
  • Códigos de controle de terminal ECMA-48

    • Universalidade: são suportados pela maioria dos terminais, com exceção do cmd.exe do Windows
    • Estabilidade: não mudam desde 1991
    • Profundidade: criar esse padrão do zero seria absurdamente difícil
    • Ergonomia: tirando a baixa legibilidade causada pelo caractere Esc, são razoáveis de usar
    • Completude: há pouquíssima preocupação com dependência de hardware
  • Plataforma web (Web API, HTML, JS, CSS etc.)

    • Universalidade: navegadores web estão instalados em praticamente todos os ambientes do mundo
    • Estabilidade: política forte de compatibilidade retroativa
    • Profundidade: criar um navegador próprio é algo tão profundo que, na prática, é inviável
    • Ergonomia: há alguma complexidade, mas a documentação e as ferramentas de desenvolvimento são excelentes
    • Completude: fora casos especiais como arquivos, áudio e vídeo, a completude é muito alta

Conclusão

  • Em vez de copiar e colar ou abusar de dependências, é indispensável ter pensamento crítico e analisar o custo total
  • Ao introduzir uma dependência, é preciso avaliar criticamente os custos e benefícios de cada uma e
    também considerar com cuidado os riscos e custos potenciais de todo o sistema, pois isso é muito mais barato e seguro no longo prazo

2 comentários

 
GN⁺ 2025-07-19
Opiniões do Hacker News
  • Foi a primeira vez que ouvi falar do TigerBeetle, então fui pesquisar. Se você está construindo um banco de dados de razão financeira em Zig, onde segurança e desempenho são críticos, e precisa aguentar um milhão de transações por segundo em um único núcleo de CPU, é totalmente razoável evitar adicionar dependências, porque o risco é alto. Mas a maioria dos desenvolvedores comuns está fazendo sistemas CRUD normais, e em geral eles não são tão fortes assim. Muitas dependências podem estar cheias de bugs, mas em muitos casos ainda têm qualidade melhor do que aquilo que a maioria dos desenvolvedores faria por conta própria. Nas empresas também, na prática, o gargalo costuma ser o nível dos desenvolvedores que conseguem contratar. Cada situação é diferente, então vale lembrar que conselhos opostos podem estar certos dentro de seus respectivos contextos

    • Eu realmente trabalho no TigerBeetle. O ponto central é o contexto. TigerBeetle e rust-analyzer têm culturas de engenharia fortes, mas resolvem problemas diferentes, então formaram culturas diferentes. As dependências mencionadas no texto são mais interfaces de sistema, como POSIX, ECMA-48 e a plataforma web, do que bibliotecas. Dependências de biblioteca, se derem problema, você pode simplesmente reescrever; já coisas fundamentais como interfaces de sistema são, na prática, impossíveis ou muito caras de trocar. É poderoso tomar a decisão de não fazer coisas que não combinam com o escopo do software. Por exemplo, se você tem uma equipe especializada em criar código de multiplicação de matrizes, pode usar bibliotecas externas para outras coisas que não sejam o trabalho principal, mas eu diria que o ideal é projetar melhor a decomposição de responsabilidades do produto. Assim, dá para isolar melhor as dependências essenciais dentro do sistema

    • O problema dessa visão é que ela só faz sentido se você estiver pensando apenas em desenvolvedores de nível mais baixo. Na área de tecnologia existe uma tendência de diluir conselhos para o menor denominador comum, mas, sinceramente, nos lugares em que trabalhei quase nunca vi ambientes em que os desenvolvedores fossem tão fracos a ponto de não conseguirem copiar uma dependência quebrada e resolver o problema. Também há muito desprezo por CRUD, mas abstrações ruins causam desperdício enorme de tempo e muito sofrimento em CRUD. Até coisas populares costumam ter vários problemas e baixa produtividade, a menos que você esteja falando de algo bem básico, nível tutorial introdutório

    • Isso não tem relação com o nível do desenvolvedor em si. Independentemente da toolchain ou do produto que você usa, no fim você está usando dependências feitas por outras pessoas. Quase ninguém ao seu redor implementa código de multiplicação de matrizes do zero, e mesmo essas pessoas não saem implementando por conta própria bibliotecas open source sem relação com o trabalho delas. Em geral, as pessoas só ficam obcecadas com dependências em si por exigências regulatórias, interesse pessoal ou apego a alguma biblioteca específica. Se todo mundo aplicasse esse princípio de forma total, estaríamos todos vivendo na praia catando areia

    • A opinião de que “desenvolvedores CRUD medianos não são fortes” é generalizante demais. A maioria dos desenvolvedores não pode escolher o sistema em que vai trabalhar, e durante o desenvolvimento sempre faltam recursos. É preciso usar dependências “baratas” para conseguir lançar software funcional rapidamente. Parece uma fala de quem não entende bem essa realidade

    • O TigerBeetle é uma startup relativamente recente, ainda nem chegou à versão 1.0 estável. Acho cedo demais para dizer se essa abordagem realmente funciona

  • O NIH (Not Invented Here) em si pode ser extremamente útil quando usado com um julgamento realista sobre até onde vai sua responsabilidade. Por exemplo, um framework de frontend web que se encaixa exatamente no meu domínio pode muitas vezes valer a pena ser criado e mantido internamente. Mas com banco de dados, engine de jogos, servidor web, funcionalidades básicas de criptografia e coisas assim, a conversa é outra. Se o problema é tão difícil que não dá para resolver com soluções existentes, então talvez seja melhor repensar a definição do problema primeiro. Na minha opinião, redefinir o problema sai muito mais barato do que recriar toda a suíte de testes do SQLite do zero

    • Bancos de dados, engines de jogos, servidores web e primitivas criptográficas também têm muitos casos em que fazer internamente é a melhor opção. Se arquivos simples com índices em runtime já bastam, muitas vezes até o SQLite é escolha demais. Em muitos jogos, especialmente em equipes pequenas, uma engine customizada pode ser vantajosa. A principal vantagem de uma engine pronta é o pipeline, mas ela vem com uma sobrecarga grande. Um servidor web não é mais complexo que um app FastCGI. Em criptografia também nem todo cenário é um problema de segurança; em algo como simples verificação de hash, implementar por conta própria pode ser aceitável. Não acho bom ter uma impotência aprendida diante de temas que parecem difíceis. Também é importante lembrar que, só porque uma solução existente resolve o problema, isso não quer dizer que ela seja a forma ótima ou mais eficiente de resolvê-lo

    • Então por que existem tantos engines de banco de dados? No fim, sistemas computacionais complexos têm trade-offs próprios. Restrições, escalabilidade, concorrência, segurança, características dos dados, forma de armazenamento — há muitas possibilidades. Eu tendo a confiar mais numa dependência cara quando ela própria busca minimizar outras dependências. Sistemas automatizados de gerenciamento de dependências muitas vezes acabam complicando ainda mais o problema, então uma gestão manual cuidadosa pode reduzir a carga

    • Vejo dois motivos para usar dependências de terceiros. (1) Quando são publicadas pelo próprio provedor do serviço e têm, em termos relativos, um ciclo de vida alinhado ao dele. (2) Quando substituem código complexo que eu não quero escrever. O caso (1) não tem problema, porque há uma razão de negócio. Só é preciso aceitar que atualizações desse serviço podem trazer mudanças grandes. No caso (2), o valor depende do nível de complexidade do código que estou evitando. Adotar uma dependência significa gastar tempo e recursos com atualizações/testes no ritmo da outra parte e assumir essa responsabilidade

    • Você logo encontra problemas que um RDBMS não resolve bem. RDBMSs são desenhados em torno de modificações concorrentes de dados e suporte a conjuntos de dados mutáveis; se você não precisa disso, um índice simples já pode trazer um ganho enorme de desempenho. Se os dados forem imutáveis, é possível fazer uma implementação própria muito mais rápida que um RDBMS

    • O exemplo de RDBMS é interessante. A Wikipédia lista mais de 100 RDBMSs, e cada um tem problemas que resolve e problemas que não resolve. É o resultado de gente pensando em soluções práticas e realmente colocando isso em execução

  • Dependências trazem risco, mas não usar nenhuma pode deixar você para trás em desenvolvimento e time-to-market. Por isso o processo de gestão de dependências é importante

    1. Considerar apenas dependências open source
    2. Na adoção de uma nova dependência, além do code review, analisar licença, esforço para removê-la depois, histórico de vulnerabilidades e bugs, continuidade de atualizações, vitalidade da comunidade etc.
    3. Se possível, verificar periodicamente questões de segurança das dependências. Em larga escala, isso vira um problema de custo. (Ideia relacionada: https://blog.majid.info/supply-chain-vetting/)
    4. Só adotar dependências cujo custo de manutenção/fork você conseguir suportar. No mínimo, você já deveria ter conseguido compilar a partir do código-fonte
    5. Fazer fork preventivo de todas as dependências. Um repositório pode simplesmente desaparecer de repente, como no caso do left-pad
    • Os itens 4 e 5 são realmente importantes, mas costumam ser esquecidos. Até em projetos pessoais, quando volto depois de deixá-los parados por um tempo, já aconteceu de as dependências estarem obsoletas ou o repositório ter sido apagado. Por isso, hoje em dia, faço fork privado do próprio código-fonte e testo a compilação eu mesmo, além de manter fork em nível de código-fonte até das dependências das dependências. Assim, mesmo que o ecossistema mude drasticamente depois, dá para minimizar o dano. Acabei passando a preferir bibliotecas em código-fonte a binários

    • No item 5, fazer fork pode ser um peso excessivo. Outra abordagem boa é vendorizar as dependências em seu próprio git ou num proxy de cache. Isso combina especialmente com projetos de longa duração. Quando há muitos arquivos de dependência, como em NodeJS, ferramentas como Yarn e PNPM tendem a ser mais eficientes

    • Sobre o item 4, dependências famosas como SQLite vão durar muito mais do que o produto que eu estou criando. Se eu achar que meu produto vai sobreviver mais do que esse open source, isso sim seria uma postura arrogante. Também não tenho intenção de compilar o kernel do Linux eu mesmo

    • Todo código deveria, no mínimo, conseguir ser compilado sem conexão de rede. O ideal é não depender de artefatos binários, embora isso nem sempre seja realisticamente possível

    • Ótima percepção. Vou adicionar isso ao meu documento de processo de adoção de dependências. O problema são casos como JavaScript, em que a árvore de dependências é profunda demais

  • Pela minha longa experiência, no fim tudo é “depende” (It Depends™). Quando eu era mais jovem, insistia em seguir princípios sem exceções, e isso me levou a produzir o pior código ao forçar bibliotecas ou paradigmas inadequados. Hoje, aquilo que uso com frequência eu mesmo transformo em bibliotecas e separo em pacotes, e para o resto preencho só o que não consigo fazer com dependências externas. Se eu conseguir aceitar a qualidade e a manutenibilidade, também adoto soluções externas com tranquilidade

  • O setor de energia tende a evitar dependências de propósito. Se você introduz dependências externas, precisa revisar todas as mudanças. Ferramentas de código com AI ajudaram muito, e eu as uso principalmente para gerar ferramentas CLI. Também faço documentação OpenAPI com LLM e a sirvo usando a biblioteca padrão do Go. O próprio LLM é uma dependência externa, mas as ferramentas CLI produzidas por ele não têm relação direta com o código real, então a exigência de qualidade é menor. Claro que desenvolvedores frontend não vão querer trabalhar sem React, mas esses produtos ficam do lado de fora, então viram exceção. Se você fornecer ferramentas pequenas que reduzam a obsessão dos engenheiros por dependências de qualidade, fica mais fácil aplicar uma política de minimização de dependências

    • Às vezes o LLM cospe no código trechos de open source usados no treinamento; nesses casos, será que isso não é praticamente igual a fazer fork da dependência e tratá-la como se fosse sua?
  • Saber distinguir dependências boas de ruins é uma habilidade importante. Na minha opinião, dependências pagas geralmente saem perdendo. Há uma boa chance de terem sido desenhadas para induzir lock-in. “Minimalismo de dependências” é um bom conceito (tweet relacionado do VitalikButerin)

    • Dependências pagas só têm um ponto de suporte, então, se a empresa fornecedora fechar, o projeto inteiro fica em risco. Como a maioria das empresas não dura para sempre, é essencial avaliar se o futuro da dependência pode afetar a trajetória do seu projeto

    • Já tive experiência ruim com dependências pagas impostas por equipes sem formação técnica. Por outro lado, dependências “open core” amplamente usadas pela comunidade, como Sidekiq, parecem bem mais confiáveis porque a chance de sumirem de repente é muito menor. A vantagem do pago é que, quando a empresa está saudável, você não precisa se preocupar tanto com suporte

    • Existe vendor lock-in tanto em componentes pagos quanto gratuitos fornecidos por empresas. Gerenciar esse risco é papel da equipe de integração, que precisa encontrar alternativas ou modularizar para controlar a exposição

    • Se for pago, tem que ser entregue obrigatoriamente por meio de uma interface baseada em padrão aberto ou com implementações alternativas disponíveis, para evitar lock-in. Se houver outras opções, a possibilidade de troca continua existindo

    • Dizer que dependência paga é ruim pode ser um sinal de orçamento insuficiente. Eu quero que dar suporte ao meu código seja o “trabalho” de alguém. Se há muitos mantenedores ou muitos desenvolvedores assumindo responsabilidade voluntariamente, isso traz estabilidade; e, mesmo em projetos pessoais, ainda que o código seja aberto, é preciso haver alguém que ajude quando surgir um problema. Essa responsabilidade de não abandonar a solução, mesmo se a empresa parar, é importante

  • Muita gente tem uma obsessão por escrever código novo, mas na prática, em 90% dos casos, até uma dependência ruim ainda é muito mais eficiente

    • Dependência é uma faca de dois gumes. Em grandes empresas de software, muitas vezes sai mais barato abandonar a manutenção do código e reescrever. Em agências pequenas de web/branding, um backend de alta qualidade praticamente não é necessário. Por outro lado, aqueles temidos padrões enterprise surgiram justamente para permitir isolamento e manutenção de código que ainda se preserve sem dependências externas, mesmo que a documentação e a memória institucional desapareçam em cinco anos. Dependências externas trazem dois riscos: serem descontinuadas ou passarem por mudanças destrutivas. No fim, isso afeta o fluxo de desenvolvimento de funcionalidades. Se o componente é interno, esses trade-offs também podem ser controlados internamente. Para SaaS, faz sentido usar dependências rapidamente em busca de sucesso de curto prazo; se segurança e suporte de longo prazo forem indispensáveis, é preciso pensar mais longe para ter sucesso. Escrever código novo quase nunca é o gargalo de uma organização

    • Tenho curiosidade sobre quão seriamente sua empresa trata vulnerabilidades de segurança e licenças. Antes eu era bem permissivo com dependências, mas depois que fui para uma empresa rígida com segurança e licenciamento, minha visão mudou bastante

  • A diferença entre biblioteca e framework também é importante. Biblioteca é uma ferramenta que faz bem uma coisa; framework define a estrutura inteira da aplicação. A comunidade Go evita frameworks grandes e prefere a biblioteca padrão, bibliotecas leves e, se necessário, até copiar/colar código-fonte. Por exemplo, frameworks como Gin (web API) e GORM (ORM) são convenientes, mas limitam a estrutura interna e aumentam a complexidade. O SDK padrão do Go já é bastante forte, então acho certo evitar dependências além do necessário

  • O autor é da Nova Zelândia. Há um pano de fundo da mentalidade do Number 8 wire na Nova Zelândia, ou seja, a atitude de “dar um jeito com o que se tem e com habilidade manual” (artigo da wiki sobre Number 8 wire)

    • Mesmo desenvolvedores experientes de outros países que não a Nova Zelândia muitas vezes concordam com isso de forma parecida. Quase todo mundo já sofreu com uma dependência escolhida de forma errada ou com upgrades desnecessários de bibliotecas

    • Como alguém da Nova Zelândia, sinto que essa mentalidade do Number 8 Wire já morreu há uns 20 anos

    • Descobri isso hoje pela primeira vez. Lembrou o bordão australiano “She'll buff out, mate”

  • A escala e a base comercial de uma dependência também afetam a escalabilidade. Se é uma ferramenta usada em implantações 100 a 1000 vezes maiores que a minha, a chance de ela bater no limite dentro do meu problema é menor. Nessa escala, bugs também tendem a ser encontrados ou corrigidos antes, o que no fim me beneficia em termos de segurança

    • Também há casos em que bibliotecas grandes simplesmente não funcionam em ambientes pequenos. Por exemplo, o compilador de Protocol Buffers para Swift antigamente quebrava em campos inesperados. Muitas empresas grandes também não testam esses caminhos fora do objetivo principal em larga escala

    • Já encontrei bugs graves em bibliotecas famosas feitas por grandes empresas (Meta, Google, Microsoft etc.). Mesmo reportando a issue, a correção demorava muito e havia enorme resistência a mudanças. Nessas situações, implementar por conta própria acabou sendo mais rápido e ainda melhorou o desempenho. Especialmente no mercado de consultoria, pedidos irracionais de clientes costumam desviar a direção do trabalho. À medida que cresceu minha confiança como desenvolvedor em conseguir fazer essas coisas sozinho, comecei a sentir que muitas vezes valia mais a pena implementar eu mesmo do que depender de uma dependência externa gigantesca. Claro, não faço coisas realmente massivas como navegadores ou modelos de AI, mas implemento eu mesmo, por exemplo, mecanismos locais de inferência, renderizadores HTML e até um banco de dados de grafos feito por mim. O cliente não espera novidade ou “inovação”; ele quer redução de risco. Quando eu desenvolvo diretamente, fica muito mais fácil cumprir cronogramas. Muitas vezes é mais eficiente construir do que gastar tempo mergulhando na documentação do Google ou de outras big techs. Nos últimos três meses, trabalhando 12 horas por dia para salvar um projeto abandonado pela equipe anterior, tenho pensado bastante nisso tarde da noite