Interpretação do princípio DRY

10

No momento, estou lutando com esse conceito de DRY (não se repita) na minha codificação. Estou criando essa função na qual temo que esteja se tornando muito complexo, mas estou tentando seguir o princípio DRY.

createTrajectoryFromPoint(A a,B b,C c,boolean doesSomething,boolean doesSomething2)

Eu disse que esta função usa 3 parâmetros de entrada e, em seguida, a função fará algo ligeiramente diferente, dadas as combinações booleanas doesSomethinge doesSomething2. No entanto, o problema que estou tendo é que essa função está crescendo muito em complexidade a cada novo parâmetro booleano adicionado.

Portanto, minha pergunta é: é melhor ter várias funções diferentes que compartilham muito da mesma lógica (portanto, violando o princípio DRY) ou uma função que se comporta de maneira ligeiramente diferente, considerando vários parâmetros, mas tornando-a muito mais complexa (mas preservando SECO)?

Albinoswordfish
fonte
3
A lógica compartilhada / comum pode ser fatorada em funções privadas que todas as diferentes createTrajectory...funções públicas chamam?
FrustratedWithFormsDesigner
Poderia ser, mas as essas funções privadas ainda seria necessário para obter esses parâmetros booleanos
Albinoswordfish
2
Eu acho que isso seria / será muito mais fácil de responder, dado algum tipo de exemplo concreto. Minha reação imediata é que a dicotomia que você está representando não é totalmente real - ou seja, essas não são as únicas duas opções. Como um aparte, eu consideraria qualquer uso de a booleancomo um parâmetro suspeito na melhor das hipóteses.
Jerry Coffin
Relacionado: Por que o DRY é importante?
Steven Jeuris
Por que você não está considerando as coisas condicionais em suas próprias funções?
Rig

Respostas:

19

argumentos booleanos para acionar diferentes caminhos de código em uma única função / método é um terrível cheiro de código .

O que você está fazendo viola os princípios de acoplamento solto e alta coesão e responsabilidade única , que são muito mais importantes que o DRY em precedência.

Isso significa que as coisas devem depender de outras coisas somente quando for necessário ( acoplamento ) e que devem fazer uma coisa e apenas uma coisa (muito bem) ( coesão ).

Por sua própria omissão, isso é muito acoplado (todos os sinalizadores booleanos são um tipo de dependência de estado, que é uma das piores!) E possui muitas responsabilidades individuais misturadas (excessivamente complexas).

O que você está fazendo não está no espírito de DRY de qualquer maneira. DRY é mais sobre repetição (o que Rsignifica REPEAT). Evitar copiar e colar é sua forma mais básica. O que você está fazendo não está relacionado à repetição.

Seu problema é que a decomposição do seu código não está no nível correto. Se você acha que terá código duplicado, essa deve ser sua própria função / método parametrizado apropriado, não copiar e colar, e os outros devem ser nomeados descritivamente e delegados à função / método principal.

Comunidade
fonte
Ok, parece ser do jeito que eu sou escrita não é muito bom (a minha suspeita inicial) Eu não sou realmente certo o que esse 'Espírito de DRY' é, porém
Albinoswordfish
4

O fato de você estar passando booleanos para fazer a função fazer coisas diferentes é uma violação do Princípio da Responsabilidade Única. Uma função deve fazer uma coisa. Deve fazer apenas uma coisa e deve fazê-lo bem.

Parece que você precisa dividi-lo em várias funções menores com nomes descritivos, separando os caminhos de código correspondentes aos valores desses booleanos.

Depois de fazer isso, você deve procurar por código comum nas funções resultantes e fatorá-lo em suas próprias funções. Dependendo de quão complexa é essa coisa, você pode até considerar uma classe ou duas.

Obviamente, isso pressupõe que você esteja usando um sistema de controle de versão e que tenha um bom conjunto de testes, para poder refatorar sem medo de quebrar alguma coisa.

Dima
fonte
3

Por que você não cria outra função contendo toda a lógica de sua função antes de decidir fazer algo ou algo2 e, em seguida, possui três funções como:

createTrajectoryFromPoint(A a,B b,C c){...}

dosomething(A a, B b, C c){...}

dosomething2(A a, B b, C c){...}

E agora, passando três tipos de parâmetros iguais para três funções diferentes, você se repetirá novamente, portanto, você deve definir uma estrutura ou classe contendo A, B, C.

Como alternativa, você pode criar uma classe contendo os parâmetros A, B, C e uma lista de operações a serem realizadas. Adicione quais operações (algo, algo2) você deseja que aconteça com esses parâmetros (A, B, C) registrando operações com o objeto. Então, tenha um método para chamar todas as operações registradas no seu objeto.

public class MyComplexType
{
    public A a{get;set;}
    public B b{get;set;}
    public C c{get;set;}

    public delegate void Operation(A a, B b, C c);
    public List<Operation> Operations{get;set;}

    public MyComplexType(A a, B b, C c)
    {
        this.a = a;
        this.b = b;
        this.c = c   
        Operations = new List<Operation>();
    }

    public CallMyOperations()
    {
        foreach(var operation in Operations)
        {
            operation(a,b,c);
        }
    }
}
Mert Akcakaya
fonte
Para explicar as possíveis combinações de valores para os booleanos dosomething e dosomething2, você precisaria de 4 funções, não 2, além da função createTrajectoryFromPoint de base. Essa abordagem não se adapta bem à medida que o número de opções aumenta e até mesmo nomear as funções se torna tedioso.
JGWeissman 17/05
2

O DRY pode ser levado longe demais, é melhor usar o princípio de responsabilidade única (SRP) em conjunto com o DRY. Adicionar sinalizadores bool a uma função para fazer com que versões ligeiramente diferentes do mesmo código possam ser um sinal de que você está fazendo muito com uma função. Nesse caso, eu sugeriria a criação de uma função separada para cada caso que seus sinalizadores representem; quando você escrever cada função, será bastante aparente se houver uma seção comum que possa ser movida para uma função privada sem passar todos os sinalizadores. , se não houver uma seção aparente do código, você realmente não está se repetindo, você tem vários casos diferentes, mas semelhantes.

Ryathal
fonte
1

Geralmente, passo por várias etapas com esse problema, parando quando não consigo descobrir como ir além.

Primeiro, faça o que você fez. Vá duro com o DRY. Se você não acabar com uma grande bagunça peluda, estará pronto. Se, como no seu caso, você não tiver um código duplicado, mas cada valor booleano tiver seu valor verificado em 20 locais diferentes, vá para a próxima etapa.

Segundo, divida o código em blocos. Os booleanos são referenciados apenas uma vez (bem, talvez duas vezes) para direcionar a execução para o bloco correto. Com dois booleanos, você acaba com quatro blocos. Cada bloco é quase idêntico. DRY se foi. Não faça de cada bloco um método separado. Isso seria mais elegante, mas colocar todo o código em um método torna mais fácil, ou até possível, para qualquer pessoa que faz manutenção, ver que precisa fazer cada alteração em quatro locais. Com código bem organizado e um monitor alto, as diferenças e os erros serão quase óbvios. Agora você tem código de manutenção e ele será executado mais rapidamente do que a bagunça emaranhada original.

Terceiro, tente pegar linhas de código duplicadas de cada bloco e transformá-las em métodos simples e agradáveis. Às vezes você não pode fazer nada. Às vezes você não pode fazer muito. Mas tudo que você faz leva você de volta ao DRY e torna o código um pouco mais fácil de seguir e mais seguro de manter. Idealmente, seu método original pode acabar sem código duplicado. Nesse ponto, você pode dividi-lo em vários métodos sem os parâmetros booleanos ou não. A conveniência do código de chamada é agora a principal preocupação.

Eu adicionei minha resposta ao grande número já aqui por causa do segundo passo. Eu odeio código duplicado, mas se é a única maneira inteligível de resolver um problema, faça-o de forma que alguém saiba rapidamente o que você está fazendo. Use vários blocos e apenas um método. Torne os blocos o mais idênticos possíveis em nomes, espaçamento, alinhamentos, ... tudo. As diferenças devem saltar para o leitor. Pode tornar óbvio como reescrevê-lo de uma maneira SECA e, se não, mantê-lo será razoavelmente simples.

RalphChapin
fonte
0

Uma abordagem alternativa é substituir os parâmetros booleanos pelos parâmetros da interface, pelo código para manipular os diferentes valores booleanos refatorados nas implementações da interface. Então você teria

createTrajectoryFromPoint(A a,B b,C c,IX x,IY y)

onde você tem implementações de IX e IY que representam os diferentes valores para os booleanos. No corpo da função, onde quer que você tenha

if (doesSomething)
{
     ...
}
else
{
     ...
}

você substitui-o por uma chamada para um método definido no IX, pelas implementações que contêm os blocos de código omitidos.

JGWeissman
fonte