A Engenharia de Software, como é ensinada hoje, é totalmente focada na programação orientada a objetos e na visão orientada a objetos 'natural' do mundo. Existe uma metodologia detalhada que descreve como transformar um modelo de domínio em um modelo de classe com várias etapas e muitos artefatos (UML), como diagramas de casos de uso ou diagramas de classes. Muitos programadores internalizaram essa abordagem e têm uma boa idéia sobre como projetar um aplicativo orientado a objetos do zero.
O novo hype é a programação funcional, ensinada em muitos livros e tutoriais. Mas e a engenharia de software funcional? Ao ler sobre Lisp e Clojure, deparei-me com duas declarações interessantes:
Os programas funcionais geralmente são desenvolvidos de baixo para cima em vez de de cima para baixo ('On Lisp', Paul Graham)
Os programadores funcionais usam mapas nos quais os programadores OO usam objetos / classes ('Clojure for Java Programmers', palestra de Rich Hickley).
Então, qual é a metodologia para um design sistemático (baseado em modelo?) De um aplicativo funcional, ou seja, no Lisp ou no Clojure? Quais são as etapas comuns, quais artefatos eu uso, como os mapeio do espaço do problema para o espaço da solução?
Respostas:
Graças a Deus que o pessoal da engenharia de software ainda não descobriu a programação funcional. Aqui estão alguns paralelos:
Muitos "padrões de design" OO são capturados como funções de ordem superior. Por exemplo, o padrão Visitor é conhecido no mundo funcional como uma "dobra" (ou se você é um teórico de cabeça pontuda, um "catamorfismo"). Nas linguagens funcionais, os tipos de dados são principalmente árvores ou tuplas, e todo tipo de árvore tem um catamorfismo natural associado a ele.
Essas funções de ordem superior geralmente vêm com certas leis da programação, também conhecidas como "teoremas livres".
Programadores funcionais usam diagramas com muito menos força do que programadores OO. Muito do que é expresso nos diagramas OO é expresso em tipos ou em "assinaturas", que você deve considerar como "tipos de módulos". Haskell também tem "classes de tipo", que é um pouco como um tipo de interface.
Os programadores funcionais que usam tipos geralmente pensam que "uma vez que os tipos são acertados; o código praticamente se escreve".
Nem todas as linguagens funcionais usam tipos explícitos, mas o How To Design Programs livro , um excelente livro para aprender Scheme / Lisp / Clojure, depende muito de "descrições de dados", que estão intimamente relacionadas aos tipos.
Qualquer método de design baseado na abstração de dados funciona bem. Por acaso, acho que isso é mais fácil quando a linguagem tem tipos explícitos, mas funciona mesmo sem ela. Um bom livro sobre métodos de design para tipos de dados abstratos, facilmente adaptável à programação funcional, é Abstração e especificação no desenvolvimento de programas de Barbara Liskov e John Guttag, o primeiro edição. Liskov ganhou o prêmio Turing em parte por esse trabalho.
Outra metodologia de design exclusiva do Lisp é decidir quais extensões de idioma seriam úteis no domínio do problema em que você está trabalhando e, em seguida, usar macros higiênicas para adicionar essas construções ao seu idioma. Um bom lugar para ler sobre esse tipo de design é o artigo de Matthew Flatt, Criando idiomas na raquete . O artigo pode estar atrás de um paywall. Você também pode encontrar material mais geral sobre esse tipo de design pesquisando o termo "linguagem incorporada específica do domínio"; para conselhos e exemplos específicos além do que Matthew Flatt cobre, eu provavelmente começaria com On Lisp de Graham ou talvez ANSI Common Lisp .
Etapas comuns:
Identifique os dados em seu programa e as operações nele e defina um tipo de dados abstrato que representa esses dados.
Identifique ações ou padrões comuns de computação e expresse-os como funções ou macros de ordem superior. Espere executar esta etapa como parte da refatoração.
Se você estiver usando uma linguagem funcional digitada, use o verificador de tipos cedo e frequentemente. Se você estiver usando Lisp ou Clojure, a melhor prática é escrever contratos de funções primeiro, incluindo testes de unidade - é o desenvolvimento orientado a testes ao máximo. E você desejará usar qualquer versão do QuickCheck que tenha sido portada para a sua plataforma, que no seu caso se parece com ClojureCheck . É uma biblioteca extremamente poderosa para a construção de testes aleatórios de código que usam funções de ordem superior.
fonte
Para Clojure, recomendo voltar à boa e velha modelagem relacional. Out of the Tarpit é uma leitura inspiradora.
fonte
Pessoalmente, acho que todas as boas práticas usuais do desenvolvimento de OO também se aplicam à programação funcional - apenas com alguns pequenos ajustes para levar em conta a visão de mundo funcional. Do ponto de vista da metodologia, você realmente não precisa fazer nada fundamentalmente diferente.
Minha experiência vem de ter me mudado do Java para o Clojure nos últimos anos.
Alguns exemplos:
Entenda o seu domínio de negócios / modelo de dados - igualmente importante se você deseja projetar um modelo de objeto ou criar uma estrutura de dados funcional com mapas aninhados. De certa forma, o FP pode ser mais fácil porque incentiva você a pensar no modelo de dados separadamente das funções / processos, mas ainda precisa fazer as duas coisas.
Orientação de serviço no design - na verdade, funciona muito bem da perspectiva do FP, pois um serviço típico é realmente apenas uma função com alguns efeitos colaterais. Penso que a visão "de baixo para cima" do desenvolvimento de software às vezes adotada no mundo Lisp é na verdade apenas bons princípios de design de API orientada a serviços de outra forma.
Desenvolvimento Orientado a Testes - funciona bem em linguagens FP, de fato às vezes até melhor porque as funções puras se prestam extremamente bem a escrever testes claros e repetíveis, sem a necessidade de configurar um ambiente estável. Você também pode criar testes separados para verificar a integridade dos dados (por exemplo, este mapa possui todas as chaves esperadas, para equilibrar o fato de que, em uma linguagem OO, a definição de classe o aplicaria em tempo de compilação).
Prototipagem / iteração - funciona tão bem com o FP. Você pode até criar protótipo ao vivo com os usuários se for extremamente bom em criar ferramentas / DSL e usá-los no REPL.
fonte
A programação OO une fortemente os dados ao comportamento. A programação funcional separa os dois. Portanto, você não possui diagramas de classes, mas estruturas de dados e tipos de dados algébricos. Esses tipos podem ser escritos para corresponder muito bem ao seu domínio, incluindo a eliminação de valores impossíveis por construção.
Portanto, não existem livros e livros, mas existe uma abordagem bem estabelecida para, como diz o ditado, tornar valores impossíveis irrepresentáveis.
Ao fazer isso, você pode fazer uma variedade de escolhas sobre a representação de certos tipos de dados como funções e, inversamente, representar certas funções como uma união de tipos de dados, para obter, por exemplo, serialização, especificação mais rígida, otimização etc. .
Então, como você escreve funções sobre seus anúncios, estabelece um tipo de álgebra - ou seja, existem leis fixas que se essas funções. Alguns talvez sejam idempotentes - o mesmo após vários aplicativos. Alguns são associativos. Alguns são transitivos, etc.
Agora você tem um domínio sobre o qual possui funções que compõem de acordo com leis bem comportadas. Um simples DSL incorporado!
Ah, e dadas propriedades, é claro que você pode escrever testes aleatórios automatizados deles (ala QuickCheck) .. e isso é apenas o começo.
fonte
Design orientado a objetos não é a mesma coisa que engenharia de software. A engenharia de software tem a ver com todo o processo de como passamos dos requisitos para o sistema em funcionamento, dentro do prazo e com uma baixa taxa de defeitos. A programação funcional pode ser diferente da OO, mas não elimina requisitos, projetos detalhados e de alto nível, verificação e teste, métricas de software, estimativas e todas essas outras "coisas de engenharia de software".
Além disso, os programas funcionais exibem modularidade e outra estrutura. Seus projetos detalhados devem ser expressos em termos dos conceitos nessa estrutura.
fonte
Uma abordagem é criar uma DSL interna na linguagem de programação funcional de sua escolha. O "modelo" é um conjunto de regras de negócios expressas na DSL.
fonte
Veja minha resposta para outro post:
Como Clojure aborda a Separação de Preocupações?
Concordo que mais precisa ser escrito sobre o assunto sobre como estruturar aplicativos grandes que usam uma abordagem FP (além disso, é necessário fazer mais para documentar as UIs orientadas por FP)
fonte
Embora isso possa ser considerado ingênuo e simplista, acho que as "receitas de design" (uma abordagem sistemática à solução de problemas aplicada à programação, conforme defendida por Felleisen et al. Em seu livro HtDP ) estariam próximas do que você parece estar procurando.
Aqui, alguns links:
http://www.northeastern.edu/magazine/0301/programming.html
http://citeseerx.ist.psu.edu/viewdoc/summary?doi=10.1.1.86.8371
fonte
Encontrei recentemente este livro: Modelagem de Domínio Funcional e Reativo
Eu acho que está perfeitamente de acordo com sua pergunta.
Na descrição do livro:
fonte
Existe o estilo "cálculo de programa" / "projeto por cálculo" associado ao professor Richard Bird e ao grupo de álgebra de programação da Universidade de Oxford (Reino Unido). Não acho que seja exagero considerar essa metodologia.
Pessoalmente, embora goste do trabalho produzido pelo grupo AoP, não tenho disciplina para praticar design dessa maneira. No entanto, essa é a minha falha, e não o cálculo do programa.
fonte
Eu descobri que o Behavior Driven Development é um ajuste natural para o desenvolvimento rápido de códigos no Clojure e no SBCL. A verdadeira vantagem de alavancar o BDD com uma linguagem funcional é que eu costumo escrever testes de unidade de granulação mais refinada do que costumo usar linguagens procedurais porque faço um trabalho muito melhor de decompor o problema em partes menores de funcionalidade.
fonte
Honestamente, se você quiser receitas de design para programas funcionais, dê uma olhada nas bibliotecas de funções padrão, como o Haskell's Prelude. No FP, os padrões geralmente são capturados por procedimentos de ordem superior (funções que operam em funções). Portanto, se um padrão é visto, geralmente uma função de ordem superior é simplesmente criada para capturar esse padrão.
Um bom exemplo é o fmap. Essa função assume uma função como argumento e a aplica a todos os "elementos" do segundo argumento. Como faz parte da classe de tipo Functor, qualquer instância de um Functor (como uma lista, gráfico, etc ...) pode ser passada como um segundo argumento para essa função. Ele captura o comportamento geral de aplicar uma função a todos os elementos de seu segundo argumento.
fonte
Bem,
Geralmente, muitas linguagens de programação funcional são usadas nas universidades há muito tempo para "pequenos problemas com brinquedos".
Eles estão ficando mais populares agora, já que o OOP tem dificuldades com a "programação paralela" por causa do "estado". E, em algum momento, o estilo funcional é melhor para problemas em mãos, como o Google MapReduce.
Estou certo de que, quando os funcionários funcionarem, [tentar implementar sistemas maiores que 1.000.000 de linhas de código], alguns deles virão com novas metodologias de engenharia de software com palavras de efeito :-). Eles devem responder à antiga pergunta: como dividir o sistema em pedaços, para que possamos "morder" cada um, um de cada vez? [trabalhe iterativo, de maneira evolutiva e evolutiva] usando o Estilo Funcional.
Mas serão utilizados programas funcionais para sistemas tão grandes? Eles se tornarão o fluxo principal? Essa é a questão .
E ninguém pode vir com uma metodologia realista sem implementar sistemas tão grandes, sujando as mãos. Primeiro você deve sujar as mãos e sugerir uma solução. Soluções-Sugestões sem "dores reais e sujeira" serão "fantasia".
fonte