Aprendendo Arquitetura de Software
(matklad.github.io)- 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_unwinde 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
- São bons livros frequentemente recomendados, e especialmente Software Engineering at Google ajudou a organizar nomes importantes sobre unit test e integration test
- Ainda assim, pessoalmente não pareceram livros transformadores
1 comentários
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
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
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
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
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”
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
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
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
É 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
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
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