Introdução de código por DRY e OOD


14

Estou procurando orientação sobre o acoplamento DRY x código. Não gosto de duplicar meu código e também não gosto de acoplamento de código entre módulos não relacionados. Portanto, refatoro o código duplicado se encontrar o código duplicado identicamente um ano após a introdução da duplicação. No entanto, tenho experimentado situações cada vez mais em que o mundo real é muito mais imprevisível e, depois de refatorar o código, surgem situações que exigem a criação do código novamente.

Por exemplo, se eu tivesse um código para lidar com carros a gasolina, SUVs a gasolina, carros elétricos e SUVs elétricos, digamos que eu refatorasse o código duplicado na hierarquia "gasolina" e na hierarquia "elétrica", ambas descendentes da hierarquia "veículo". Por enquanto, tudo bem. E então, minha empresa introduz um carro híbrido e um semi híbrido, que exigiriam alterações essenciais na minha hierarquia original. Talvez fosse necessário "composição" entre a hierarquia da gasolina e a elétrica.

Claramente, a duplicação de código é ruim porque aumenta o tempo necessário para implementar uma alteração comum a todos os produtos acima. Mas refatorar o código comum torna igualmente difícil a introdução de variações específicas do produto e leva a um grande "salto de classe" quando é preciso encontrar a linha de código para corrigir um erro - uma alteração em uma classe pai de nível superior poderia disparar erros de regressão entre todos os descendentes.

Como encontrar um equilíbrio ideal entre o acoplamento de código DRY e o indesejado?


1
É sempre bom não seguir as regras cegamente, mas envolver o cérebro primeiro.
precisa saber é o seguinte

justo o suficiente - portanto, diretrizes, não regras.
precisa saber é o seguinte

Você leu a página do Wiki em DRY (ou OAOO, como costumava ser chamada)? wiki.c2.com/?OnceAndOnlyOnce Foi onde muita discussão inicial aconteceu. (Na verdade, Ward inventou a Wiki especificamente para discussões sobre padrões e práticas.)
Jörg W Mittag

Respostas muito relacionada, mesmo que a questão não chega a soar como uma duplicata: softwareengineering.stackexchange.com/questions/300043/...
Hulk

Respostas:


8

digamos que refatorei o código duplicado na hierarquia "gasolina" e na hierarquia "elétrica", ambas descendentes da hierarquia "veículo". Por enquanto, tudo bem.

E então, minha empresa introduz um carro híbrido e um semi híbrido, que exigiriam alterações essenciais na minha hierarquia original. Talvez fosse necessário "composição" entre a hierarquia da gasolina e a elétrica

Eu acho que essa é uma das principais razões pelas quais as pessoas estão adotando a composição em detrimento da herança.

A herança o leva a uma grande reestruturação quando você tem uma mudança conceitual como a que você descreve.

Quando a reestruturação é "muito grande / difícil", as pessoas escrevem código duplicado para evitá-lo.

Em vez de remotamente a duplicata movendo o código para a cadeia de herança, você pode movê-lo para uma classe ou serviço auxiliar e, em seguida, injetar essa classe como parte de uma composição, quando necessário.

Se o design resultante é OOP está aberto a debate


1
+1 por apontar que herança é o problema, não SECO. A maneira mais fácil de reutilizar o código é remover dependências desnecessárias, e muitas vezes as pessoas sentem falta do fato de que uma classe pai (e seus descendentes) são todas dependências ao usar a herança. Somente quando você reduzir ou eliminar as dependências que você realmente obter o código reutilizável.
Greg Burghardt

3
" Se o design resultante é OOP está aberto a debate ". Tudo está aberto ao debate. A pergunta a fazer é: ela está aberta a um debate racional e sensível? A resposta é não. Penso que o seu último parágrafo prejudica uma resposta muito boa.
David Arno

@Davidarno realmente não segui-lo, eu li OOD no título como Design Orientado a Objetos? Então estou abordando a questão sem arrasto através do debate
Ewan

1
@Ewan: estou aqui com David Arno. Eu acho que é bem conhecido há mais de duas décadas que a crença "se não há herança, então não é OOP" é uma falácia.
Doc Brown

caramba, esse é exatamente o tipo de discussão que eu estava tentando evitar. há pontos de vista de ambos os lados, não existe uma resposta definitiva
Ewan

3

Você está correto, seguindo o princípio DRY, pode aumentar o acoplamento entre módulos não relacionados. Especialmente em sistemas de software maiores, isso pode levar a situações nas quais não seguir o DRY pode ser a melhor alternativa.

Infelizmente, seu exemplo não é adequado para demonstrar isso - os problemas descritos lá são causados ​​por erros clássicos no uso incorreto de herança abaixo do ideal. No entanto, pelo que escrevi acima, não importa se você refatorar o código comum para uma classe base comum ou para a classe auxiliar (composição). Em ambos os casos, pode-se ser forçado a colocar o código comum em uma biblioteca L e fazer referência a essa biblioteca a partir de dois programas A e B.

Vamos supor que A e B não estivessem completamente relacionados antes, eles podem ser versionados, liberados e implantados independentemente. Ao colocar código comum em uma lib L compartilhada, no entanto, novos requisitos para A podem induzir alterações em L, que agora podem causar alterações em B. Portanto, isso causa a necessidade de testes adicionais e, provavelmente, um novo ciclo de liberação e implantação para B.

Então, como você pode lidar com essa situação, se não estiver disposto a abandonar o princípio DRY? Bem, existem algumas táticas conhecidas para abordar isso:

  1. Mantenha A, B e L como parte do mesmo produto, com um número de versão comum, um processo comum de compilação, liberação e implantação, com um alto grau de automação

  2. ou faça de L um produto por conta própria, com números de versão menores (sem alterações incompatíveis) e números de versão principais (talvez contendo alterações recentes) e permita que A e B permitam que cada um faça referência a uma linha de versão diferente de L.

  3. Torne L o mais sólido possível e cuide da compatibilidade com as versões anteriores. Quanto mais módulos em L puderem ser reutilizados sem modificação (OCP), menos mudanças de quebra ocorrerão. E os outros princípios do "SOLID" estão ajudando a apoiar esse objetivo.

  4. Use testes automáticos especialmente para L, mas também para A e B.

  5. Cuidado com o que você coloca na L. A lógica comercial, que deveria existir apenas em um local do sistema, é um bom candidato. Coisas que apenas "parecem iguais" e podem variar de maneira diferente no futuro são ruins candidatos.

Observe que quando A e B são desenvolvidos, mantidos e evoluídos por equipes diferentes e não relacionadas, o princípio DRY se torna bem menos importante - o DRY é sobre manutenção e evolutibilidade, mas permitir que duas equipes diferentes forneçam esforços de manutenção individuais às vezes pode ser mais eficaz do que amarrar seus produtos juntos por causa de um pouco de reutilização.

Então, no final, é uma troca. Se você deseja seguir o princípio DRY em sistemas maiores, precisa investir muito mais esforço na criação de componentes robustos e reutilizáveis ​​- geralmente mais do que o esperado. Você precisa confiar em seu julgamento quando vale a pena e quando não.


1

O DRY é mais uma regra estrutural. Isso significa que, se você leva ao extremo, é tão ruim quanto se você o ignora. Aqui está uma metáfora para tirar você da mentalidade estrutural.

Ame seus gêmeos. Mate os clones.

Quando você copia e cola cegamente para evitar digitação no teclado, está criando clones sem pensar. Claro que eles fazem o que você quer, mas eles o pesam, porque agora mudar esse comportamento é muito caro.

Quando você reproduz um comportamento idêntico com um código idêntico por causa de uma responsabilidade diferente, passa a ter a mesma necessidade agora, mas que pode sujeitar a alterações diferentes, você tem um irmão gêmeo que deve estar livre para mudar e crescer como indivíduo à medida que o tempo passa.

Você pode escanear o DNA (código) da maneira que quiser. Clones e gêmeos são difíceis de distinguir se tudo que você faz é olhar para eles. O que você precisa é entender o contexto em que eles vivem. Por que eles nasceram. Qual será o destino final deles?

Digamos que você trabalhe para a empresa ABC. É administrado por três executivos da empresa, A, B e C. Todos querem que você faça um produto de software, mas cada um tem seu próprio departamento. Todos querem que você comece pequeno. Basta enviar uma mensagem simples por enquanto. Então você escreve "Olá Mundo".

A odeia, quer que você coloque o nome da empresa lá.

B adora, quer que você o deixe em paz e adicione o nome da empresa a uma tela inicial.

C só quer uma calculadora e achou que a mensagem era apenas uma maneira de você começar.

Uma coisa é certa: esses caras têm ideias muito diferentes sobre o que é esse projeto. Às vezes eles vão concordar. Às vezes, eles o puxam em direções diferentes.

Quando você duplica o código porque está criando a capacidade desse código variar independentemente, está criando um irmão gêmeo. Quando você duplica o código porque a digitação é um problema e copiar e colar é fácil, você cria clones malignos.

A, B e C são mestres diferentes que seu código deve veicular. Nenhuma linha de código pode atender a mais de um mestre por muito tempo.


Antes de tudo, muito obrigado a todos, por todos os comentários.
user2549686
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.