Quantos padrões de design e níveis de abstração são necessários? [fechadas]


29

Como posso dizer que meu software tem muita abstração e muitos padrões de design, ou vice-versa, como sei se ele deve ter mais deles?

Os desenvolvedores com quem trabalho estão programando de maneira diferente a respeito desses pontos.

Alguns abstraem cada pequena função, usam padrões de design sempre que possível e evitam redundância a qualquer custo.

Os outros, inclusive eu, tentam ser mais pragmáticos e escrevem código que não se encaixa perfeitamente em todos os padrões de design, mas é muito mais rápido de entender porque menos abstração é aplicada.

Eu sei que isso é uma troca. Como posso saber quando há abstração suficiente no projeto e como sei que ele precisa de mais?

Exemplo, quando uma camada genérica de armazenamento em cache é gravada usando o Memcache. Será que realmente precisamos Memcache, MemcacheAdapter, MemcacheInterface, AbstractCache, CacheFactory, CacheConnector, ... ou isso é mais fácil de manter e ainda bom código ao utilizar apenas metade dessas classes?

Encontrei isso no Twitter:

insira a descrição da imagem aqui

( https://twitter.com/rawkode/status/875318003306565633 )


58
Se você está tratando padrões de design como itens que você retira de um balde e usa para montar programas, está usando muitos.
Blrfl


5
Você pode pensar nisso padrões de design como padrões de fala que as pessoas usam. Porque, em certo sentido, expressões idiomáticas, metáforas etc. são todos padrões de design. Se você estiver usando um idioma a cada frase ... isso provavelmente é muito frequente. Mas eles podem ajudar a esclarecer pensamentos e ajudar a compreender o que de outra forma seria uma longa parede de prosa. Não existe realmente uma maneira correta de responder "com que frequência devo usar metáforas?" - depende do julgamento do autor.
Primeiro-

8
Não há como uma única resposta SE possa cobrir adequadamente esse tópico. Isso leva literalmente anos de experiência e orientação para se controlar. Isso é claramente muito amplo.
jpmc26

5
Siga o princípio básico de design na indústria da aviação: "Simplifique e adicione mais leveza". Quando você descobre que a melhor maneira de corrigir um erro é simplesmente excluir o código que o contém, porque ele ainda não faz nada útil, mesmo que não tenha erros, você está começando a acertar o design!
alephzero

Respostas:


52

Quantos ingredientes são necessários para uma refeição? Quantas peças você precisa para construir um veículo?

Você sabe que possui pouca abstração quando uma pequena alteração na implementação leva a uma cascata de alterações em todo o código. Abstrações apropriadas ajudariam a isolar a parte do código que precisa ser alterada.

Você sabe que possui muita abstração quando uma pequena alteração na interface leva a uma cascata de alterações em todo o seu código, em diferentes níveis. Em vez de alterar a interface entre duas classes, você pode modificar dezenas de classes e interfaces apenas para adicionar uma propriedade ou alterar um tipo de argumento de método.

Além disso, não há realmente nenhuma maneira de responder à pergunta, fornecendo um número. O número de abstrações não será o mesmo de projeto para projeto, de um idioma para outro e até de um desenvolvedor para outro.


28
Se você tiver apenas duas interfaces e centenas de classes implementando-as, alterar as interfaces levaria a uma cascata de alterações, mas isso não significa que há muita abstração, pois você só tem duas interfaces.
Tulains Córdova

O número de abstrações nem será o mesmo para diferentes partes do mesmo projeto!
T. Sar - Restabelece Monica

Dica: mudar do Memcache para outro mecanismo de cache (Redis?) É uma alteração na implementação .
Ogre Psalm33

Suas duas regras (diretrizes, como você quiser chamá-las) não funcionam, como demonstrado por Tulains. Eles também são lamentavelmente incompletos, mesmo que o sejam. O restante da postagem é uma não resposta, dizendo pouco mais do que não podemos fornecer uma resposta com escopo razoável. -1
jpmc26

Eu diria que, no caso de duas interfaces e centenas de classes implementá-las, é possível que você tenha sobrecarregado suas abstrações. Eu certamente vi isso em projetos que reutilizam uma abstração muito vaga em muitos lugares ( interface Doer {void prepare(); void doIt();}), e torna-se doloroso refatorar quando essa abstração não se encaixa mais. A parte principal da resposta é que o teste se aplica quando uma abstração precisa mudar - se nunca muda, nunca causa dor.
James_pic

24

O problema com os padrões de design pode ser resumido com o provérbio "quando você está segurando um martelo, tudo parece um prego". O ato de aplicar um padrão de design não está melhorando seu programa. Na verdade, eu diria que você está criando um programa mais complicado se estiver adicionando um padrão de design. A questão permanece: você está ou não fazendo bom uso do padrão de design ou não, e este é o cerne da questão: "Quando temos muita abstração?"

Se você estiver criando uma interface e uma superclasse abstrata para uma única implementação, adicionou dois componentes adicionais ao seu projeto que são supérfluos e desnecessários. O objetivo de fornecer uma interface é ser capaz de lidar com isso igualmente em todo o seu programa sem saber como ele funciona. O objetivo de uma superclasse abstrata é fornecer comportamento subjacente para implementações. Se você tiver apenas uma implementação, obterá todas as interfaces de complicação e as classes abstact e nenhuma das vantagens.

Da mesma forma, se você estiver usando um padrão Factory, e se encontrar convertendo uma classe para usar a funcionalidade disponível apenas na superclasse, o padrão Factory não adicionará nenhuma vantagem ao seu código. Você adicionou apenas uma classe adicional ao seu projeto que poderia ter sido evitada.

TL; DR Meu argumento é que o objetivo da abstração não é abstrato por si só. Ele serve a um propósito muito prático em seu programa e, antes de decidir usar um padrão de design ou criar uma interface, você deve se perguntar se, ao fazer isso, o programa é mais fácil de entender, apesar da complexidade adicional ou do programa ser mais robusto apesar da complexidade adicional (de preferência ambos). Se a resposta for negativa ou negativa, dedique alguns minutos para considerar por que você quis fazer isso e, se possível, isso pode ser feito de uma maneira melhor, sem necessariamente a necessidade de adicionar abstração ao seu código.


A analogia do martelo seria o problema de conhecer apenas um padrão de design. Os padrões de design devem criar um kit de ferramentas completo para seleção e aplicação, quando apropriado. Você não seleciona uma marreta para quebrar uma noz.
Pete Kirkham

@PeteKirkham É verdade, mas mesmo toda uma variedade de padrões de design à sua disposição pode não ser adequada para um problema específico. Se uma marreta não é mais adequada para quebrar uma porca, nem uma chave de fenda, nem uma fita métrica porque você está com falta do martelo, isso não faz da marreta a escolha certa para o trabalho, apenas faz é a ferramenta mais adequada à sua disposição. Isso não significa que você deva usar uma marreta para quebrar uma noz. Heck, se estamos sendo franco, o que você realmente precisa é de um quebra-nozes, não um martelo ..
Neil

Quero um exército de esquilos treinados para quebrar minhas nozes.
icc97

6

TL: DR;

Eu não acho que exista um número "necessário" de níveis de abstrações abaixo dos quais há muito pouco ou acima dos quais há muito. Como no design gráfico, um bom design de POO deve ser invisível e deve ser dado como garantido. O mau design sempre se destaca como um polegar dolorido.

Resposta longa

Provavelmente, você nunca saberá em quantos níveis de abstração está construindo.

A maioria dos níveis de abstração é invisível para nós e nós os tomamos como garantidos.

Esse raciocínio me leva a esta conclusão:

Um dos principais objetivos da abstração é salvar o programador da necessidade de ter em mente todo o funcionamento do sistema o tempo todo. Se o design obriga a saber muito sobre o sistema para adicionar algo, provavelmente há pouca abstração. Eu acho que abstração ruim (design ruim, design anêmico ou excesso de engenharia) também pode forçar você a saber demais para adicionar algo. Em um extremo, temos um design baseado em uma classe divina ou em um monte de DTO; no outro extremo, temos algumas estruturas de OR / persistência que fazem com que você pule através de incontáveis ​​aros para conquistar um olá mundo. Nos dois casos, você também sabe demais.

A má abstração adere a um sino de Gauss no fato de que, uma vez que você passa por um ponto ideal, começa a atrapalhar. A boa abstração, por outro lado, é invisível e não pode haver muito, porque você não percebe que está lá. Pense em quantas camadas e mais camadas de APIs, protocolos de rede, bibliotecas, bibliotecas de SO, sistemas de arquivos, camadas de harware, etc., seu aplicativo é construído e é um dado adquirido.

Outro objetivo principal da abstração é a compartimentação, de modo que os erros não permeiam além de uma determinada área, ao contrário do casco duplo e dos tanques separados, impedindo que um navio inunde completamente quando uma parte do casco tem um buraco. Se modificações no código acabam criando bugs em áreas aparentemente não relacionadas , é provável que exista pouca abstração.


2
"Provavelmente você nunca saberá em quantos níveis de abstração está construindo." - De fato, o ponto principal de uma abstração é que você não sabe como ela é implementada; como você não sabe (e não pode ) saber quantos níveis de abstração ela oculta.
Jörg W Mittag

4

Padrões de design são simplesmente soluções comuns para problemas. É importante conhecer os padrões de design, mas eles são apenas sintomas de código bem projetado (bom código ainda pode ser anulado pelo conjunto de quatro padrões de design), não a causa.

Abstrações são como cercas. Eles ajudam a separar regiões do seu programa em partes testáveis ​​e intercambiáveis ​​(requisitos para criar código não-frágil e não rígido). E muito parecido com cercas:

  • Você deseja abstrações em pontos de interface naturais para minimizar seu tamanho.

  • Você não quer mudá-los.

  • Você quer que eles separem coisas que podem ser independentes.

  • Ter um no lugar errado é pior do que não tê-lo.

  • Eles não devem ter grandes vazamentos .


4

Refatoração

Eu não vi a palavra "refatoração" mencionada sequer uma vez até agora. Aqui vamos nos:

Sinta-se à vontade para implementar um novo recurso o mais diretamente possível. Se você possui apenas uma classe simples e simples, provavelmente não precisa de uma interface, uma superclasse, uma fábrica etc. para isso.

Se e quando você perceber que expande a classe de maneira que ela fique muito gorda, é hora de separá-la. Naquele momento, faz muito sentido pensar em como você realmente deve fazer isso.

Os padrões são uma ferramenta mental

Padrões, ou mais especificamente o livro "Design Patterns" da turma de quatro, são ótimos, entre outros motivos, porque eles constroem uma linguagem para os desenvolvedores pensarem e conversarem. É fácil dizer "observador", "fábrica" ​​ou "fachada" e todo mundo sabe exatamente o que isso significa, imediatamente.

Então, minha opinião seria que todo desenvolvedor deveria ter um conhecimento passageiro sobre pelo menos os padrões do livro original, simplesmente para poder falar sobre os conceitos de OO sem sempre ter que explicar o básico. Você realmente deve usar os padrões toda vez que a possibilidade de fazê-lo aparecer? Mais provável que não.

Bibliotecas

As bibliotecas provavelmente são a única área em que pode estar para errar ao lado de muitas opções baseadas em padrões, em vez de poucas. Alterar algo de uma classe "gorda" para algo com mais derivada de padrão (geralmente isso significa mais e menores classes) mudará radicalmente a interface; e essa é a única coisa que você normalmente não deseja alterar em uma biblioteca, porque é a única coisa que realmente interessa ao usuário da sua biblioteca. Eles não se importam menos com o modo como você lida com sua funcionalidade internamente, mas se importam muito se precisam constantemente mudar de programa quando você faz uma nova versão com uma nova API.


2

O ponto da abstração deve ser, acima de tudo, o valor que é trazido ao consumidor da abstração, ou seja, o cliente da abstração, os outros programadores e, freqüentemente, você mesmo.

Se, como um cliente que está consumindo as abstrações, você acha que precisa misturar e combinar muitas abstrações diferentes para concluir o trabalho de programação, então há potencialmente muitas abstrações.

Idealmente, as camadas devem reunir várias abstrações inferiores e substituí-las por uma abstração simples e de nível superior que seus consumidores possam usar sem precisar lidar com nenhuma dessas abstrações subjacentes. Se eles tiverem que lidar com as abstrações subjacentes, a camada estará vazando (por estar incompleta). Se o consumidor precisar lidar com muitas abstrações diferentes, talvez as camadas estejam faltando.

Depois de considerar o valor das abstrações para os programadores consumidores, podemos avaliar e considerar a implementação, como a do DRY-ness.

Sim, trata-se de facilitar a manutenção, mas devemos considerar primeiro a situação da manutenção de nossos consumidores, fornecendo abstrações e camadas de qualidade; depois, considere facilitar nossa própria manutenção em termos de aspectos de implementação, como evitar redundância.


Exemplo, quando uma camada de armazenamento em cache genérico é gravada usando o Memcache. Realmente precisamos de Memcache, MemcacheAdapter, MemcacheInterface, AbstractCache, CacheFactory, CacheConnector, ... ou é mais fácil manter e ainda assim um bom código ao usar apenas metade dessas classes?

Temos que olhar para a perspectiva do cliente e, se suas vidas são facilitadas, é bom. Se suas vidas são mais complexas, então é ruim. No entanto, pode ser que exista uma camada ausente que agrupe essas coisas em algo simples de usar. Internamente, isso pode muito bem melhorar a manutenção da implementação. No entanto, como você suspeita, também é possível que seja apenas um excesso de engenharia.


2

A abstração foi projetada para facilitar a compreensão do código. Se uma camada de abstração tornar as coisas mais confusas - não faça isso.

O objetivo é usar o número correto de abstrações e interfaces para:

  • minimizar o tempo de desenvolvimento
  • maximizar a manutenção do código

Resumo somente quando necessário

  1. Quando você descobre que está escrevendo uma super turma
  2. Quando permitirá reutilização significativa de código
  3. Se a abstração tornar o código se tornará significativamente mais claro e fácil de ler

Não abstraia quando

  1. Fazer isso não terá uma vantagem na reutilização ou clareza de código
  2. Fazer isso tornará o código significativamente mais longo / mais complexo, sem nenhum benefício

Alguns exemplos

  • Se você tiver apenas um cache em todo o programa, não abstraia, a menos que pense que provavelmente acabará com uma superclasse
  • Se você tiver três tipos diferentes de buffers, use uma abstração de interface comum para todos eles

2

Acho que essa pode ser uma meta-resposta controversa e estou um pouco atrasada para a festa, mas acho que é muito importante mencionar isso aqui, porque acho que sei de onde você vem.

O problema com a maneira como os padrões de design são usados ​​é que, quando ensinados, eles apresentam um caso como este:

Você tem esse cenário específico. Organize seu código dessa maneira. Aqui está um exemplo inteligente, mas um tanto artificial.

O problema é que, quando você começa a fazer engenharia de verdade, as coisas não são tão simples assim. O padrão de design sobre o qual você leu não se encaixa perfeitamente no problema que você está tentando resolver. Sem mencionar que as bibliotecas que você está usando violam totalmente tudo o que é declarado no texto, explicando esses padrões, cada um de sua maneira especial. E, como resultado, o código que você escreve "parece errado" e você faz perguntas como esta.

Além disso, gostaria de citar Andrei Alexandrescu, ao falar sobre engenharia de software, que afirma:

A engenharia de software, talvez mais do que qualquer outra disciplina de engenharia, exibe uma rica multiplicidade: você pode fazer a mesma coisa de muitas maneiras corretas, e há infinitas nuances entre certo e errado.

Talvez isso seja um exagero, mas acho que isso explica perfeitamente um motivo adicional pelo qual você pode se sentir menos confiante no seu código.

É em tempos como este que a voz profética de Mike Acton, líder de motores de jogos da Insomniac, grita na minha cabeça:

CONHEÇA SEUS DADOS

Ele está falando sobre as entradas para o seu programa e as saídas desejadas. E depois há esta gema de Fred Brooks do Mythical Man Month:

Mostre-me seus fluxogramas e oculte suas mesas, e continuarei confuso. Mostre-me suas tabelas e geralmente não precisarei de seus fluxogramas; eles serão óbvios.

Portanto, se eu fosse você, raciocinaria sobre o meu problema com base no meu caso de entrada típico e se alcançaria a saída correta desejada. E faça perguntas como esta:

  • Os dados de saída do meu programa estão corretos?
  • É produzido com eficiência / rapidez para o meu caso de entrada mais comum?
  • Meu código é fácil o suficiente para raciocinar localmente, tanto para mim quanto para meus colegas de equipe? Caso contrário, posso simplificar?

Quando você faz isso, a pergunta "quantas camadas de abstração ou padrões de design são necessários" torna-se muito mais fácil de responder. Quantas camadas de abstração você precisa? Tantos quanto necessário para atingir esses objetivos, e não mais. "E os padrões de design? Eu não usei nenhum!" Bem, se os objetivos acima foram alcançados sem a aplicação direta de um padrão, tudo bem. Faça com que funcione e passe para o próximo problema. Comece com seus dados, não com o código.


2

Arquitetura de software está inventando idiomas

Com cada camada de software, você cria a linguagem na qual você (ou seus colegas de trabalho) deseja expressar sua próxima solução de camada superior (então, apresentarei alguns análogos de linguagem natural na minha postagem). Seus usuários não querem passar anos aprendendo a ler ou escrever esse idioma.

Essa visão me ajuda a decidir sobre questões arquiteturais.

Legibilidade

Essa linguagem deve ser facilmente entendida (tornando o código da próxima camada legível). O código é lido com muito mais frequência do que escrito.

Um conceito deve ser expresso com uma palavra - uma classe ou interface deve expor o conceito. (As línguas eslavônicas geralmente têm duas palavras diferentes para um verbo em inglês, então você precisa aprender o dobro do vocabulário. Todas as línguas naturais usam palavras únicas para vários conceitos).

Os conceitos que você expõe não devem conter surpresas. Isso é principalmente convenções de nomenclatura, como métodos get e set, etc. E os padrões de design podem ajudar porque eles fornecem um padrão de solução padrão, e o leitor vê "OK, eu recebo os objetos de uma fábrica" ​​e sabe o que isso significa. Mas se simplesmente instanciar uma classe concreta faz o trabalho, eu preferiria isso.

Usabilidade

O idioma deve ser fácil de usar (facilitando a formulação de "sentenças corretas").

Se todas essas classes / interfaces do MemCache ficarem visíveis para a próxima camada, isso criará uma curva de aprendizado acentuada para o usuário até que ele entenda quando e onde usar qual dessas palavras para o conceito único de cache.

A exposição apenas das classes / métodos necessários facilita ao usuário encontrar o que ele precisa (consulte a cotação de Antoine de Saint-Exupery no DocBrowns). Expor uma interface em vez da classe de implementação pode facilitar isso.

Se você expuser uma funcionalidade em que um padrão de design estabelecido possa ser aplicado, é melhor segui-lo do que inventar algo diferente. Seu usuário entenderá as APIs seguindo um padrão de design mais facilmente do que algum conceito completamente diferente (se você sabe italiano, espanhol será mais fácil para você do que chinês).

Sumário

Introduzir abstrações se isso facilitar o uso (e vale a pena a sobrecarga de manter a abstração e a implementação).

Se o seu código tiver uma subtarefa (não trivial), resolva-o "da maneira esperada", ou seja, siga o padrão de design apropriado em vez de reinventar um tipo diferente de roda.


1

O importante a considerar é quanto o código de consumo que realmente lida com a lógica de negócios precisa saber sobre essas classes relacionadas ao cache. Idealmente, seu código deve se importar apenas com o objeto de cache que ele deseja criar e talvez uma fábrica para criar esse objeto se um método construtor não for suficiente.

O número de padrões usados ​​ou o nível de herança não são muito importantes, desde que cada nível possa ser justificado para outros desenvolvedores. Isso cria um limite informal, pois cada nível adicional é mais difícil de justificar. A parte mais importante é quantos níveis de abstração são afetados por alterações nos requisitos funcionais ou nos negócios. Se você pode fazer uma alteração em apenas um nível para um único requisito, é provável que você não tenha um excesso de resumo ou um resumo insuficiente, se alterar o mesmo nível para várias alterações não relacionadas, provavelmente está um resumo e precisará separar outras preocupações.


-1

Primeiro, a citação no Twitter é falsa. Os novos desenvolvedores precisam criar um modelo; as abstrações normalmente os ajudam a "entender a imagem". Desde que as abstrações façam sentido, é claro.

Em segundo lugar, seu problema não é muito ou poucas abstrações, é que aparentemente ninguém decide sobre essas coisas. Ninguém é o dono do código, nenhum plano / projeto / filosofia é implementado; qualquer outro cara pode fazer o que quer que pareça adequado para esse momento. Qualquer que seja o estilo que você escolha, deve ser um.


2
Vamos abster-nos de descartar a experiência como "falsa". Abstrações demais são um problema real. Infelizmente, as pessoas adicionam abstração antecipadamente, por "melhores práticas", em vez de resolver um problema real. Além disso, ninguém pode decidir "sobre essas coisas" ... as pessoas deixam empresas, as pessoas entram, ninguém se apropria da lama.
Rawkode
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.