É aceitável copiar e colar códigos longos, porém simples, em vez de agrupá-los em uma classe ou função?

29

Suponha que eu tenha um segmento de código para conectar-se à Internet e mostrar os resultados da conexão como ele:

HttpRequest* httpRequest=new HttpRequest();
httpRequest->setUrl("(some domain .com)");
httpRequest->setRequestType(HttpRequest::Type::POST);
httpRequest->setRequestData("(something like name=?&age=30&...)");
httpRequest->setResponseCallback([=](HttpClient* client, HttpResponse* response){
    string responseString=response->getResponseDataString();
        if(response->getErrorCode()!=200){
            if(response->getErrorCode()==404){
                Alert* alert=new Alert();
                alert->setFontSize(30);
                alert->setFontColor(255,255,255);
                alert->setPosition(Screen.MIDDLE);
                alert->show("Connection Error","Not Found");
            }else if((some other different cases)){
                (some other alert)
            }else
                Alert* alert=new Alert();
                alert->setFontSize(30);
                alert->setPosition(Screen.MIDDLE);
                alert->setFontColor(255,255,255);
                alert->show("Connection Error","unknown error");
            }
        }else{
            (other handle methods depend on different URL)
        }
}

o código é longo e é comumente usado, mas o código acima não exige itens extras, como função e classe personalizadas (HttpRequest e Alert são fornecidos por estrutura por padrão) e, embora o segmento de código seja longo, é simples e não complexo (é longo apenas porque existem pacotes de configurações como url, tamanho da fonte ...) e o segmento de código tem pequenas variações entre as classes (por exemplo: url, dados de solicitação, casos de manipulação de código de erro, manipulação normal casos ...)

Minha pergunta é: é aceitável copiar e colar códigos longos, mas diretos, em vez de agrupá-los em uma função para reduzir a dependência do código?

ggrr
fonte
89
Imagine que você tenha um bug nesse código, como não liberar objetos que você aloca. (Sua estrutura libera os Alertobjetos?) Agora imagine que você precisa encontrar todas as instâncias copiadas desse código para corrigir o erro. Agora imagine que não é você quem deve fazer isso, mas um assassino maluco que sabe que você foi quem criou todas essas cópias em primeiro lugar.
Sebastian Redl
8
E, BTW, misturar rede e exibição de erros em um só lugar já é um grande não-não, IMHO.
sleske
11
Não nunca. Inaceitável. Se você estivesse no meu projeto, não estaria mais no meu projeto e estaria em um programa de treinamento ou PIP.
Nhgrif
10
E o supervisor diz: "Esta caixa de alerta no meio da tela é um terror absoluto. Estou assistindo a jifs de gatos e o pop-up está bloqueando minha visão toda vez que aparece. Mova-o para o canto superior direito . " 3 semanas depois "O que diabos você fez ?! Eu não consigo mais fechar meus brincos de gato porque o SEU pop-up está cobrindo o X no canto superior direito, conserte-o."
precisa saber é o seguinte
11
Acho que todos aqui parecem pensar que é uma má ideia. Mas, para reverter a questão, por que você NÃO colocaria esse código em uma classe ou função separada?
Karl Gjertsen

Respostas:

87

Você precisa considerar o custo da mudança. E se você quisesse mudar como as conexões são feitas? Quão fácil seria? Se você tiver muito código duplicado, encontrar todos os locais que precisam ser alterados pode ser bastante demorado e propenso a erros.

Você também precisa considerar a clareza. Provavelmente, ter que analisar 30 linhas de código não será tão fácil de entender como uma única chamada para uma função "connectToInternet". Quanto tempo será perdido tentando entender o código quando novas funcionalidades precisarem ser adicionadas?

Existem certos casos raros em que a duplicação não é um problema. Por exemplo, se você estiver fazendo um experimento e o código será descartado no final do dia. Mas, em geral, o custo da duplicação supera a pequena economia de tempo em não precisar extrair o código para uma função separada .

Consulte também https://softwareengineering.stackexchange.com/a/103235/63172

Vaughn Cato
fonte
19
... e quem sabe se essas 30 linhas são realmente as mesmas que você examinou anteriormente ou se alguém mudou para uma porta ou endereço IP diferente em sua cópia, por qualquer motivo. A suposição implícita de que " qualquer grupo de cerca de 30 linhas começando com o HttpRequestsão todos iguais " é uma falácia fácil de se fazer.
null
Excelente ponto. Certa vez, trabalhei no código em que havia conexões de banco de dados copiadas e coladas por toda parte, mas algumas tinham diferenças sutis na configuração. Eu não tinha idéia se estes eram importantes, mudanças deliberadas ou diferenças apenas aleatório
user949300
54

Não.

De fato, mesmo seu código "simples" deve ser dividido em partes menores. Ao menos dois.

Um para fazer a conexão e lidar com a resposta 200 normal. Por exemplo, e se você mudar de um POST para um PUT em alguns casos? E se você estiver fazendo zilhões dessas conexões e precisar de algum multiencadeamento ou pool de conexões? Ter o código em um único local, com um argumento para o método, tornará isso muito mais fácil

Da mesma forma, outro para lidar com erros. Por exemplo, se você alterar a cor ou o tamanho da fonte do alerta. Ou você está tendo problemas com conexões intermitentes e deseja registrar os erros.

user949300
fonte
Você também pode citar SRP: ter um bloco de código que tem um único propósito torna muito mais fácil de entender e manter ..
Roland Tepp
Este é um caso em que DRY e SRP realmente se alinham. Às vezes não.
usar o seguinte comando
18

é aceitável copiar e colar ...

Não.

Para mim, o argumento decisivo é este:

... é comumente usado ...

Se você usa um trecho de código em mais de um local, quando ele muda, é necessário alterá- lo em mais de um local ou você começa a obter inconsistências - "coisas estranhas" começam a acontecer (ou seja, você introduz Bugs).

é simples e não é complexo ...

E, portanto, deve ser ainda mais fácil refatorar em uma função.

... existem pacotes de configurações como URL, tamanho da fonte ...

E o que os usuários gostam de mudar? Fontes, tamanhos, cores, etc., etc.

Agora; em quantos lugares você precisará alterar o mesmo trecho de código para obter a mesma cor / fonte / tamanho novamente? (Resposta recomendada: apenas uma ).

... o segmento de código tem pequenas variações entre classes (por exemplo: url, dados de solicitação, casos de tratamento de código de erro, casos de tratamento normal ...)

Variação => parâmetro (s) da função.

Phill W.
fonte
"E o que os usuários gostam de mudar? Fontes, tamanhos de fonte, cores, etc." Deseja realmente mudar isso em dezenas de locais?
Dan
8

Isso realmente não tem nada a ver com copiar e colar. Se você pegar o código de outro lugar, no segundo, o código é seu e sua responsabilidade; portanto, se ele é copiado ou gravado completamente por você, não faz diferença.

Nos seus alertas, você toma algumas decisões de design. Provavelmente, decisões de design semelhantes devem ser tomadas para todos os alertas. Portanto, é provável que você deva ter um método em algum lugar "ShowAlertInAStyleSuitableForMyApplication" ou talvez um pouco mais curto, e isso deve ser chamado.

Você terá muitas solicitações http com tratamento de erro semelhante. Você provavelmente não deve duplicar o tratamento de erros repetidamente, mas extrair o tratamento de erros comum. Especialmente se o tratamento de erros se tornar um pouco mais elaborado (e quanto aos erros de tempo limite, 401 e assim por diante).

gnasher729
fonte
6

A duplicação é boa em algumas circunstâncias. Mas não neste. Esse método é muito complexo. Há um limite inferior, quando a duplicação é mais fácil do que "fatorar" um método.

Por exemplo:

def add(a, b)
    return a + b
end

é estúpido, basta fazer a + b.

Mas quando você se torna um pouco mais complexo, geralmente fica exagerado.

foo.a + foo.b

Deve se tornar

foo.total
def foo
    ...
    def total
        return self.a + self.b
    end
end

No seu caso, vejo quatro "métodos". Provavelmente em diferentes classes. Um para fazer a solicitação, um para obter a resposta, outro para exibir erros e algum tipo de retorno de chamada para ser chamado após o retorno da resposta para processar a resposta. Pessoalmente, provavelmente adicionaria uma espécie de "invólucro" além disso, para facilitar as chamadas.

No final, para fazer uma solicitação da Web, eu gostaria que uma chamada se parecesse com:

Web.Post(URI, Params, ResponseHandler);

Essa linha é o que eu teria em todo o meu código. Então, quando eu precisava fazer alterações em "como eu recebo coisas", eu podia fazer isso rapidamente, com muito menos esforço.

Isso também mantém o código SECO e ajuda com o SRP .

coteyr
fonte
0

Em um projeto de qualquer tamanho / complexidade, desejo encontrar código quando necessário para os seguintes fins:

  1. Corrija quando estiver quebrado
  2. Alterar a funcionalidade
  3. Reutilize.

Não seria adorável ingressar em um projeto em andamento ou continuar trabalhando em um projeto por vários anos e quando uma nova solicitação para "conectar-se à Internet e mostrar os resultados da conexão" estava em um local fácil de encontrar devido a uma bom design em vez de depender de fazer uma pesquisa em todo o código para uma solicitação de ativação? Provavelmente é mais fácil encontrar com o Google.

Não se preocupe, eu sou o novo cara e refatorarei esse bloco de código porque estou muito chateado por me juntar a essa equipe sem noção com a terrível base de código ou agora estou sob muita pressão como o resto você e apenas copiará e colará. Pelo menos, manterá o chefe longe das minhas costas. Então, quando o projeto for realmente identificado como um desastre, serei o primeiro a recomendar que o reescrevamos copiando e colando a versão na melhor e mais recente estrutura que nenhum de nós entende.

JeffO
fonte
0

Uma classe e / ou uma função é melhor, pelo menos na minha opinião. Pela primeira vez, ele torna o arquivo menor, o que é um ganho muito alto se você lida com aplicativos da Web ou aplicativos para dispositivos com pouco armazenamento (IoT, telefones mais antigos etc.)

E, obviamente, o melhor ponto é que, se você tem algo para mudar devido a novos protocolos, etc., basta alterar o conteúdo da função e, inúmeras vezes, coloca essa função em algum lugar que pode estar em arquivos diferentes, dificultando ainda mais a sua execução. encontre e mude.

Eu escrevi um intérprete inteiro para poder mudar melhor do MySQL para o MySQLi em PHP, porque só preciso mudar meu intérprete e tudo está funcionando, embora esse seja um exemplo um pouco extremo.

My1
fonte
-1

Para decidir se um código deve ser duplicado ou movido para uma função chamada duas vezes, tente determinar qual é mais provável:

  1. Será necessário alterar os dois usos do código da mesma maneira.

  2. Será necessário alterar pelo menos um uso do código para que eles sejam diferentes.

No primeiro caso, provavelmente será melhor ter uma função para lidar com ambos os usos; neste último caso, provavelmente será melhor ter um código separado para os dois usos.

Ao decidir se um pedaço de código que será usado uma vez deve ser escrito em linha ou extraído para outra função, descubra como alguém descreveria completamente o comportamento necessário da função. Se uma descrição completa e precisa do comportamento necessário da função for maior ou maior que o próprio código, movê-lo para uma função separada pode tornar as coisas mais difíceis de entender do que mais fáceis. Ainda pode valer a pena fazer se houver uma alta probabilidade de que um segundo chamador precise usar a mesma função, e quaisquer alterações futuras na função precisarão afetar os dois chamadores, mas, na ausência de tal consideração, a legibilidade favoreceria a divisão das coisas. até o nível em que o código e uma descrição do comportamento necessário seriam igualmente longos.

supercat
fonte