É preferível projetar de cima para baixo ou de baixo para cima?

31

Pelo que entendi, o design de cima para baixo consiste em refinar o conceito abstrato de alto nível em partes menores de concreto e compreensíveis, até que o menor bloco de construção seja definido. Por outro lado, de baixo para cima define peças de baixo nível e depois construa gradualmente blocos de nível superior até que todo o sistema seja formado.

Na prática, é melhor combinar os dois métodos: começa com uma especificação de alto nível para especificar completamente o conhecimento do domínio, seu relacionamento e restrições. Quando o problema é bem entendido, os menores blocos de construção são criados para criar o sistema.

O processo de:

  • Criando especificação de requisitos
  • Criar uma especificação de design (com diagramas)
  • Implemento
  • Entregar
  • Repita (no desenvolvimento iterativo, em vez de fazer um pedaço inteiro em cada fase, fazemos um pouco cada um repetidamente e temos reuniões diárias para se adaptar às exigências dinâmicas do cliente)

parece perfeitamente normal para mim (com especificações como planos). Ele tem suas falhas, mas é por isso que desenvolvemos iterativamente: em vez de gastar tempo em uma fase, diz, a análise de requisitos para estudar todas as coisas possíveis no conhecimento do domínio que estão sujeitas a alterações (possivelmente diárias), fazemos um pouco de análise, um pouco de design e depois implementá-lo.

Outra maneira é que cada iteração é uma forma de mini cascata, onde a análise é feita em alguns dias (ou uma semana). O mesmo se aplica ao design. O restante do tempo é gasto na implementação. Existe algo inerentemente errado na abordagem de cima para baixo em combinação com o desenvolvimento iterativo?

Em seu ensaio, Programming Bottom Up , Paul Graham parece incentivar a construção de baixo para cima completamente, ou programá-lo de baixo para cima, mas não a fase de análise / design de requisitos:

Programadores Lisp experientes dividem seus programas de maneira diferente. Além do design de cima para baixo, eles seguem um princípio que pode ser chamado de design de baixo para cima - alterando o idioma para se adequar ao problema.

Até onde eu entendi, o que ele quis dizer é que Lisper ainda executa um projeto de cima para baixo, mas programa de baixo para cima, isso é verdade? Outro ponto que ele escreveu:

Vale ressaltar que o design de baixo para cima não significa apenas escrever o mesmo programa em uma ordem diferente. Quando você trabalha de baixo para cima, geralmente acaba com um programa diferente. Em vez de um único programa monolítico, você obterá uma linguagem maior com operadores mais abstratos e um programa menor escrito nela. Em vez de um lintel, você receberá um arco.

Isso significa que, durante o período de escrita de um programa no Lisp, você acaba com uma ferramenta genérica?

Amumu
fonte
Talvez se você fornecer um link para o livro, artigo ou artigo ao qual você está se referindo, outras pessoas possam fazer uma declaração mais fundamentada em relação à sua pergunta. Além disso, não está claro do que se trata sua pergunta. Você está pedindo conselhos sobre algumas especificidades em relação a um design que deseja implementar ou fazendo perguntas sobre o artigo que está referenciando. Talvez seja mais fácil ler e responder se você o dividir em duas perguntas feitas separadamente.
S.Robins 12/02
@ S.Robins Para resumir, demonstrei minha abordagem usual de cima para baixo para desenvolver software. No entanto, algumas pessoas preferem de baixo para cima, e uma delas é Paul Graham, que é uma pessoa famosa. Peço para entender como o design de baixo para cima ajuda em geral, e especificamente no Lisp, pois possui recursos especiais para incentivar (como sugeriu Paul).
Amumu 12/02/12
1
Amumu Eu editei o exemplo, pois as duas respostas parecem ter pulado de qualquer maneira. Tente manter suas perguntas concisas e faça apenas uma pergunta por pergunta .
yannis

Respostas:

35

De cima para baixo é uma ótima maneira de descrever o que você sabe ou recriar o que já construiu.

O maior problema de cima para baixo é que muitas vezes simplesmente não há "topo". Você mudará de idéia sobre o que o sistema deve fazer ao desenvolver o sistema e ao explorar o domínio. Como pode ser o seu ponto de partida algo que você não sabe (ou seja, o que deseja que o sistema faça)?

Um "local" de cima para baixo é uma coisa boa ... alguns pensamentos à frente da codificação são claramente bons. Mas pensar e planejar demais não é, porque o que você está visualizando não é o cenário real (a menos que você já tenha estado lá antes, ou seja, se você não está construindo, mas reconstruindo). De cima para baixo global ao construir coisas novas é apenas um absurdo.

De baixo para cima deve ser (globalmente) a abordagem, a menos que você conheça 100% do problema, você precisa da codificação da solução conhecida e não se preocupa em procurar possíveis soluções alternativas.

A abordagem Lisp é o destilado de baixo para cima. Você não apenas constrói de baixo para cima, mas também pode moldar os tijolos da maneira que precisa. Nada é fixo, a liberdade é total. É claro que a liberdade assume responsabilidade e você pode fazer coisas horríveis usando mal esse poder.

Mas código horrível pode ser escrito em qualquer idioma. Mesmo em linguagens que são moldadas como gaiolas para a mente, projetadas com a esperança de que, com essas linguagens, até os macacos possam ter bons programas em funcionamento (uma idéia tão errada em tantos níveis que dói apenas pensar nisso).

Seu exemplo é sobre um servidor web. Agora, em 2012, este é um problema bem definido, você tem especificações a serem seguidas. Um servidor web é apenas um problema de implementação. Especialmente se você pretende escrever um servidor da Web substancialmente idêntico aos outros bilhões de servidores da Web que existem, nada é realmente claro, exceto algumas minúcias. Até o seu comentário sobre a RSA ainda está falando sobre um problema claramente definido, com especificações formais.

Com um problema bem definido, com especificações formais e soluções já conhecidas, a codificação está apenas se conectando nos pontos. De cima para baixo é bom para isso. Este é o paraíso do gerente de projetos.

Em muitos casos, no entanto, não há uma abordagem conhecida e comprovada a ser usada para conectar os pontos. Na verdade, muitas vezes é difícil dizer até quais são os pontos.

Suponha, por exemplo, que você seja solicitado a instruir uma máquina de corte automática a alinhar as peças a serem cortadas com um material impresso que não esteja perfeitamente em conformidade com o logotipo repetitivo teórico. Você recebe as peças e fotos do material tiradas pela máquina.

O que é uma regra de alinhamento? Você decide. O que é um padrão, como representá-lo? Você decide. Como alinhar as peças? Você decide. As peças podem ser "dobradas"? Depende, alguns não e outros sim, mas é claro que não muito. O que fazer se o material estiver muito deformado para que uma peça o corte de forma aceitável? Você decide. Todos os rolos de material são idênticos? Claro que não, mas você não pode incomodar o usuário para adaptar as regras de alinhamento para cada rolo ... isso seria impraticável. Quais fotos estão vendo as câmeras? O material, o que quer que isso signifique ... pode ser colorido, pode ser preto sobre preto, onde apenas o reflexo da luz torna o padrão evidente. O que significa reconhecer um padrão? Você decide.

Agora tente projetar a estrutura geral de uma solução para esse problema e faça uma cotação, em dinheiro e tempo. Minha aposta é que até a arquitetura do seu sistema ... (sim, a arquitetura) estará errada. A estimativa de custo e tempo serão números aleatórios.

Nós o implementamos e agora é um sistema operacional, mas mudamos de idéia sobre a própria forma do sistema várias vezes. Adicionamos subsistemas inteiros que agora não podem ser alcançados a partir dos menus. Mudamos de mestre / escravo em protocolos mais de uma vez. Provavelmente agora temos conhecimento suficiente para tentar reconstruí-lo melhor.

Outras empresas, é claro, resolveram o mesmo problema ... mas, a menos que você esteja em uma dessas empresas, provavelmente seu projeto detalhado de cima para baixo será uma piada. Podemos projetá-lo de cima para baixo. Você não pode porque nunca fez isso antes.

Provavelmente você também pode resolver o mesmo problema. Trabalhando de baixo para cima, no entanto. Começando com o que você sabe, aprendendo o que não sabe e somando.

Novos sistemas de software complexos são desenvolvidos, não projetados. De vez em quando alguém começa a projetar um novo e complexo sistema de software mal especificado do início (observe que, com um grande projeto de software complexo, existem apenas três possibilidades: a] a especificação é imprecisa, b] a especificação é errada e auto-contraditória ou c] ambos ... e na maioria das vezes [c] é o caso).

Esses são os projetos típicos de grandes empresas, com milhares e milhares de horas lançadas apenas nos slides do powerpoint e nos diagramas UML. Invariavelmente, eles falham completamente após queimar quantidades embaraçosas de recursos ... ou, em alguns casos muito excepcionais, eles finalmente entregam um software muito caro que implementa apenas uma pequena parte das especificações iniciais. E esse software invariavelmente é profundamente odiado pelos usuários ... não o tipo de software que você compraria, mas o tipo de software que você usa porque é forçado a fazê-lo.

Isso significa que eu acho que você deveria pensar apenas em codificar? Claro que não. Mas, na minha opinião, a construção deve começar do fundo (tijolos, código concreto) e deve subir ... e seu foco e atenção aos detalhes devem, em certo sentido, "desaparecer" à medida que você se afasta do que tem. De cima para baixo é frequentemente apresentado como se você colocasse o mesmo nível de detalhe para todo o sistema de uma só vez: mantenha-o dividindo todos os nós até que tudo fique óbvio ... nos módulos da realidade, o subsistema é "cultivado" a partir de sub-rotinas. Se você não tiver uma experiência anterior no problema específico, seu design descendente de um subsistema, módulo ou biblioteca será horrível. Você pode criar uma boa biblioteca depois de saber quais funções colocar, e não o contrário.

Muitas das idéias do Lisp estão ficando mais populares (funções de primeira classe, fechamentos, digitação dinâmica como padrão, coleta de lixo, metaprogramação, desenvolvimento interativo), mas o Lisp ainda é hoje (entre os idiomas que conheço) bastante singular em como é fácil modelar o código para o que você precisa.

Parâmetros de palavras-chave, por exemplo, já estão presentes, mas, se não estiverem presentes, poderão ser adicionados. Eu fiz isso (incluindo verificação de palavras-chave em tempo de compilação) para um compilador Lisp de brinquedo que estou experimentando e não é preciso muito código.

Com o C ++, o máximo que você pode obter é um grupo de especialistas em C ++ dizendo que os parâmetros das palavras-chave não são tão úteis, ou uma implementação de modelo incrivelmente complexa, quebrada e com meio respaldo que na verdade não é tão útil. As classes C ++ são objetos de primeira classe? Não e não há nada que você possa fazer sobre isso. Você pode ter introspecção em tempo de execução ou tempo de compilação? Não e não há nada que você possa fazer sobre isso.

Essa flexibilidade de linguagem do Lisp é o que o torna ótimo para a construção de baixo para cima. Você pode criar não apenas sub-rotinas, mas também a sintaxe e a semântica da linguagem. E, em certo sentido, o próprio Lisp é ascendente.

6502
fonte
Sim, o que eu quis dizer foi um local de cima para baixo. Você continua refinando o requisito e evoluindo-o através das iterações. O ponto de cima para baixo é que queremos obter a estrutura de código o mais correta possível. Refatoração contínua. Por exemplo, no começo você deseja passar uma string para uma função, mas mais tarde você precisa de uma classe inteira para satisfazer as alterações, e a refatoração é demorada se a função já estiver sendo usada em todos os lugares.
Amumu
Reconstruir algo claramente conhecido de uma maneira claramente conhecida, então de cima para baixo está ok. Seu navegador da web é um exemplo disso. Mesmo usando uma linguagem como C ++ que obriga a antecipar e especificar formalmente (usando uma sintaxe terrível) todos os tipos de valores contidos em todas as variáveis ​​do seu programa ainda é uma opção viável.
6502
Não acho que meu exemplo seja claramente conhecido, porque presumo que ele seja feito por alguém com conhecimento básico de comunicação de dados, e o código C ++ é o resultado de um design muito básico do conhecimento do domínio (neste caso, redes) . É um exemplo de design de evolução. Na próxima iteração, o desenvolvedor aprende mais sobre o conhecimento do domínio e, assim, estende seu design para se ajustar ao conhecimento evoluído (como aspectos adicionais de segurança em todas as camadas do servidor da web). Em seguida, ele refina a implementação de acordo com o novo design.
Amumu 12/02/2012
O ponto aqui é que a solução real derivada de uma linguagem independente de domínio (como linguagem natural, linguagem matemática ... depende do domínio). O ato de codificar é apenas um mapeamento detalhado da solução gerada para o código.
Amumu 12/02/2012
1
Oi Mr.6502. Depois de um ano, à medida que aprendi mais coisas, começo a ver o que você disse se tornar mais verdadeiro. Eu criei outro segmento: programmers.stackexchange.com/questions/179000/… . Se você tiver tempo, espero que você esclareça meu pensamento novamente: D.
11552 Amumu
6

Eu não sei como esta resposta se aplica a Lisp, mas eu acabei de ler ágeis princípios, padrões e práticas , e o autor, Tio Bob , defende fortemente abordagem top-down para C # (também aplicável para C ++) com o qual estou plenamente aceita.

No entanto, diferentemente de outras respostas que chegaram à conclusão de que a abordagem de cima para baixo significa que, na primeira interação, você entrega apenas documentos e o design geral, o livro aponta para outro método: TDD combinado com o design evolutivo.

A idéia é que você comece do topo e defina seus níveis mais altos de abstração (ou local mais alto) e, assim que eles forem definidos, faça com que eles façam um trabalho útil para que o recurso nº 1 esteja funcionando imediatamente. Depois, à medida que você adiciona mais e mais recursos, refata seu código e desenvolve o design conforme necessário, mantendo sempre os princípios do SOLID. Dessa forma, você não terá muitas camadas de abstração e não terá um design de baixo nível que não se encaixa na arquitetura geral. Se você não tem certeza do que isso significa, o livro que mencionei acima tem um capítulo inteiro com um exemplo em que os desenvolvedores pegam conceitos e começam com diagramas UML e classes de baixo nível, apenas para perceber que metade dessas classes não é necessária quando a codificação começar. Essencialmente, com essa abordagem, o código de baixo nível é naturalmente pressionado à medida que mais detalhes de alto nível são definidos no projeto.

E, por fim, se você pratica o SOLID, não deve se deparar com uma situação em que abstrações de alto nível estejam definidas e, em seguida, entrar em detalhes e, de repente, descobrir que não há OOD ou abstrações. Isso não é realmente culpa do design de cima para baixo, mas da engenharia preguiçosa.

Se você estiver interessado em ler mais sobre XP e design evolucionário, aqui está uma boa leitura de Martin Fowler, outro grande autor: "Is Design Dead?"

DXM
fonte
Perfeito. É disso que estou falando. Também é bom saber sobre o princípio do SOLID, e o Agile suporta a abordagem de cima para baixo (eu pensei que, com TDD e XP, as pessoas saltam para codificar o mais rápido possível e evitam a documentação).
Amumu 13/02/12
@ Amumu: Adicionei um link para outro artigo escrito por Fowler. Você pode ler mais sobre a diferença entre a codificação de cowboys sem design / documentação e o que o XP / Agile está realmente promovendo. Enquanto pessoalmente, acredito firmemente que a documentação tem valor e tenho promovido ativamente minha equipe para manter nossos documentos de design, fui mudando gradualmente meus pontos de vista. Agora, nossas "tarefas de design" são do tipo quadro branco / guardanapo, enquanto os documentos reais são atualizados somente depois que a história é escrita. Nós também estamos escalando para trás sobre o que realmente está documentado para docs só cobrem de alto nível / Arquitectura coisas
DXM
3

Para mim, as observações mais importantes que Paul Graham faz em seu artigo são:

Os programadores [...] do Lisp seguem um princípio que pode ser chamado de projeto de baixo para cima - alterando o idioma para se adequar ao problema. No Lisp, você não apenas escreve seu programa para o idioma, mas também cria o idioma para o seu programa.

Ou, como é conhecido nos círculos C ++: Design de Biblioteca é Design de Linguagem (Bjarne Stroustrup)

A idéia principal do design descendente é: primeiro você planeja e depois codifica. Beanow está correto quando escreve , que há problemas quando o código executável chega mais tarde no processo. No design de baixo para cima, você sempre tem código e esse código que pode ser testado.

Além disso, seu código não é plano. Com isso, quero dizer, ele tende a ter mais níveis de abstrações menores. No design de cima para baixo, as pessoas geralmente acabam com grandes abstrações até um nível arbitrário e, abaixo disso, sem abstrações. Código OTOH projetado de baixo para cima geralmente contém menos estruturas de controle e dados de baixo nível, pois é provável que sejam abstraídas.

pillmuncher
fonte
2

Idealmente, escrever um programa em qualquer idioma, não apenas no Lisp, permite escrever todo um conjunto de ferramentas genéricas que podem acelerar o seu próximo programa ou aprimorar o seu atual.

Na prática, acompanhar essas ferramentas pode ser difícil. A documentação é pobre e desorganizada e as pessoas que os conhecem saem. Na prática, a reutilização de código costuma ser mais problemática do que vale a pena. Porém, se o código estiver documentado e organizado adequadamente, e os programadores permanecerem (ou mantiverem seu próprio suprimento de código útil), uma grande quantidade de trabalho poderá ser salva ao pegar o código do armazém em vez de reconstruí-lo.

Todo o design deve estar de cima para baixo ou você não saberia o que estava fazendo. Você está construindo um galpão ou um carro? Você não pode descobrir aquele com design de baixo para cima. Mas se você for construir uma roda dianteira esquerda, poderá pensar em precisar de mais rodas posteriormente, tanto para este projeto quanto para outros. E se você construir uma roda reutilizável, terá quatro pelo preço de uma. (E 18 para o reboque que você construirá em seguida, tudo de graça.)

Observe que, diferentemente dos carros e rodas reais, se você construiu uma "roda" de software, criou um número infinito deles.

Mais sobre o projeto de cima para baixo: enquanto você precisa começar com isso, sinto-me obrigado a salientar que, se você não pode montar uma roda, precisa descobrir isso antes de trabalhar muito em seu carro. Então, você precisa trabalhar de baixo para cima quase em paralelo com o trabalho de cima para baixo. Além disso, saber que você pode construir uma roda pode sugerir muitos projetos em que você nunca teria pensado antes, como o reboque do trator. Eu acho que a abordagem de cima para baixo tem que dominar, mas com um toque muito leve.

Portanto, para continuar parafraseando Paul Graham, idealmente, quando você escreve um programa, acaba com muitas partes reutilizáveis ​​que podem economizar muito tempo no programa original e em outras. Para me distanciar um pouco de Paul Graham, isso funciona em qualquer idioma (embora alguns incentivem o processo mais do que outros).

O inimigo desse processo quase mágico são programadores com perspectivas de muito curto prazo. Isso pode resultar de defeitos de personalidade, mas mais comumente da troca de tarefas e responsabilidades com muita rapidez, tanto dentro como entre empresas empregadoras.

RalphChapin
fonte
Uau, recebi uma resposta de 16 de fevereiro e a caixa de entrada não mostrou nada. Eu concordo com você, é sempre necessário ter um plano, mesmo que seja muito leve, e depois desenvolver um pouco e refinar o plano. É perfeitamente razoável. No entanto, não entendo por que muitos desenvolvedores adoram planejar tudo em sua mente enquanto escrevem código. É possível economizar bastante tempo e eles podem se concentrar para trabalhar mais no problema real, em vez de refatorar continuamente a arquitetura do código ao longo do processo de implementação.
Amumu
@ Amumu Uma razão para o planejamento durante a escrita: Alguns programadores sabem muito sobre o que estão fazendo no teclado e no mouse são o gargalo de codificação, não o processo de planejamento. (Código reutilizável lhes permitam fazer um trabalho novo, criativo que iria deixar de pensar ser a parte mais difícil de novo.)
RalphChapin
@ Amumu Outra razão para planejar enquanto escreve: com algumas combinações de idioma / programador / problema, o idioma é a melhor maneira de pensar sobre o problema. A linguagem é a maneira mais fácil de colocar seus pensamentos em "papel". Eu li apenas alguns bits aqui e ali sobre o Lisp, mas, a partir disso, suspeito que seja especialmente bom para isso. (Alguns idiomas foram criados como uma maneira de expressar um problema, em vez de conseguir que uma máquina o resolvesse.) Se for tão fácil reorganizar seu código quanto seus pensamentos, você também pode codificar. Mas o programador, a linguagem e o problema devem se misturar muito bem para que isso funcione.
RalphChapin
É confiável. A idéia do planejamento é separar o conhecimento do domínio e as identificações de relacionamento entre os conceitos do domínio e o processo de programação real. Imagine se uma classe é usada por uma dúzia de outras, sem um plano adequado para criar a interface para se comunicar com outras classes, podemos facilmente entrar no inferno da integração e refatoração. Nesse caso, o tempo perdido para digitar e reestruturar o código excede muito o tempo gasto para identificação no papel.
Amumu
Vamos dar um exemplo: suponha que estamos escrevendo um aplicativo cliente / servidor. A primeira coisa que precisamos identificar é o protocolo, definindo as mensagens de troca, como a mensagem é apresentada na memória (cabeçalho 16 bytes, id 4 bytes ...). O diagrama não é igual à modelagem / planejamento: abstratt.com/blog/2009/05/03/on-code-being-model . Não precisamos usar UML ou diagrama para modelar, pois a UML se concentra apenas no OOP, e o tempo gasto digitando no diagrama de classes UML não é menor que digitar o código real, se não mais.
Amumu
1

O que há de errado com a abordagem de cima para baixo em combinação com o desenvolvimento iterativo?

Usar uma abordagem de cima para baixo com desenvolvimento iterativo não fornece nenhum código de trabalho nas primeiras iterações. Ele fornece documentos de design e os quais o cliente terá dificuldade em fornecer feedback. Respostas como "sim, eu acho (isso é abstrato demais para mim)" não ajudarão você a especificar as especificações do sistema desejado pelo cliente.

Em vez disso, você apenas cria uma visão geral do que é solicitado. (Requisitos) a serem usados ​​como orientação geral para o que define um produto acabado e o que merece prioridade de implementação. A partir daí, você cria produtos de trabalho para cada iteração com a qual o cliente pode realmente brincar para ver se era isso que eles tinham em mente. Caso contrário, não será um problema, pois não foram gastas centenas de horas para projetar um sistema que funcione diferente do que o cliente pede agora.

Paul Graham incentivou a construir de baixo para cima completamente? Ou apenas programe de baixo para cima, mas não a fase de análise / criação de requisitos?

Eu não vou falar por outra pessoa. Também não li o ensaio dele. A resposta que estou lhe dando vem da minha educação e das experiências.

Isso significa que, durante o período de criação de um programa no Lisp, você acaba com uma ferramenta genérica que pode ser usada para escrever programas semelhantes ao original, não é?

Usar essa abordagem significa que você terá mais blocos de construção abstratos. Simplesmente porque você precisará criar subsistemas autônomos que possam ser apresentados imediatamente, não será possível criar um design de cima para baixo fortemente entrelaçado e que tenha que ser implementado de uma só vez. Melhora a reutilização, a manutenção, mas acima de tudo permite uma resposta mais flexível às mudanças.

Beanow
fonte
1
Negrito, aos meus olhos, é desnecessário quando você está já bloco citando ...
MK12