O seguinte SOLID leva à criação de uma estrutura no topo da pilha de tecnologia?

70

Eu gosto do SOLID e tento o meu melhor para usá-lo e aplicá-lo no desenvolvimento. Mas não posso deixar de sentir que a abordagem do SOLID transforma seu código em código 'framework' - ou seja, código que você criaria se estivesse criando uma estrutura ou biblioteca para outros desenvolvedores usarem.

Geralmente pratiquei 2 modos de programação - criando mais ou menos exatamente o que é solicitado por meio de requisitos e KISS (programação típica), ou criando lógica, serviços muito genéricos e reutilizáveis, etc., que fornecem a flexibilidade que outros desenvolvedores podem precisar (programação de estrutura) .

Se o usuário realmente deseja que um aplicativo faça xey, faz sentido seguir o SOLID e adicionar um monte de pontos de entrada de abstração, quando você nem sabe se esse é um problema válido para começar com? Se você adicionar esses pontos de entrada da abstração, está realmente cumprindo os requisitos do usuário ou está criando uma estrutura em cima da estrutura existente e da pilha de tecnologias para facilitar futuras adições? Nesse caso, você está atendendo aos interesses do cliente ou do desenvolvedor?

Isso é algo que parece comum no mundo Java Enterprise, onde parece que você está projetando sua própria estrutura em cima do J2EE ou Spring para que seja um UX melhor para o desenvolvedor, em vez de se concentrar no UX para o usuário?

Igneous01
fonte
12
O problema com a maioria das pequenas regras práticas de programação é que elas estão sujeitas a interpretação, casos extremos e, às vezes, as definições de palavras nessas regras não são claras em uma inspeção mais detalhada. Eles podem significar essencialmente uma grande variedade de coisas para pessoas diferentes. Ter algum pragmatismo não ideológico geralmente permite tomar decisões mais sábias.
Mark Rogers
11
Você faz parecer que seguir os princípios do SOLID implica um grande investimento, muito trabalho extra. Não é, é praticamente gratuito. E provavelmente poupará a você ou a alguém um grande investimento no futuro, pois facilita a manutenção e a extensão do seu código. Você continua fazendo perguntas como "devemos fazer nossa lição de casa ou fazer o cliente feliz?" Estas não são compensações.
Martin Maat
11
@ MartinMaat Acho que as formas mais extremas do SOLID implicam grande investimento. Ou seja. Software corporativo. Fora do software corporativo, você teria muito pouco motivo para abstrair seu ORM, pilha de tecnologia ou banco de dados, porque há uma chance muito boa de você continuar com a pilha escolhida. No mesmo sentido, vinculando-se a uma estrutura, banco de dados ou ORM específico, você está violando os princípios do SOLID porque está acoplado à sua pilha. Esse nível de flexibilidade do SOLID não é necessário na maioria dos trabalhos.
Igneous01
11
Veja também o efeito da plataforma interna .
Maxpm 28/09/18
11
Transformar a maioria dos códigos em algo como uma estrutura não soa nada terrível. Isso só se torna terrível se se tornar excessivo. Mas as estruturas podem ser mínimas e opinativas. Não tenho certeza se isso seria uma consequência inevitável de seguir o SOLID, mas é definitivamente uma consequência possível e que eu acho que você deveria adotar.
Konrad Rudolph

Respostas:

84

Sua observação está correta, os princípios do SOLID são IMHO feitos com bibliotecas reutilizáveis ​​ou código de estrutura em mente. Quando você segue todos eles cegamente, sem perguntar se faz sentido ou não, corre o risco de generalizar demais e investir muito mais esforço em seu sistema do que provavelmente o necessário.

Isso é uma troca e precisa de alguma experiência para tomar as decisões corretas sobre quando generalizar e quando não. Uma possível abordagem para isso é seguir o princípio YAGNI - não torne seu código SÓLIDO "apenas por precaução" - ou, para usar suas palavras: não

forneça a flexibilidade que outros desenvolvedores possam precisar

em vez disso, forneça a flexibilidade que outros desenvolvedores realmente precisam assim que precisam , mas não antes.

Portanto, sempre que você tiver uma função ou classe em seu código, não tem certeza se ela pode ser reutilizada, não a coloque no seu framework agora. Aguarde até ter um caso real de reutilização e refatorar para "SOLID o suficiente para esse caso". Não implemente mais configurabilidade (seguindo o OCP) ou pontos de entrada de abstração (usando o DIP) em uma classe que você realmente precise para o caso real de reutilização. Adicione a próxima flexibilidade quando o próximo requisito de reutilização estiver realmente presente.

Obviamente, essa maneira de trabalhar sempre exigirá uma certa refatoração na base de códigos de trabalho existente. É por isso que os testes automáticos são importantes aqui. Portanto, tornar seu código SÓLIDO o suficiente desde o início para testá-lo por unidade não é uma perda de tempo, e isso não contradiz o YAGNI. Os testes automáticos são um caso válido para "reutilização de código", pois o código em jogo é usado tanto no código de produção quanto nos testes. Mas lembre-se, basta adicionar a flexibilidade que você realmente precisa para fazer os testes funcionarem, nem menos, nem mais.

Esta é realmente a velha sabedoria. Há muito tempo antes que o termo SOLID ficou popular, alguém me disse antes de tentar escrever re código utilizável, devemos escrever utilizável código. E ainda acho que é uma boa recomendação.

Doc Brown
fonte
23
Pontos extras de contenção: aguarde até ter 3 casos de uso em que você vê a mesma lógica antes de refatorar seu código para reutilização. Se você começar a refatorar com duas peças, é fácil acabar em uma situação em que a alteração de requisitos ou um novo caso de uso acaba quebrando a abstração que você fez. Além disso, mantenha os refatores limitados a itens com o mesmo caso de uso: 2 componentes podem ter o mesmo código, mas fazem coisas totalmente diferentes e, se você mesclar esses componentes, acaba ligando essa lógica, o que pode causar problemas posteriormente.
Nzall 27/09/18
8
Eu geralmente concordo com isso, mas sinto que ele é muito focado em aplicativos "únicos": você escreve o código, funciona, tudo bem. No entanto, existem muitos aplicativos com "suporte de longa data". Você pode escrever um código e, 2 anos depois, os requisitos de negócios mudam para que você precise ajustar o código. A essa altura, muitos outros códigos podem depender dele - nesse caso, os princípios do SOLID facilitarão a alteração.
R. Schmitz
3
"Antes de tentar escrever código reutilizável, devemos escrever código utilizável" - Muito sensato!
Graham
10
Provavelmente, vale a pena notar que esperar até que você tenha um caso de uso real tornará seu código SOLID melhor , porque trabalhar em hipóteses é muito difícil e é provável que você adivinhe quais serão as necessidades futuras. Nosso projeto tem vários casos em que as coisas foram projetadas para serem SÓLIDAS e flexíveis para necessidades futuras ... exceto que as necessidades futuras se revelaram coisas que ninguém pensava na época, então nós dois precisamos refatorar e ter flexibilidade extra. ainda não precisava - o que precisava ser mantido em face da refatoração ou descartado.
precisa saber é o seguinte
2
também geralmente você ainda precisará escrever código testável, o que geralmente significa ter uma primeira camada de abstração para poder alternar da implementação concreta para a de teste.
Walfrat
49

Pela minha experiência, ao escrever um aplicativo, você tem três opções:

  1. Escreva o código apenas para atender aos requisitos,
  2. Escreva um código genérico que preveja os requisitos futuros, além de atender aos requisitos atuais,
  3. Escreva um código que atenda apenas aos requisitos atuais, mas de uma maneira fácil de alterar posteriormente para atender a outras necessidades.

No primeiro caso, é comum acabar com um código fortemente acoplado que carece de testes de unidade. Claro que é rápido escrever, mas é difícil de testar. E é uma dor real certa mudar depois quando os requisitos mudarem.

No segundo caso, uma quantidade enorme de tempo é gasta tentando antecipar necessidades futuras. E com muita freqüência esses requisitos futuros previstos nunca se materializam. Este parece o cenário que você está descrevendo. É um desperdício de esforço na maioria das vezes e resulta em código desnecessariamente complexo, que ainda é difícil de mudar quando um requisito que não era esperado para surgir.

O último caso é o único a apontar na minha opinião. Use TDD ou técnicas semelhantes para testar o código à medida que avança e você acabará com um código fracamente acoplado, fácil de modificar e ainda rápido de escrever. E, ao fazer isso, você naturalmente segue muitos dos princípios do SOLID: pequenas classes e funções; interfaces e dependências injetadas. Liskov é geralmente mantida feliz também, pois classes simples, com responsabilidades únicas, raramente desrespeitam seu princípio de substituição.

O único aspecto do SOLID que realmente não se aplica aqui é o princípio de aberto / fechado. Para bibliotecas e estruturas, isso é importante. Para um aplicativo independente, não muito. Realmente é um caso de escrever código que segue " SLID ": fácil de escrever (e ler), fácil de testar e fácil de manter.

David Arno
fonte
uma das minhas respostas favoritas neste site!
TheCatWhisperer
Não sei como você conclui que 1) é mais difícil de testar do que 3). É mais difícil fazer alterações, com certeza, mas por que você não pode testar? Na verdade, é mais fácil testar um pedaço de software obstinado do que um mais geral.
Sr. Lister
@ MrLister Os dois andam de mãos dadas, 1. é mais difícil de testar que 3. porque a definição implica que não está escrito "de uma maneira que seja fácil mudar mais tarde para atender a outras necessidades".
Mark Booth
11
+0; IMVHO você está interpretando mal (embora de maneira comum) a maneira como 'O' (aberto-fechado) funciona. Veja, por exemplo, codeblog.jonskeet.uk/2013/03/15/… - mesmo em pequenas bases de código, é mais sobre ter unidades de código independentes (por exemplo, classes, módulos, pacotes, o que for), que podem ser testadas isoladamente e adicionadas / removido conforme a necessidade. Um exemplo seria um pacote de métodos utilitários - independentemente da maneira como você os agrupa, eles devem ser 'fechados', ou seja, autônomos e 'abertos', ou seja, extensíveis de alguma forma.
vaxquis
Aliás, até o tio Bob segue esse caminho em um ponto: “O que [aberto-fechado] significa é que você deve se esforçar para colocar seu código em uma posição tal que, quando o comportamento muda da maneira esperada, você não precisa fazer varredura muda para todos os módulos do sistema. Idealmente, você poderá adicionar o novo comportamento adicionando novo código e alterando pouco ou nenhum código antigo. ” <- isso obviamente se aplica mesmo a aplicativos pequenos, se eles pretendem ser modificados ou corrigidos (e, IMVHO, que é geralmente o caso, esp quando estão em causa as correções. rir )
vaxquis
8

A perspectiva que você tem pode ser distorcida pela experiência pessoal. Essa é uma inclinação escorregadia de fatos individualmente corretos, mas a inferência resultante não é, mesmo que pareça correta à primeira vista.

  • As estruturas têm um escopo maior do que os pequenos projetos.
  • As práticas inadequadas são significativamente mais difíceis de lidar em bases de código maiores.
  • Construir uma estrutura (em média) requer um desenvolvedor mais qualificado do que construir um projeto pequeno.
  • Os desenvolvedores melhores seguem mais as boas práticas (SOLID).
  • Como resultado, as estruturas têm uma maior necessidade de boas práticas e tendem a ser construídas por desenvolvedores com uma experiência mais próxima com as boas práticas.

Isso significa que, quando você interage com estruturas e bibliotecas menores, o código de boas práticas com o qual você interage será mais comumente encontrado nas estruturas maiores.

Essa falácia é muito comum, por exemplo, todos os médicos pelos quais fui tratado eram arrogantes. Portanto, concluo que todos os médicos são arrogantes. Essas falácias sempre sofrem uma inferência geral baseada em experiências pessoais.

No seu caso, é possível que você tenha experimentado predominantemente boas práticas em estruturas maiores e não em bibliotecas menores. Sua observação pessoal não está errada, mas é uma evidência anedótica e não é universalmente aplicável.


2 modos de programação - criando mais ou menos exatamente o que é solicitado via requisitos e KISS (programação típica), ou criando lógica, serviços muito genéricos e reutilizáveis, etc., que fornecem a flexibilidade que outros desenvolvedores podem precisar (programação de estrutura)

Você está confirmando isso aqui. Pense no que é uma estrutura. Não é uma aplicação. É um "modelo" generalizado que outras pessoas podem usar para criar todos os tipos de aplicativos. Logicamente, isso significa que uma estrutura é construída em uma lógica muito mais abstrata para ser útil a todos.

Os construtores de estruturas são incapazes de usar atalhos, porque eles nem sabem quais são os requisitos dos aplicativos subseqüentes. Construir uma estrutura inerentemente os incentiva a tornar seu código utilizável para outras pessoas.

Os construtores de aplicativos, no entanto, têm a capacidade de comprometer a eficiência lógica porque estão focados na entrega de um produto. Seu principal objetivo não é o funcionamento do código, mas a experiência do usuário.

Para uma estrutura, o usuário final é outro desenvolvedor, que estará interagindo com seu código. A qualidade do seu código é importante para o usuário final.
Para um aplicativo, o usuário final é um não desenvolvedor, que não estará interagindo com seu código. A qualidade do seu código não tem importância para eles.

É exatamente por isso que os arquitetos de uma equipe de desenvolvimento costumam atuar como impositores de boas práticas. Eles estão a um passo da entrega do produto, o que significa que tendem a olhar o código objetivamente, em vez de se concentrar na entrega do próprio aplicativo.


Se você adicionar esses pontos de entrada da abstração, está realmente cumprindo os requisitos do usuário ou está criando uma estrutura em cima da estrutura existente e da pilha de tecnologias para facilitar futuras adições? Nesse caso, você está atendendo aos interesses do cliente ou do desenvolvedor?

Esse é um ponto interessante, e é (na minha experiência) a principal razão pela qual as pessoas ainda tentam justificar evitar boas práticas.

Para resumir os pontos abaixo: Ignorar as boas práticas só pode ser justificada se seus requisitos (como atualmente conhecidos) forem imutáveis ​​e nunca haverá nenhuma alteração / adição à base de código. Alerta de spoiler: Isso raramente é o caso.
Por exemplo, quando escrevo um aplicativo de console de 5 minutos para processar um arquivo específico, não uso boas práticas. Como só vou usar o aplicativo hoje, e ele não precisa ser atualizado no futuro (seria mais fácil escrever um aplicativo diferente caso eu precise de um novamente).

Digamos que você possa criar um aplicativo de má qualidade em 4 semanas e em 6 semanas. À primeira vista, construir de má qualidade parece melhor. O cliente obtém seu aplicativo mais rapidamente e a empresa precisa gastar menos tempo com os salários dos desenvolvedores. Vitória / vitória, certo?

No entanto, esta é uma decisão tomada sem pensar no futuro. Devido à qualidade da base de código, fazer uma grande alteração na versão mal feita levará 2 semanas, enquanto as mesmas alterações na versão adequada levar 1 semana. Pode haver muitas dessas mudanças no futuro.

Além disso, há uma tendência de que as mudanças requeiram inesperadamente mais trabalho do que você pensava inicialmente em bases de código construídas de maneira péssima, provavelmente aumentando o tempo de desenvolvimento para três semanas em vez de duas.

E também há a tendência de perder tempo caçando bugs. Esse é geralmente o caso em projetos nos quais a criação de log foi ignorada devido a restrições de tempo ou a pura relutância em implementá-lo, porque você trabalha distraidamente com a suposição de que o produto final funcionará conforme o esperado.

Nem precisa ser uma grande atualização. No meu atual empregador, vi vários projetos que foram construídos de forma rápida e suja e, quando o menor erro / alteração precisava ser feito devido a uma falta de comunicação nos requisitos, que levou a uma reação em cadeia da necessidade de refatorar módulo após módulo . Alguns desses projetos acabaram em colapso (e deixaram para trás uma bagunça insustentável) antes mesmo de lançar sua primeira versão.

As decisões de atalho (programação rápida e suja) são benéficas apenas se você puder garantir conclusivamente que os requisitos estão exatamente corretos e nunca precisarão ser alterados. Na minha experiência, nunca encontrei um projeto em que isso fosse verdade.

Investir o tempo extra em boas práticas é investir no futuro. Bugs e alterações futuras serão muito mais fáceis quando a base de código existente for baseada em boas práticas. Já estará pagando dividendos após apenas duas ou três alterações.

Flater
fonte
11
Essa é uma boa resposta, mas devo esclarecer que não estou dizendo que abandonamos as boas práticas, mas que nível de 'boas práticas' buscamos? É uma boa prática abstrair seu ORM em todos os projetos, porque você 'pode' precisar trocá-lo por outro posteriormente? Acho que não, há certos níveis de acoplamento que estou disposto a aceitar (ou seja, estou vinculado à estrutura, linguagem, ORM, banco de dados que foi escolhido). Se seguirmos o SOLID em seu grau extremista, estamos realmente apenas implementando nossa própria estrutura no topo da pilha escolhida?
precisa saber é o seguinte
Você está negando a experiência do OP como "falácia". Não é construtivo.
precisa saber é o seguinte
@ max630 Não estou negando. Passei boa parte da resposta explicando por que as observações do OP são válidas.
Flater 28/09
11
@ Igneous01 O SOLID não é uma estrutura. O SOLID é uma abstração, mais comumente encontrada em uma estrutura. Ao implementar qualquer tipo de abstração (incluindo o SOLID), sempre há uma linha de razoabilidade. Você não pode simplesmente abstrair por causa da abstração, gastaria idades criando um código tão generalizado que é difícil de seguir. Apenas abstraia o que você razoavelmente suspeita que lhe será útil no futuro. No entanto, não caia na armadilha de supor que você está vinculado, por exemplo, ao seu servidor de banco de dados atual. Você nunca sabe que novo banco de dados será lançado amanhã.
Flater 28/09
@ Igneous01 Em outras palavras, você tem a idéia certa por não querer abstrair tudo, mas sinto que você está inclinando-se um pouco demais nessa direção. É muito comum que os desenvolvedores suponham que os requisitos atuais sejam definidos e tomem decisões de arquitetura com base nessa suposição (desejável).
Flater 28/09
7

Como o SOLID transforma código simples em código de estrutura? Eu não sou um stan para o SOLID de forma alguma, mas não é realmente óbvio o que você quer dizer aqui.

  • BEIJO é a essência do S Princípio Responsabilidade ingle.
  • Não há nada no O pen / Princípio fechada (pelo menos como eu entendo-ver Jon Skeet ) que vai contra o código da escrita para fazer uma coisa assim. (De fato, quanto mais focado o código, mais importante se torna a parte "fechada".)
  • O Princípio da Substituição de L iskov não diz que você deve permitir que as pessoas subclasses de suas aulas. Diz que, se você subclasse suas classes, suas subclasses devem cumprir o contrato de suas superclasses. Esse é apenas um bom design OO. (E se você não possui nenhuma subclasse, ela não se aplica.)
  • BEIJO é também a essência da I nterface Segregação Princípio.
  • A D ependency Inversion Principle é o único que pode ver remotamente aplicação, mas eu acho que é muito mal compreendido e exagerado. Isso não significa que você precisa injetar tudo com Guice ou Spring. Significa apenas que você deve abstrair onde apropriado e não depender dos detalhes da implementação.

Admito que não penso em termos do SOLID, porque vim através das escolas de programação Gang of Four e Josh Bloch , não da escola Bob Martin. Mas acho que, se você pensa em "SOLID" = "adicionando mais camadas à pilha de tecnologia", está lendo errado.


PS Não venda os benefícios do "melhor UX para o desenvolvedor" a curto prazo. O código passa a maior parte de sua vida em manutenção. Um desenvolvedor é você .

David Moles
fonte
11
Em relação ao SRP - pode-se argumentar que qualquer classe com um construtor viola o SRP, porque você pode transferir essa responsabilidade para uma fábrica. Em relação ao OCP - esse é realmente um problema no nível da estrutura, porque depois de publicar uma interface para consumo para uso externo, você não pode modificá-la. Se a interface for usada apenas dentro do seu projeto, é possível alterar o contrato, porque você pode alterar o contrato dentro do seu próprio código. Em relação ao ISP - pode-se argumentar que uma interface deve ser definida para cada ação individual (preservando, assim, o SRP), e preocupa usuários externos.
Igneous01
3
1) alguém poderia, mas duvido que alguém que valha a pena ouvir tenha. 2) você pode se surpreender com a rapidez com que um projeto pode crescer até um tamanho que modificar livremente as interfaces internas se torna uma má idéia. 3) veja 1) e 2). Basta dizer que acho que você está lendo demais os três princípios. Mas os comentários não são realmente o lugar para abordar esses argumentos; Sugiro que você coloque cada uma delas como uma pergunta separada e veja que tipo de respostas você recebe.
David Moles
4
@ Igneous01 Usando essa lógica, você também pode abandonar getters e setters, pois pode criar uma classe separada para cada setter de variável e uma para cada getter. IE: class A{ int X; int Y; } class A_setX{ f(A a, int N) { a.X = N; }} class A_getX{ int f(A a) { return X; }} class A_setY ... etc.Acho que você está olhando de um ponto de vista meta demais com sua reivindicação de fábrica. A inicialização não é um aspecto do problema do domínio.
Aaron
@Aaron This. As pessoas podem usar o SOLID para apresentar argumentos ruins, mas isso não significa fazer coisas ruins = "seguir o SOLID".
David Moles