3 pontos por GN⁺ 2023-12-29 | 1 comentários | Compartilhar no WhatsApp
  • Alguns projetos de software são do tipo sangue quente, que só se mantêm com atividade contínua de desenvolvimento; se a atividade pode ser interrompida e retomada depois, eles estão mais próximos do tipo sangue frio
  • Projetos de sangue frio escolhem tecnologia entediante para que build e testes não quebrem mesmo após longos períodos de pausa, e evitam depender de serviços externos que podem mudar ou desaparecer
  • Aquisição ou encerramento de serviços dos quais se depende, upgrade de compilador e fim do suporte a pacotes acabam voltando como custo de manutenção quando se tenta reiniciar projetos com pouca atividade
  • Códigos que, como projetos pessoais, ficam 1, 2 ou 3 anos sem receber atenção têm dificuldade de gerar calor contínuo, então precisam ser projetados desde o início com a premissa de uma baixa taxa de mudança
  • O gerador de site estático usado no blog, desde o primeiro commit em 2012, continua funcionando quase sem alterações apenas com Python 2, 4 módulos de terceiros incluídos no repositório, execução local e deploy via rsync over ssh

Como manter projetos sob a metáfora dos animais de sangue frio

  • Em uma aula de história natural em 2004, o professor colocou sob a câmera um filhote de painted turtle trazido do congelador e conduziu a aula assim
  • O filhote de painted turtle era uma das poucas espécies capazes de sobreviver mesmo em estado congelado
  • Durante uma hora, a tartaruga começou com movimentos quase imperceptíveis e, no fim, havia se deslocado cerca de metade da tela
  • Animais de sangue quente precisam manter a temperatura corporal em uma faixa estreita, e humanos têm problemas ao sair de algo próximo de 37°C
  • Animais de sangue frio ajustam o metabolismo à temperatura ao redor, ficam ativos quando está quente e se movem mais devagar à medida que o corpo e o ambiente esfriam
  • Projetos de software também se dividem de forma parecida
    • Software de sangue quente funciona bem quando há movimento e calor constantes no projeto
    • Se ficar parado por 6 meses, pode parecer um projeto morto quando for retomado

Condições e exemplos de software de sangue frio

  • O motivo de projetos de sangue quente ficarem difíceis de retomar é o acúmulo de mudanças externas
    • O serviço do qual o CI depende pode ser adquirido ou ficar sem dinheiro e parar de funcionar
    • Ao adicionar uma nova dependência, pode passar a ser necessário um upgrade de compilador
    • Outros pacotes podem perder suporte e deixar de funcionar com o compilador mais recente
  • Projetos em que alguém trabalha sozinho, muda algo apenas quando tem inspiração e depois passa mais de 1 ano sem tocar neles são difíceis de operar como software de sangue quente
  • Um projeto de sangue frio, como o filhote de painted turtle congelado, precisa poder ser retomado daqui a 1 ano exatamente do ponto em que parou
  • Para isso, usa boring technology e evita depender de serviços externos cujos scripts de build e teste podem mudar, quebrar ou desaparecer por completo
  • As dependências ficam incluídas no próprio repositório do projeto, como no modelo de vendored dependencies
  • O software que move este blog é um exemplo de projeto de sangue frio
    • O primeiro commit foi em 8 de janeiro de 2012 e era um pequeno gerador de site estático para substituir uma instalação antiga de Wordpress
    • Foi escrito em Python 2, depende de 4 módulos de terceiros, e todos eles estão comitados no repositório do projeto
    • Todo o processo roda localmente, e o resultado é publicado com rsync over ssh
    • Tirando algumas pequenas melhorias, ele continuou funcionando sem modificações e a expectativa é que siga funcionando também daqui a 12 anos

1 comentários

 
GN⁺ 2023-12-29
Opiniões do Hacker News
  • O framework web Express, do ecossistema Node e JavaScript, ainda mantém a versão principal 4.x.x há mais de 10 anos https://www.npmjs.com/package/express?activeTab=versions
    Mesmo assim, é amplamente usado, com mais de 17 milhões de downloads por semana https://www.npmjs.com/package/express, e, embora não tenha todos os recursos nem o melhor desempenho https://fastify.dev/benchmarks/, é bom por permitir desenvolvimento rápido e estável e planejamento de longo prazo
    Não é preciso se preocupar com o fim de patches de segurança para versões antigas nem com mudanças bruscas de API; e Go é ainda mais estável, pois, graças à ampla biblioteca padrão e à promessa de compatibilidade, programas com mais de 10 anos ainda conseguem rodar https://go.dev/doc/go1compat

    • Foi uma surpresa agradável perceber que o Express já tem 13 anos
      Quando surgiu, era frequentemente tratado como lixo para falsos programadores incompetentes só por ter sido escrito em JavaScript, mas depois ajudou várias empresas a criar serviços excelentes que geram dinheiro de verdade com Express, e hoje deve estar lidando com uma quantidade enorme de requisições
      Hoje em dia também escrevo bastante em Go, mas ainda fico plenamente satisfeito em criar serviços com Express e, no geral, considero-o um bom software
    • Dizem que o Express v5 deve sair em breve (https://github.com/expressjs/express/issues/4920)
    • CakePHP também oferece esse tipo de estabilidade, e por isso RoR acaba me afastando
      Não é que eu realmente o odeie; é mais que provavelmente não o escolheria por causa da esteira interminável de upgrades de versão
  • Python é um péssimo exemplo de software de sangue-frio
    breaking changes constantes tanto no runtime quanto nas ferramentas, e o autor também está numa situação em que precisa continuar usando Python 2, cujo suporte já acabou há muito tempo
    Exemplos melhores seriam linguagens como Go ou Java, nas quais código de 10 anos atrás ainda roda bem com ferramentas modernas; ou, de forma ainda mais extrema, Perl, em que código de 30 anos atrás ainda funciona bem

    • É verdade
      Ao criar software, você acaba cometendo erros que permitem que usuários façam algo de uma forma que você não pretendia; no mundo Java, isso é resolvido adicionando recursos mais novos, seguros e com intenção mais clara, e incentivando os usuários a migrarem
      Python é parecido, mas com um “e em breve vamos desligar o recurso antigo” junto; Java não faz isso
      Por exemplo, o método equals de java.net.URL é conhecido como um design quebrado e é fortemente desaconselhado, mas continua sendo suportado há mais de 20 anos
      O operador vazio do Python Airflow por um tempo aceitava o nome DummyOperator, mas, como “dummy” já foi usado historicamente e culturalmente como termo pejorativo, os mantenedores passaram a recomendar EmptyOperator e quebraram o nome antigo
      Ao fazer upgrade, o código dava erro no momento do carregamento até que o nome da referência fosse alterado; pessoalmente, acho que eu não quebraria os usuários desse jeito
      No mundo Java, uma mudança de nome dessas poderia ser resolvida com substituição de texto, então eles provavelmente teriam continuado a dar suporte até surgir um motivo realmente insustentável para não fazê-lo
      Por isso, no geral, vejo que Java e dependências de bibliotecas Java podem ser atualizados com muito mais liberdade do que Python
    • Maven é excelente
      Se você usar uma versão Java LTS e escolher boas dependências, consegue fazer tudo rodar de novo a qualquer momento
      Em Python, numa disciplina de machine learning, uma dependência introduziu da noite para o dia uma mudança de API que quebrava tudo, e o instrutor não percebeu porque estava usando a versão mais recente de algumas semanas antes, quando começou a preparar a aula
    • Não entendo muito bem o que significa dizer que “há breaking changes constantes”
      A migração de Python 2 para 3 foi uma breaking change, mas foi uma mudança única, não “breaking changes constantes”
      Se você permanecer na mesma versão principal, versões menores novas não quebram código antigo; por exemplo, código 2.x antigo roda bem no 2.7, e código 3.x antigo roda bem até no 3.12
      Mudanças de versão menor podem adicionar novos recursos, mas código 3.x antigo não quebra só por não usar a palavra-chave async ou type hints
    • Concordo
      Esse é um dos motivos pelos quais evito Python sempre que possível
      Sinto que há pouca chance de código Python escrito hoje continuar funcionando daqui a alguns anos, e considero isso um problema bem grande
    • Sobre a afirmação de que “código Java de 10 anos atrás roda bem com ferramentas modernas”, não sei não
      Mesmo tentando executar código Java de 3 anos atrás com um SDK novo, sempre havia algo quebrado
  • Trabalho com mainframes IBM (z/OS), e quase não vi nada que chegue tão perto da IBM em manter compatibilidade retroativa
    Acho que o Microsoft Windows fica em segundo lugar, e a ABI do kernel Linux em terceiro, mas, olhando o ecossistema Linux como um todo, isso é apenas uma pequena parte
    A maior parte do restante se aproxima mais de churn, e no open source parece raro alguém querer gastar seu tempo livre com compatibilidade retroativa
    Economicamente, isso parece um dilema do prisioneiro: todos empurram o custo de manter compatibilidade para os outros e, no fim, criam mais trabalho inútil para todo mundo

    • Há uma tendência grande no open source de correr atrás do novo e reluzente, mas é difícil dizer que isso se aplica a todos
      Por exemplo, olhando para a comunidade de computação retrô, não é raro escrever drivers para fazer hardware novo funcionar em sistemas operacionais antigos
    • Receber para fazer manutenção certamente ajuda muito
      Sem remuneração, no fim depende de quanto você valoriza a plataforma que está criando, e eu decidi mirar diretamente o kernel Linux por meio de chamadas de sistema por causa do compromisso comprovado dele com estabilidade de ABI
      Por outro lado, com a linguagem de programação que eu mesmo criei, fico com vontade de continuar corrigindo coisas porque quero deixá-la o mais “perfeita” possível
      Coloquei um aviso no README dizendo que ela ainda está em estágio inicial de desenvolvimento e é instável, caso alguém queira usá-la de forma insana
      Imagino que quem cria Ruby ou Python se sinta de modo parecido; a linguagem parece um filho, você quer que ela tenha sucesso, então pode achar que precisa corrigir erros como o fato de print ter sido uma palavra-chave
    • Não é só uma questão de compatibilidade retroativa; se você deixa algo sem cuidados por um tempo, também há uma grande probabilidade de quebrar por motivos aleatórios
      Muitas vezes, inclusive, o que quebra são justamente as partes voltadas à compatibilidade retroativa
      No meu emprego anterior, criávamos apps Node conteinerizados, e o CI construía as imagens a partir do código-fonte Node; de repente, os deploys de serviços que não eram mexidos havia um tempo começaram a falhar
      Descobrimos que o Dockerfile se baseava em uma imagem do Ubuntu cujo período de suporte havia acabado, e os repositórios de atualização tinham sido movidos para repositórios arquivados, então não dava para construir a imagem sem corrigir o Dockerfile
      É um exemplo de software que quebra mesmo sem ser tocado, e por isso prefiro Go e binários únicos
      Se você empacotar como release, nem precisa recompilar, e uma imagem Docker Distroless não tem dependências além do meu binário
      Usei Go por muito tempo e nunca tive problemas de software se deteriorando com a idade; vários tipos de problemas que eu sentia ao usar Node ou PHP desapareceram
      No lado do Node, o segundo maior problema são os padrões de indireção dos frameworks, e o primeiro é o gerenciamento de pacotes
      Problemas de peer dependency como “instalei a versão X, mas o módulo Y precisa da versão Z” aparecem o tempo todo
  • Muitos engenheiros, ao procurar bibliotecas no GitHub, verificam a data do último commit
    Há uma tendência a pensar que, quanto mais recente o commit, melhor suportada é a biblioteca
    Mas, se um projeto arquivado faz exatamente o que você precisa, tem zero bugs e foi estável por anos, é como encontrar uma joia escondida em um brechó
    Hoje em dia, muitos engenheiros descartam automaticamente bibliotecas que não são atualizadas “continuamente”, e parecem achar que isso é algo bom

    • Para uma biblioteca permanecer estática, o ambiente em que ela é usada também precisa ser estático
      O ambiente moderno de desenvolvimento de software muitas vezes não é assim, e o front-end web é um exemplo típico de mudanças frequentes
      Uma biblioteca totalmente independente pode ficar bem sem atualizações, mas uma biblioteca que depende de um framework de front-end web causará problemas se não for atualizada para acompanhar as mudanças do ecossistema
    • Embora, estritamente falando, isso nem sempre esteja certo, olhar a recência das atualizações é uma ótima heurística
      Não sei os números reais, mas acredito que, na esmagadora maioria dos casos, ausência de atividade recente significa “abandonado”, não “concluído e sem bugs”
    • Já vi gráficos mostrando como as linguagens de programação mudaram com o tempo e quanto do código original ainda permanece
      Algumas linguagens ficaram quase irreconhecíveis em relação à versão 1.0, enquanto outras mantiveram a maior parte do código escrito e apenas adicionaram coisas por cima
      No fim, essa tendência parece se refletir também na comunidade e no ecossistema
      Lembro que Clojure aparecia perto do topo da lista por quase não fazer mudanças que quebram compatibilidade, e uma biblioteca cuja última alteração foi há 5 anos ainda roda perfeitamente na versão atual da linguagem
      O fato de ser da família Lisp, permitindo estender o núcleo da linguagem sem mudanças upstream, também parece ajudar, embora naturalmente isso tenha seus próprios defeitos
      Ainda assim, foi bom porque me fez parar de pensar que “frescor” é igual a “qualidade”
      Hoje em dia uso com mais frequência bibliotecas que quase não mudam há anos do que bibliotecas criadas no ano passado, e não tenho grandes problemas
    • Depende da linguagem
      Algumas linguagens lançam versões a cada 1 ou 2 anos e acrescentam sintaxes novas e elegantes ou tipos abstratos de dados na biblioteca padrão para substituir padrões usados com frequência, mas que eram desajeitados
      A comunidade dessa linguagem passa a considerar a nova sintaxe “idiomática” quase imediatamente, e entende que código escrito no antigo jeito mais pesado deve ser corrigido
      O motivo para mudar uma base de código específica geralmente é que o jeito antigo é mais obscuro em comparação com a nova sintaxe e dificulta manutenção e code review
      Se a nova sintaxe existisse desde o começo, ninguém acharia que o jeito antigo era bom código; portanto, o argumento é atualizar o código para melhorar a legibilidade para novos desenvolvedores e reduzir a barreira de entrada para contribuições
      Se uma biblioteca implementada em uma linguagem desse tipo não é atualizada há mais de 3 anos, isso muitas vezes é um mau sinal
      Pode significar que o desenvolvedor não está conectado à comunidade o suficiente para manter o código em forma idiomática e facilmente legível por outros desenvolvedores que aprenderam a forma mais moderna da linguagem, e talvez nem tenha interesse em aceitar PRs externos
    • Se “zero bugs” significar zero issues no GitHub, é preciso ter cuidado
      Pode haver vulnerabilidades de segurança; ninguém as reporta apenas porque o projeto está marcado como abandonado
  • O único software que consegue viver sem atualizações é aquele que foi feito corretamente desde o início
    Se for um software só para você, isso é relativamente fácil: é bem provável que seus gostos não mudem muito mesmo depois de 10 anos e, como n é pequeno, dá para ignorar problemas pequenos em que se usa uma função O(n^2) mesmo havendo uma O(n)
    Mas, se for um software que outras pessoas vão usar, os requisitos são diferentes, e surgem problemas do tipo em que, para um N grande o suficiente, uma função O(n) passa a ter valor
    Seja para uso próprio ou para os outros, podem aparecer problemas imprevistos
    Por exemplo, ao processar arquivos com mais de 1 GB, ele pode travar, mas você nunca ligou para isso porque normalmente só usava arquivos com menos de 100 KB; ao tentar corrigir, talvez tenha que reescrever metade do sistema
    A maior objeção à ideia de que software que não muda é inerentemente melhor do que software que muda com frequência está aí
    Um software que não muda pode ter sido perfeito desde o início, mas também pode haver um horror escondido nas profundezas, e é difícil distinguir isso de antemão
    Isso também não quer dizer que software atualizado rapidamente seja inerentemente melhor do que software atualizado lentamente; há muitos fatores além da velocidade das atualizações

    • Acho que a ideia não é que o software nunca deva mudar
      Se os requisitos mudam, é claro que o software também deve mudar
      Mas, ao longo de 10 anos, muita coisa pode acontecer sem relação com mudanças de requisitos
      Projetos open source podem ser abandonados ou mudar de direção, softwares comerciais podem ser descontinuados, empresas podem ser adquiridas, regras da App Store ou da Play Store podem mudar, APIs podem desaparecer ou mudar de preço a ponto de destruir a viabilidade econômica de um projeto
      Toolchains, frameworks, linguagens de programação, paradigmas e boas práticas também mudam
      Acho que o ponto central é impedir que mudanças externas, sem relação com os requisitos, me obriguem a mudar
      É um bom princípio, mas, como sempre, há trade-offs
      Ser estável é diferente de ficar obsoleto, e essa diferença muitas vezes se decide na segurança
      E se for fácil atender a um novo requisito importante, mas para isso for preciso atualizar uma biblioteca vendored em 7 versões principais, causando um monte de quebras não relacionadas?
      E se já não houver pessoas suficientes familiarizadas com um conjunto de ferramentas parado no tempo, e ninguém quiser aprendê-lo?
      Escolher dependências com cuidado e conservadorismo é bom, mas acho que deixar de acompanhar até mesmo as mudanças dessas poucas dependências mantidas pequenas já é ir um passo longe demais
  • Concordo com o sentimento do texto
    Odeio o fato de que até um app mobile feito há apenas alguns anos agora exige dezenas de horas para ser corrigido e ter uma atualização enviada
    Também é interessante a parte final em que o autor chama seu gerador de site estático de software de sangue frio e diz que ele roda em Python 2
    Python 2 está ficando cada vez mais difícil de instalar hoje em dia, e no fim esse projeto também vai se tornar um projeto de sangue quente

    • Tenho um pequeno projeto pessoal de hobby (iOS e macOS) que não desenvolvo regularmente, mas uso com frequência como usuário, e o mantenho compilando e rodando nos sistemas operacionais mais recentes
      Toda vez que atualizo o Xcode, preciso corrigir coisinhas para que o projeto compile e funcione de forma limpa; isso é realmente irritante e deveria ser totalmente inaceitável
      As mensagens recentes do histórico do git são todas variações de “corrigido para funcionar no Xcode mais recente”
      Se essas mudanças no SDK ou no sistema operacional subjacente fossem necessárias por ameaças de segurança, eu até entenderia em alguma medida, mas quase nunca é o caso
      Na maior parte das vezes são mudanças idiotas como descontinuar APIs, adicionar warnings por padrão e dizer que agora se deve usar este framework em vez daquele
      Plataformas e frameworks precisam parar de ser alvos móveis de propósito, especialmente quando já são sistemas operacionais muito estáveis e confiáveis
      Deveria ser possível tirar um projeto de 10 anos do congelador e fazê-lo compilar e rodar tão limpo quanto há 10 anos
      Esses fornecedores de sistemas operacionais são empresas de trilhões de dólares, então não quero ouvir a desculpa de que manter compatibilidade retroativa exige muito esforço de engenharia
  • Continuo mantendo um projeto pessoal paralelo
    Comecei há 12 ou 13 anos em PHP puro; depois reescrevi em Laravel e, por volta de 2017, reescrevi de novo em Symfony
    Houve períodos de 6 a 18 meses em que, por trabalhar full-time como freelancer, eu não tinha energia e fazia só 2 ou 3 commits muito pequenos, mas, quando tinha tempo, adicionava funcionalidades, fazia upgrades, experimentava e aprendia
    Foi muito útil para aprender a manter um projeto no longo prazo
    Aprendi coisas como atualizar dependências, remover o desnecessário, verificar atualizações de segurança, procurar oportunidades de simplificação (de Vagrant para Docker, de Vue + Axios + Webpack etc. para Htmx) e também aprendi o que evitar
    Pessoalmente, passei a evitar dependências recém-criadas, microsserviços e infraestruturas complexas como Kubernetes
    Recentemente criei várias funcionalidades, atualizei para PHP 8.2 e Symfony 7 e também integrei recursos baseados em ChatGPT, então acho que, se eu quiser, posso descansar por 1 a 3 anos
    Nos últimos 4 ou 5 anos, esse projeto gerou uma receita parecida com a renda anual média de um freelancer, então não é um projeto paralelo obscuro adormecido

    • PHP parece horrível quando se volta a ele, mas acho que é um exemplo de quem realmente preserva compatibilidade retroativa, mesmo arcando com o próprio prejuízo
      Depois de alguns anos sem usar, voltei e vi que aquelas funções horríveis de manipulação de imagens continuavam exatamente como estavam quando saí, 8 anos antes
    • Estou pensando em aprender Symfony de forma mais nativa, e fiquei curioso sobre o motivo de ter migrado de algo como Laravel para Symfony
  • Além do que foi dito no texto, um modelo de ameaças inerentemente seguro é importante
    Por exemplo, um site inteiro precisa lidar continuamente com atacantes e spambots, então é inerentemente mais próximo de sangue quente
    Por outro lado, uma página estática como TiddlyWiki pode nem sequer ser colocada na web, e o navegador é uma plataforma extremamente estável, então é muito melhor

  • A diferença de preferência entre projetos de sangue-frio e projetos de sangue-quente parece estar relacionada ao Buxton Index mencionado em https://www.cs.utexas.edu/users/EWD/transcriptions/EWD11xx/EWD1175.html

    • Lendo o texto linkado, o Buxton Index seria o período que indica em escala de quantos anos um sujeito, como uma pessoa ou organização, faz seus planos
      Uma pequena mercearia de bairro teria cerca de 0,5 ano; um verdadeiro cristão, infinito; um político médio de olho na reeleição, cerca de 4 anos; a maior parte da indústria, um pouco mais do que isso; e gestores que precisam escrever relatórios trimestrais, bem menos
      O motivo pelo qual o Buxton Index é importante é que a cooperação estreita entre sujeitos com Buxton Indexes muito diferentes inevitavelmente fracassa e leva a acusações morais
      O lado com prazo mais curto é acusado de ser superficial e míope, enquanto o lado com prazo mais longo é acusado de negligência profissional, fuga de responsabilidade ou de pegar carona
      Também acabam considerando um ao outro burro
      A vantagem do Buxton Index é que, por ser um conceito numérico simples, ele é moralmente neutro e eleva a diferença acima de uma disputa moral
      Isso é especialmente importante ao pensar em colaboração entre academia e indústria
    • Soa muito parecido com preferência temporal: https://en.wikipedia.org/wiki/Time_preference
  • Esse nome é muito ruim
    Animais de sangue-frio dependem muito do ambiente, enquanto animais de sangue-quente reduzem a dependência da temperatura externa por meio do metabolismo
    De qualquer forma, é desnecessariamente ambíguo
    Se simplesmente chamasse de “software sem dependências externas”, daria para eliminar o parágrafo explicativo prolixo

    • É a única resposta que acerta em cheio o problema central do texto, mas, claro, ninguém aqui a recomendou
      Eu já não gosto de textos de desenvolvimento de software que pulam para conclusões rasas com metáforas inadequadas tiradas da natureza; gosto menos ainda quando fazem isso entendendo completamente errado o próprio fenômeno natural usado na metáfora
      O fato de algumas espécies, incluindo tartarugas-pintadas, sobreviverem ao congelamento não se deve ao sangue-frio, mas a proteínas anticongelantes especiais
      Outros lagartos ou animais de sangue-frio teriam seus próprios tecidos rompidos ao descongelar
    • Também houve quem lesse a interpretação biológica de forma mais generosa
      https://lobste.rs/s/hitos3/cold_blooded_software#c_mxjzwh