1 pontos por GN⁺ 2024-08-17 | 1 comentários | Compartilhar no WhatsApp
  • Com base em uma citação de Linus Torvalds, um bom design começa por definir de forma estável as estruturas de dados e seus relacionamentos antes de escrever código
  • Um modelo de dados bem projetado simplifica naturalmente a lógica da aplicação e torna o software mais confiável e fácil de entender
  • Adiar o modelo de dados aumenta o trabalho depois, mas definir bem a estrutura no início facilita migrações e a expansão de sistemas complexos
  • Em um projeto, em vez de otimizar um algoritmo complexo, a reestruturação dos dados eliminou a própria categoria do problema, substituindo uma função de 500 linhas por uma de 50 linhas e uma estrutura de dados
  • Na prática, é preciso aplicar tipos mais rígidos em interfaces e bancos de dados, e projetar primeiro o fluxo de dados e as interações entre componentes antes dos detalhes do código

A estrutura de dados determina o design do código

  • Linus Torvalds vê o Git como um design simples com estruturas de dados estáveis e documentadas, enfatizando a forma de organizar o código ao redor dos dados
    • A frase central é: “Programadores ruins se preocupam com o código, bons programadores se preocupam com as estruturas de dados e seus relacionamentos”
    • Um dos motivos do sucesso do Git está em ter sido projetado com foco nos dados
  • Boas estruturas de dados facilitam o design e a manutenção do código, além de aumentar a confiabilidade do software, a compreensibilidade do sistema e a legibilidade do código
    • A lógica da aplicação muitas vezes segue o modelo de dados
    • Pensar no modelo de dados só depois aumenta o trabalho futuro
    • Um modelo de dados bem projetado facilita migrações posteriores e a expansão de sistemas complexos
  • Em casos reais de projeto, reestruturar os dados teve mais impacto do que refinar ainda mais algoritmos complexos
    • A mudança na estrutura de dados eliminou toda uma categoria de problemas
    • Uma função de 500 linhas foi substituída por uma função de 50 linhas e uma estrutura de dados bem projetada
    • O novo código ficou mais rápido e também mais fácil de entender e manter
    • Porém, como foi necessário reestruturar os dados existentes, o esforço foi deslocado para as camadas inferiores

É melhor mover a complexidade para o lado dos dados

  • A “Rule of Representation”, de The Art of Unix Programming, explica que o conhecimento deve ser colocado nos dados para tornar a lógica do programa simples e robusta
    • A lógica procedural é difícil de verificar para humanos, mas estruturas de dados complexas são mais fáceis de modelar e raciocinar sobre
    • Um diagrama de árvore de ponteiros com 50 nós pode ter mais poder de expressão e explicação do que um fluxograma de 50 linhas de programa
    • Representar uma tabela de transformação como inicialização de array pode ser mais transparente e claro do que escrever o mesmo conteúdo com uma instrução switch
    • Se for preciso escolher entre colocar a complexidade no código ou na estrutura de dados, é melhor movê-la para a estrutura de dados

Na prática, projeta-se primeiro o fluxo de dados

  • A forma mais direta de colocar isso em prática é começar pelos dados
    • Aplicar tipos mais rígidos a interfaces ou bancos de dados pode reduzir a complexidade do código
    • É preciso dedicar mais tempo antecipadamente para pensar melhor nas estruturas de dados
    • Isso não significa que o código não seja importante; todos os elementos importam juntos
    • Antes de entrar nos detalhes do código, é útil adotar uma abordagem de mais alto nível para entender como os dados fluem e como os componentes interagem
  • Como exemplo de exigência para Senior Engineer (L5), em empresas FAANG geralmente se inclui a elaboração de documentos de design de alto nível para sistemas mais complexos
    • Isso também inclui liderar o planejamento da equipe e construir bons roadmaps para funcionalidades de médio e grande porte
    • A capacidade de projetar primeiro o fluxo de dados e as interações entre componentes está ligada a um impacto de engenharia em níveis mais altos

1 comentários

 
GN⁺ 2024-08-17
Comentários do Hacker News
  • Esse post no Substack parece ter simplesmente copiado várias citações desta postagem no Stack Exchange: https://softwareengineering.stackexchange.com/questions/1631...

  • “Mostre-me seus fluxogramas [código] e esconda suas tabelas [schema], e eu continuarei confuso. Mostre-me suas tabelas [schema], e normalmente eu não precisarei dos fluxogramas [código]; eles serão óbvios.” — Fred Brooks, “The Mythical Man Month”, capítulo 9

    • Quando eu era criança e estava viciado em Quake, certa vez escrevi para John Carmack perguntando se ele tinha algum conselho para alguém que queria ser programador e quais eram seus livros favoritos. Surpreendentemente, ele respondeu com bastante consideração, e incluiu isto:
      “Leia The Mythical Man Month. Lembro de ter achado que um livro tão antigo não teria nada relevante a dizer sobre desenvolvimento de software hoje em dia, mas eu estava errado.”
    • Essa citação é tão certeira que vim aqui para compartilhá-la. A única exceção é quando o custo de mudar o schema do banco de dados se torna muito maior do que o custo de mudar o código.
      Aí os desenvolvedores de aplicação começam a abusar do banco de dados porque conseguem se mover mais rápido e têm mais coisas para fazer
  • Estruturas de dados e tipos não são a mesma coisa. Estruturas de dados são padrões de bits e referências a outros padrões de bits, isto é, ponteiros ou relações.
    Tipos impõem restrições a esses padrões de bits conforme são usados em uma linguagem de programação, mas também podem expressar muitos outros recursos de linguagem. Criar hierarquias de tipos complexas por meio de abstrações desnecessárias não significa “se preocupar com estruturas de dados”, e isso é um modo de falha em que até engenheiros brilhantes caem com frequência

    • É um ponto sutil, mas importante. Tipos podem ser uma ferramenta útil para restringir e especificar o schema de estruturas de dados, mas se preocupar com tipos e se preocupar com estruturas de dados são coisas bem diferentes
    • Tipos são estruturas de dados reconhecidas pela linguagem. Assim, as ferramentas conseguem realizar verificações que não conseguiriam em estruturas de dados comuns
    • Estruturas de dados são algoritmos parados. Em cada operação, algo é embaralhado e movido, mas no geral ficam quietas, como uma máquina de Turing cuja manivela as pessoas giram só de vez em quando.
      Tipos são bits no disco
    • Boa observação. Igualar estruturas de dados a tipos é uma simplificação excessiva que perde o ponto principal.
      O que se queria dizer aqui originalmente é algo mais próximo de pensar o problema com mais profundidade e não escolher uma estrutura que vá te atrapalhar depois. Por exemplo, veja até onde os pipes do Unix se espalharam e para quantos domínios e casos de uso eles foram estendidos. É uma ótima forma de visualizar como construir sistemas respeitando as restrições de pessoas e máquinas.
      Levou bastante tempo até Ken Thompson e outros perceberem que algo como pipes fazia sentido no Unix. Não foi um insight obtido facilmente; exigiu persistência e trabalho subsequente para encontrar os blocos de construção corretos de um sistema
    • Você pode atribuir tipos diferentes à mesma estrutura de dados. É isso que o operador typedef de Pascal faz
  • O Linus sempre resume bem aquilo que os outros pensavam de forma vaga. O que o texto diz também se parece com DDD, que virou uma habilidade perdida.
    Aqui, “perdida” quer dizer que a maioria dos desenvolvedores que encontro hoje se interessa mais em ficar movendo algoritmos e JSON de um lado para o outro do que em entender o domínio com que trabalham e modelar entidades e interações. Em projetos modernos baseados em AWS, isso aparece como conjuntos de GSIs do DynamoDB sem muita justificativa, objetos anêmicos e camadas de “serviço” que mais parecem scripts com gambiarra em cima de gambiarra. Talvez houvesse uma suposição implícita de que, dentro das fronteiras de serviço, o contexto de domínio acabaria sendo bem definido o bastante, mas não acho que seja uma boa suposição.
    Não sei em que ponto nossa indústria perdeu o rigor de projeto. Se foi na faculdade, no pipeline de entrevistas, por termos baixado os padrões, ou se foi tudo isso junto

    • Acho que a indústria nunca levou projeto de software realmente a sério. Sempre é tratado de forma pejorativa, associado a figuras vistas como politicamente incorretas ou irrelevantes, e atrai uma enxurrada de comentários de gente querendo dizer que tudo é ruim porque alguém fez alguma coisa errada uma vez.
      Pior ainda, projeto comete o grande pecado de não ser fácil de automatizar. Por isso, as pessoas seguem sem senso crítico o projeto imposto pelas ferramentas e se incomodam com a ideia de que deveriam pensar mais profundamente sobre o que estão fazendo. Todo mundo quer terceirizar esse raciocínio para “especialistas”.
      Também é um problema o fato de isso não ser ensinado direito, exigir anos de aprendizado por conta própria e ser visto como algo menos concreto que código, portanto menos importante. Mas essa crença acaba limitando o nível do que se consegue construir ao estágio de iniciante avançado. Os programadores, coletivamente, parecem escolher manter o padrão o mais baixo possível e, nesse tema, quase existe uma mentalidade de caranguejo puxando os outros para baixo
    • Modelo de domínio anêmico já foi identificado como antipadrão há bastante tempo[1]. Em geral aparece junto com obsessão por tipos primitivos[2], o que resulta em todo tipo de código de validação e verificação espalhado por aí para lidar com tipos primitivos como strings e números.
      Também surge muito código duplicado que não parece duplicação do ponto de vista sintático, mas funcionalmente faz a mesma coisa.
      1 https://martinfowler.com/bliki/AnemicDomainModel.html
      2 https://wiki.c2.com/?PrimitiveObsession
    • A indústria recompensa principalmente escrever código, e não projetar software.
      Acho que isso acontece porque as consequências de código ruim são menos visíveis. Uma ponte ruim cai; código ruim só é refatorado ou substituído por ainda mais código. Um arquivo de texto que a gerência não entende é trocado por outro arquivo de texto que a gerência também não entende.
      E, assim que algo funciona, vira estado permanente. Nada é mais permanente do que uma gambiarra temporária que funciona perfeitamente. Mas 1000 gambiarras temporárias não formam um sistema bem projetado. Acho que amadurecer no desenvolvimento de software é passar a focar mais em dados e relações do que em escrever código. Você precisa conseguir transformar isso em código, mas não transformar código funcional em modelo de dados; precisa transformar dados e relações em código
    • Ainda não vi uma justificativa convincente para por que objetos anêmicos seriam algo tão condenado. A maior parte das funções de DDD que vi também não passava de getters e setters verbosos.
      Só porque entidades de domínio podem conter toda a lógica não quer dizer que necessariamente devam conter. Por exemplo, se você precisa verificar se um nome de usuário já existe, como exatamente faria isso dentro de uma entidade de domínio que “não pode depender” da camada de acesso a dados? Costumam recomendar algo como um “serviço de domínio”, mas aí a lógica de negócio fica espalhada em vários lugares, o que me parece ir contra a proposta do DDD.
      Gosto bastante de DDD como filosofia, mas detesto os padrões de “DDD tático”. Acho que gente demais confunde Domain-Driven Design com Domain-Driven Implementation. Tento criar domínios ricos quando faz sentido, mas isso não serve para todo projeto e procuro não ficar preso à terminologia. Não me importo se o tipo “Name” é um value object ou um aggregate root. Para mim, o mais importante são os bounded contexts. Também reconheço que às vezes o DDD pode aumentar a complexidade da aplicação sem trazer quase nenhum ganho. Eu nunca diria que é uma solução universal.
      Vou continuar usando DDD, mas é difícil não sentir que DDD parece uma tentativa de dizer “viu, programação orientada a objetos também não é tão ruim assim?”. E nem sei se ele realmente consegue atingir esse objetivo
    • Durante décadas, o desempenho de CPU, o tamanho da memória, o espaço em disco e a velocidade de rede cresceram exponencialmente, e isso eliminou a maior parte do custo de um projeto ruim. Então os macacos de código puderam continuar produzindo código lixo praticamente na mesma velocidade com que batiam no teclado, e no geral isso passava batido
  • Acho isso interessante porque, antes de começar na engenharia profissional, eu fazia análise de dados e estatística diariamente com sistemas estatísticos como Matlab, R e o Python do começo.
    Por isso, minha visão de engenharia sempre foi baseada em duas coisas: estado funcional e gerenciamento de fluxos de trabalho de dados.
    Depois de trabalhar 10 anos com engenharia de software, vi que a maioria dos engenheiros mais “científicos”, como Minsky e Shannon, descrevia o mundo da computação em termos de gerenciamento de estado, transformação de dados e gestão do overhead computacional. Todos os grandes nomes e pioneiros do software davam enorme importância a dados e estado; na verdade, a computação inicial era praticamente só isso, e esperava-se que esse padrão continuasse no futuro.
    Por outro lado, não há nenhuma consistência nas premissas fundamentais que seriam sempre verdadeiras no projeto de sistemas de engenharia e que todos deveriam seguir; quando existem, em geral se parecem mais com modismos. Na maior parte do software em produção, cronogramas de negócio influenciam muito mais as prioridades e a estrutura de engenharia do que robustez, antifragilidade e gerenciamento de estado.
    Organizações profissionais como guildas ou sindicatos são rejeitadas quase universalmente pelos engenheiros de software. Como não há penalidade por não levar o IEEE a sério, na prática ninguém leva. Como resultado, não existe nenhum mecanismo que imponha prática profissional ou autorregulação, como existe na engenharia civil ou biomédica, e mesmo nessas áreas isso já é usado no limite mínimo.
    No geral, o estado atual do desenvolvimento de software se separou completamente de suas raízes, que eram muito elevadas e filosóficas, e hoje é conduzido de fato por empresas que priorizam sistemas que dão dinheiro para quem já tem dinheiro. Então aquilo que é “bom” quase não tem relação com aquilo que recebe incentivos

  • “Mostre-me o fluxograma [código] e esconda as tabelas [estruturas de dados], e eu continuarei confuso. Mostre-me as tabelas, e normalmente o fluxograma não será necessário. Eles serão óbvios.” — Fred Brooks

    • Essa citação parece ignorar que o modelo de persistência e as estruturas de dados reais podem ser diferentes e talvez até devam ser.
      Fazer uma correspondência 1:1 com as tabelas subjacentes é extremamente limitante e, na minha visão, leva a modelos que desperdiçam a expressividade oferecida pelas linguagens modernas
  • Isso é essencialmente a perspectiva da programação funcional e da teoria das categorias.
    Existe um objeto de dados, e sua estrutura impõe restrições sobre como ele pode ser transformado. Então, toda a lógica do programa passa a tratar de transformações que preservam essa estrutura.
    As transformações ficam mais simples e mais fáceis de raciocinar, e no fim sobra um grafo em que as transformações são arestas e as estruturas são nós. Em geral, é mais fácil de raciocinar do que um programa imperativo arbitrário

    • Essa não é a perspectiva da programação funcional nem da teoria das categorias. É a perspectiva de toda filosofia de linguagem, e pessoas que preferem orientação a objetos ou programação procedural defenderiam exatamente a mesma coisa. Definir corretamente os tipos de dados é importante e isso se aplica a todas as linguagens e paradigmas.
      A perspectiva da programação funcional está mais próxima da ideia de que objetos não devem ser transformados e de que mutações devem ser evitadas, o que é uma discussão separada desta. O núcleo da teoria das categorias trata de padrões de relações que aparecem em comum em várias áreas da matemática, e não tem absolutamente nada a ver com o que está sendo discutido aqui. Talvez a intenção fosse falar de teoria dos tipos, mas isso também não tem relação
  • A conclusão a que cheguei há algum tempo é esta: tudo o que fazemos no código tem muito mais chance de durar menos do que uma boa decisão tomada sobre dados.
    https://www.swyx.io/data-outlasts-code-but

    • Boas decisões são invisíveis. Só as decisões ruins parecem sobreviver para sempre
  • Esse princípio também se aplica ao nível de negócios. Continuo lidando com analistas de negócios que ficam obcecados com processos (código) e não dedicam tempo primeiro para entender as entidades e suas relações (dados).
    Como resultado, quando chega a hora de construir algo, eles não conseguem se comunicar com os desenvolvedores sobre como o modelo de dados deveria ser. Os processos são implementados, e o modelo de dados é montado na hora, em vez de ser cuidadosamente projetado