Reinventando o design do sistema para Scala


35

Muitas, muitas, luas atrás, fiz meu mestrado em Engenharia de Software Orientada a Objetos. Eu cobri tudo: iniciação do projeto, requisitos, análise, design, arquitetura, desenvolvimento, etc. etc. Meu livro de TI favorito de todos os tempos foi o Desenvolvimento de Software Orientado a Objetos, uma Abordagem Baseada na Experiência (IBM-1996). Um livro criado por um grupo de verdadeiros especialistas de seu tempo. Ele descreve uma abordagem centrada no produto de trabalho para análise orientada a objetos, métodos de design e desenvolvimento.

Eu projetei, desenvolvi e fiquei feliz e no topo do meu jogo, mas comecei a me sentir um pouco antiquado: movimentos ágeis se tornaram moda do dia e renomearam algumas abordagens iterativas e incrementais bem conhecidas com novas palavras da moda. De repente, desenvolvedores inexperientes começaram a franzir a testa quando eu disse "requisitos" ou "arquitetura" como se essas coisas fossem substituídas pela mágica.

O design e o desenvolvimento perderam a graça e eu estava prestes a deixar todo o setor de TI para trás.

Então eu descobri Scala. Oh, como chuva suave em uma estrada poeirenta. Tudo ficou claro, o ar ficou doce novamente e a pouca luz do meu disco rígido tremeluzia profundamente na noite, alegremente me fazendo companhia no meu mundo de descobertas.

Eu gosto do design do sistema. Sou um arquiteto no coração, mais que um programador. Adoro analisar, projetar, pensar, discutir, melhorar - adoro designs simples, limpos e nítidos.

Como projetamos soluções Scala puras?

Certamente, diagramas de seqüência convencionais, diagramas de interação, diagramas de objetos, etc., poderiam ser substituídos ou melhorados para a qualidade de mesclar sistemas imperativos e funcionais orientados a objetos. Certamente há uma abertura para algo totalmente diferente!

Não estou procurando complexidade inchada - estou procurando simplicidade flexível. Muito parecido com Scala é realmente simples e fácil (embora diferente), mas pode ser estendido para atender às suas necessidades, como um par de calças de ioga. Deve haver um sistema de design para esse adorável idioma que comece simples e possa ser estendido para atender às suas necessidades e domínio. Certamente UML não pode ser isso!

Então, como projetamos sistemas Scala puros? Imagine por um momento, você tem o luxo de projetar um sistema completo do zero e sabe que só estará usando o Scala - como serão os seus modelos? Que tipos de diagramas você teria para descrever a estrutura e o comportamento? Como você modelaria Opções, correspondências, mixins, Objetos singleton, etc., sem ser atraído pela complexidade de estender as técnicas de modelagem existentes, em vez de um conjunto de ferramentas inovador, leve e inovador.

Existe tal projeto / processo / solução ou é hora de inventar uma?


+1 em "pode ​​ser estendido para atender às suas necessidades, como um par de calças de ioga".
23412 Russell

FWIW, vi uma pergunta sobre UML e Scala, que pode ser do seu interesse. Além disso, se você for para o lado da função, vale a pena examinar as notações da teoria da categoria. Gregory Meredith geralmente cria links para algumas apresentações interessantes sobre o assunto, embora eu não tenha certeza do que ele tem em seu blog - vale a pena conferir de qualquer maneira.
Daniel C. Sobral

Vale muito ;-) Me dá instruções para cavar. Obrigado Daniel
Jack

Vale a pena conferir, a UML "modificado" para Scala, github.com/mnn/Dia2Scala/blob/master/other/Notation.md
WeiChing Lin

Respostas:


12

Eu realmente não posso responder a essa pergunta, mas deixe-me oferecer alguns pensamentos.

Sou estudante de engenharia da computação em uma universidade e, no semestre passado, eu e um grupo fizemos um grande projeto de software no qual nossa língua principal era o Scala. Fizemos nosso design da maneira tradicional: casos de uso, um modelo de domínio, diagramas de sequência, diagramas de classe UML; a carga usual. E, como você já sabe, eles não são adequados. Aqui estão algumas razões.

Primeiro, considere os diagramas de sequência. A sensação de um diagrama de seqüência é de alguns atores estáveis, de vida longa e semi-autônomos, conversando entre si. No Scala, os objetos tendem a ser criados por um curto período de tempo. Além disso, as classes de caso incentivam objetos "burros", apenas com dados e sem código, de modo que o conceito de passar mensagens está fora de lugar. Mas o desafio mais difícil para os diagramas de sequência é que eles não têm uma boa maneira de incorporar funções de primeira classe; em um design OO usando apenas um retorno de chamada aqui ou ali, ele pode funcionar, mas se esse for seu principal meio de transferência de controle, você encontrará o diagrama de sequência que reflete pouco do seu código real.

Os diagramas de classe UML são mais acessíveis ao Scala; sim, os mixins não são bem-sucedidos, mas isso é algo que pode ser "aprimorado" ao invés de revisado. Mas acho que isso pode estar errado.

Considere novamente por que Scala tem "objetos burros". Se você escrever uma classe de caso sem métodos:

case class NewInvite(user: User, league: League)

você não precisa de nenhum método, porque os tipos de membros prometem algo sobre o que você pode fazer com eles. Na verdade, eles prometem tudo . E, em todos os escopos dentro do programa, as variáveis ​​dentro do escopo e seus tipos prometem algo sobre onde o código pode ir nesse ponto.

Então, talvez, se recuarmos um pouco e, em vez de pensarmos minuciosamente nos tipos em si, projetar nosso programa em termos de quais contextos nosso código viverá e o que é prometido ao programador (e, eventualmente, ao usuário). ) em cada um desses contextos, encontraremos uma descrição mais apropriada para Scala. No entanto, estou apenas adivinhando aqui e acho que você está certo de que muito mais precisa ser explorado nesta área.


"O que é prometido para o programador" - que tem um belo anel para ele ;-)
Jack

12

A UML surgiu das "guerras da bolha" do final dos anos 80 e início dos anos 90. Sempre foi um compromisso para a notação , não um método de design. Muitas das calunias atribuídas à UML são secretamente o resultado de um método de design que diz "Etapa 1: desenhar um modelo de classe".

Sugiro que não precisamos de novos métodos de design tanto quanto devemos revitalizar alguns métodos antigos. Comece definindo e alocando responsabilidades, depois passe a entender suas implementações e siga com boas anotações e representações para comunicar essas idéias.

Responsabilidades

O CRC funciona tão bem para o Scala quanto para qualquer outra linguagem OO, ou seja, funciona muito bem! Modelar a alocação de responsabilidades por atores antropomorfizados e entender suas colaborações ainda funciona bem.

Em larga escala, você ainda deve projetar com objetos. Os limites de objetos são limites de encapsulamento. Mantenha o estado mutável e os detalhes de implementação dentro desses limites do objeto. As classes de caso são bons objetos de interface, assim como coleções de baunilha e estruturas de dados.

Noções básicas sobre implementações

Passe essas estruturas de dados através dos limites do objeto, em vez de outros objetos, e você encontrará: a) é mais fácil manter o interior de um objeto oculto; eb) as implementações funcionais do comportamento do objeto fazem muito sentido.

Você não vai querer tratar a estrutura em larga escala do seu programa como "um vasto conjunto de pequenas funções". Em vez disso, você gravitará naturalmente em interfaces estreitas entre partes isoladas do sistema. Eles parecem e se parecem muito com objetos, então vá em frente e os implemente como objetos!

Representação e Notação

Portanto, nessa estrutura de design, você ainda precisa tirar os pensamentos da cabeça e compartilhá-los com outra pessoa.

Sugiro que a UML ainda possa ser útil para descrever a estrutura em larga escala do seu sistema. Porém, não ajudará nos níveis mais baixos de detalhes.

No detalhe fino, tente técnicas de programação alfabetizadas.

Eu também gosto de usar minidocs. Um minidoc é um documento de uma ou duas páginas que descreve algumas facetas específicas do comportamento do sistema. Ele deve ficar próximo ao código para ser atualizado e deve ser curto o suficiente para permitir o aprendizado just-in-time. É preciso alguma prática para atingir o nível certo de abstração: ele precisa ser específico o suficiente para ser útil, mas não tão específico que seja invalidado pela próxima alteração de código.


9

Como projetamos soluções Scala puras?

Eu acho que você está tomando o ângulo errado com esta pergunta. Seu design geral de soluções não deve estar vinculado a um idioma específico; pelo contrário, deve corresponder ao problema em questão. O melhor de Scala é que ele é flexível o suficiente para não atrapalhar quando chega a hora de implementar seu design. Java, por outro lado, impõe algumas decisões de design (principalmente, a mentalidade de tudo é uma classe) que farão com que pareça um pouco desajeitado.

Ao projetar, tente modelar o estado explicitamente . Dados imutáveis ​​e estado explícito levam a designs muito mais fáceis de raciocinar. Na maioria das vezes, isso se traduz em uma solução de programação funcional, embora, ocasionalmente, você possa achar que é mais eficiente ou mais direto usar mutação e um estilo imperativo; Scala acomoda os dois muito bem.

Outro motivo pelo qual a Scala é tão boa em se destacar é a capacidade de criar DSLs que atendem às suas necessidades . Você pode criar sua própria linguagem pequena que resolve um determinado problema da maneira "normal" e simplesmente implementá-la no Scala.

Em resumo, meu ponto principal é que você não deve tentar "projetar soluções Scala puras". Você deve projetar soluções da maneira mais natural e compreensível possível, e acontece que o Scala é uma ferramenta incrivelmente flexível que pode ajudá-lo a implementar essas soluções de várias maneiras. Quando tudo o que você conhece é sobre um martelo, todo problema começa a parecer um prego; portanto, não deixe de explorar as várias abordagens disponíveis. Não tente fechar o processo de design para as etapas X, Y e Z, para não perder as possibilidades de A a W.


4

Você está certo de que o OOD é limitado e desatualizado. Sua principal vantagem é que ele é tão bem especificado e ritualizado, com nomes sofisticados para cada padrão trivial e truque de design. Você não encontrará nenhum sistema comparável para abordagens mais modernas e racionais do design de sistemas.

Só posso listar algumas coisas: uma abordagem semântica do design, um subconjunto chamado Programação Orientada a Linguagem (e é muito usada pela comunidade Scala) e Design Orientado a Domínio. A última é uma visão simplificada e OOPised sobre uma abordagem de design semântico mais genérica; você só poderá aproveitá-la se gostar de OOD.

Eu também recomendaria aprender um truque ou dois das outras comunidades de programação funcional - por exemplo, Haskell e Scheme, nem todo o seu conhecimento em design já havia sido transferido para a Scala.


3

A pergunta era: como projetamos soluções Scala puras?

Respostas como,

  1. Usamos XYZ porque ....
  2. Nós usamos ABC, mas assim ...
  3. Começamos a usar o XYZ, mas depois descobrimos que o ABC era melhor porque ...

indicaria alguma maturidade, ou existência geralmente aceita, de tal conjunto de ferramentas ou solução.

Comentários sobre se é apropriado considerar um conjunto de ferramentas de design específico do Scala é uma questão diferente. Minha opinião sobre isso é que o Scala é revolucionário na maneira como combina programação imperativa e funcional. O Scala captura uma incrível quantidade de práticas, conceitos e princípios de desenvolvimento, e, portanto, ao encontrar uma solução que seja perfeita para o Scala, provavelmente descobriremos uma solução para a qual um subconjunto também servirá muito bem a outros idiomas.

É seguro dizer então que sabemos a resposta para a pergunta de fallback:

"... é hora de inventar uma?"

Poderia ser sim ? (Onde ' yes ' não prescreve os meios para a solução. Isso pode ser alcançado estendendo uma solução existente ou criando uma a partir do zero. Admite, porém, que existem deficiências significativas nas opções de design existentes)

Você pode votar em minha resposta se não concordar. Eu vou aceitar isso como um homem.


Revolucionário? Por quê? Lisp tinha sido uma combinação perfeita de abordagens imperativas e funcionais por décadas. Scala é interessante por causa de seu sistema de tipos.
SK-logic,

3
Desculpas se "revolucionário" é uma palavra forte e forte. Não estou sugerindo que Scala tenha reinventado a roda - mas com certeza remodelou a borracha. De fato, Scala é uma mistura adorável de toda a bondade de muitas linguagens de programação. Tenho certeza de que empresta muito do Lisp também. Para mim, e não estou reivindicando outros, a linguagem Scala parece revolucionária, pois estou pensando no código de uma maneira que é naturalmente natural. Uma linguagem de design / modelagem que faça o mesmo certamente seria complementar. Esse era meu único argumento - sem ofender Lisp e todas as outras grandes línguas.
Jack

2

Eu não diferenciaria entre idiomas, faria da mesma maneira em java. No entanto, eu sou da escola de OO, e não da funcional (uma algébrica de Haskell ou a de Lisp). O design do aplicativo Haskell é realmente diferente do Scala ou Clojure. Além disso, o design do aplicativo Scala.js é diferente devido ao DOM com estado - é difícil combater uma parte com estado do aplicativo que é obrigatória. Com o tempo, me acostumei a fazê-lo da maneira OO, enquanto tentava eliminar o estado e a mutabilidade o máximo possível. Se eu fosse um fã de nível de tipo ou escalaz, meus aplicativos provavelmente pareceriam bem diferentes.

  1. Decidir sobre a pilha de tecnologia e as principais decisões de design, como responsabilidade do cliente / servidor, natureza distribuída - realmente precisamos da persistência dos dados? O que melhor se adapta à nossa aplicação? (certamente ainda não bibliotecas ou estruturas)

  2. Começando com apenas um App.scalaarquivo, onde eu defino os principais tipos (características e classes de casos) e funções - muito contemplando e pensando enquanto estou no AFK. É tão fácil brincar com ele enquanto ele está em um arquivo. Até o protótipo pode surgir se for um aplicativo simples. Dedico muito tempo a esta fase, porque nada é mais importante do que ter o protótipo mais correto, principalmente transparente e sensível possível. Tudo isso ajuda a tornar nossa contemplação mais precisa e aumenta a probabilidade de sucesso.

  3. Agora, quando comprovamos a correção de nossa solução, temos uma visão clara e todos os problemas em potencial são revelados, é hora de pensar em que tipo de bibliotecas ou metodologias de terceiros trazer. Precisamos de framework de reprodução ou apenas algumas bibliotecas? Nós vamos fazer esses atores com estado, realmente precisamos da resiliência de Akka, .., .. ou talvez não? Nós usamos o Rx para esses fluxos? O que usamos para a interface do usuário para não ficarmos loucos depois que ela tiver 10 recursos interativos?

  4. divida o App.scalaarquivo em vários, porque novos traços e classes apareceram e ficaram maiores. Sua interface deve contar sobre seus papéis. Agora é hora de pensar em utilizar classes de tipos e polimorfismos ad-hoc sempre que possível. Para que quem chama sua interface, ela deve ser flexível para eles. O objetivo é implementar apenas a funcionalidade geral, deixando os detalhes para o chamador. Às vezes, o que você está tentando implementar pode ser do tipo Turing-completo, portanto, você deve fornecer uma maneira de os chamadores implementarem os detalhes sozinhos em vez de acumular solicitações de recursos no GitHub. É difícil adicionar essas coisas mais tarde. Deve ser pensado desde o início. Além de projetar uma DSL.

  5. pensando no design porque o aplicativo crescerá e deverá estar pronto para suportar novos recursos, otimizações, persistência, interface do usuário, comunicação remota etc. - coisas que ainda não foram implementadas ou que foram de alguma forma ridicularizadas ou abstraídas. Lembre-se de que, embora esteja tudo em um arquivo, é muito fácil mudar as coisas. O cérebro humano começa a perdê-lo depois que ele se espalha por mais de 10 arquivos. E é como um tumor que cresce, é assim que a superengenharia começa. Percebi que meus aplicativos terminam com algumas funções parciais longas que formam uma espécie de espinha dorsal dele. Acho fácil trabalhar com isso. Eu acho que fui infectado por atores Akka.

  6. Agora que os primeiros recursos reais existem, não apenas o funcionamento principal, é hora de começar a escrever testes. Por que tão tarde? Porque durante esse período, você provavelmente passou por grandes reescritas e teria perdido tempo mantendo esses testes. Não se deve escrever testes, a menos que se tenha certeza de que não haverá grandes reformulações. Eu prefiro testes de integração que suportam facilmente refatoração contínua e não vou gastar mais tempo testando que realmente desenvolvendo coisas. Aconteceu comigo algumas vezes que passei muito tempo mantendo os testes. Corrigir bugs em potencial certamente renderia mais do que testes ruins. Testes ruins ou escritos desde o primeiro momento podem causar grandes dores. Note que eu certamente não sou fã de TDD - IMHO é besteira.

  7. Refatorando, tornando e mantendo o design do aplicativo legível, as funções dos componentes são claras. À medida que o aplicativo cresce, não há como permanecer assim, a menos que você seja um programador genial e, se você ler esta resposta, provavelmente não o é :-) Nem eu. Normalmente, existem algumas coisas com estado porque você não sabia como evitar isso - que pode causar problemas, tente eliminá-los. Certifique-se também de eliminar odores de código que o incomodam há algum tempo. Nesse ponto, seu gerente de projeto (se você tiver um) provavelmente começa a sacudir a cabeça. Diga a ele que vai valer a pena.

  8. Feito, o aplicativo foi bem projetado? Depende: os componentes têm papéis bem definidos que realmente fazem sentido mesmo fora de sua cabeça? Você poderia pegar alguns deles e de código aberto sem muito esforço? Existe uma clara separação de preocupações entre eles? Você pode adicionar um novo recurso sem se preocupar com o travamento em outro local (sinal de que você sabe exatamente o que está fazendo)? De um modo geral, deve ser legível como um livro com soluções pragmáticas diretas para os problemas. Qual é IMHO o principal sinal de um bom design.

Em suma, é apenas um trabalho árduo e tempo que você provavelmente não obterá de seus gerentes. Tudo o que descrevi aqui leva muito tempo, esforço, café, chá e, principalmente, o pensamento do AFK. Quanto mais você der, melhor o design. Não existe uma receita geral para projetar aplicações, senso comum, pragmatismo, KISS, trabalho duro, experiência significa bom design.

Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.