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:
Respostas:
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.
fonte
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.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.
fonte
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.
fonte
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 .
fonte
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.
fonte
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.
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.
fonte
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:
Resumo somente quando necessário
Não abstraia quando
Alguns exemplos
fonte
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:
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:
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:
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:
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:
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.
fonte
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.
fonte
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.
fonte
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.
fonte