Existem desvantagens significativas para depender de abstrações?

9

Eu estava lendo este wiki no Stable Abstractions Principle (SAP) .

O SAP afirma que, quanto mais estável um pacote, mais abstrato ele deve ser. Isso implica que, se um pacote for menos estável (com maior probabilidade de alteração), deverá ser mais concreto. O que realmente não entendo é por que esse deveria ser o caso. Certamente, em todos os casos, independentemente da estabilidade, devemos depender de abstrações e ocultar a implementação concreta?

SteveCallender
fonte
Tente usar um componente com o qual você se sinta confortável, não usando as abstrações fornecidas, mas fazendo tudo com todos os detalhes em um nível menor do que você está acostumado. Isso lhe dará uma boa impressão das vantagens e desvantagens da abstração.
Kilian Foth
2
Você leu o artigo vinculado e / ou o livro no qual o artigo se baseia?
Jörg W Mittag
11
+1 Boa pergunta, especialmente porque eu não acho que a relação entre estabilidade e abstração seja imediatamente intuitiva. A página 11 deste artigo ajuda, seu exemplo do caso abstrato faz sentido, mas talvez alguém possa escrever um exemplo mais claro do caso concreto. Pedido de suspensão.
Mike Suporta Monica
Com que domínio do problema você está lidando com essas abstrações? Conforme observado em C2: "Na modelagem de domínios do mundo real - o mundo de clientes, funcionários, faturas, listas de materiais, produtos, SKUs, paycheques, etc. - é possível encontrar abstrações estáveis. Domínios computacionais - o mundo de pilhas, filas, funções, árvores, processos, threads, widgets gráficos, relatórios, formulários etc. - têm muito mais probabilidade de serem estáveis ​​". e "Em alguns domínios, abstrações estáveis ​​são difíceis de encontrar". Sem saber qual o problema que você está tentando resolver com a SAP, é difícil dar uma boa resposta.
@ JörgWMittag e Mike - Sim, eu li o artigo. Apenas sinto que falta uma explicação sobre o porquê "pacotes instáveis ​​devem ser concretos". Na página 13 do referido artigo, ele mostra um gráfico, mas na verdade não explica em muitos detalhes por que (1,1) o gráfico deve ser evitado? A ideia de que basicamente instável significa dependências menos aferentes e que não há necessidade de usar abstração? Se assim for ... não é boa prática para uso abstração de qualquer maneira, apenas no caso das alterações de estabilidade com mudanças de requisitos ..
SteveCallender

Respostas:

7

Pense nos seus pacotes como uma API, para pegar o exemplo do artigo, use definições para Readerwith string Reader.Read()e Writerwith void Writer.Write(string)como sua API abstrata.

Você pode criar uma classe Copycom um método Copier.Copy(Reader, Writer)e a implementação Writer.Write(Reader.Read())e talvez algumas verificações de sanidade.

Agora, você faz implementações concretas, por exemplo FileReader, FileWriter, KeyboardReadere DownloadThingsFromTheInternetReader.

E se você quiser alterar sua implementação FileReader? Não tem problema, basta alterar a classe e recompilar.

E se você quiser alterar a definição de sua abstração Reader? Opa, você não pode simplesmente mudar isso, mas você também vai ter que mudar Copier, FileReader, KeyboardReadere DownloadThingsFromTheInternetReader.

Esta é a lógica por trás do Princípio da Abstração Estável: Torne suas concretizações menos estáveis ​​que as abstrações.

Resíduo
fonte
11
Concordo com tudo o que você diz, mas acredito que a definição de estabilidade dos autores e a sua diferem. Você trata a estabilidade como necessidade de mudança, diz o autor "a estabilidade não é uma medida da probabilidade de um módulo mudar; é uma medida da dificuldade em mudar um módulo". Então, minha pergunta é mais: por que é benéfico para pacotes que são facilmente alterados serem mais concretos do que abstratos?
SteveCallender
11
@SteveCallender É uma diferença sutil: a definição do autor de "estabilidade" é o que a maioria das pessoas chama de "necessidade de estabilidade". Por exemplo, quanto mais módulos dependem de um módulo, mais "estável" ele precisa ser.
Residuum
6

Por causa de YAGNI .

Se você atualmente possui apenas uma implementação de uma coisa , por que se preocupar com uma camada extra e inútil? Isso só levará a complexidade desnecessária. Pior ainda, às vezes você fornece uma abstração pensando no dia em que uma segunda implementação chegará ... e esse dia nunca acontece. Que desperdício de trabalho!

Também acho que a verdadeira pergunta a se fazer não é "Preciso depender de abstrações?" mas sim "Preciso de modularidade?". E a modularidade nem sempre é necessária, veja abaixo.

Na empresa em que estou trabalhando, alguns dos softwares que desenvolvo estão fortemente ligados a algum dispositivo de hardware com o qual ele deve se comunicar. Esses dispositivos são desenvolvidos para cumprir objetivos muito específicos e são praticamente modulares. :-) Uma vez que o primeiro dispositivo produzido sai da fábrica e está instalado em algum lugar, tanto o seu firmware e hardware nunca pode mudar, nunca .

Portanto, posso ter certeza de que algumas partes do software nunca evoluirão. Essas partes não precisam depender de abstrações, pois existe apenas uma implementação e essa nunca será alterada. Declarar abstrações nessas partes do código apenas confundirá todos e levará mais tempo (sem produzir nenhum valor).

Visto
fonte
11
Costumo concordar com YAGNI, mas me pergunto sobre o seu exemplo. Você nunca está repetindo nenhum código nos diferentes dispositivos? Acho difícil acreditar que não exista um código comum nos dispositivos da mesma empresa. Além disso, como os clientes gostam quando você não corrige bugs no firmware? Você está dizendo que nunca há erros, nunca ? Se você tiver o mesmo código com erros em 4 implementações diferentes, precisará corrigir o erro 4 vezes, se não estiver em um módulo comum.
Fuhrmanator 11/11/2015
11
O código comum do @ Fuhrmanator é diferente das abstrações. Código comum pode significar apenas um método ou biblioteca auxiliar - nenhuma abstração é necessária.
Eilon
@ Fuhrmanator É claro que temos código comum nas bibliotecas, mas como Eilon disse, nem tudo depende de abstrações (algumas partes, no entanto). Eu nunca disse que nunca há bugs, eu disse que eles não podem ser corrigidos (por razões que estão fora do escopo da pergunta do OP).
manchado
@Eilon meu comentário foi sobre modularidade nem sempre é necessário (não abstrações).
Fuhrmanator 11/11/2015
@ Spotted Não há problema em não conseguir consertar. É apenas um exemplo bastante específico e não é típico da maioria dos softwares.
Fuhrmanator 11/11/2015
6

Acho que você talvez esteja confuso com a palavra estável escolhida por Robert Martin. Aqui é onde eu acho que a confusão começa:

Isso implica que, se um pacote for menos estável (com maior probabilidade de alteração), deverá ser mais concreto.

Se você ler o artigo original , verá (grifo meu):

A definição clássica da palavra estabilidade é: "Não é facilmente movido". Essa é a definição que usaremos neste artigo. Ou seja, a estabilidade não é uma medida da probabilidade de um módulo mudar; ao contrário , é uma medida da dificuldade em mudar um módulo .

Claramente, os módulos que são mais difíceis de mudar, serão menos voláteis. Quanto mais difícil for a mudança do módulo, ou seja, quanto mais estável for, menos volátil será.

Eu sempre lutei com a escolha do autor da palavra estável , pois eu (como você) tendem a pensar no aspecto "provável" da estabilidade, ou seja, improvável que mude . A dificuldade implica que a alteração desse módulo quebrará muitos outros módulos e será muito trabalho para corrigir o código.

Martin também usa as palavras independente e responsável , que para mim transmitem muito mais significado. Em seu seminário de treinamento, ele usou uma metáfora sobre os pais de crianças em crescimento e como eles deveriam ser "responsáveis", porque seus filhos dependem deles. Divórcio, desemprego, encarceramento etc. são ótimos exemplos do mundo real do impacto negativo que as mudanças nos pais terão sobre os filhos. Portanto, os pais devem ser "estáveis" para o benefício de seus filhos. A propósito, essa metáfora de filhos / pais não está necessariamente relacionada à herança no POO!

Então, seguindo o espírito de "responsável", criei significados alternativos para difícil mudar (ou não deveria mudar ):

  • Obrigatório - o que significa que outras classes dependem dessa classe e, portanto, não devem mudar.
  • Eis aqui - ibid.
  • Restringido - as obrigações dessa classe limitam sua facilidade de alteração.

Então, inserindo essas definições na instrução

quanto mais estável um pacote, mais abstrato deve ser

  • quanto mais obrigatório um pacote, mais abstrato deve ser
  • o mais visto um pacote é , mais abstrato deve ser
  • quanto mais restrito um pacote, mais abstrato deve ser

Vamos citar o Princípio de Abstrações Estáveis ​​(SAP), enfatizando as palavras confusas estável / instável:

Pacotes que são maximamente estáveis devem ser maximamente abstratos. Pacotes instáveis devem ser concretos. A abstração de um pacote deve ser proporcional à sua estabilidade .

Esclarecendo-o sem estas palavras confusas:

Pacotes que são maximamente devidos a outras partes do sistema devem ser abstratos maximamente. Pacotes que podem mudar sem dificuldade devem ser concretos. A abstração de um pacote deve ser proporcional à dificuldade em modificar .

TL; DR

O título da sua pergunta pergunta:

Existem desvantagens significativas para depender de abstrações?

Eu acho que se você criar as abstrações corretamente (por exemplo, elas existem porque muito código depende delas), não haverá desvantagens significativas.

Fuhrmanator
fonte
0

Isso implica que, se um pacote for menos estável (com maior probabilidade de alteração), deverá ser mais concreto. O que eu realmente não entendo é por que esse deveria ser o caso.

Abstrações são coisas difíceis de mudar no software porque tudo depende delas. Se o seu pacote for alterado com frequência e fornecer abstrações, as pessoas que dependem dele serão forçadas a reescrever grande parte do código delas quando você alterar alguma coisa. Mas se o seu pacote instável fornecer algumas implementações concretas, muito menos código terá que ser reescrito após as alterações.

Portanto, se seu pacote for alterado com frequência, ele deve fornecer concretos, não abstrações. Caso contrário ... quem diabos vai usá-lo? ;)

Vladislav Rastrusny
fonte
0

Lembre-se da métrica de estabilidade de Martin e do que ele entende por "estabilidade":

Instability = Ce / (Ca+Ce)

Ou:

Instability = Outgoing / (Incoming+Outgoing)

Ou seja, um pacote é considerado completamente instável se todas as suas dependências forem de saída: ele usa outras coisas, mas nada o usa. Nesse caso, só faz sentido que isso seja concreto. Também será o tipo de código mais fácil de alterar, já que nada mais o usa e, portanto, nada mais pode quebrar se esse código for modificado.

Enquanto isso, quando você tem o cenário oposto de "estabilidade" completa com um pacote usado por uma ou mais coisas, mas não usa nada por si só, como um pacote central usado pelo software, é quando Martin diz que isso deve ser feito. abstrato. Isso também é reforçado pela parte DIP do SOLI (D), o Princípio de Inversão de Dependências, que basicamente afirma que as dependências devem fluir uniformemente em direção a abstrações para códigos de baixo e alto nível.

Ou seja, as dependências devem fluir uniformemente em direção à "estabilidade" e, mais precisamente, as dependências devem fluir em direção a pacotes com mais dependências de entrada do que as dependências de saída e, além disso, as dependências devem fluir em direção a abstrações. A essência da lógica por trás disso é que as abstrações fornecem espaço para substituir um subtipo por outro, oferecendo esse grau de flexibilidade para que as partes concretas que implementam a interface sejam alteradas sem interromper as dependências recebidas nessa interface abstrata.

Existem desvantagens significativas para depender de abstrações?

Bem, na verdade, eu discordo de Martin aqui pelo menos no meu domínio, e aqui preciso introduzir uma nova definição de "estabilidade", como "falta de motivos para mudar". Nesse caso, eu diria que as dependências devem fluir para a estabilidade, mas as interfaces abstratas não ajudam se as interfaces abstratas forem instáveis ​​(pela minha definição de "instável", como propenso a ser repetidamente alterado, e não o de Martin). Se os desenvolvedores não conseguirem corrigir as abstrações e os clientes mudarem de idéia repetidamente de maneiras que tornem as tentativas abstratas de modelar o software incompletas ou ineficazes, não seremos mais beneficiados pela flexibilidade aprimorada das interfaces abstratas para proteger o sistema contra alterações em cascata que quebram a dependência . No meu caso pessoal, encontrei mecanismos ECS, como os encontrados em jogos AAA,mais concreta : em relação aos dados brutos, mas esses dados são altamente estáveis ​​(como em "é improvável que precise ser alterado"). Eu sempre achei a probabilidade de algo que requer mudanças futuras ser uma métrica mais útil do que a proporção de acoplamentos eferentes para o total para orientar as decisões de SE.

Então, eu alteraria um pouco o DIP e apenas diria: "as dependências devem fluir para componentes com a menor probabilidade de exigir alterações adicionais", independentemente de esses componentes serem interfaces abstratas ou dados brutos. Tudo o que importa para mim é a probabilidade de que eles possam exigir mudanças diretas no design. As abstrações são úteis apenas neste contexto de estabilidade se algo, por ser abstrato, reduzir essa probabilidade.

Para muitos contextos que podem ser o caso de engenheiros e clientes decentes que antecipam as necessidades do software antecipadamente e projetam abstrações estáveis ​​(como imutáveis), enquanto essas abstrações oferecem a eles todo o espaço necessário para trocar implementações concretas. Mas em alguns domínios, as abstrações podem ser instáveis ​​e propensas a serem inadequadas, enquanto os dados exigidos pelo mecanismo podem ser muito mais fáceis de antecipar e estabilizar com antecedência. Portanto, nesses casos, pode ser realmente mais benéfico do ponto de vista de manutenção (a facilidade de alterar e estender o sistema) para que as dependências fluam em direção aos dados, em vez de abstrações. Em um ECS, as partes mais instáveis ​​(como as partes mais frequentemente alteradas) são geralmente a funcionalidade que reside nos sistemas (PhysicsSystem, por exemplo), enquanto as partes mais estáveis ​​(como é menos provável que sejam alteradas) são os componentes que consistem apenas em dados brutos ( MotionComponentpor exemplo) que todos os sistemas usam.


fonte