10 pontos por GN⁺ 4 시간 전 | 1 comentários | Compartilhar no WhatsApp
  • Projeto de software é aprendido mais profundamente em projetos reais do que em aulas, quando você assume responsabilidade e o problema passa a ser seu
  • Conway’s Law é a visão de que o software repete a estrutura social da organização, e a diferença entre código científico e código industrial também pode surgir dos incentivos
  • O rust-analyzer facilita o foco de colaboradores de alta eficiência com build rápida, suporte a stable, remoção de dependências em C e testes que levam apenas alguns segundos
  • O rust-analyzer protege funcionalidades independentes com catch_unwind e reduziu o padrão exigido para PRs, mas aplica critérios de qualidade muito mais rigorosos ao spine central
  • Estruturas experimentais podem se tornar a realidade de longo prazo, e o rust-analyzer também passou de um protótipo de arquitetura LSP para a manutenção de mais um compilador

Projeto de software é melhor aprendido na prática

  • Projeto de software é aprendido melhor ao assumir responsabilidade em projetos reais e resolver os problemas diretamente do que em aulas formais
  • Mais do que quando assumiu o papel de “arquiteto” em disciplinas de arquitetura na universidade e em projetos de curso, o aprendizado realmente começou quando os problemas de projeto passaram a ser responsabilidade própria no segundo projeto real, IntelliJ Rust
  • No IntelliJ Rust, houve alguns erros, mas nada fatal, e foi possível aprender muito nesse processo
  • Engenharia de software também tem um lado simples o bastante para que pessoas curiosas a aprendam pensando a partir dos princípios e lendo vários textos

Estrutura de incentivos e Conway’s Law

  • Conway’s Law é a visão de que o software repete a estrutura social da organização que o produz
  • A diferença entre software industrial e código científico pode vir não tanto do conhecimento de como construir software, mas da estrutura de incentivos que leva as pessoas a produzi-lo
  • Situações como “um PhD que precisa publicar um artigo em 3 meses” podem ser um fator importante na forma que o código científico assume
  • Há basicamente duas formas de lidar com a estrutura de incentivos
  • Projetar ou mover os incentivos do projeto

    • Oportunidades de projetar ou ajustar a estrutura de incentivos de um projeto são raras, mas quando aparecem, o impacto é grande
    • O ponto central de TIGER_STYLE não está na lista de regras em si, mas no contexto social que faz com que essas regras se tornem boas escolhas
  • Se não puder mudar, adapte-se às restrições

    • Quase nunca a estrutura de incentivos vem do jeito que se quer, e, se não for possível mudá-la, é preciso se adaptar a ela
    • Mesmo em projetos de software industrial, quase nunca há “tempo para fazer direito”, e é preciso fazer o melhor possível dentro das restrições dadas

Como o rust-analyzer alinhou estrutura e participantes

  • rust-analyzer é um projeto com profundidade e amplitude ao mesmo tempo
  • No lado profundo, por ser um compilador, ele consegue atrair colaboradores excelentes e dedicados
  • No lado amplo, IDEs clássicas têm muitas funcionalidades especializadas, então o projeto é adequado para quem está aprendendo Rust ou para colaboradores ocasionais de fim de semana gastarem uma ou duas horas resolvendo um incômodo próprio
  • O motivo de o rust-analyzer insistir em não exigir build do rustc, compilar em stable, não ter dependências em C e fazer toda a suíte de testes terminar em poucos segundos era atrair colaboradores de alta eficiência
  • A ideia era lapidar o sistema de build para que as pessoas pudessem se concentrar no borrow checker sem se preocupar com outras coisas
  • Para atrair colaboradores de fim de semana, o interior do rust-analyzer foi dividido em várias funcionalidades independentes, cada uma protegida em runtime com catch_unwind
  • O padrão para aceitar PRs de funcionalidades foi reduzido para “o caminho feliz funciona e há testes”, aceitando até mesmo que aquele código pudesse falhar com crash
  • Mas duas condições eram necessárias
    • Problemas de qualidade precisavam ficar isolados dentro de cada funcionalidade individual e não se espalhar para outras partes
    • Crashes em runtime não poderiam ser visíveis para o usuário, e, para isso, as funcionalidades do rust-analyzer precisavam operar sobre snapshots imutáveis e não poder corromper os dados
  • Em contrapartida, critérios de qualidade muito mais rígidos foram aplicados ao spine central que sustenta as funcionalidades

O risco de estruturas experimentais virarem realidade de longo prazo

  • Ao adaptar-se à estrutura de incentivos em vez de corrigi-la, é preciso ter cuidado com o fato de que o futuro é incerto e geralmente pode se concretizar da forma mais inconveniente possível
  • A motivação original do rust-analyzer era evitar escrever mais um compilador paralelo dentro do IntelliJ Rust e validar, como protótipo, uma arquitetura melhor para LSP, devolvendo esse aprendizado ao rustc
  • Por isso, o código era muito experimental, inclusive no núcleo
  • No fim, acabou sendo necessário manter mais um compilador
  • De forma semelhante, o projeto uutils começou como um destino principal para pessoas aprendendo Rust e acabou se tornando a implementação de coreutils do Ubuntu

Materiais e livros que valem consultar

  • Não existe um único livro com a resposta certa, e a prática parece ser um elemento indispensável
  • Boundaries by Gary Bernhardt
    • Traz conselhos concretos muito sólidos e foi um material que estimulou uma exploração em nível mais alto
  • How to Test
    • A importância dos testes foi entendida de imediato, mas demorou muito para reconhecer que muitos conselhos populares sobre testes não eram práticos e para conceitualizar o que realmente funciona
  • ∅MQ guide e textos de Pieter Hintjens
    • Foram materiais que apresentaram esse modo de pensar no estilo de Conway’s Law
    • A arquitetura de desenvolvimento de funcionalidades do rust-analyzer é uma aplicação de optimistic merging
  • Reflections on a decade of coding by Jamii
    • Trata de questões muito meta e é excelente a ponto de ser o primeiro item da coleção de links
  • Ted Kaminski blog
    • No formato de notas para um livro que não existe, é o mais próximo de uma teoria consistente sobre desenvolvimento de software
  • Software Engineering at Google e The Philosophy of Software Design, de Ousterhout

1 comentários

 
GN⁺ 4 시간 전
Comentários no Hacker News
  • Resumindo em uma cola: um bom design deve ter uma ideia central permeando o todo e seguir na direção de minimizar surpresas
    Se o sistema permitir, as pessoas no fim vão usá-lo assim, e uma solução que começa com “se todo mundo simplesmente fizer X” não é solução
    Separe a parte que transforma os dados da parte que os usa, modelos de dados duram mais que código, e acoplamento está na raiz de muitos problemas
    Controle de versão é inevitável, estado deve aparecer de forma explícita, e cada informação deve ter uma única fonte da verdade
    Gaste mais tempo dando nomes, se é difícil testar então o design está errado, e decisões não documentadas acabam sendo motivo de arrependimento
    Comunicação tem custo, então deve ser justificada antes de ser paga, e o trabalho do engenheiro é resolver problemas com heurísticas em meio a informação incompleta

    • A maior parte disso na verdade não parece ter muito a ver com arquitetura de software; no máximo, “isolar partes do sistema” poderia se encaixar aqui
      O próprio texto também não era muito consistente do ponto de vista de arquitetura de software
      A visão arquitetural 4+1 é uma boa estrutura conceitual para pensar no panorama geral, tirando o UML, e a série Pattern-Oriented Software Architecture também organiza bem as várias arquiteturas a que as pessoas chegam
      Grady Booch também chegou a manter um handbook de arquitetura de software, mas hoje parece quase parado; na época, a mailing list registrava grandes arquiteturas de sistemas de empresas ou de grandes projetos open source
      Ao cavar esse tipo de material, dá para ver que arquiteturas são construídas com focos diferentes, como escala, segurança, desempenho, interoperabilidade e fail-safe, e que cada objetivo traz trade-offs realistas
    • Não concordo com tudo, mas acrescentando algumas coisas: o objetivo primário do software é resolver o problema da frente, e o secundário é resolver problemas futuros prováveis com o mínimo de esforço
      Se for melhor por esse critério, então algo que parece design ruim na prática pode ser design bom
      Interfaces devem ser fáceis de usar corretamente e difíceis de usar de forma errada, e é preciso pensar em como alguém que não conhece o projeto vai utilizá-las
      Código correto deve ser fácil de escrever e código suspeito deve chamar atenção, e bugs devem ser empurrados o máximo possível para a esquerda
      É melhor eliminar uma classe de bugs do que corrigir um bug só, e interfaces são mais difíceis de mudar do que implementações, então se a interface estiver certa, uma implementação feia pode ser aceitável
      Comentários e documentação devem explicar por que o código tem aquela forma, e mesmo que exista um jeito aparentemente mais simples, se alguma restrição impedir isso, vale registrar
      Do ponto de vista de dados, não se deve repetir; se o mesmo fato é armazenado em vários lugares, cedo ou tarde vai divergir e virar bug
      Sair do caminho já bem pavimentado tem custo, e quando realmente vale a pena tudo bem, mas esse custo não deve ser subestimado
      Muitas vezes uma tecnologia chata que parece pior é na verdade melhor, e a expectativa não deve ser “vale a pena fazer isso?”, mas “vale a pena fazer isso em comparação com fazer outra coisa?”
      Mesmo achando que você é mais inteligente que os outros, há problemas em que inteligência sozinha não basta; alguns problemas só podem ser descobertos quando explodem de verdade, então é preciso aprender com os erros dos outros
      Fricção é uma assassina silenciosa
    • Migração de dados é inevitável, então precisa ser planejada com antecedência, e isso é consequência do versionamento
      Planejar é bom, mas às vezes é preciso tentar na prática, e tudo custa dinheiro
      Se você projeta sem considerar custo, depois acaba forçado a fazer escolhas difíceis
    • Depois de mais de um ano fazendo um videogame, estou construindo um motor sustentável e um pipeline de dados separado, uma camada totalmente separada de renderização/gerenciamento de recursos e uma forma explícita de transição de estados, e quase toda essa lista se aplica exatamente
      Mesmo em um projeto solo, as restrições do motor servem como guia do tipo “adicione assim esse recurso novo estranho que apareceu durante os testes”
      Não é preciso ficar carregando um documento enorme para lembrar algo como “para criar um novo efeito sonoro executado várias vezes em certa transição de estado, faça assim”
    • Trabalho nessa área, e como o nosso lado modela a indústria médica, tudo é menos abstrato do que em outros domínios
      O designer de sistemas precisa entender a indústria; não precisa adotar completamente a terminologia e os hábitos de modelagem deles, mas precisa entender os motivos e os pontos de vista com que olham para os datasets
      Houve partes em que simplificamos deliberadamente a complexidade do mercado de saúde para remover excesso de definição desnecessário e oferecer um modelo mais integrado, mas só deu para fazer essas mudanças com confiança porque entendíamos bem o domínio do problema
      Em especial, nomes quase nunca morrem
      Às vezes morrem, mas mudar nomes exige um esforço extremo, então vale muito a pena gastar bastante tempo fazendo especialistas de domínio revisarem propostas de nomenclatura para verificar se não falta nada
      Algumas ideias dá para empurrar, mas o lado de negócios, como vendas e marketing, continua exigindo a terminologia do setor e pressionando para que o modelo reflita a visão atual da indústria
      Se você decidiu quebrar esse fluxo, essa ruptura precisa ter intenção e propósito claros
      A propriedade mais importante em software é manutenibilidade
      Quanto custa construir também é uma pergunta importante, mas o custo de operar tem impacto muito maior, porque inclui não só infraestrutura como também pedidos de funcionalidades acumulados, refatoração de código e manutenção de versões de software de terceiros
  • Listas de recomendação costumam ser boas, como A Philosophy of Software Design, do Ousterhout, mas em geral ficam mais perto de desenvolvimento de software em sentido amplo do que de arquitetura de software em si
    Para ver arquitetura, recomendo clássicos como Software Architecture: Perspectives on an Emerging Discipline, de Shaw/Garlan, e os textos de Mary Shaw
    Também são bons artigos mais recentes como Myths and Mythconceptions: What Does It Mean to Be a Programming Language, Anyhow? e Revisiting Abstractions for Software Architecture and Tools to Support Them, que exploram por que a área de arquitetura de software não seguiu o rumo esperado
    Na prática, vale observar por que Unix pipes and filters e REST deram certo, onde e por que quebram, e arquitetura hexagonal também é central
    Pessoalmente, também gosto de Beyond Procedure Calls as Component Glue: Connectors Deserve Metaclass Status, que liga arquitetura de software a protocolos metaobjeto e tenta enxergá-los como uma nova base para linguagens de programação e para a programação
    É uma resposta ao texto de Mary Shaw Procedure Calls Are the Assembly Language of Software Interconnection: Connectors Deserve First-Class Status, perguntando como seria uma linguagem de alto nível se chamadas de procedimento fossem a linguagem assembly
    Talvez a arquitetura de software ainda tenha um futuro mais brilhante e mais prático

    • O que é arquitetura hexagonal?
    • A pergunta “se chamadas de procedimento são assembly, como seria uma linguagem de alto nível?” me parece, mesmo eu não sendo tão versado em teoria de linguagens de programação e ferramentas de engenharia de software, meio que a ideia básica de cálculo lambda, LISP, APL, Clojure, TCL e coisas assim
      Com algumas estruturas de dados e tipos, e um pequeno conjunto de funções básicas, dá para compor o resto
      Uma coisa de que gosto em Lisp é que tipos mais complexos, especialmente os vindos de FFI, são sempre opacos
      Eu gostaria de ver uma implementação de CLOS em que, ao definir uma struct numa linguagem parecida com C, você ganhasse um conjunto padrão de funções
  • A melhor forma de aprender arquitetura não é criar um projeto grande o bastante, e sim fazer manutenção nele
    E isso deveria acontecer em pelo menos dois ou três projetos
    Se o projeto for pequeno demais, qualquer arquitetura funciona, e “grande” talvez seja melhor medido não por linhas de código, mas pelo número de pessoas — ou melhor ainda, de equipes — que já trabalharam nele
    É preciso haver pelo menos dois projetos diferentes para haver comparação, e já vi gente ficar presa por décadas num único projeto sem conhecer formas modernas de resolver problemas
    Mas na prática costuma acontecer de quem criou o projeto ser promovido a arquiteto; quem fez manutenção raramente vira
    No Google isso fica ainda mais claro: para ser promovido é preciso lançar coisas novas; manutenção não promove, e se possível é melhor sair logo depois do lançamento
    Paradoxalmente, a melhor posição para virar arquiteto pode ser a do contratado externo jogado na manutenção de projetos legados que ninguém na empresa quer assumir
    Essas pessoas precisam manter a arquitetura e passam por vários projetos, então conseguem comparar
    Por outro lado, se cobram por hora, há o risco de complicarem demais a arquitetura para faturar mais tempo

  • Nesse contexto, recomendo muito Architecture of Open Source Applications
    É uma série de livros em que cada capítulo foi escrito por um mantenedor do projeto correspondente, então dá para aprender arquitetura por exemplos
    Você entende não só o que é a arquitetura, mas também as restrições que a produziram, normalmente a história e a visão mutável do projeto
    Por causa das limitações naturais de um livro com muitos autores, nem todos os capítulos são igualmente bons ou interessantes, e todos já são antigos, mas ainda assim vale a leitura
    http://aosabook.org/

  • Eu queria gastar mais tempo construindo um modelo mental melhor do projeto em que trabalho, mas quando começo a pegar ranço da linguagem de programação, de certas escolhas de arquitetura ou de partes que ficaram complexas demais a ponto de não parecer valer o tempo, minha motivação cai bastante
    Depende do projeto, mas trabalhar como “desenvolvedor full stack” parece tirar a diversão da programação
    Já passo 40 horas por semana olhando para o projeto mais tedioso imaginável

    • É melhor simplesmente largar isso
      Não existe um único projeto impecável
      E, se a linguagem de programação é um problema tão grande assim, talvez seja melhor mudar de barco
      Acho que todo mundo deveria conseguir trabalhar com várias linguagens, mas no fim a escolha é sua
    • Paralisia por análise é sempre um risco, então o objetivo deve ser flexibilidade, não perfeição
      É preciso confirmar se as decisões em que você gasta mais tempo são realmente as mais importantes
      Estruturas de dados bem projetadas influenciam desempenho e manutenibilidade muito mais do que framework, linguagem ou plataforma
      Pessoalmente, como convivo com TDAH todos os dias, preciso continuar me empurrando para que haja progresso, e isso significa escolher decisões pouco importantes e fechá-las para concentrar a reflexão cuidadosa no espaço de problemas que sobra
    • Parece mais uma situação em que você precisa regular melhor as emoções
  • Falar em “código limpo” ou “código bonito” não ajuda muito um júnior a aprender boas práticas de arquitetura de software
    Quando um júnior pergunta por que usar ORM e um sênior responde “porque fica mais limpo”, o que sobra é só uma interrogação
    É melhor definir uma lista clara de objetivos
    Critérios como manutenibilidade, desempenho e escalabilidade, eficiência, resiliência, observabilidade, testabilidade e cobertura de testes, segurança e facilidade de leitura para novos desenvolvedores se equilibram entre si
    Quanto mais critérios você adiciona, mais fácil fica tomar decisões melhores quando houver dúvida, e eles também fazem sentido para pessoas de fora da equipe de desenvolvimento, o que ajuda a alinhar com o cliente pelo que ele está pagando
    O projeto passa a maior parte da vida em modo de manutenção, e isso é um bom sinal de que deu certo, então manutenibilidade também pode ser definida
    Um bom ponto de partida é a capacidade de adicionar novas funcionalidades sem quebrar a arquitetura e, melhor ainda, sem quebrar sequer a assinatura de um único método
    É preciso ter muito cuidado com abstração
    A frase “abstrações muitas vezes escondem o quão simples é o que você quer” faz sentido, e ORM é um ótimo exemplo disso
    Na maioria dos casos, dados devem ser tratados como cidadãos de primeira classe, e isso também melhora a colaboração com DBAs
    Também é um desafio pensar fora do happy path sem cair em otimização prematura, e isso ajuda a evitar correr para implementar uma boa ideia sem avaliar os prós e os contras
    Se eu imagino que a próxima pessoa que vai mexer no meu código está tendo o pior dia da vida, isso me ajuda a escrever de um jeito mais agradável de ler
    Comentários espalhados, variáveis locais deixadas mesmo quando poderiam ser omitidas, nomes de variáveis — tudo isso entra aí
    Frameworks devem ser escolhidos com cuidado: são bons servos, mas maus senhores
    A formulação do texto “seja um engenheiro, não um frameworker” está correta

    • Comparados a um framework opinativo, frameworks modulares valem ouro
      Eu não desgosto de opiniões fortes em si; em bibliotecas ou ferramentas isso pode até ser uma excelente característica
      A biblioteca ou ferramenta em questão provavelmente tem muita expertise de domínio no seu próprio espaço
      Mas em frameworks opiniões fortes tendem a virar “se você tem um martelo, tudo parece prego”
      Isso não acontece sempre, nem sempre é um problema, mas quando a ortodoxia de um domínio é aplicada de forma ampla, a justificativa dela pode ser específica daquele domínio e quebrar em um contexto mais geral
      Por isso prefiro frameworks modulares, nos quais você possa plugar outro ORM ou outra camada de integração com persistência, trocar router, validador e outros componentes que não se encaixem no problema
      Isso é especialmente valioso quando o ecossistema do framework oferece alternativas de vários paradigmas
      Porque assim você pode encontrar uma ferramenta pronta que quase serve, com desvantagens claras e que possa ser complementada quando necessário
      Para manutenibilidade, é muito importante lutar contra a síndrome do NIH
      Esse é um risco constante que drena recursos numa velocidade surpreendente
      Ao mesmo tempo, alguns componentes realmente podem trazer grandes benefícios quando você os ajusta por conta própria ou passa a possuí-los por completo; a parte difícil é julgar o que pertence a qual lado
      Concordo profundamente com colocar manutenibilidade no topo da lista
  • Do ponto de vista da engenharia de sistemas, arquitetura de software é parecida com projeto hidráulico
    É muito importante, mas as pessoas não moram dentro do encanamento; elas moram na casa que tem encanamento
    Se o encanamento não considerar o restante da casa, consertar depois pode sair caríssimo

  • Bom texto
    Aprender arquitetura de software é entender que não existe uma única resposta certa
    Isso é arte e ciência ao mesmo tempo
    Como leitura, recomendo Simplify IT - The art and science towards simpler IT solution
    https://nocomplexity.com/documents/reports/SimplifyIT.pdf

  • As palestras do Gary Bernhardt são realmente especiais
    Estão cheias de ideias que levam a outros lugares interessantes