Duplicação de código ilusório

56

O instinto usual é remover qualquer duplicação de código que você vê no código. No entanto, eu me encontrei em uma situação em que a duplicação é ilusória .

Para descrever a situação com mais detalhes: Estou desenvolvendo um aplicativo Web e a maioria das visualizações é basicamente a mesma - elas exibem uma lista de itens pelos quais o usuário pode rolar e escolher, uma segunda lista que contém itens selecionados e um "Salvar "para salvar a nova lista.

Pareceu-me que o problema é fácil. No entanto, todas as visualizações têm suas próprias peculiaridades - às vezes você precisa recalcular algo, às vezes é necessário armazenar alguns dados adicionais etc. Isso eu resolvi inserindo ganchos de retorno de chamada no código lógico principal.

Existem tantas diferenças minuciosas entre as visualizações que estão se tornando cada vez menos sustentáveis, porque eu preciso fornecer retornos de chamada para basicamente todas as funcionalidades, e a lógica principal começa a parecer uma enorme sequência de chamadas de retorno de chamada. No final, não estou economizando tempo ou código, porque cada exibição tem seu próprio código que é executado - tudo em retornos de chamada.

Os problemas são:

  • as diferenças são tão pequenas que o código se parece quase exatamente em todas as visualizações,
  • existem tantas diferenças que, quando você olha para os detalhes, codificar não é nem um pouco parecido

Como devo lidar com essa situação?
Ter uma lógica principal composta inteiramente de chamadas de retorno de chamada é uma boa solução?
Ou devo duplicar o código e diminuir a complexidade do código baseado em retorno de chamada?

Mael
fonte
38
Normalmente, acho útil deixar a duplicação inicialmente. Depois de ter alguns exemplos, é muito mais fácil ver o que é comum e o que não é e criar uma maneira de compartilhar as partes comuns.
Winston Ewert
7
Muito boa pergunta - uma coisa a considerar não é apenas a duplicação física do código, mas a duplicação semântica. Se uma alteração em uma parte do código significa necessariamente que a mesma alteração seria duplicada nas demais, essa parte provavelmente é candidata à refatoração ou abstração. Às vezes você pode normalizar até o ponto em que realmente se prende, então eu também consideraria as implicações práticas para tratar a duplicação como semanticamente distintas - elas podem ser superadas pelas consequências de tentar desduplicar.
Ant
Lembre-se, você só pode reutilizar o código que faz a mesma coisa. Se seu aplicativo fizer coisas diferentes em telas diferentes, será necessário retornos de chamada diferentes. Sem ifs, ands, ou buts sobre isso.
corsiKa
13
Minha regra pessoal é: se eu fizer uma alteração no código em um único local, será um erro se não fizer exatamente a mesma alteração em qualquer outro lugar? Nesse caso, é o tipo ruim de duplicação. Se não tiver certeza, escolha o que for mais legível por enquanto. No seu exemplo, as diferenças de comportamento são intencionais e não são consideradas erros; portanto, algumas duplicações são boas.
Ixrec 16/10/2015
Você pode estar interessado em ler sobre Programação Orientada a Aspectos.
21715 Ben Jackson

Respostas:

53

Em última análise, você deve fazer um julgamento sobre se deve combinar código semelhante para eliminar a duplicação.

Parece haver uma tendência infeliz de adotar princípios como "Não se repita" como regras que devem ser seguidas rotineiramente. De fato, essas não são regras universais, mas diretrizes que devem ajudá-lo a pensar e desenvolver um bom design.

Como tudo na vida, você deve considerar os benefícios versus os custos. Quanto código duplicado será removido? Quantas vezes o código é repetido? Quanto esforço será necessário para escrever um design mais genérico? Quanto você provavelmente desenvolverá o código no futuro? E assim por diante.

Sem conhecer seu código específico, isso não está claro. Talvez haja uma maneira mais elegante de remover a duplicação (como a sugerida por LindaJeanne). Ou, talvez simplesmente não haja repetição verdadeira o suficiente para justificar a abstração.

A atenção insuficiente ao design é uma armadilha, mas também tenha cuidado com o excesso de design.


fonte
Seu comentário sobre "tendências infelizes" e seguindo cegamente as orientações está no local, eu acho.
Mael
11
@Mael Você está dizendo que, se não mantiver esse código no futuro, não terá boas razões para obter o design certo? (sem ofensa, só quero saber o que você pensa sobre isso)
Spotted
2
@Mael É claro que poderíamos considerar apenas uma infeliz mudança de frase! : D No entanto, acho que devemos ser tão rigorosos conosco quanto somos com os outros ao escrever código (eu me considero um outro quando leio meu próprio código duas semanas depois de escrevê-lo).
Visto
2
@ user61852, você não gostaria muito de The Codeless Code .
precisa
11
@ user61852, haha - mas o que se faz todos dependem (em informações não dado na questão)? Poucas coisas são menos úteis que o excesso de certeza.
43

Lembre-se que DRY é sobre conhecimento . Não importa se dois pedaços de código parecem semelhantes, idênticos ou totalmente diferentes, o que importa é se o mesmo conhecimento sobre seu sistema pode ser encontrado nos dois.

Um conhecimento pode ser um fato ("o desvio máximo permitido do valor pretendido é de 0,1%") ou pode ser algum aspecto do seu processo ("essa fila nunca contém mais de três itens"). É essencialmente qualquer informação codificada no seu código-fonte.

Portanto, ao decidir se algo é duplicação que deve ser removida, pergunte se é duplicação de conhecimento. Caso contrário, é provavelmente uma duplicação acidental, e a extração para algum lugar comum causará problemas quando mais tarde você desejar criar um componente semelhante em que a parte aparentemente duplicada seja diferente.

Ben Aaronson
fonte
12
Este! O foco do DRY é evitar alterações duplicadas .
Matthieu M.
Isso é bastante útil.
11
Eu pensei que o foco do DRY era garantir que não haja dois bits de código que devam se comportar de maneira idêntica, mas não. O problema não é o trabalho duplicado porque as alterações de código precisam ser aplicadas duas vezes, o problema real é quando uma alteração de código precisa ser aplicada duas vezes, mas não é.
gnasher729
3
@ gnasher729 Sim, esse é o ponto. Se dois trechos de código tiverem duplicação de conhecimento , você esperaria que, quando um precisasse mudar, o outro também precisasse mudar, levando ao problema que você descreve. Se eles tiverem duplicação acidental , quando um precisar mudar, o outro poderá precisar permanecer o mesmo. Nesse caso, se você extraiu um método comum (ou qualquer outro), agora você tem um problema diferente para lidar com
Ben Aaronson
11
Também duplicação essencial e duplicação acidental , consulte Um Doppelgänger acidental em Ruby e eu editei meu código e agora é difícil trabalhar com ele. O que aconteceu? . Duplicidades acidentais também ocorrem nos dois lados de um limite de contexto . Resumo: mesclar duplicatas somente se fizer sentido para seus clientes que essas dependências sejam modificadas simultaneamente .
Eric
27

Você já pensou em usar um padrão de estratégia ? Você teria uma classe View que contém o código e as rotinas comuns chamados por várias visualizações. Filhos da classe View conteriam o código específico para essas instâncias. Todos eles usariam a interface comum que você criou para o View e, portanto, as diferenças seriam encapsuladas e coerentes.

LindaJeanne
fonte
5
Não, eu não considerei isso. Obrigado pela sugestão. De uma rápida leitura sobre o padrão de estratégia, parece algo que estou procurando. Definitivamente vou investigar mais.
Mael
3
existe um padrão de método de modelo . Você também pode considerar isso
Shakil
5

Qual é o potencial de mudança? Por exemplo, nosso aplicativo possui 8 áreas de negócios diferentes, com um potencial de 4 ou mais tipos de usuários para cada área. As visualizações são personalizadas com base no tipo de usuário e na área.

Inicialmente, isso foi feito usando a mesma exibição com algumas verificações aqui e ali para determinar se coisas diferentes devem aparecer. Com o tempo, algumas áreas de negócios decidiram fazer coisas drasticamente diferentes. No final, basicamente migramos para uma visualização (visualizações parciais, no caso do ASP.NET MVC) por parte da funcionalidade por área de negócios. Nem todas as áreas de negócios têm a mesma funcionalidade, mas se alguém deseja a funcionalidade de outra, essa área terá sua própria visão. É muito menos complicado para a compreensão do código, bem como para a testabilidade. Por exemplo, fazer uma alteração em uma área não causará uma alteração indesejada em outra área.

Como o @ dan1111 mencionou, pode ser uma decisão judicial. Com o tempo, você pode descobrir se funciona ou não.

ps2goat
fonte
2

Um problema pode ser o fato de você estar fornecendo uma interface (interface teórica, não recurso de idioma) para apenas um único nível da funcionalidade:

A(a,b,c) //a,b,c are your callbacks or other dependencies

Em vez de vários níveis, dependendo de quanto controle é necessário:

//high level
A(a,b,c)
//lower
A myA(a,b)
B(myA,c)
//even lower
A myA(a)
B myB(myA,b)
C myC(myB,c)
//all the way down to you just having to write the code yourself

Até onde eu entendi, você apenas expõe a interface de alto nível (A), ocultando os detalhes da implementação (as outras coisas lá).

Ocultar os detalhes da implementação tem vantagens e você acabou de encontrar uma desvantagem - o controle é limitado, a menos que você inclua explicitamente recursos para cada coisa que seria possível ao usar diretamente as interfaces de baixo nível.

Então, você tem duas opções. Você usa apenas a interface de baixo nível, usa a interface de baixo nível porque a interface de alto nível foi muito trabalhosa para manter ou expõe as interfaces de alto e baixo nível. A única opção sensata é oferecer interfaces de alto e baixo nível (e tudo o que há entre elas), supondo que você queira evitar código redundante.

Então, ao escrever outra coisa, você analisa todas as funcionalidades disponíveis até agora (inúmeras possibilidades, você decide quais podem ser reutilizadas) e as reúne.

Use um único objeto onde você precise de pouco controle.

Use a funcionalidade de nível mais baixo quando alguma estranheza precisar acontecer.

Também não é muito preto e branco. Talvez sua grande classe de alto nível possa razoavelmente cobrir todos os casos de uso possíveis. Talvez os casos de uso sejam tão variados que apenas a funcionalidade primitiva de nível mais baixo seja suficiente. Cabe a você encontrar o equilíbrio.

Waterlimon
fonte
1

Já existem outras respostas úteis. Vou adicionar o meu.

Duplicação é ruim porque

  1. confunde o código
  2. confunde a nossa compreensão do código, mas o mais importante
  3. porque se você mudar algo aqui e também precisar mudar algo , poderá esquecer / introduzir bugs / .... e é difícil nunca esquecer.

Portanto, o ponto é: você não está eliminando a duplicação por causa disso ou porque alguém disse que é importante. Você está fazendo isso porque deseja reduzir bugs / problemas. No seu caso, parece que se você alterar algo em uma exibição, provavelmente não precisará alterar a mesma linha exata em todas as outras exibições. Então você tem duplicação aparente , não duplicação real.

Outro ponto importante é nunca reescrever do zero algo que está funcionando agora apenas com base em uma questão de princípio, como Joel disse (você já deve ter ouvido falar dele ....). Portanto, se suas opiniões estiverem funcionando, continue melhorando passo a passo e não seja vítima do "pior erro estratégico que qualquer empresa de software pode cometer".

Francesco
fonte