Ha mai senso che un refattore finisca con un LOC più alto? [chiuso]


25

Ci sono casi in cui un codice più dettagliato (come in più istruzioni logiche) è più pulito di un codice più conciso?



18
Certo che lo fa. Anche quando si elimina la duplicazione, il codice per definire la nuova funzione estratta occupa spazio a sé stante. Scrivere una nuova funzione può richiedere quattro righe e salvarne solo due, ed è comunque una cosa utile da fare.
Kilian Foth,

5
"codice più conciso"? Odio davvero la convinzione errata che un numero di righe più piccolo significhi un codice "migliore". Non In realtà, di solito è il contrario. Arriva un punto - ed è raggiunto molto velocemente - in cui stipare sempre più significato in sempre meno spazio rende il codice più difficile da capire. In effetti, c'è un'intera competizione - The International Obfuscated C Code Contest - in cui molti vincitori si affidano a quei limiti della comprensione umana per scrivere un codice impenetrabile.
Andrew Henle,

1
Il titolo della domanda e la domanda stessa stanno ponendo domande diverse. Ad esempio, un'istruzione if può essere cambiata in un'espressione ternaria che è la stessa logicamente ma solo 1 riga.
Captain Man,

1
-1 *** codice più dettagliato (come in più istruzioni logiche) *** verbosità e il numero di istruzioni logiche sono due cose non correlate. È una cattiva forma cambiare le definizioni.
Pieter B,

Risposte:


70

Per rispondere, facciamo un esempio del mondo reale che mi è successo. In C # una libreria che mantengo, avevo il seguente codice:

TResult IConsFuncMatcher<T, TResult>.Result() =>
    TryCons(_enumerator) is var simpleMatchData && !simpleMatchData.head.HasValue
        ? _emptyValue.supplied
            ? _emptyValue.value
            : throw new NoMatchException("No empty clause supplied");
        : _recursiveConsTests.Any() 
            ? CalculateRecursiveResult() 
            : CalculateSimpleResult(simpleMatchData);

Discutendo questo con i coetanei, il verdetto unanime fu che le espressioni ternarie annidate, insieme all'uso "intelligente" di is varrisultarono in codice conciso, ma difficile da leggere.

Quindi l'ho rifattorizzato per:

TResult IConsFuncMatcher<T, TResult>.Result()
{
    var simpleMatchData = TryCons(_enumerator);

    if (!simpleMatchData.head.HasValue)
    {
        return _emptyValue.supplied
            ? _emptyValue.value
            : throw new NoMatchException("No empty clause supplied");
    }

    return _recursiveConsTests.Any() 
        ? CalculateRecursiveResult() 
        : CalculateSimpleResult(simpleMatchData);
}

La versione originale conteneva solo un'espressione composta con un implicito return. La nuova versione ora contiene una dichiarazione di variabile esplicita, ifun'istruzione e due esplicite returns. Contiene più istruzioni e più righe di codice. Tuttavia, tutti quelli che ho consultato hanno ritenuto più facile leggere e ragionare, che sono aspetti chiave del "codice pulito".

Quindi la risposta alla tua domanda è un "sì" enfatico, più dettagliato può essere più pulito del codice conciso ed è quindi un valido refactoring.


34
Al giorno d'oggi, il cervello degli sviluppatori è una risorsa più scarsa rispetto a disco, CPU, RAM o larghezza di banda della rete. Quelle altre cose sono importanti, e in alcune applicazioni potrebbero essere il tuo fattore limitante, ma nella maggior parte dei casi, vuoi ottimizzare per la capacità dei tuoi sviluppatori di capire prima il codice e poi quelle altre cose.
anaximander,

2
@anaximander, Assolutamente d'accordo. Scrivi il codice affinché altre persone leggano prima e il compilatore secondo. Questo è il motivo per cui trovo utile che altri peer rivedano il mio codice, anche se sono l'unico a svilupparlo.
David Arno,

4
Se lo stavo rivedendo, suggerirei di invertire l'ordine delle dichiarazioni di reso e di rimuoverlo !dalla condizione. Vorrei anche suggerire di inserire il secondo ritorno in un else. Ma anche così com'è, è un enorme miglioramento.
Martin Bonner supporta Monica il

2
@DavidArno Vedo quella logica, e se if (!foo.HasValue)è un idioma nel tuo codice, ancora più fortemente. Tuttavia ifnon è davvero un'uscita anticipata, è un "fai questo o quello a seconda".
Martin Bonner sostiene Monica il

2
@fabric Il confronto dei valori booleani è pericoloso. Lo evito il più possibile.
Martin Bonner supporta Monica il

30

1. Mancanza di correlazione tra LOC e qualità del codice.

L'obiettivo del refactoring è migliorare la qualità di un pezzo di codice.

LOC è una metrica di base che, a volte, è correlata alla qualità di un pezzo di codice: ad esempio, è probabile che un metodo con alcune migliaia di LOC abbia problemi di qualità. Va notato, tuttavia, che LOC non è l' unica metrica e in molti casi manca della correlazione con la qualità. Ad esempio, un metodo 4 LOC non è necessariamente più leggibile o più gestibile di un metodo 6 LOC.

2. Alcune tecniche di refactoring consistono nell'aggiunta di LOC.

Se prendi un elenco di tecniche di refactoring , puoi facilmente individuare quelle che consistono nell'aggiungere LOCs intenzionalmente . Esempi:

Entrambe sono tecniche di refactoring molto utili e il loro effetto sul LOC è completamente irrilevante quando si considera se usarle o meno.

Evitare di utilizzare LOC.

LOC è una metrica pericolosa. È molto facile da misurare ed è molto difficile da interpretare correttamente.

Fino a quando non acquisisci familiarità con le tecniche di misurazione della qualità del codice, considera innanzitutto di evitare di misurare LOC. Il più delle volte, non otterrai nulla di rilevante e ci sarebbero casi in cui ti indurrà a indurre in errore la qualità del tuo codice.


Hai riformattato la tua risposta e migliorato la qualità aggiungendo più LOTTO (righe di testo): p
grinch

12

Se vuoi vedere il risultato finale di minimizzare solo il conteggio dei byte o il conteggio LoC del tuo codice sorgente, dai un'occhiata agli invii al sito di Stack Exchange Code Golf .

Se il tuo codice sorgente è ridotto in questo modo, presto avrai un casino non mantenibile. Anche se sei la persona che ha scritto questo codice e lo capisci completamente in quel momento, quanto sarai efficiente quando tornerai ad esso tra sei mesi? Non ci sono prove che tale codice minimo esegua effettivamente anche più velocemente.

Il codice dovrebbe essere scritto in modo tale che qualsiasi membro del tuo team possa guardarlo e capire cosa sta facendo immediatamente.


Forse ridondante, ma solo per precisarlo; se si refactoring il codice golf per leggibilità si finisce sempre con più LoC
JollyJoker

1

Sì, il refactoring può sicuramente provocare più righe di codice.

Il caso più comune di IMO è quando prendi un codice che non è generico e lo rendi più generico / flessibile . La generalizzazione del codice fa aumentare notevolmente le righe di codice (a volte di un fattore due o più).

Se ti aspetti che il nuovo codice generico venga utilizzato da altri (anziché semplicemente come un componente software interno) come libreria, di solito finisci per aggiungere il codice unittest e il markup della documentazione in-code che aumenterà nuovamente le righe di codice.

Ad esempio, ecco uno scenario molto comune che accade per ogni sviluppatore di software:

  • il tuo prodotto ha bisogno di una nuova funzionalità urgente ad alta priorità o di correzione di bug o miglioramento in due settimane (o qualunque periodo di tempo sia considerato urgente per le dimensioni del tuo progetto / dimensioni dell'azienda / ecc.)
  • lavori sodo e fornisci XYZ in tempo e funziona. Congratulazioni! Ottimo lavoro!
  • Durante lo sviluppo di XYZ, la progettazione / implementazione del codice esistente non supportava davvero XYZ, ma è stato possibile inserire XYZ nella base di codice
  • il problema è che lo spessore è brutto e ha un odore di codice terribile perché hai fatto alcune cose difficili / intelligenti / brutte / cattive pratiche-ma-kinda-funziona
  • quando trovi il tempo successivo rifatti il ​​codice che può cambiare molte delle classi o aggiungere un nuovo livello di classi e la tua nuova soluzione è "fatta bene" e non ha più il cattivo odore di codice ... comunque facendolo "modo giusto" ora occupa molto più righe di codice.

Alcuni esempi concreti che mi vengono in mente:

  • per un'interfaccia a riga di comando potresti avere 5000 righe di codice if / else-if o potresti usare callbacks ... ogni callback sarebbe molto più piccolo e più facile da leggere / testare / verificare / debug / etc ma se conti le righe di codificare le brutte 5000 righe del codice if / else-if sarebbe probabilmente più piccolo
  • per un pezzo di codice di elaborazione che supporta N metodi di elaborazione , è possibile utilizzare nuovamente le istruzioni if ​​/ else che sembrerebbero il più brutto ...
    • o potresti passare ai callback che sarebbero più belli / migliori ma i callback prendono più righe di codice (comunque compila ancora il tempo)
    • oppure potresti estrarre ulteriormente e creare plugin che possono essere modificati in fase di esecuzione. i plug-in sono utili perché non è necessario ricompilare il prodotto principale per ogni nuovo plug-in o modifica in un plug-in esistente. e puoi pubblicare l'API in modo che altri possano estendere il prodotto. MA ancora un approccio a plugin utilizza più righe di codice.
  • per una GUI crei un nuovo fantastico widget
    • tu o un collega notate che il nuovo widget sarebbe ottimo per XYZ e ABC ma in questo momento il widget è strettamente integrato solo per funzionare con XYZ
    • rifatti il ​​widget in modo che funzioni per entrambi ma ora aumenta il numero totale di righe di codice
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.