Codice pulito - Devo cambiare il letterale 1 in una costante?


14

Per evitare numeri magici, spesso sentiamo che dovremmo dare a un letterale un nome significativo. Ad esempio:

//THIS CODE COMES FROM THE CLEAN CODE BOOK
for (int j = 0; j < 34; j++) {
    s += (t[j] * 4) / 5;
}

-------------------- Change to --------------------

int realDaysPerIdealDay = 4;
const int WORK_DAYS_PER_WEEK = 5;
int sum = 0;
for (int j = 0; j < NUMBER_OF_TASKS; j++) {
    int realTaskDays = taskEstimate[j] * realDaysPerIdealDay;
    int realTaskWeeks = (realdays / WORK_DAYS_PER_WEEK);
    sum += realTaskWeeks;
}

Ho un metodo fittizio come questo:

Spiega: suppongo di avere un elenco di persone da servire e, per impostazione predefinita, spendiamo $ 5 per acquistare solo cibo, ma quando abbiamo più di una persona, dobbiamo acquistare acqua e cibo, dobbiamo spendere più soldi, forse $ 6. Cambierò il mio codice, ti prego di concentrarmi sul letterale 1 , la mia domanda al riguardo.

public int getMoneyByPersons(){
    if(persons.size() == 1){ 
        // TODO - return money for one person
    } else {
        // TODO - calculate and return money for people.
    }

}

Quando chiesi ai miei amici di rivedere il mio codice, uno disse che dare un nome per il valore 1 avrebbe prodotto un codice più pulito, e l'altro disse che non abbiamo bisogno di un nome costante qui perché il valore è significativo da solo.

Quindi, la mia domanda è: dovrei dare un nome per il valore letterale 1? Quando un valore è un numero magico e quando non lo è? Come posso distinguere il contesto per scegliere la soluzione migliore?


Da dove personsviene e cosa descrive? Il tuo codice non ha alcun commento, quindi è difficile indovinare cosa sta facendo.
Hubert Grzeskowiak,

7
Non c'è niente di più chiaro di 1. Però cambierei "size" in "count". E moneyService è stupido com'è, dovrebbe essere in grado di decidere quanti soldi restituire a seconda della raccolta delle persone. Quindi passerei le persone a prendere Money e lascerei risolvere eventuali casi eccezionali.
Martin Maat,

4
È inoltre possibile estrarre l'espressione logica dalla clausola if in un nuovo metodo. Forse chiamalo IsSinglePerson (). In questo modo estrai un po 'più di quella sola variabile ma rendi anche la clausola if un po' più leggibile ...
selmaohneh,


2
Forse questa logica sarebbe più adatta in moneyService.getMoney ()? Ci sarebbe mai un momento in cui avresti bisogno di chiamare getMoney nel caso di 1 persona? Ma sarei d'accordo con il sentimento generale che 1 sia chiaro. I numeri magici sono numeri in cui dovresti grattarti la testa chiedendo come il programmatore è arrivato a quel numero .. cioèif(getErrorCode().equals(4095)) ...
Neil,

Risposte:


23

No. In questo esempio, 1 è perfettamente significativo.

Tuttavia, cosa succede se persons.size () è zero? Sembra strano che persons.getMoney()funzioni per 0 e 2 ma non per 1.


Sono d'accordo che 1 è significativo. Grazie, a proposito, ho aggiornato la mia domanda. Puoi vederlo di nuovo per avere la mia idea.
Jack,

3
Una frase che ho sentito più volte nel corso degli anni è che i numeri magici sono costanti senza nome diversi da 0 e 1. Mentre posso potenzialmente pensare ad esempi in cui questi numeri dovrebbero essere nominati, è una regola abbastanza semplice seguire il 99% del tempo.
Baldrickk,

@Baldrickk A volte, anche altri letterali numerici non devono essere nascosti dietro un nome.
Deduplicatore,

@Deduplicator ti vengono in mente alcuni esempi?
Baldrickk,

@Baldrickk See amon .
Deduplicatore,

18

Perché un pezzo di codice contiene quel particolare valore letterale?

  • Questo valore ha un significato speciale nel dominio del problema ?
  • O questo valore è solo un dettaglio dell'implementazione , dove quel valore è una conseguenza diretta del codice circostante?

Se il valore letterale ha un significato che non è chiaro dal contesto, allora sì, dare quel valore a un nome attraverso una costante o una variabile è utile. Successivamente, quando il contesto originale viene dimenticato, il codice con nomi di variabili significativi sarà più gestibile. Ricorda, il pubblico per il tuo codice non è principalmente il compilatore (il compilatore funzionerà felicemente con un codice orribile), ma i futuri manutentori di quel codice - che apprezzeranno se il codice è in qualche modo autoesplicativo.

  • Nel tuo primo esempio, il significato di letterali, come 34, 4, 5non è evidente dal contesto. Invece, alcuni di questi valori hanno un significato speciale nel dominio del problema. Era quindi bello dare loro dei nomi.

  • Nel tuo secondo esempio, il significato del letterale 1è molto chiaro dal contesto. L'introduzione di un nome non è utile.

In effetti, l'introduzione di nomi per valori ovvi può anche essere negativa in quanto nasconde il valore reale.

  • Ciò può oscurare i bug se il valore indicato viene modificato o errato, specialmente se la stessa variabile viene riutilizzata in parti di codice non correlate.

  • Un pezzo di codice potrebbe anche funzionare bene per un valore specifico, ma potrebbe non essere corretto nel caso generale. Introducendo un'astrazione non necessaria, il codice non è ovviamente più corretto.

Non esiste un limite di dimensione per i letterali "ovvi" perché dipende totalmente dal contesto. Ad esempio, il letterale 1024potrebbe essere del tutto evidente nel contesto del calcolo della dimensione del file, oppure il letterale 31nel contesto di una funzione hash o il letterale padding: 0.5emnel contesto di un foglio di stile CSS.


8

Ci sono diversi problemi con questo pezzo di codice che, a proposito, può essere abbreviato in questo modo:

public List<Money> getMoneyByPersons() {
    return persons.size() == 1 ?
        moneyService.getMoneyIfHasOnePerson() :
        moneyService.getMoney(); 
}
  1. Non è chiaro perché una persona sia un caso speciale. Suppongo che ci sia una regola aziendale specifica che dice che ottenere denaro da una persona è radicalmente diverso dall'ottenere denaro da più persone. Tuttavia, devo andare a guardare dentro entrambi getMoneyIfHasOnePersone getMoney, nella speranza di capire perché ci sono casi distinti.

  2. Il nome getMoneyIfHasOnePersonnon sembra giusto. Dal nome, mi aspetterei che il metodo verifichi se esiste una sola persona e, in tal caso, ottenere denaro da lui; altrimenti, non fare nulla. Dal tuo codice, questo non è ciò che sta accadendo (o stai facendo la condizione due volte).

  3. C'è qualche motivo per restituire una List<Money>piuttosto che una raccolta?

Tornando alla tua domanda, poiché non è chiaro il motivo per cui esiste un trattamento speciale per una persona, il numero uno dovrebbe essere sostituito da una costante, a meno che non ci sia un altro modo per rendere esplicite le regole. Qui, uno non è molto diverso da qualsiasi altro numero magico. Potresti avere regole commerciali che affermano che il trattamento speciale si applica a una, due o tre persone o solo a più di dodici persone.

Come posso distinguere il contesto per scegliere la soluzione migliore?

Fai qualunque cosa renda il tuo codice più esplicito.

Esempio 1

Immagina il seguente codice:

if (sequence.size() == 0) {
    return null;
}

return this.processSequence(sequence);

Lo zero qui è un valore magico? Il codice è piuttosto chiaro: se non ci sono elementi nella sequenza, non elaboriamolo e restituiamo un valore speciale. Ma questo codice può anche essere riscritto in questo modo:

if (sequence.isEmpty()) {
    return null;
}

return this.processSequence(sequence);

Qui, non più costante e il codice è ancora più chiaro.

Esempio 2

Prendi un altro pezzo di codice:

const result = Math.round(input * 1000) / 1000;

Non ci vuole troppo tempo per capire cosa fa in linguaggi come JavaScript che non hanno round(value, precision)sovraccarico.

Ora, se vuoi introdurre una costante, come verrebbe chiamata? Il termine più vicino che puoi ottenere è Precision. Così:

const precision = 1000;
const result = Math.round(input * precision) / precision;

Migliora la leggibilità? Può essere. Qui, il valore di una costante è piuttosto limitato e potresti chiederti se hai davvero bisogno di eseguire il refactoring. La cosa bella qui è che ora la precisione viene dichiarata una sola volta, quindi se cambia, non rischi di fare un errore come:

const result = Math.round(input * 100) / 1000;

cambiando il valore in una posizione e dimenticando di farlo nell'altra.

Esempio 3

Da quegli esempi, potresti avere l'impressione che i numeri debbano essere sostituiti da costanti in ogni caso . Questo non è vero. In alcune situazioni, avere una costante non porta al miglioramento del codice.

Prendi il seguente codice:

class Point
{
    ...
    public void Reset()
    {
        x, y = (0, 0);
    }
}

Se si tenta di sostituire gli zeri con una variabile, la difficoltà sarebbe quella di trovare un nome significativo. Come lo chiameresti? ZeroPosition? Base? Default? Introdurre una costante qui non migliorerebbe il codice in alcun modo. Lo renderebbe leggermente più lungo, e proprio quello.

Tali casi sono tuttavia rari. Quindi ogni volta che trovi un numero nel codice, fai lo sforzo cercando di trovare come il codice può essere refactored. Chiediti se esiste un significato commerciale per il numero. Se sì, una costante è obbligatoria. Se no, come chiameresti il ​​numero? Se trovi un nome significativo, va benissimo. In caso contrario, è probabile che tu abbia trovato un caso in cui la costante non è necessaria.


Vorrei aggiungere 3a: non usare la parola denaro nei nomi delle variabili, usare importo, saldo o simili. Una raccolta di denaro non ha senso, la raccolta di importi o saldi lo è. (OK, ho una piccola raccolta di denaro straniero (o meglio monete) ma questa è una questione diversa dalla non programmazione).
Piegato l'

3

È possibile creare una funzione che accetta un singolo parametro e restituisce quattro volte diviso per cinque che offre un alias pulito in ciò che fa mentre si utilizza ancora il primo esempio.

Sto solo offrendo la mia solita strategia, ma forse imparerò anche qualcosa.

Quello che sto pensando è.

// Just an example name  
function normalize_task_value(task) {  
    return (task * 4) / 5;  // or to `return (task * 4) / NUMBER_OF_TASKS` but it really matters what logic you want to convey and there are reasons to many ways to make this logical. A comment would be the greatest improvement

}  

// Is it possible to just use tasks.length or something like that?  
// this NUMBER_OF_TASKS is the only one thats actually tricky to   
// understand right now how it plays its role.  

function normalize_all_task_values(tasks, accumulator) {  
    for (int i = 0; i < NUMBER_OF_TASKS; i++) {  
        accumulator += normalize_task_value(tasks[i]);
    }
}  

Scusate se sono fuori base ma sono solo uno sviluppatore javascript. Non sono sicuro di quale composizione lo giustifichi, immagino che non tutto debba essere in un array o in una lista, ma .length avrebbe molto senso. E il * 4 renderebbe la buona costante poiché la sua origine è nebulosa.


5
Ma cosa significano quei valori 4 e 5? Se uno di loro dovesse mai cambiare, saresti in grado di trovare tutti i luoghi in cui il numero viene utilizzato in un contesto simile e aggiornarli di conseguenza? Questo è il punto cruciale nella domanda originale.
Bart van Ingen Schenau,

Penso che il primo esempio sia stato abbastanza esplicativo, quindi potrebbe non essere quello a cui posso rispondere. L'operazione non dovrebbe cambiare in futuro, quando devi scriverne una nuova per realizzare ciò di cui hai bisogno. I commenti sono ciò che penso trarrebbero maggiori benefici da questo scopo. È una chiamata a una linea per ridurre nella mia lingua. Quanto è importante rendere assolutamente comprensibile al laico?
etisdew,

@BartvanIngenSchenau L'intera funzione ha un nome errato. Preferirei un nome di funzione migliore, un commento con una specifica su cosa dovrebbe restituire la funzione e un commento sul perché * 4/5 lo ottenga. I nomi costanti non sono davvero necessari.
gnasher729,

@ gnasher729: se le costanti compaiono esattamente una volta nel codice sorgente di una funzione ben denominata e ben documentata, potrebbe non essere necessario utilizzare una costante denominata. Non appena una costante appare più volte con lo stesso significato, assegnando a quella costante un nome si evita di dover capire se tutte quelle istanze del 42 letterale significano o meno la stessa cosa.
Bart van Ingen Schenau,

Al centro, se ti aspetti potresti dover cambiare il valore sì. Lo cambierei in const per rappresentare la variabile, comunque. Non penso che sia il caso e questa dovrebbe essere semplicemente una variabile intermedia che risiede nello scopo sopra di essa. Non voglio fare a pezzi e dire di rendere tutto un parametro per una funzione, ma se può cambiare, ma raramente lo farei diventare un parametro interno per quella funzione o ambito oggetto / struttura dove stai attualmente lavorando per fare quel cambiamento lasciando solo l'ambito globale.
etisdew,

2

Quel numero 1, potrebbe essere un numero diverso? Potrebbe essere 2 o 3 o ci sono ragioni logiche per cui deve essere 1? Se deve essere 1, allora usare 1 va bene. Altrimenti, puoi definire una costante. Non chiamare quella costante UNO. (L'ho visto fatto).

60 secondi in un minuto - hai bisogno di una costante? Bene, sono 60 secondi, non 50 o 70. E tutti lo sanno. In modo che possa rimanere un numero.

60 articoli stampati per pagina - quel numero avrebbe potuto facilmente essere 59 o 55 o 70. In realtà, se si modifica la dimensione del carattere, potrebbe diventare 55 o 70. Quindi qui viene richiesta una costante significativa.

È anche una questione di quanto sia chiaro il significato. Se scrivi "minuti = secondi / 60", è chiaro. Se scrivi "x = y / 60", non è chiaro. Ci devono essere alcuni nomi significativi da qualche parte.

C'è una regola assoluta: non ci sono regole assolute. Con la pratica capirai quando usare i numeri e quando usare le costanti nominate. Non farlo perché lo dice un libro, fino a quando non capisci perché lo dice.


"60 secondi in un minuto ... E tutti lo sanno. Quindi può rimanere un numero." - Non è un argomento valido per me. Cosa succede se nel mio codice sono presenti 200 posizioni in cui viene utilizzato "60"? Come faccio a trovare i luoghi in cui viene utilizzato come secondi al minuto rispetto ad altri usi forse altrettanto "naturali" di esso? - In pratica, tuttavia, anche io oso usarlo minutes*60, a volte anche hours*3600quando ne ho bisogno senza dichiarare costanti aggiuntive. Per giorni probabilmente scriverei d*24*3600o d*24*60*60perché 86400è vicino al limite in cui qualcuno non riconoscerà quel numero magico da uno sguardo.
JimmyB,

@JimmyB Se stai usando "60" su 200 posizioni nel tuo codice, è molto probabile che la soluzione stia eseguendo il refactoring di una funzione piuttosto che introdurre una costante.
Klut,

0

Ho visto una buona quantità di codice come nell'OP (modificato) nei recuperi di DB. La query restituisce un elenco, ma le regole aziendali indicano che può esserci un solo elemento. E poi, ovviamente, qualcosa è cambiato per "solo questo caso" in un elenco con più di un elemento. (Sì, ho detto un bel po 'di volte. È quasi come se ... nm)

Quindi, piuttosto che creare una costante, vorrei (nel metodo del codice pulito) creare un metodo per dare un nome o chiarire ciò che il condizionale è destinato a rilevare (e incapsulare il modo in cui lo rileva):

public int getMoneyByPersons(){
  if(isSingleDepartmentHead()){ 
    // TODO - return money for one person
  } else {
    // TODO - calculate and return money for people.
  }
}

public boolean isSingleDepartmentHead() {
   return persons.size() == 1;
}

Molto preferibile unificare il trattamento. Un elenco di n elementi può essere trattato in modo uniforme qualunque sia n.
Deduplicatore

Ho visto se non lo fosse, dove il singolo caso era, in effetti, un valore (marginale) diverso da quello che sarebbe stato se lo stesso utente fosse stato parte di un elenco di dimensioni n. In un OO ben fatto, questo potrebbe non essere un problema, ma, quando si ha a che fare con sistemi legacy, una buona implementazione di OO non è sempre fattibile. Il meglio che abbiamo ottenuto è stata una facciata OO ragionevole sulla logica DAO.
Kristian H,
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.