Dividere il calcolo del valore restituito e l'istruzione return in metodi a una riga?


26

Ho avuto una discussione con un collega sulla rottura di returnun'istruzione e l'affermazione che calcola il valore di ritorno in due righe.

Per esempio

private string GetFormattedValue()
{
    var formattedString = format != null ? string.Format(format, value) : value.ToString();
    return formattedString;
}

invece di

private string GetFormattedValue()
{
    return format != null ? string.Format(format, value) : value.ToString();
}

Per quanto riguarda il codice, non vedo davvero un valore nella prima variante. Per me, quest'ultimo è più chiaro, in particolare per i metodi così brevi. La sua argomentazione era che la prima variante è più facile da eseguire il debug, il che è un merito piuttosto piccolo, dal momento che VisualStudio ci consente un'ispezione molto dettagliata delle dichiarazioni, quando l'esecuzione viene interrotta a causa di un punto di interruzione.

La mia domanda è, se è ancora un punto valido per scrivere il codice in modo meno chiaro, solo per rendere più facile il debug uno sguardo? Ci sono ulteriori argomenti per la variante con il calcolo diviso e l' returnistruzione?


18
Non lavorando su VS, ma presumo che non sia possibile impostare un breakpoint condizionale su un'espressione complicata (o sarebbe complicato entrare), quindi probabilmente metterebbe assegnazione e ritorno in istruzioni separate, solo per comodità. Molto probabilmente il compilatore avrebbe escogitato lo stesso codice per entrambi.
fino al

1
Ciò dipende forse dalla lingua, specialmente nei linguaggi in cui le variabili hanno un comportamento (forse complesso) dell'oggetto invece del comportamento puntatore. L'affermazione di @Paul K è probabilmente vera per le lingue con comportamento puntatore, le lingue in cui gli oggetti hanno un comportamento a valore semplice e le lingue con compilatori maturi e di alta qualità.
MSalters il

4
"poiché VisualStudio ci consente un'ispezione molto dettagliata delle dichiarazioni, quando l'esecuzione viene interrotta a causa di un punto di interruzione" - è così. Quindi, come si ottiene il valore restituito se la funzione ha restituito una struttura con più di un membro? (e il supporto per quella funzione è al massimo discutibile, ci sono molte combinazioni in cui non ottieni affatto il valore di ritorno).
Voo

2
In che modo avere "un'ispezione molto dettagliata delle dichiarazioni" nel debug che qualcuno sta usando OGGI, rende una cattiva opzione per scrivere il codice in modo che sia facile eseguire il debug in QUALSIASI debug?
Ian,

16
Lo infastidisce ulteriormente riducendo l'intero corpo della funzione aprivate string GetFormattedValue() => string.Format(format ?? "{0}", value);
Graham il

Risposte:


46

L'introduzione della spiegazione delle variabili è un noto refactoring che a volte può aiutare a rendere meglio leggibili le espressioni complicate. Tuttavia, nel caso mostrato,

  • la variabile aggiuntiva non "spiega" nulla che non sia chiaro dal nome del metodo circostante
  • l'affermazione diventa ancora più lunga, quindi (leggermente) meno leggibile

Inoltre, le versioni più recenti del debugger di Visual Studio possono mostrare il valore restituito di una funzione nella maggior parte dei casi senza introdurre una variabile superflua (ma attenzione, ci sono alcuni avvertimenti, dai un'occhiata a questo post SO più vecchio e alle diverse risposte ).

Quindi, in questo caso specifico, sono d'accordo con te, tuttavia, ci sono altri casi in cui una variabile esplicativa può effettivamente migliorare la qualità del codice.


Concordo anche sul fatto che ci sono sicuramente casi in cui è utile, senza dubbio.
Paul Kertscher il

2
Di solito uso resultcome nome della variabile. Non molto più lungo e più facile da eseguire il debug
edc65, il

26
@ edc65: un nome generico come result spesso aggiunge solo rumore al codice e raramente aumenta la leggibilità, che è esattamente il punto della mia risposta. Questo può essere giustificato nel contesto in cui aiuta il debug, ma eviterei quando si utilizza un debugger che non ha bisogno di una variabile separata.
Doc Brown,

6
@JonHanna strumento lungo lungo secondo me. Il nome resulttrasmette l'informazione che questo è il valore risultante dalla funzione, in modo che tu possa guardarlo prima che la funzione ritorni.
edc65,

1
@ edc65 ma questo lo rende utile. Quindi ora quando leggo il tuo codice non mi rendo subito conto che non lo è. Quindi il tuo codice è diventato meno leggibile.
Jon Hanna il

38

Dati i fatti che:

a) Non vi è alcun impatto sul codice finale poiché il compilatore ottimizza la variabile.

b) Avere separato migliora la capacità di debug.

Personalmente sono giunto alla conclusione che è una buona pratica separarli il 99% delle volte.

Non ci sono svantaggi materiali nel farlo in questo modo. L'argomento che blocca il codice è un termine improprio, perché il codice gonfiato è un problema banale rispetto al codice illeggibile o difficile da eseguire il debug. Inoltre, questo metodo non può di per sé creare un codice confuso, che dipende interamente dallo sviluppatore.


9
Questa è la risposta corretta per me. Ciò semplifica l'impostazione di un punto di interruzione e la visualizzazione del valore durante il debug e non ha alcun aspetto negativo di cui sono a conoscenza.
Matthew James Briggs il

Per il punto b, in Visual Studio Code, basta inserire il punto di interruzione sul ritorno e quindi aggiungere l'espressione: GetFormattedValue () e che mostrerà il risultato quando viene raggiunto il punto di interruzione, quindi la riga aggiuntiva non è necessaria. Ma è più facile vedere la gente del posto con una riga aggiuntiva in quanto non richiederà l'aggiunta di alcuna espressione aggiuntiva nel debugger. Quindi, davvero una questione di preferenze personali.
Jon Raynor il

3
@JonRaynor per il valore restituito, posizionare il punto di interruzione sulla parentesi di chiusura della funzione. Cattura il valore restituito, anche in funzioni con più rendimenti.
Baldrickk,

16

Spesso, l'introduzione di una variabile solo per citarne alcuni risultati è molto utile quando rende il codice più auto-documentante. In questo caso non è un fattore perché il nome della variabile è molto simile al nome del metodo.

Si noti che i metodi a una riga non hanno alcun valore intrinseco. Se una modifica introduce più righe ma rende il codice più chiaro, è una buona modifica.

Ma in generale, queste decisioni dipendono fortemente dalle tue preferenze personali. Ad esempio, trovo entrambe le soluzioni confuse perché l'operatore condizionale viene utilizzato inutilmente. Avrei preferito un'istruzione if. Ma nella tua squadra potresti aver concordato convenzioni diverse. Quindi fai qualunque cosa suggeriscano le tue convenzioni. Se le convenzioni tacciono su un caso come questo, si noti che si tratta di un cambiamento estremamente secondario che non ha importanza nel lungo periodo. Se questo schema si verifica ripetutamente, forse avviare una discussione su come una squadra vuole gestire questi casi. Ma questo sta dividendo i peli tra "buon codice" e "forse un po 'di codice leggermente migliore".


1
"Trovo che entrambe le soluzioni siano confuse perché l'operatore condizionale viene utilizzato inutilmente." - Non è un esempio del mondo reale, dovevo solo inventarmi qualcosa, in fretta. Certamente questo potrebbe non essere il miglior esempio.
Paul Kertscher il

4
+1 per dire essenzialmente che questa è una differenza "sotto il radar" che (a parità di altre cose) non vale la pena preoccuparsi.
TripeHound,

3
@Mindwin, quando uso l'operatore ternario, di solito lo divido in più righe in modo che sia chiaro qual è il caso vero e qual è il caso falso.
Arturo Torres Sánchez,

2
@ ArturoTorresSánchez Lo faccio anch'io, ma invece di a ?e a :io uso if() {e } else {- - - - \\ :)
Mindwin

3
@Mindwin, ma non posso farlo quando sono nel mezzo di un'espressione (come un inizializzatore di oggetti)
Arturo Torres Sánchez,

2

In risposta alle tue domande:

La mia domanda è, se è ancora un punto valido per scrivere il codice in modo meno chiaro, solo per rendere più facile il debug uno sguardo?

Sì. In effetti, parte della tua precedente affermazione mi sembra (senza offesa) un po 'miope (vedi grassetto sotto) " La sua argomentazione era che la variante precedente è più facile da eseguire il debug - il che è piuttosto un merito , dal momento che VisualStudio ci consente un'ispezione molto dettagliata delle dichiarazioni, quando l'esecuzione viene interrotta a causa di un punto di interruzione " .

Rendere il debugging più semplice non è (quasi) mai "di piccolo merito " perché secondo alcune stime il 50% del tempo di un programmatore viene impiegato per il debug ( software di debug reversibile ).

Ci sono ulteriori argomenti per la variante con il calcolo della divisione e l'istruzione return?

Sì. Alcuni sviluppatori sostengono che il calcolo suddiviso è più facile da leggere. Questo, ovviamente, aiuta con il debug ma aiuta anche quando qualcuno sta cercando di decifrare qualsiasi regola aziendale che il tuo codice possa eseguire o applicare.

NOTA: le regole aziendali possono essere meglio servite in un database poiché possono cambiare spesso. Tuttavia, una chiara codifica in questo settore è ancora fondamentale. ( Come costruire un motore di regole aziendali )


1

Andrei ancora oltre:

private string GetFormattedValue()
{
    if (format != null) {
        formattedString = string.Format(format, value);
    } else {
        formattedString = value.ToString()
    }
    return formattedString;
}

Perché?

L'uso di operatori ternari per una logica più complessa sarebbe illeggibile, quindi per uno schema più complesso useresti uno stile come quello sopra. Usando sempre questo stile, il tuo codice è coerente e più facile da analizzare per un essere umano. Inoltre, introducendo questo tipo di coerenza (e usando lint di codice e altri test) è possibile evitare goto failerrori di tipo.

Un altro vantaggio è che il rapporto sulla copertura del codice ti farà sapere se hai dimenticato di includere un test per formatnon è nullo. Questo non sarebbe il caso dell'operatore ternario.


La mia alternativa preferita - se sei nel "ottieni un ritorno il più rapidamente possibile" e non contro più resi da un metodo:

private string GetFormattedValue()
{
    if (format != null) {
        return string.Format(format, value);
    }

    return value.ToString();
}

Quindi, puoi guardare l'ultimo ritorno per vedere quale è l'impostazione predefinita.

Tuttavia, è importante essere coerenti e fare in modo che tutti i metodi seguano l'una o l'altra convenzione.


1
Il primo esempio sembra una cattiva pratica perché value.ToString()viene chiamato inutilmente quando il formato è non nullo. Nel caso generale, ciò potrebbe includere calcoli non banali e potrebbe richiedere più tempo della versione che include una stringa di formato. Si consideri, ad esempio, un oggetto valueche memorizza PI in un milione di cifre decimali e una stringa di formato che richiede solo le prime cifre.
Steve

1
perché no private string GetFormattedValue() => string.Format(format ?? "{0}", value); Stesso effetto e utilizzare i test unitari per garantire la correttezza invece di fare affidamento sul debugger.
Berin Loritsch,

1
Mentre sono d'accordo che un ternario può essere meno chiaro, il terminatore null può rendere le cose più chiare. Almeno in questo caso.
Berin Loritsch,

1
Caro diario, oggi ho letto che scrivere codice chiaro e conciso usando paradigmi, idiomi e operatori ben noti (esistenti da circa 40 anni) è, virgolette, virgolette doppie essendo virgolette doppie intelligenti, non quotate - ma invece scrivere codice eccessivamente prolisso che viola ASCIUTTO e non usando gli operatori, gli idiomi e i paradigmi sopra menzionati, mentre invece cerca di evitare tutto ciò che potrebbe sembrare criptico solo a un bambino di cinque anni senza precedenti programmi di programmazione - è invece chiarezza . Diavolo, avrei dovuto diventare davvero vecchio, mio ​​caro diario ... Avrei dovuto imparare Go quando ne ho avuto l'occasione.
vaxquis,

1
"L'uso di operatori ternari per una logica più complessa sarebbe illeggibile" Sebbene sia effettivamente il caso (e ho visto persone complicare la logica), ciò non è vero per il codice del PO, e non è neppure una cosa specifica per gli operatori ternari. L'unica cosa che posso dire con piena fiducia è che la linea è troppo lunga. gist.github.com/milleniumbug/cf9b62cac32a07899378feef6c06c776 è come lo riformattare.
Milleniumbug,

1

Non penso che tale tecnica possa essere giustificata dalla necessità di eseguire il debug. Ho incontrato questo approccio me stesso mille volte, e di tanto in tanto continuo a farlo, ma tengo sempre presente ciò che Martin Fowler ha detto sul debug :

Le persone sottostimano anche il tempo che impiegano per il debug. Sottovalutano quanto tempo possono dedicare all'inseguimento di un lungo bug. Con i test, lo so subito quando ho aggiunto un bug. Ciò mi consente di correggere immediatamente il bug, prima che possa strisciare e nascondersi. Ci sono alcune cose più frustranti o che perdono tempo rispetto al debug. Non sarebbe molto più veloce se non creassimo i bug in primo luogo?


Martin Fowler è un uomo intelligente e mi è piaciuto leggere le sue (e le tue) opinioni. Mentre sono fermamente convinto che i test siano necessari e che occorra dedicare più tempo a tale sforzo, il fatto che siamo tutti esseri umani fallibili suggerisce che nessuna quantità di test eliminerà tutti i bug. Quindi, il debug farà sempre parte del processo di sviluppo e supporto del programma.
tale852150,

1

Penso che alcune persone si stiano bloccando su questioni tangenziali alla domanda, come l'operatore ternario. Sì, molte persone lo odiano, quindi forse è bene allevarlo comunque.

Per quanto riguarda il focus della tua domanda, spostando la dichiarazione restituita fuori per far riferimento a una variabile ...

Questa domanda fa 2 ipotesi con cui non sono d'accordo:

  1. Che la seconda variante sia più chiara o più facile da leggere (dico che è vero il contrario), e

  2. che tutti usano Visual Studio. Ho usato Visual Studio molte volte e posso usarlo bene, ma di solito sto usando qualcos'altro. Un ambiente di sviluppo che forza un IDE specifico è uno di cui sarei scettico.

Rompere qualcosa con una variabile nominata raramente rende qualcosa di più difficile da leggere, quasi sempre fa il contrario. Il modo specifico in cui qualcuno lo fa potrebbe causare problemi, come se un sovrano autodocumentazione lo faccia, var thisVariableIsTheFormattedResultAndWillBeTheReturnValue = ...ovviamente, è un male, ma questo è un problema separato. var formattedText = ...è ok.

In questo caso specifico , e probabilmente in molti casi poiché stiamo parlando di 1-line, la variabile non ti direbbe molto che il nome della funzione non ti dice già. Pertanto, la variabile non aggiunge molto. L'argomento di debug potrebbe ancora essere valido, ma ancora una volta, in questo caso specifico, non vedo nulla che possa essere il tuo focus durante il debug e può sempre essere facilmente modificato in seguito se in qualche modo qualcuno ha bisogno di quel formato per il debug o altro.

In generale, e hai chiesto la regola generale (il tuo esempio era proprio quello, un esempio di una forma generalizzata), tutti i punti fatti a favore della variante 1 (2-liner) sono corretti. Quelle sono buone linee guida da avere. Ma le linee guida devono essere flessibili. Ad esempio, il progetto a cui sto lavorando ora ha un massimo di 80 caratteri per riga, quindi divido molte righe, ma trovo comunemente righe 81-85 caratteri che sarebbero scomode da dividere o ridurre la leggibilità e le lascio passare il limite.

Poiché è improbabile che aggiunga valore, non farei 2 righe per l'esempio specifico fornito. Vorrei fare la variante 2 (1-liner) perché i punti non sono abbastanza forti da fare diversamente in questo caso.

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.