Duplicação de código sem abstração óbvia

14

Você já encontrou um caso de duplicação de código em que, ao examinar as linhas de código, não era possível encaixar uma abstração temática que descreve fielmente seu papel na lógica? E o que você fez para resolver isso?

É duplicação de código, então, idealmente, precisamos fazer alguma refratoria, como, por exemplo, tornar sua própria função. Mas como o código não tem uma boa abstração para descrevê-lo, o resultado seria uma função estranha para a qual não conseguimos nem descobrir um bom nome, e cujo papel na lógica não é óbvio apenas olhando para ele. Isso, para mim, prejudica a clareza do código. Podemos preservar a clareza e deixá-la como está, mas prejudicamos a manutenção.

Qual você acha que é a melhor maneira de abordar algo assim?

EpsilonVector
fonte

Respostas:

18

Às vezes, a duplicação de código é o resultado de um "trocadilho": duas coisas parecem iguais, mas não são.

É possível que o excesso de abstração possa quebrar a verdadeira modularidade do seu sistema. Sob o regime da modularidade, você precisa decidir "o que provavelmente mudará?" e "o que é estável?". Tudo o que é estável é colocado na interface, enquanto o que é instável é encapsulado na implementação do módulo. Então, quando as coisas mudam, a alteração que você precisa fazer é isolada para esse módulo.

A refatoração é necessária quando o que você achou estável (por exemplo, essa chamada da API sempre leva dois argumentos) precisa mudar.

Então, para esses dois fragmentos de código duplicados, eu perguntaria: uma alteração necessária para um significa necessariamente que o outro também deve ser alterado?

Como você responde a essa pergunta pode fornecer uma melhor visão sobre o que pode ser uma boa abstração.

Os padrões de design também são ferramentas úteis. Talvez seu código duplicado esteja percorrendo alguma forma e o padrão do iterador deva ser aplicado.

Se o seu código duplicado tiver vários valores de retorno (e é por isso que você não pode executar um método simples de extração), talvez você deva criar uma classe que contenha os valores retornados. A classe poderia chamar um método abstrato para cada ponto que varia entre os dois fragmentos de código. Você faria duas implementações concretas da classe: uma para cada fragmento. [Este é efetivamente o padrão de design do Método de Modelo, que não deve ser confundido com o conceito de modelos em C ++. Como alternativa, o que você está vendo pode ser melhor resolvido com o padrão Estratégia.]

Outra maneira natural e útil de pensar sobre isso é com funções de ordem superior. Por exemplo, criando lambdas ou usando classes internas anônimas para que o código passe para a abstração. Geralmente, você pode remover a duplicação, mas, a menos que exista realmente uma relação entre elas [se uma mudar, a outra deve mudar], você poderá estar prejudicando a modularidade, sem ajudá-la.

Macneil
fonte
4

Quando você encontra uma situação como essa, é melhor pensar em abstrações "não tradicionais". Talvez você tenha muita duplicação em uma função e fatorar uma função antiga simples não se encaixa muito bem porque você precisa passar muitas variáveis. Aqui, uma função aninhada no estilo D / Python (com acesso ao escopo externo) funcionaria muito bem. (Sim, você pode criar uma classe para manter todo esse estado, mas se você a estiver usando apenas em duas funções, esta é uma solução feia e detalhada para não ter funções aninhadas.) Talvez a herança não se encaixe bem, mas uma mixin iria funcionar bem. Talvez o que você realmente precise seja uma macro. Talvez você deva considerar alguma metaprogramação ou reflexão / introspecção de modelos ou mesmo programação generativa.

Obviamente, de um ponto de vista pragmático, tudo isso é difícil, se não impossível, se a sua linguagem não os suportar e não tiver recursos de metaprogramação suficientes para implementá-los de maneira limpa dentro da linguagem. Se for esse o caso, não sei o que dizer, exceto "obtenha uma linguagem melhor". Além disso, aprender uma linguagem de alto nível com muitos recursos de abstração (como Ruby, Python, Lisp ou D) pode ajudar a programar melhor em linguagens de nível inferior, onde algumas das técnicas ainda podem ser utilizáveis, mas menos óbvias.

dsimcha
fonte
+1 para muitas técnicas excelentes compactadas em um espaço apertado. (Bem, teria sido uma das técnicas descritas também.)
Macneil
3

Pessoalmente, eu o ignoro e segui em frente. As chances são de que, se for um caso estranho, é melhor duplicá-lo, você pode passar anos refatorando e o próximo desenvolvedor dará uma olhada e desfará sua alteração!

Toby
fonte
2

Sem um exemplo de código, é difícil dizer por que seu código não possui abstração prontamente identificável. Com essa ressalva, aqui estão algumas idéias:

  • em vez de criar uma nova função para armazenar o código comum, divida a funcionalidade em várias partes distintas;
  • agrupe pequenos pedaços com base em tipos de dados comuns ou comportamento abstrato;
  • reescreva o código duplicado, considerando as novas peças;
  • se o novo código ainda desafia uma abstração clara, divida-o em pequeno também e repita o processo.

A maior dificuldade deste exercício é que sua função provavelmente está incorporando muitos comportamentos não relacionados em um determinado nível de abstração e você precisa lidar com alguns deles em níveis mais baixos. Você supõe corretamente que a clareza é essencial para manter o código, mas tornar o comportamento do código claro (sua condição atual) é muito diferente de tornar clara a intenção do código.

Torne abstrato o como das partes de código menores, fazendo com que suas assinaturas de função identifiquem o quê, e as partes maiores devem ser mais fáceis de classificar.

Huperniketes
fonte