È accettabile copiare e incollare codice lungo ma semplice invece di racchiuderlo in una classe o funzione?


29

Supponiamo che io abbia un segmento di codice per connettermi a Internet e mostrare i risultati della connessione in questo modo:

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)
        }
}

il codice è lungo e viene comunemente utilizzato, ma il codice sopra riportato non richiede elementi aggiuntivi come la funzione e la classe personalizzate (HttpRequest e Alert sono entrambi forniti dal framework per impostazione predefinita) e sebbene il segmento di codice sia lungo, è semplice e non complesso (è lungo solo perché ci sono fasci di impostazioni come url, dimensione del carattere ...) e il segmento di codice presenta piccole variazioni tra le classi (ad esempio: url, dati di richiesta, casi di handle di codice di errore, handle normale casi ...)

La mia domanda è: è accettabile copiare e incollare codice lungo ma semplice invece di racchiuderli in una funzione per ridurre la dipendenza del codice?


89
Immagina di avere un bug in quel codice, come non liberare oggetti che assegni. (Il framework libera gli Alertoggetti?) Ora immagina di dover trovare ogni istanza copiata di questo codice per correggere il bug. Ora immagina che non sei tu che devi farlo, ma un pazzo assassino di ascia che sa che sei stato tu a creare tutte queste copie in primo luogo.
Sebastian Redl,

8
E a proposito, mescolare la rete e la visualizzazione degli errori in un unico posto è già un grande no-no, IMHO.
sleske,

11
No. Mai. Totalmente inaccettabile. Se fossi nel mio progetto, non saresti più nel mio progetto e faresti parte di un programma di formazione o PIP.
nhgrif,

10
E arriva il supervisore che dice "Questa casella di avviso al centro del mio schermo è un terrore assoluto. Sto guardando gif di gatti e il pop-up mi sta bloccando la vista ogni volta che si presenta. Per favore spostalo in alto a destra ". 3 settimane dopo "Che diamine hai fatto ?! Non riesco più a chiudere le mie gif di gatto perché il TUO pop-up copre la X in alto a destra, correggilo."
MonkeyZeus,

11
Penso che tutti qui sembrano pensare che questa sia una cattiva idea. Ma per invertire la domanda, perché NON inserire questo codice in una classe o funzione separata?
Karl Gjertsen,

Risposte:


87

Devi considerare il costo del cambiamento. Cosa succede se si desidera modificare la modalità di connessione? Quanto sarebbe facile? Se hai un sacco di codice duplicato, trovare tutti i luoghi che devono essere cambiati potrebbe richiedere molto tempo ed essere soggetto ad errori.

Devi anche considerare la chiarezza. Molto probabilmente, dover guardare 30 righe di codice non sarà così facile da capire come una singola chiamata a una funzione "connectToInternet". Quanto tempo andrà perso nel tentativo di comprendere il codice quando è necessario aggiungere nuove funzionalità?

Ci sono alcuni rari casi in cui la duplicazione non è un problema. Ad esempio, se stai facendo un esperimento e il codice verrà gettato via alla fine della giornata. Ma in generale, il costo della duplicazione supera i piccoli risparmi di tempo di non dover estrarre il codice in una funzione separata .

Vedi anche https://softwareengineering.stackexchange.com/a/103235/63172


19
... e chissà se queste 30 linee sono davvero le stesse di quelle che hai visto in precedenza o se qualcuno è passato a un'altra porta o indirizzo IP nella sua copia per qualsiasi motivo. L'assunto implicito che " qualsiasi gruppo di circa 30 righe che iniziano con la stessa HttpRequestsono tutte uguali " è un errore facile da realizzare.
null,

@null Ottimo punto. Una volta ho lavorato su un codice in cui c'erano collegamenti con la connessione al database copiati e incollati dappertutto, ma alcuni presentavano sottili differenze nell'installazione. Non avevo idea se questi fossero cambiamenti importanti, intenzionali o solo differenze casuali
user949300,

54

No.

In effetti, anche il tuo codice "semplice" dovrebbe essere suddiviso in parti più piccole. Almeno due.

Uno per effettuare la connessione e gestire la normale risposta 200. Ad esempio, cosa succede se si passa da un POST a un PUT in alcuni casi? Cosa succede se si stanno realizzando miliardi di connessioni e si necessita di multi-threading o pool di connessioni? Avere il codice in un unico posto, con un argomento per il metodo, renderà tutto molto più semplice

Allo stesso modo, un altro per gestire gli errori. Ad esempio, se si modifica il colore o la dimensione del carattere dell'avviso. Oppure stai riscontrando problemi con le connessioni intermittenti e desideri registrare gli errori.



Potresti anche citare SRP: avere un blocco di codice che ha solo uno scopo rende molto più facile capire e mantenere ..
Roland Tepp,

Questo è un caso in cui DRY e SRP si allineano effettivamente. A volte no.
user949300

18

è accettabile copiare e incollare ...

No.

Per me, l'argomento decisivo è questo:

... è comunemente usato ...

Se usi un pezzo di codice in più di un posto, allora, quando cambia, devi cambiarlo in più di un posto o inizi a ottenere incongruenze - iniziano a verificarsi "cose ​​strane" (cioè introduci i bug).

è semplice e non complesso ...

E così dovrebbe essere ancora più facile fare il refactoring in una funzione.

... ci sono fasci di impostazioni come url, dimensione del carattere ...

E cosa amano cambiare gli utenti ? Caratteri, dimensioni dei caratteri, colori, ecc. Ecc.

Adesso; in quanti posti dovrai cambiare lo stesso codice per riportarli tutti dello stesso colore / carattere / dimensione? (Risposta consigliata: solo una ).

... il segmento di codice presenta piccole variazioni tra le classi (ad es. url, dati di richiesta, casi di handle di codici di errore, casi di handle normali ...)

Variazione => parametro / i di funzione.


"E cosa piace cambiare agli utenti? Caratteri, dimensioni dei caratteri, colori, ecc." Questo è un affare. Vuoi davvero cambiarlo in dozzine di posizioni?
Dan,

8

Questo non ha davvero nulla a che fare con copia e incolla. Se prendi il codice da altrove, al secondo momento prendi il codice è il tuo codice e la tua responsabilità, quindi se è copiato o scritto completamente da solo non fa differenza.

Nei tuoi avvisi prendi alcune decisioni di progettazione. Molto probabilmente decisioni di progettazione simili dovrebbero essere prese per tutti gli avvisi. Quindi è probabile che dovresti avere un metodo da qualche parte "ShowAlertInAStyleSuitableForMyApplication" o forse un po 'più corto, e che dovrebbe essere chiamato.

Avrai molte richieste http con una gestione degli errori simile. Probabilmente non dovresti duplicare la gestione degli errori ancora e ancora e ancora, ma estrarre la gestione degli errori comuni. Soprattutto se la gestione degli errori diventa un po 'più elaborata (per quanto riguarda gli errori di timeout, 401 e così via).


6

La duplicazione è OK in alcune circostanze. Ma non in questo. Questo metodo è troppo complesso. Esiste un limite inferiore, quando la duplicazione è più semplice della "fattorizzazione" di un metodo.

Per esempio:

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

è stupido, basta fare un + b.

Ma quando diventi un po ', un po' più complesso, di solito sei molto oltre il limite.

foo.a + foo.b

dovrebbe diventare

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

Nel tuo caso, vedo quattro "metodi". Probabilmente in classi diverse. Uno per effettuare la richiesta, uno per ottenere la risposta, uno per visualizzare gli errori e un tipo di richiamata da richiamare dopo che la risposta ritorna per elaborare la risposta. Personalmente aggiungerei probabilmente un "wrapper" in aggiunta a quello per facilitare le chiamate.

Alla fine, per effettuare una richiesta Web, vorrei che una chiamata fosse simile a:

Web.Post(URI, Params, ResponseHandler);

Quella riga è ciò che avrei su tutto il mio codice. Quindi, quando avevo bisogno di apportare modifiche a "come ottengo le cose", potevo farlo rapidamente, con molto meno sforzo.

Ciò mantiene anche il codice DRY e aiuta con SRP .


0

In un progetto di qualsiasi dimensione / complessità, voglio essere in grado di trovare il codice quando ne ho bisogno per i seguenti scopi:

  1. Risolvilo quando è rotto
  2. Cambia la funzionalità
  3. Riutilizzalo.

Non sarebbe bello aderire a un progetto in corso o continuare a lavorare su un progetto per diversi anni e quando una nuova richiesta per "connettersi a Internet e mostrare i risultati della connessione" era in una posizione facile da trovare a causa di un buon design invece di fare affidamento su una ricerca nel codice per una richiesta http? Probabilmente è comunque più facile da trovare con Google.

Non ti preoccupare, sono il nuovo ragazzo e rifatterò questo blocco di codice perché sono così sconvolto per unirmi a questa squadra all'oscuro con la base di codice orribile o ora sono molto sotto pressione come il resto di voi e lo copierete e incollerete. Almeno mi toglierà il capo dalla schiena. Quindi, quando il progetto verrà identificato come un disastro, sarò il primo a raccomandare di riscriverlo copiando e incollando la versione nell'ultimo e più grande framework che nessuno di noi capisce.


0

Una classe e / o una funzione è migliore, almeno secondo me. Per una volta, rende il file più piccolo, il che è un guadagno molto pesante se si tratta di applicazioni Web o applicazioni per dispositivi con poca memoria (IoT, telefoni più vecchi, ecc.)

E ovviamente il punto migliore è che se hai qualcosa da cambiare a causa di nuovi protocolli, ecc. Devi solo cambiare il contenuto della funzione e non innumerevoli volte metti questa funzione da qualche parte che potrebbe anche essere in file diversi rendendoli ancora più difficili da trova e cambia.

Ho scritto un intero interprete SQL in modo da poter passare meglio da MySQL a MySQLi in PHP, perché devo solo cambiare il mio interprete e tutto funziona, anche se questo è un esempio un po 'estremo.


-1

Per decidere se un codice deve essere duplicato o spostato in una funzione chiamata due volte, provare a determinare quale è più probabile:

  1. Sarà necessario modificare entrambi gli usi del codice allo stesso modo.

  2. Sarà necessario modificare almeno un uso del codice in modo che differiscano.

Nel primo caso, sarà probabilmente meglio avere una funzione per gestire entrambi gli usi; in quest'ultimo caso, sarà probabilmente meglio avere un codice separato per i due usi.

Nel decidere se un pezzo di codice che verrà usato una volta debba essere scritto in linea o estratto in un'altra funzione, capire come si descriverebbe completamente il comportamento richiesto della funzione. Se una descrizione completa e accurata del comportamento richiesto della funzione sarebbe lunga o più lunga del codice stesso, spostare il codice in una funzione separata potrebbe rendere le cose più difficili da comprendere piuttosto che più facili. Potrebbe comunque valere la pena farlo se esiste un'alta probabilità che un secondo chiamante debba utilizzare la stessa funzione e qualsiasi modifica futura della funzione avrà effetto su entrambi i chiamanti, ma in assenza di tale considerazione, la leggibilità favorirebbe la divisione delle cose al livello in cui il codice e una descrizione del comportamento richiesto sarebbero ugualmente lunghi.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.