Duplicação de código ilusório

54

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

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

Pareceu-me que o problema é fácil. No entanto, toda e qualquer visão tem suas próprias peculiaridades - às vezes você precisa recalcular algo, às vezes você deve armazenar alguns dados adicionais etc. Estes, eu resolvi inserindo ganchos de retorno de chamada no código lógico principal.

Existem tantas diferenças mínimas entre as visualizações que está se tornando menos e menos passível de manutenção, porque eu preciso fornecer callbacks para basicamente todas as funcionalidades, e a lógica principal começa a parecer uma sequência enorme de invocações de retorno de chamada. No final, não estou salvando tempo nem código, porque cada visualizaçã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 parece quase exatamente igual em todas as visualizações,
  • existem tantas diferenças que quando você olha os detalhes, o código é não um pouco parecido

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

    
por Mael 16.10.2015 / 13:15
fonte

6 respostas

52

Em última análise, você precisa fazer uma chamada de avaliação para combinar um código semelhante para eliminar a duplicação.

Parece haver uma tendência infeliz a adotar princípios como "não se repita" como regras que devem ser seguidas de rotina em todos os momentos. Na verdade, 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. Quanta código duplicado será removido? Quantas vezes o código é repetido? Quanto esforço será para escrever um projeto mais genérico? Quanto você pode desenvolver o código no futuro? E assim por diante.

Sem saber 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 suficiente para justificar a abstração.

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

    
por 16.10.2015 / 13:57
fonte
42

Lembre-se de que DRY é sobre conhecimento . Não importa se duas partes de código são semelhantes, idênticas ou totalmente diferentes, o que importa é se a mesma parte do conhecimento sobre o seu sistema puder ser encontrada em ambas.

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

Então, quando você está decidindo se algo é uma duplicação que deve ser removida, pergunte se é uma duplicação de conhecimento. Se não, provavelmente é uma duplicação acidental, e extraí-lo para algum lugar comum causará problemas quando você desejar criar um componente similar onde a parte aparentemente duplicada é diferente.

    
por 16.10.2015 / 17:50
fonte
27

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

    
por 16.10.2015 / 13:41
fonte
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 visão com algumas verificações aqui e ali para determinar se coisas diferentes deveriam aparecer. Com o tempo, algumas das áreas de negócios decidiram fazer coisas drasticamente diferentes. No final, basicamente migramos para uma visualização (visões parciais, no caso da ASP.NET MVC) por peça de 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 que a outra possui, essa área obtém sua própria visão. É muito menos complicado para a compreensão do código, bem como para testabilidade. Por exemplo, fazer uma alteração em uma área não causará uma alteração indesejada em outra área.

Como o @ dan1111 mencionou, ele pode se resumir a um julgamento. Com o tempo, você pode descobrir se funciona ou não.

    
por 17.10.2015 / 00:14
fonte
2

Um problema pode ser que você esteja 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ê só expõe a interface de alto nível (A), escondendo os detalhes da implementação (as outras coisas lá).

Ocultar os detalhes da implementação traz vantagens e você acaba encontrando uma desvantagem - o controle é limitado, a menos que você adicione 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. Ou você usa apenas a interface de baixo nível, use a interface de baixo nível porque a interface de alto nível era muito trabalhosa para manter ou expor interfaces de alto e baixo nível. A única opção sensata é oferecer interfaces de nível alto e baixo (e tudo o que estiver entre elas), supondo que você queira evitar código redundante.

Então, ao escrever outra de suas coisas, você analisa toda a funcionalidade disponível que você escreveu até agora (inúmeras possibilidades, até você para decidir quais podem ser reutilizadas) e as une.

Use um único objeto onde você precisa 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 cobrir razoavelmente todos os possíveis casos de uso. Talvez os casos de uso sejam tão variados que nada além da funcionalidade primitiva de nível mais baixo seja suficiente. Até você para encontrar o equilíbrio.

    
por 17.10.2015 / 22:44
fonte
1

Já existem outras respostas úteis. Eu adicionarei o meu.

A duplicação é ruim porque

  1. desordena o código
  2. desordena nossa compreensão do código mas mais importante
  3. porque se você mudar alguma coisa aqui e você também tem que mudar alguma coisa , você poderia esquecer / introduzir bugs / .... e é difícil nunca esquecer.

Então, 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 quer reduzir erros / problemas. No seu caso, parece que, se você alterar algo em uma visualização, provavelmente não será necessário alterar a mesma linha em todas as outras exibições. Então você tem aparente duplicação , não duplicação real.

Outro ponto importante é nunca reescrever do zero algo que está funcionando agora apenas com base em um assunto de princípio, como Joel disse (você já poderia ter ouvido falar dele ...). Portanto, se suas visualizações estiverem funcionando, avance passo a passo e não caia no "pior erro estratégico que qualquer empresa de software pode fazer".

    
por 20.10.2015 / 14:40
fonte