Muito simples, por que eu gostaria de escrever código que funcione para todos os casos e dados escalonáveis quando tudo o que preciso fazer é repetir o mesmo processo algumas vezes com alguns pequenos ajustes?
É improvável que precise editá-lo novamente em breve.
Parece muito menos trabalho para apenas ir ...
function doStuff1(){/*.a.*/}
function doStuff2(){/*.b.*/}
function doStuff3(){/*.c.*/}
E se eu precisar adicionar algo ...
function doStuff4(){/*.d.*/}
E se eu precisar removê-lo, eu o removo.
É mais difícil descobrir como transformar tudo isso em um padrão direto, no qual eu possa apenas alimentar dados e lidar com todos os casos, e fazer um monte de mudanças que eu não sinto que vou ter. façam.
Por que ficar SECO quando parece que um corte + colar rápido será muito menos trabalhoso?
code-quality
dry
Incógnito
fonte
fonte
Respostas:
Se você se repetir, poderá criar problemas de manutenção. Se todos os doStuff1-3 tiverem código estruturado da mesma forma e você resolver um problema em um, você poderá facilmente esquecer de corrigi-lo em outros lugares. Além disso, se você precisar adicionar um novo caso para lidar, basta passar parâmetros diferentes para uma função em vez de copiar e colar em todo o lugar.
No entanto, o DRY é frequentemente levado ao extremo por programadores inteligentes. Às vezes, para não se repetir, é necessário criar abstrações tão obtusas que seus colegas de equipe não possam segui-las. Às vezes, a estrutura de duas coisas é apenas vagamente semelhante, mas diferente o suficiente. Se doStuff1-4 for diferente o suficiente, de modo que refatorá-los para não se repetir faz com que você precise escrever um código não natural ou sofrer backflips de codificação inteligentes que farão com que sua equipe olhe para você, então pode ser bom repetir-se. Eu me inclinei para trás para não me repetir algumas vezes de maneiras não naturais e me arrependi do produto final.
Eu sempre erro do lado do DRY, no raro caso de me repetir quando penso que os benefícios na legibilidade valem os riscos de alguém esquecer de corrigir um bug em vários lugares.
Levando em consideração esse conselho, parece que no seu caso
Eu definitivamente trabalharia duro para não me repetir no seu caso. Assumindo "ajustes" mínimos - eles podem ser manipulados com parâmetros diferentes que afetam o comportamento ou talvez injetados na dependência para executar subtarefas diferentes.
Últimas palavras famosas. Você vai se arrepender de pensar que, quando um engenheiro júnior ajusta / corrige / refatora, um faz coisas e nem percebe que os outros existem. Hilaridade segue. Não ocorre principalmente azia. Cada linha de código custa mais. Quantos caminhos de código você deve testar com tantas funções repetidas? Se uma função, você apenas precisa testar um caminho principal com algumas modificações comportamentais. Se você copiar e colar, deverá testar todos os doStuff separadamente. As probabilidades são de que você perderá uma e um cliente poderá ter um bug indesejável e alguns emails indesejados em sua caixa de entrada.
fonte
Porque DRY será menos trabalhos depois.
SECA: (Não se repita)
Uma função recebendo um argumento.
C&P: (copiar e colar)
26 gazilhões de funções fazem essencialmente a mesma coisa, mas com uma diferença de 2 caracteres.
Que tal atualizar nossa impressão para especificar exatamente o que é impressão?
SECO:
Feito.
C&P:
Você precisa voltar e alterar todas as funções .
Qual você acha que seria mais fácil depurar?
fonte
Porque , aplicado ao seu exemplo:
+ legibilidade
Menos código geralmente se traduz em menos ruído . (nem sempre...)
+ flexibilidade
Se você já teve que mudar o comportamento do
doStuffX
, você vai querer se matar ou quem o escreveu,+ extensibilidade
Se você extraiu as partes distintas para uma estrutura de dados de sua escolha e depois iterou sobre ela chamando um genérico
doStuff
, você também pode adicionar uma linha em sua estrutura de dados na qual deseja uma nova entrada ou remover uma e mudar o comportamento significa apenas ediçãodoStuff
. Mais fácil de manter .+ eficiência de custos
menos código aqui significa:
+ (possível) otimização gerenciada
Dependendo do idioma, o compilador / intérprete pode ter uma chance maior de determinar que o genérico
doStuff
sempre faz as coisas quase idênticas, muitas vezes uma chamada após a outra, e pode incorporá- lo ou tentar otimizá- lo. Provavelmente não faria X variações dedoStuffX
.+ teste e qualidade
O teste é mais fácil:
doStuff
precisa de teste, e é isso. Bem, não exatamente, mas isso já cobre mais . Somente suas expectativas de IO variam e precisam ser testadas sob condições diferentes, mas ainda é muito mais fácil de testar e mais sustentável do que todas as variações dedoStuffX
.No geral, isso representa um código mais sustentável e uma eficiência de desenvolvimento aprimorada para sua equipe, e é uma das muitas boas práticas para ajudá-lo a produzir software mais robusto e confiável.
fonte
Como todo mundo fez um ótimo trabalho ao explicar os problemas de manutenção com código duplicado, vou dizer o seguinte:
Grande parte da programação exige que você pense no futuro, não apenas no presente imediato. Você está certo de que copiar e colar é mais fácil agora, mas é improvável que a declaração precise editá-la novamente em breve " mostra que você não está pensando corretamente. Sim, você pode comprar um pouco de tempo com um copiar / colar rápido e sujo, mas, ao fazer isso, você mostra que não pode olhar além do seu problema imediato e pensar no amanhã.Tem certeza de que nunca precisará revisitar esse código? Você pode 100% garantir que não precisará revisitá-lo quando seu próximo conjunto de recursos precisar ser implementado? Esses são problemas para amanhã e precisam ser considerados ao projetar hoje.
Obviamente, há momentos em que copiar / colar será necessário. Como desenvolvedor de interface do usuário, descobri que há momentos em que tenho que violar o princípio DRY. É péssimo, estremeço toda vez que acontece e, felizmente, é raro. Mas não acontecer.
A diferença é que, ao violar o DRY, você deve ter um motivo muito convincente para fazê-lo, e a afirmação: É mais difícil descobrir como transformar todos esses elementos em um padrão direto não é realmente um deles. A menos que você esteja sofrendo muito tempo e faça seu chefe gritar para conseguir algo nas próximas horas ou perder seu emprego, não acho que essa seja uma lógica válida.
Não tome isso da maneira errada: não estou tentando castigá-lo ou castigá-lo, mas tente fazê-lo ver onde sua mentalidade está errada. Programadores investem em preguiça futura; DRY é uma maneira de conseguir isso. O trabalho que você faz hoje para resolver um problema de projeto difícil será recompensado amanhã.
fonte
Se esse for realmente o caso, você poderá se safar, mas com mais freqüência estará trabalhando no código que precisa ser mantido. Isso significa estender a funcionalidade, corrigir bugs e outras melhorias. Se você tiver pequenas variações do mesmo código em 10 locais diferentes, e um dia você voltar ao código e precisar fazer uma alteração, agora você tem a tarefa propensa a erros de fazer a mesma alteração em 10 locais diferentes (desculpe, não eram 11 lugares, você esqueceu um e agora tem um bug).
Se você puder generalizar o problema que está tentando resolver, poderá tornar seu código mais fácil de estender e corrigir, se surgirem erros.
fonte
Como afirmei na resposta a outra pergunta, minha abordagem é a seguinte:
Ou seja, até 2, outro princípio (YAGNI) vence o DRY. Mas a partir de 3 (ou 4, se eu sou realmente preguiçoso!), Parece que vou precisar e, por isso, sigo DRY.
Atualizar
Algumas idéias adicionais da minha experiência recente. Eu tive que adaptar / integrar dois componentes A e B desenvolvidos por outra equipe em nosso produto. Primeiro: os dois componentes A e B são muito semelhantes entre si, então eu já estava perturbado pelo fato de eles terem uma arquitetura um pouco diferente. Segundo: tive que adaptá-los para ter prazer em usar subclasses e apenas substituir o que realmente precisava.
Então comecei a refatorar esses dois componentes (cada um dos quais consiste em cerca de 8 classes C ++): eu queria ter uma arquitetura comum para A e B e, em seguida, adicionar os recursos necessários ao definir subclasses. Dessa maneira, nossos dois novos componentes A 'e B' seriam derivados dos existentes.
Depois de duas semanas tentando obter uma estrutura comum e bem definida do código existente e tendo que explicar durante nossas reuniões diárias que eu estava fazendo pouco progresso porque o código original era muito confuso, conversei com meu chefe. Observamos que não precisaríamos de mais do que esses dois novos componentes A 'e B' (não haveria quatro ou seis deles, apenas esses dois).
Ok, seja assim: fiz uma cópia maciça e renomeei as classes de A e B e comecei a adaptar a cópia do código. Consegui que funcionasse em mais duas semanas (ainda estou corrigindo alguns erros agora).
Vantagens: Temos a funcionalidade quase concluída agora e, quando corrigimos todos os bugs, terminamos. Economizamos toda a refatoração e teste de A e B.
Desvantagens: Duas semanas atrás, a outra equipe mudou outro componente C, usado por A e B. Eles adaptaram A e B, mas A 'e B' também foram quebrados e tivemos que alterá-los nós mesmos. Isso introduziu um novo bug que tivemos que corrigir. Esse trabalho extra provavelmente seria desnecessário se A 'e B' tivessem compartilhado a maior parte de seu código com A e B.
Portanto: a duplicação de código é sempre perigosa. Penso que é sempre uma questão de encontrar compromissos e, muitas vezes, não é fácil.
fonte
Apenas para esclarecer, pois não encontro isso em nenhuma das outras respostas:
O princípio DRY mencionado por Andy Hunt e Dave Thomas não se limita a impedir a duplicação de código. Ele também defende a geração de código e qualquer processo de automação. Ironicamente, os resultados da geração de código podem até ser código duplicado ...
A razão pela qual já foi explicada minuciosamente nas outras respostas, mas o comentário de Falcon resume bem o suficiente IMHO:
fonte
Existe algo como muito seco. Quando isso acontece, dois conceitos que parecem em algum momento semelhantes o suficiente para garantir o código de fatoração (1) podem vir a ser diferentes o suficiente para merecer implementações separadas.
Em outras palavras, o acoplamento SECO e solto às vezes entra em conflito. Se você espera que o doStuff1 e seus amigos diverjam a cada nova versão do software, não há problema em duplicar o código.
Na minha experiência, pode ser difícil julgar para onde o seu software está indo no futuro e, por esse motivo, o DRY geralmente é uma escolha segura.
Código que foi excessivamente "seco" geralmente possui fluxo de controle complexo e muitos parâmetros. O que inicialmente era uma função simples foi posteriormente estendido para suportar uma nova funcionalidade controlada por um parâmetro extra. Após duas ou três iterações, a função não é mais mantida. Corrija um erro que ocorre em uma configuração e você introduz novos erros em outras configurações.
É compreensível que a qualidade do código geralmente diminua à medida que o código evolui, mas vi casos em que uma função multiparâmetros com espaguete if-then-else no corpo era o resultado de um esforço de refatoração bem-intencionado, mas mal conduzido.
(1) Estou usando a palavra "código", mas isso também se aplica ao design.
fonte
Eu tenho que mencionar os problemas com o DRY no mundo do banco de dados relacional. Os bancos de dados são projetados para um desempenho rápido e bom, usando lógica baseada em conjunto e através de consultas sargable. Os princípios DRY geralmente fazem com que o desenvolvedor grave consultas não-suscetíveis de Sargable ou use a lógica Row-by-agonizing-Row para aproveitar o código existente em várias situações. A DRY e a otimização do desempenho geralmente estão em desacordo e, no mundo dos bancos de dados, o desempenho geralmente é muito mais crítico do que a capacidade de manutenção. Isso não significa que você não deva usar os princípios DRY, apenas esteja ciente de como isso afetará a usabilidade geral do banco de dados. Os desenvolvedores de aplicativos consideram DRY primeiro e o desempenho segundo, os desenvolvedores de banco de dados acham a integridade dos dados primeiro, o desempenho segundo, a segurança dos dados em terceiro (desempenho e segurança podem trocar de lugar em alguns sistemas).
Percebi, em geral, que quanto mais camadas de abstração você coloca no banco de dados, mais lenta elas se tornam. Não estou dizendo que não desejo que as pessoas que projetam os programas da base de dados não trabalhem melhor ao permitir que os desenvolvedores usem DRY sem afetar o desempenho do banco de dados, mas não projeto software de banco de dados nesse nível , então talvez o conflito entre abstração e desempenho no banco de dados seja mais difícil de corrigir do que eu suponho. No entanto, temos que trabalhar com os sistemas como eles são construídos atualmente. Podemos pedir uma melhor implementação dos princípios DRY em versões futuras que também não prejudicam o desempenho (e que melhorou ao longo dos anos, mas ainda é problemático), mas, enquanto isso, devemos considerar se DRY é o movimento certo para esse banco de dados. nesse momento.
Mas muitas vezes os mesmos recursos que você deseja usar para garantir o cumprimento do princípio DRY são os que causam enormes problemas ao banco de dados. Não estou dizendo que nunca use DRY, mas não exagere.
Exemplos do que estou falando. Você precisa importar dados de um milhão de registros uma vez por mês. Os registros já podem ser adicionados manualmente através da interface do usuário chamando um processo armazenado. Este processo, por ter sido projetado para importações de registro único, adiciona apenas um registro por vez. Usando DRY para evitar o código de inserção em dois lugares, você escreve um cursor para chamar o proc repetidamente, em vez de gravar as importações baseadas em conjuntos necessárias. O tempo para a importação vai dos 30 minutos necessários para usar a lógica baseada em conjunto a 18 horas. Agora, a maneira correta de aderir ao DRY nesse caso seria corrigir o processo para lidar com várias importações de registros. Infelizmente, muitas vezes é impossível ou muito difícil enviar uma matriz para um proc (dependendo do back end do banco de dados) e, alterando o proc, você acaba quebrando o aplicativo.
Funções escalares e funções com valor de tabela também são usadas para implementar os princípios DRY e, novamente, elas podem afetar seriamente o desempenho, especialmente se você precisar usá-las de maneira a impedir que os índices sejam úteis.
As visualizações também são boas para a implementação de DRY. No entanto, se você implementar o DRY através do uso de visualizações que chamam visualizações que chamam outras visualizações, chegará rapidamente ao ponto em que as consultas atingirão o tempo limite sob carga. Na verdade, você pode precisar gerar conjuntos de dados de milhões de registros quando precisar apenas de três no final. Portanto, uma visão de um nível de um conjunto complexo de junções para implementar DRY pode ser excelente (eu mesmo tenho uma que usamos para garantir que todos os relatórios financeiros usem o mesmo conjunto básico de tabelas e cálculos de certas coisas), mais de dois níveis e você precisa considerar se está criando uma bagunça no desempenho.
fonte
Não vejo os pontos principais da minha resposta acima, então aqui vai. Não olhe para DRY tanto como uma regra contrafazendo algo. Pode ser redigido assim, mas pode realmente servir a um propósito bem diferente e positivo. É um sinal para parar, pensar e encontrar uma resposta melhor. Me desafia a procurar oportunidades para projetar uma solução melhor. É o lado bom de um mau cheiro no meu código que me leva a repensar meu design e me faz fazê-lo muito melhor. DRY não se trata apenas de uma violação de sintaxe muito pequena. Isso me desafia a modularizar. Desafia-me a componente. Isso indica repetição que me lembra de pensar em usar modelos e geração de código em vez de força bruta e ignorância. Isso me ajuda a descobrir que devo encontrar algum tempo para automatizar minha automação. Isso leva você a um estilo de vida parcimonioso! Ele ajuda você a gastar mais do seu tempo fazendo coisas novas e mais legais, em vez de detalhes chatos e antigos. E oferece boas maneiras, bom hálito e um estilo de vida saudável! Bem, talvez eu me perca um pouco ...
fonte
Eu tenho um projeto antigo, em que alguns dos ex-desenvolvedores não se preocupavam com o DRY. Portanto, toda a base de código estava cheia de métodos auxiliares, como GetSystemTimeAsString (), LogToFile () e muitas outras coisas. Alguns métodos foram levemente personalizados para necessidades especiais, mas a maioria era apenas copiar e colar.
Infelizmente, alguns dos métodos tinham bugs sutis, como char array, não suficientemente longos em alguns casos, usando coisas inseguras como strcpy (), etc.
Portanto, foi uma verdadeira PITA encontrar todos os fragmentos de código, harmonizá-los e corrigir os erros. E ainda estamos harmonizando e consertando coisas.
Você nunca sabe, se cometeu um erro no seu primeiro método e depois precisa corrigi-lo várias vezes, porque acabou de copiá-lo. E se você quiser usar alguns dos métodos posteriormente, como você sabe, qual dos 5 métodos na base de código é o mais adequado para o seu caso agora? Então você apenas copia um, personaliza e aqui começa de novo ...
fonte
Sim, não se preocupe com DRY se você estiver escrevendo o Throwaway Code .
Mas o DRY é importante, é claro, se você planeja manter o código.
fonte
A fraseologia "não se repita" é um pouco simplista demais. O importante é "evitar ter uma informação potencialmente mutável encapsulada em dois locais independentes ".
Se um programa deve processar widgets, cada um com três woozles e muitos loops do formulário
então, a expectativa de que os widgets devam conter três woozles seria encapsulada em cada um desses loops, e seria difícil atualizar o código para acomodar qualquer outro número de woozles por widget. Por outro lado, se alguém dissesse
e cada loop foi reescrito
esse design pode facilitar a alteração do número de woozles por widget.
É importante observar, no entanto, que, embora seja desejável consolidar informações como o número de woozles por widget em um único ponto, nem sempre é prático. Às vezes, pode ser necessário codificar a lógica, que só funcionará se as coisas tiverem um tamanho específico. Por exemplo, se cada woozle tem um valor e se deseja encontrar a mediana associada a um widget específico, pode ser possível classificar os valores e usar o do meio, e essa abordagem funcionaria com qualquer número de woozles, mas com lógica escrito à mão especificamente para encontrar a mediana de três itens, pode ser significativamente mais rápido.
Embora ter uma constante WOOZLES_PER_WIDGET possa tornar o código mais legível, deve-se comentar para deixar claro que seu valor não pode ser alterado sem outros ajustes na lógica do programa. Nesse caso, a lógica codificada para três itens e a constante WOOZLES_PER_WIDGET duplicariam as informações "cada widget possui três woozles", mas os benefícios dessa duplicação (maior velocidade de execução) poderiam compensar o custo.
fonte
Embora eu realmente concorde com os outros pôsteres, os comentários sobre a manutenção etc. são válidos.
Gostaria de acrescentar uma pequena voz dissidente ao debate.
fonte
<tl;dr>
Como não consegui ler todas as respostas repetidas, posso ter perdido alguma coisa (e a repetirei eu mesma <= ver o que fiz aqui?).
Aqui está a lista de coisas impressionantes sobre como impedir a duplicação de código!
doFoo1(a, b)
, há uma chance maior de que muitas de suas falhas e casos irritantes sejam descobertos e resolvidos. Se todos copiam o código e criamdoFoo2(specialA)
...doFuu2^n(a, b, c)
eles duplicaram os problemasdoFoo1
e criaram muito mais trabalho.</tl;dr>
Versão longa:
O problema da duplicação de código é que ela "cresce exponencialmente" (em outras palavras, se expande rapidamente) porque, ao duplicar o código, você concede, sem saber, permissão a outros (por exemplo, você não está mais em condições de julgá-los) e você os encoraja a fazer o mesmo. Você também torna mais difícil não fazê-lo, porque é mais difícil detectar e reutilizar códigos úteis quando há muitas repetições redundantes confusas na fonte. Especialmente se o código ainda não foi extraído para uma função adequadamente nomeada. Portanto, se você enfrentar um problema comum simples de resolver, provavelmente escreverá um pedaço de código para resolvê-lo ... E provavelmente não conseguirá verificar alguns casos extremos, adicionando mais códigos não testados com erros.
Outra coisa é que, para um iniciante, isso pode soar como um problema que afetará apenas grandes empresas, mas eu achei bastante prejudicial para pequenas startups (como em 10.000 linhas de código duplicado do lado do servidor). É um estado de espírito. Você não deve apenas dominar o DRY, mas se esforçar para incentivar outras pessoas a fazer o mesmo; porque, caso contrário, você se comprometerá a duplicar o código. Quando os meios de DRY estão à mão e são aplicados, é muito mais fácil aplicá-los. Quando há muito código duplicado, é muito mais fácil aplicar soluções de copiar e colar.
Coisas que considero prejudiciais na duplicação de código:
Últimas notas sobre prevenção de duplicação de código excessivamente zeloso e resumo:
Isso já foi dito antes, mas às vezes evitar a duplicação faz com que você "se incline para trás" e faça coisas que são muito sofisticadas (ou sem importância) para que outras pessoas possam entender. Escrever código ilegível (ou, como chamamos de brincadeira, código de "preservação de trabalho") é um problema em si mesmo, mesmo quando a prevenção de duplicação de código não está envolvida. No entanto, acho que, se a infra-estrutura e as melhores práticas corretas são instaladas desde o início, é muito mais fácil evitar a duplicação de código e as pessoas geralmente evitam fazer coisas não-intuitivas para evitar futuros montes desnecessários e ilegíveis de trabalho feitos para a prevenção de duplicação de código, se você faz as coisas desde o início.
O que está fazendo as coisas certas? Bem, essa é uma pergunta difícil de responder, mas uma coisa é definir quais métodos são necessários para o projeto e ver o que já foi implementado por outras pessoas (fora e) dentro da empresa e reutilizá-lo quando possível; documentando tudo o que você adiciona à base de código e tentando torná-lo um nível mais genérico do que deveria ser, mas é isso. Não exagere nos padrões de design apenas para tornar o código flexível onde ele não precisa estar.
fonte