Os princípios ANTI SOLID - CUPID primeira parte!
Autor DAN NORTH .
“Se você tivesse que oferecer alguns princípios para o desenvolvimento de software moderno, qual você escolheria?”
Em um encontro virtual recente do Extreme Tuesday Club (XTC), discutimos se os princípios SOLID estão desatualizados. Um tempo atrás eu dei uma palestra irônica sobre o assunto, então antes do encontro um dos organizadores perguntou por quais princípios eu substituiria o SOLID já que eu discordava deles. Venho pensando nisso há algum tempo e propus cinco de minha autoria, que formam a sigla CUPID.
Este artigo não é sobre esses princípios, esse será meu próximo post. É por isso que acho que precisamos deles. Quero compartilhar a história por trás e explicar por que nunca comprei o SOLID. Para fazer isso, vamos falar sobre a conversa.
Por que cada elemento do SOLID está errado ¶
A PubConf foi inventada como uma espécie de afterparty das conferências NDC. Como o nome sugere, acontece em um pub. Vários oradores dão uma palestra no estilo Ignite - 20 slides, 15 segundos por slide, avanço automático - e o público ruge, bate palmas e troveja sua aprovação de acordo. O vencedor ganha algo e todos se divertem.
Há alguns anos fui convidado para falar em um evento PubConf em Londres. Eu gosto do desafio de uma conversa restrita. Este tinha que ser engraçado de gente bêbada e em forma de Ignite. Eu estava pensando nos princípios SOLID de Robert C. Martin e, no espírito de “depende”, pensei que seria divertido ver se eu poderia refutar cada princípio com uma cara séria. Eu também queria propor uma alternativa em cada caso.
Agora, algumas palestras se escrevem sozinhas: Achei que poderia usar um slide para apresentar cada princípio, um para desafiá-lo, um para apresentar uma alternativa, cinco vezes. São 15 slides, com 45 segundos por princípio. De cima a baixo, e lá estavam meus 20 slides!
Enquanto escrevia a palestra, notei duas coisas. Primeiro, era muito mais fácil refutar cada princípio do que eu pensava (além do Princípio da Substituição de Liskov, então tive que lidar com isso de uma maneira diferente).
Em segundo lugar, a alternativa continuou sendo a mesma: Escreva código simples.
É fácil desafiar isso com "O que 'simples' significa?"
Mas eu tinha uma boa definição de trabalho para isso, então não estava muito preocupado.
Depois da conferência eu coloquei os slides no SpeakerDeck e um monte de pessoas que eu nunca conheci, começaram a atacar primeiro a premissa da palestra, depois o detalhe dos slides de uma palestra que nunca me ouviram dar, depois eu pessoalmente.
Como nunca escrevi, aqui está mais ou menos como foi a conversa. Tenha em mente que para cada princípio, tive 15 segundos para apresentá-lo, 15 segundos para desafiá-lo e 15 segundos para propor uma alternativa. Preparar? Vai!
Princípio de Responsabilidade Única ¶
O Princípio da Responsabilidade Única diz que o código deve fazer apenas uma coisa. Outro enquadramento é que deve ter “uma razão para mudar”. Chamei isso de “Princípio Inutilmente Vago”. O que é uma coisa afinal? É ETL - Extract-Transform-Load - uma coisa (a DataProcessor) ou três coisas? Qualquer código não trivial pode ter vários motivos para mudar, que podem ou não incluir o que você tinha em mente, então, novamente, isso não faz muito sentido para mim.
Em vez disso, sugeri escrever um código simples usando a heurística de que “se encaixa na minha cabeça”.
O que isso significa? Você só pode raciocinar sobre algo se isso se encaixa na sua cabeça. Por outro lado, se algo não se encaixa em sua cabeça, você não pode raciocinar sobre isso. O código deve caber na sua cabeça em qualquer nível de granularidade, seja no nível de método/função, nível de classe/módulo, componentes compostos de classes ou aplicativos distribuídos inteiros.
Você pode perguntar “De quem é a cabeça?” Para o propósito da heurística, suponho que o proprietário da cabeça possa ler e escrever código idiomático em qualquer idioma que esteja em jogo e que esteja familiarizado com o domínio do problema. Se eles precisam de mais conhecimento esotérico do que isso, por exemplo, saber com qual dos muitos sistemas internos não documentados precisamos nos integrar para realizar qualquer trabalho, então isso deve ser explícito no código para que caiba na cabeça deles.
Em cada escala deve haver integridade conceitual suficiente para que você possa compreender “o todo” naquele nível. Se você não puder, então essa é uma heurística pela qual lutar em suas atividades de reestruturação. Às vezes você pode juntar várias coisas e elas ainda cabem na sua cabeça. O agrupamento torna-os ainda mais fáceis de raciocinar do que se forem artificialmente separados porque alguém insistiu na Responsabilidade Única. Em outros casos, faz sentido decompor artificialmente uma única responsabilidade em várias etapas apenas para tornar cada uma mais fácil de raciocinar.
Princípio Aberto-Fechado ¶
Esta é a ideia de que o código deve ser aberto para extensão, ou seja, fácil de estender sem alterar, e fechado para modificação, ou seja, você pode confiar no que ele faz para não precisar mexer nele.
Este era um conselho sábio em uma época em que o código era:
-
Caro de mudar: Tente fazer uma pequena mudança e então compilar e vincular alguns milhões de linhas de C++ na década de 1990. Eu vou esperar.
-
Arriscado de mudar, porque ainda não tínhamos descoberto a refatoração, não importa
refatorar IDEs
(fora do Smalltalk) ou tdd. -
Muito complicado: você escreveria algum código, faria check-in (se estivesse usando um sistema de controle de versão como RCS ou SCCS) e depois passaria para o próximo arquivo. Você estava traduzindo a especificação funcional detalhada em código, um pedaço de cada vez. Renomear coisas era incomum; renomear arquivos duplamente. O CVS, que se tornou o sistema de controle de origem onipresente, literalmente esqueceria todo o histórico de um arquivo se você o renomeasse, era uma atividade tão incomum. Isso é fácil de ignorar em uma era de refatoração automatizada e controle de versão baseado em mudanças.
Hoje em dia, o conselho equivalente se você precisar de código para fazer outra coisa é: Altere o código para que ele faça outra coisa! Parece banal, mas pensamos em código como maleável agora como argila, onde nos tempos antigos a metáfora era mais como blocos de construção. Não houve loop de feedback entre a especificação e o código como temos com exemplos automatizados.
Nesse caso, protestei contra o "Cruft Accretion Principle". O código não é um "ativo" a ser cuidadosamente embrulhado e preservado, mas um custo, uma dívida. Todo código é um custo. Então, se eu puder pegar uma grande pilha de custo existente e substituí-lo por um custo menor e mais específico, então estou ganhando no código! Escreva um código simples que seja fácil de alterar e você terá um código aberto e fechado, da maneira que precisar.
Princípio da Substituição de Liskov ¶
Este é apenas o Princípio da Menor Surpresa aplicado à substituição de código e, como tal, é bastante sensato. Se eu lhe disser que algo é um subtipo válido da coisa que você tem, então você deve ser capaz de assumir que agirá da mesma forma em qualquer sentido que você se importe.
No entanto, a linguagem que o LSP usa de “subtipos”, juntamente com a maneira como a maioria dos desenvolvedores confunde subtipos com subclasses, e os caprichos de “propriedades desejáveis”, significa que ele tende a evocar a linguagem de herança baseada em classes de “ é-a ” e “ tem-a ”, e sua modelagem de entidade correspondente aos anos 1980.
No espírito de usar uma faca de manteiga como chave de fenda, muitos objetos podem “agir-como-um” ou “às vezes-ser-usado-como-um” ou "passar-como-um-se-você-estrábico”. Nesse contexto, o que realmente queremos são tipos pequenos e simples que podemos compor em qualquer estrutura mais complexa que precisarmos, e fazer as pazes com todas as nuances que acompanham isso. Meu conselho, Quelle Surprise(que surpresa), é “escrever código simples” que seja fácil de raciocinar.
Princípio de Segregação de Interface ¶
Isso realmente é um peixe em um barril como princípio. Por alguma razão, este causou mais controvérsia, mas para mim é o mais fácil de desmascarar. Ao pesquisar esta palestra, descobri que esse padrão surgiu quando Robert C. Martin estava discutindo um objeto de Deus no meio de algum software de impressão na Xerox. Tudo estava acontecendo em uma classe chamada Job. Sua abordagem para simplificá-la foi encontrar cada lugar onde ela era usada, descobrir quais métodos “se encaixavam” e colocá-los em uma interface intermediária. Isso teve vários benefícios imediatos:
- A coleta de métodos relacionados em diferentes interfaces mostrou todas as diferentes responsabilidades que a classe Job estava desempenhando.
*Dar a cada interface um nome revelador de intenção tornou o código mais fácil de raciocinar do que apenas passar um objeto Job.
*Ele criou a opção de dividir a classe Job em classes menores lideradas por cada interface. (Indiscutivelmente, eles não precisavam mais da interface agora.)
Tudo isso faz sentido, só que não é um princípio, é um padrão. Um princípio é algo que geralmente é um bom conselho em qualquer contexto: procure primeiro entender, depois ser entendido; Seja excelente um com o outro.
Um padrão é uma estratégia que funciona em um determinado contexto (classe Deus) que tem benefícios (componentes menores) e compensações (coisas mais separadas para gerenciar). O princípio seria não entrar nessa confusão em primeiro lugar!
Assim, argumentei que, se isso fosse um princípio, seria o “Princípio da Porta Estável”. Se você tivesse classes pequenas, baseadas em papéis, em primeiro lugar, você não estaria na posição de tentar decompor uma bagunça enorme e emaranhada.
Claro, podemos nos encontrar nesse contexto de tempos em tempos, e quando o fazemos, a segregação de interface é uma estratégia perfeitamente congruente para abrir caminho em direção à sanidade, juntamente com a construção de um conjunto de testes de caracterização e todos os outros conselhos de Mike Feathers em Trabalhando Efetivamente com Código Legado.
Princípio de Inversão de Dependência ¶
Embora não haja nada fundamentalmente errado com o DIP, não acho exagero dizer que nossa obsessão com a inversão de dependências causou bilhões de dólares em custos irrecuperáveis e desperdícios nas últimas duas décadas.
O verdadeiro princípio aqui é a inversão de opções. Uma dependência só é interessante quando pode haver várias maneiras de fornecê-la, e você só precisa inverter o relacionamento quando acredita que a conexão é importante o suficiente para se tornar uma preocupação separada. Essa é uma barra bastante alta e, principalmente, tudo o que você precisa é de um método main.
Se, em vez disso, você concordar com a ideia de que todas as dependências devem ser invertidas o tempo todo, você acabará com J2EE, OSGi, Spring ou qualquer outra estrutura de “assembléia declarativa” onde a estruturação dos componentes é em si um labirinto sinuoso de configuração. O J2EE merece uma menção especial por decidir que cada tipo de inversão de dependência - EJBs, servlets, domínios da web, local de serviço remoto, até mesmo a configuração de configuração - deve ser de propriedade de diferentes funções.
Na natureza, existem bases de código sombra inteiras onde cada classe é apoiada por exatamente uma interface, que existe apenas para satisfazer uma estrutura de fiação ou para injetar uma simulação ou stub para o teatro de testes automatizado. A promessa de “você pode simplesmente trocar o banco de dados” evapora assim que você tenta, bem, trocar o banco de dados.
A maioria das dependências não precisa ser invertida, porque a maioria das dependências não são opções, elas são apenas a maneira que vamos fazer desta vez. Portanto, minha sugestão - até agora nada surpreendente - é escrever um código simples, concentrando-se no uso em vez de reutilização.
“Se você não gosta deles, eu tenho outros” ¶
Quando olho para o SOLID, vejo uma mistura de coisas que antes eram bons conselhos, padrões que se aplicam a um contexto e conselhos que são fáceis de aplicar incorretamente. Eu não ofereceria nada disso como conselho livre de contexto para novos programadores. Então, o que eu faria em vez disso? Eu pensei que poderia haver uma correspondência de um para um para cada um dos princípios e padrões do SOLID, já que não há nada inerentemente ruim ou errado com nenhum deles, mas como diz o ditado, If I were going to Dublin, I wouldn’t start from here.”
Então, dado o que aprendi sobre desenvolvimento de software nos últimos 30 anos, há algum princípio que eu ofereceria? E eles poderiam formar uma sigla concisa? A resposta é sim, e vou descrevê-los no próximo artigo.
Artigo original:https://dannorth.net/2021/03/16/cupid-the-back-story/
Link da tradução feita por mim
https://dev.to/urielsouza29/cupid-o-anti-solid-parte-1-4o8i
Outros textos:
https://www.tabnews.com.br/uriel/cupid-para-codificacao-alegre-parte-2
https://www.tabnews.com.br/uriel/cupid-para-codificacao-alegre-parte-3