Clean Code - Devo cambiare il letterale 1 in una costante?

14

Per evitare numeri magici, spesso sentiamo che dovremmo dare un nome significativo alla lettera. 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:

Explain: I suppose that I have a list people to serve and by default, we spend $5 to buy food only, but when we have more than one person, we need to buy water and food, we must spend more money, maybe $6. I'll change my code, please focus on the literal 1, my question about it.

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

}

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

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

    
posta Jacky 11.09.2018 - 05:16
fonte

6 risposte

23

No. In questo esempio, 1 è perfettamente significativo.

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

    
risposta data 11.09.2018 - 07:20
fonte
19

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

  • Questo valore ha un significato speciale nel dominio problematico ?
  • Oppure questo valore è solo un dettaglio di 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 a quel valore un nome attraverso una costante o una variabile è utile. Successivamente, quando il contesto originale viene dimenticato, il codice con nomi di variabili significative sarà più gestibile. Ricorda, il pubblico per il tuo codice non è principalmente il compilatore (il compilatore lavorerà felicemente con codice orribile), ma i futuri manutentori di quel codice - che apprezzeranno se il codice è in qualche modo auto-esplicativo.

  • Nel tuo primo esempio, il significato di valori letterali come 34 , 4 , 5 non è evidente dal contesto. Invece, alcuni di questi valori hanno un significato speciale nel dominio del problema. Era quindi bene dare loro i 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 poiché nasconde il valore effettivo.

  • Questo può oscurare i bug se il valore con nome è cambiato o non è corretto, 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 astrazione non necessaria, il codice non è più ovviamente corretto.

Non esiste un limite di dimensione per i letterali "ovvi" perché questo dipende totalmente dal contesto. Per esempio. il letterale 1024 potrebbe essere del tutto ovvio nel contesto del calcolo della dimensione del file, o del% letterale31 nel contesto di una funzione di hash, o del% letteralepadding: 0.5em nel contesto di un foglio di stile CSS.

    
risposta data 11.09.2018 - 13:29
fonte
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 il motivo per cui una persona è un caso speciale. Suppongo che ci sia una specifica regola aziendale che dice che ottenere denaro da una persona è radicalmente diverso dall'ottenere denaro da più persone. Tuttavia, devo andare a dare un'occhiata sia a getMoneyIfHasOnePerson che a getMoney , sperando di capire perché ci sono casi distinti.

  2. Il nome getMoneyIfHasOnePerson non sembra corretto. Dal nome, mi aspetterei che il metodo verifichi se c'è una sola persona e, se questo è il 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 un List<Money> piuttosto che una raccolta?

Torna alla tua domanda, perché non è chiaro il motivo per cui esiste un trattamento speciale per una persona, la prima cifra dovrebbe essere sostituita da una costante, a meno che ci sia un'altra modo per rendere esplicite le regole. Qui, uno non è molto diverso da qualsiasi altro numero magico. Potresti avere regole aziendali che dicono che il trattamento speciale si applica a una, due o tre persone o solo a più di dodici persone.

How can I distinguish context to choose the best solution?

Fai ciò che rende il tuo codice più esplicito.

Esempio 1

Immagina la seguente parte di codice:

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

return this.processSequence(sequence);

C'è zero qui un valore magico? Il codice è piuttosto chiaro: se non ci sono elementi nella sequenza, non elaborarlo e restituire 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 molto tempo per capire cosa fa in linguaggi come JavaScript che non hanno sovraccarico di round(value, precision) .

Ora, se vuoi introdurre una costante, come si chiamerebbe? Il termine più vicino che puoi ottenere è Precision . Quindi:

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 è dichiarata solo una volta, quindi se cambia, non rischi di commettere un errore come:

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

modifica del valore in una posizione e dimentica di farlo nell'altra.

Esempio 3

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

Prendi la seguente parte di codice:

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

Se provi a sostituire gli zeri con una variabile, la difficoltà sarebbe trovare un nome significativo. Come lo chiameresti? %codice%? %codice%? %codice%? L'introduzione di una costante qui non migliorerebbe il codice in alcun modo. Lo renderebbe leggermente più lungo, e solo quello.

Tali casi sono tuttavia rari. Così ogni volta che trovi un numero nel codice, fai lo sforzo cercando di scoprire come il codice può essere rifatto. Chiediti se c'è un significato aziendale per il numero. Se sì, una costante è obbligatoria. Se no, come chiameresti il numero? Se trovi un nome significativo, è grandioso. In caso contrario, è probabile che tu abbia trovato un caso in cui la costante non è necessaria.

    
risposta data 11.09.2018 - 13:35
fonte
3

Potresti creare una funzione che accetta un singolo parametro e restituisce i tempi quattro divisi per cinque che offre un alias pulito in quello che fa mentre 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]);
    }
}  

Scusa se sono fuori base ma sono solo uno sviluppatore javascript. Non sono sicuro di quale composizione ho giustificato, suppongo che non tutto debba essere in un array o in una lista, ma la lunghezza potrebbe essere molto sensata. E il * 4 renderebbe la costante costante poiché la sua origine è nebulosa.

    
risposta data 11.09.2018 - 06:26
fonte
2

Quel numero 1, potrebbe essere un numero diverso? Potrebbe essere 2, o 3, o ci sono ragioni logiche perché deve essere 1? Se deve essere 1, quindi usare 1 va bene. Altrimenti, puoi definire una costante. Non chiamare quello costante. (L'ho visto così).

60 secondi in un minuto - hai bisogno di una costante? Bene, è 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 essere facilmente 59 o 55 o 70. In realtà, se cambi la dimensione del font, potrebbe diventare 55 o 70. Quindi qui è richiesta una costante significativa.

È anche 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 scoprirai quando usare i numeri e quando usare le costanti nominate. Non farlo perché un libro lo dice - finché non capisci perché lo dice.

    
risposta data 12.09.2018 - 01:18
fonte
0

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

Quindi, anziché creare una costante, vorrei (nel metodo del codice pulito) creare un metodo per dare un nome o chiarire che cosa il condizionale è destinato a rilevare (e incapsulare come 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;
}
    
risposta data 21.11.2018 - 12:46
fonte

Leggi altre domande sui tag