Clean Code - Soll ich das Literal 1 in eine Konstante ändern?

14

Um magische Zahlen zu vermeiden, hören wir oft, dass wir einem Literal einen aussagekräftigen Namen geben sollten. Sowie:

//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;
}

Ich habe eine Dummy-Methode wie folgt:

Erklären Sie: Ich nehme an, ich habe eine Liste von Leuten, die bedient werden müssen, und standardmäßig geben wir 5 US-Dollar aus, um nur Lebensmittel zu kaufen, aber wenn wir mehr als eine Person haben, müssen wir Wasser und Lebensmittel kaufen, müssen wir mehr Geld ausgeben, vielleicht 6 US-Dollar. Ich werde meinen Code ändern, bitte konzentrieren Sie sich auf Literal 1 , meine Frage dazu.

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

}

Als ich meine Freunde bat, meinen Code zu überprüfen, sagte einer, dass die Angabe eines Namens für den Wert 1 zu einem saubereren Code führen würde, und der andere, dass wir hier keinen konstanten Namen benötigen, da der Wert für sich genommen von Bedeutung ist.

Meine Frage lautet also: Soll ich einen Namen für den Literalwert 1 angeben? Wann ist ein Wert eine magische Zahl und wann nicht? Wie kann ich den Kontext unterscheiden, um die beste Lösung auszuwählen?

Jack
quelle
Woher kommt personsund was beschreibt es? Ihr Code enthält keinerlei Kommentare, daher ist es schwierig zu erraten, was er tut.
Hubert Grzeskowiak
7
Klarer als 1 wird es nicht. Ich würde jedoch "Größe" in "Zählen" ändern. Und moneyService ist so dumm, es sollte in der Lage sein, zu entscheiden, wie viel Geld abhängig von der Personensammlung zurückgegeben werden soll. Also würde ich Personen an getMoney übergeben und es alle Ausnahmefälle aussortieren lassen.
Martin Maat
4
Sie können den logischen Ausdruck auch aus Ihrer if-Klausel in eine neue Methode extrahieren. Nennen Sie es vielleicht IsSinglePerson (). Auf diese Weise extrahieren Sie etwas mehr als nur diese eine Variable, aber machen die if-Klausel auch etwas lesbarer ...
selmaohneh
2
Vielleicht ist diese Logik besser für moneyService.getMoney () geeignet? Würde es jemals einen Moment geben, in dem Sie getMoney bei einer Person anrufen müssten? Aber ich stimme der allgemeinen Meinung zu, dass ich klar bin. Magische Zahlen sind Zahlen , wo Sie würden der Kopf zu fragen , wie der Programmierer an dieser Zahl kam zu kratzen .. alsoif(getErrorCode().equals(4095)) ...
Neil

Antworten:

23

Nein. In diesem Beispiel ist 1 absolut aussagekräftig.

Was aber, wenn persons.size () Null ist? Scheint seltsam, dass persons.getMoney()für 0 und 2 funktioniert, aber nicht für 1.

user949300
quelle
Ich stimme zu, dass 1 sinnvoll ist. Übrigens, danke, ich habe meine Frage aktualisiert. Sie können es wieder sehen, um meine Idee zu bekommen.
Jack
3
Ein Satz, den ich im Laufe der Jahre mehrmals gehört habe, besagt, dass magische Zahlen andere unbenannte Konstanten als 0 und 1 sind. Auch wenn ich mir Beispiele vorstellen kann, in denen diese Zahlen benannt werden sollten, ist es eine recht einfache Regel, 99% der Zahlen zu befolgen Zeit.
Baldrickk
@Baldrickk Manchmal sollten auch andere numerische Literale nicht hinter einem Namen versteckt sein.
Deduplikator
@ Deduplicator irgendwelche Beispiele in den Sinn kommen?
Baldrickk
@ Baldrickk Siehe amon .
Deduplicator
19

Warum enthält ein Codeteil diesen bestimmten Literalwert?

  • Hat dieser Wert in der Problemdomäne eine besondere Bedeutung ?
  • Oder ist dieser Wert nur ein Implementierungsdetail , bei dem dieser Wert eine direkte Konsequenz des umgebenden Codes ist?

Wenn der Literalwert eine Bedeutung hat, die sich nicht aus dem Kontext ergibt, ist es hilfreich, diesem Wert einen Namen über eine Konstante oder Variable zu geben. Wenn der ursprüngliche Kontext später vergessen wird, kann Code mit aussagekräftigen Variablennamen besser verwaltet werden. Denken Sie daran, dass das Publikum für Ihren Code nicht in erster Linie der Compiler ist (der Compiler wird gerne mit schrecklichem Code arbeiten), sondern zukünftige Betreuer dieses Codes - die es zu schätzen wissen, wenn der Code etwas selbsterklärend ist.

  • In Ihrem ersten Beispiel die Bedeutung von Literalen wie 34, 4, 5aus dem Zusammenhang nicht ersichtlich. Stattdessen haben einige dieser Werte in Ihrer Problemdomäne eine besondere Bedeutung. Es war daher gut, ihnen Namen zu geben.

  • In Ihrem zweiten Beispiel wird die Bedeutung des Buchstabens 1aus dem Kontext sehr deutlich. Die Eingabe eines Namens ist nicht hilfreich.

Tatsächlich kann es auch schlecht sein, Namen für offensichtliche Werte einzufügen, da sie den tatsächlichen Wert verbergen.

  • Dies kann Fehler verschleiern, wenn der benannte Wert geändert wurde oder falsch war, insbesondere wenn dieselbe Variable in nicht verwandten Codeteilen wiederverwendet wird.

  • Ein Teil des Codes funktioniert möglicherweise auch für einen bestimmten Wert, ist jedoch im allgemeinen Fall möglicherweise falsch. Durch die Einführung einer unnötigen Abstraktion ist der Code offensichtlich nicht mehr korrekt.

Es gibt keine Größenbeschränkung für "offensichtliche" Literale, da dies vollständig vom Kontext abhängt. ZB kann das Literal 1024im Kontext der Dateigrößenberechnung völlig offensichtlich sein, oder das Literal 31im Kontext einer Hash-Funktion oder das Literal padding: 0.5emim Kontext eines CSS-Stylesheets.

amon
quelle
8

Es gibt verschiedene Probleme mit diesem Codeteil, die sich übrigens wie folgt verkürzen lassen:

public List<Money> getMoneyByPersons() {
    return persons.size() == 1 ?
        moneyService.getMoneyIfHasOnePerson() :
        moneyService.getMoney(); 
}
  1. Es ist unklar, warum eine Person ein Sonderfall ist. Ich nehme an, dass es eine bestimmte Geschäftsregel gibt, die besagt, dass das Erhalten von Geld von einer Person sich grundlegend vom Erhalten von Geld von mehreren Personen unterscheidet. Ich muss jedoch in beide hineinschauen getMoneyIfHasOnePersonund getMoneyin der Hoffnung zu verstehen, warum es unterschiedliche Fälle gibt.

  2. Der Name getMoneyIfHasOnePersonsieht nicht richtig aus. Vom Namen würde ich erwarten, dass die Methode prüft, ob es eine einzelne Person gibt, und wenn dies der Fall ist, Geld von ihm bekommt; Sonst nichts tun. In Ihrem Code ist dies nicht der Fall (oder Sie führen die Bedingung zweimal aus).

  3. Gibt es einen Grund, List<Money>eher eine als eine Sammlung zurückzugeben?

Zurück zu Ihrer Frage, da unklar ist, warum es für eine Person eine Sonderbehandlung gibt, sollte die Ziffer 1 durch eine Konstante ersetzt werden, es sei denn , es gibt eine andere Möglichkeit, die Regeln explizit zu machen. Hier unterscheidet man sich nicht sehr von jeder anderen magischen Zahl. Sie könnten Geschäftsregeln haben, die besagen, dass die Sonderbehandlung für eine, zwei oder drei Personen oder nur für mehr als zwölf Personen gilt.

Wie kann ich den Kontext unterscheiden, um die beste Lösung auszuwählen?

Sie tun alles, was Ihren Code expliziter macht.

Beispiel 1

Stellen Sie sich den folgenden Code vor:

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

return this.processSequence(sequence);

Ist Null hier ein magischer Wert? Der Code ist ziemlich klar: Wenn die Sequenz keine Elemente enthält, verarbeiten wir ihn nicht und geben einen speziellen Wert zurück. Dieser Code kann aber auch so umgeschrieben werden:

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

return this.processSequence(sequence);

Hier nicht mehr konstant, und der Code ist noch klarer.

Beispiel 2

Nehmen Sie einen weiteren Code:

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

Es dauert nicht allzu lange, um zu verstehen, was es in Sprachen wie JavaScript tut, die es nicht haben round(value, precision) überladen sind.

Wenn Sie nun eine Konstante einführen möchten, wie würde sie heißen? Der nächste Begriff, den Sie bekommen können, ist Precision. So:

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

Verbessert es die Lesbarkeit? Es könnte sein. Hier ist der Wert einer Konstante eher begrenzt, und Sie können sich fragen, ob Sie das Refactoring wirklich durchführen müssen. Das Schöne daran ist, dass die Genauigkeit jetzt nur einmal deklariert wird. Wenn sie sich also ändert, riskieren Sie nicht, einen Fehler zu machen, wie zum Beispiel:

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

Ändern des Werts an einem Ort und Vergessen des Werts an dem anderen Ort.

Beispiel 3

Aus diesen Beispielen können Sie den Eindruck gewinnen, dass Zahlen in jedem Fall durch Konstanten ersetzt werden sollten . Das ist nicht wahr. In einigen Situationen führt eine Konstante nicht zu einer Verbesserung des Codes.

Nehmen Sie den folgenden Code:

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

Wenn Sie versuchen, Nullen durch eine Variable zu ersetzen, besteht die Schwierigkeit darin, einen aussagekräftigen Namen zu finden. Wie würdest du es nennen? ZeroPosition? Base? Default? Das Einführen einer Konstante hier würde den Code in keiner Weise verbessern. Es würde es etwas länger machen, und genau das.

Solche Fälle sind jedoch selten. Wenn Sie also eine Nummer im Code finden, bemühen Sie sich, herauszufinden, wie der Code überarbeitet werden kann. Fragen Sie sich, ob die Nummer eine geschäftliche Bedeutung hat. Wenn ja, ist eine Konstante obligatorisch. Wenn nein, wie würden Sie die Nummer nennen? Wenn Sie einen aussagekräftigen Namen finden, ist das großartig. Wenn nicht, haben Sie wahrscheinlich einen Fall gefunden, in dem die Konstante nicht erforderlich ist.

Arseni Mourzenko
quelle
Ich würde 3a hinzufügen: Verwenden Sie das Wort Geld nicht in Variablennamen, verwenden Sie Betrag, Guthaben oder dergleichen. Eine Sammlung von Geld macht keinen Sinn, eine Sammlung von Beträgen oder Guthaben. (OK, ich habe eine kleine Sammlung von ausländischem Geld (oder besser Münzen), aber das ist eine andere Sache, die nicht mit der Programmierung zu tun hat).
Bent
3

Sie können eine Funktion erstellen, die einen einzelnen Parameter verwendet und das vierte mal geteilt durch das fünffache zurückgibt. Dabei wird ein sauberer Alias ​​für die Funktionsweise angegeben, während das erste Beispiel verwendet wird.

Ich biete nur meine eigene gewohnte Strategie an, aber vielleicht lerne ich auch etwas.

Was ich denke ist.

// 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]);
    }
}  

Tut mir leid, wenn ich nicht in der Basis bin, aber ich bin nur ein Javascript-Entwickler. Ich bin mir nicht sicher, welche Komposition ich dafür verantwortlich gemacht habe. Ich denke nicht, dass alles in einem Array oder einer Liste enthalten sein muss, aber eine Länge würde viel Sinn ergeben. Und die * 4 würde die gute Konstante machen, da ihr Ursprung nebulös ist.

ewig
quelle
5
Aber was bedeuten diese Werte 4 und 5? Wenn sich einer von ihnen ändern muss, können Sie dann alle Orte finden, an denen die Nummer in einem ähnlichen Kontext verwendet wird, und diese Orte entsprechend aktualisieren? Das ist der springende Punkt in der ursprünglichen Frage.
Bart van Ingen Schenau
Ich denke, das erste Beispiel war ziemlich verdammt selbsterklärend, so dass es vielleicht nicht eines ist, das ich beantworten kann. Die Operation sollte sich in Zukunft nicht ändern, wenn Sie eine neue schreiben müssen, um das zu erreichen, was benötigt wird. Ich denke, Kommentare würden diesem Zweck am meisten nützen. Es ist ein einzeiliger Anruf, der in meiner Sprache reduziert werden soll. Wie wichtig ist es, für den Laien absolut verständlich zu machen?
etisdew
@BartvanIngenSchenau Die gesamte Funktion ist falsch benannt. Ich würde einen besseren Funktionsnamen bevorzugen, einen Kommentar mit einer Spezifikation, was die Funktion zurückgeben soll, und einen Kommentar, warum * 4/5 dies erreicht. Konstante Namen werden nicht wirklich benötigt.
gnasher729
@ gnasher729: Wenn die Konstanten genau einmal im Quellcode einer gut benannten, gut dokumentierten Funktion vorkommen, ist es möglicherweise nicht erforderlich, eine benannte Konstante zu verwenden. Sobald eine Konstante mehrmals mit derselben Bedeutung auftaucht, müssen Sie nicht herausfinden, ob all diese Instanzen des Literal 42 dasselbe bedeuten oder nicht, wenn Sie dieser Konstante einen Namen geben.
Bart van Ingen Schenau
Wenn Sie damit rechnen, müssen Sie möglicherweise den Wert yes ändern. Ich würde es jedoch in eine Konstante ändern, um die Variable darzustellen. Ich glaube nicht, dass dies der Fall ist, und dies sollte einfach eine Zwischenvariable sein, die im darüber liegenden Bereich liegt. Ich möchte nicht herausfinden und sagen, dass alles ein Parameter für eine Funktion ist, aber wenn es sich ändern kann, würde ich es selten zu einem internen Parameter für die Funktion oder den Objekt- / Strukturbereich machen, in dem Sie gerade daran arbeiten, diese Änderung vorzunehmen der globale Geltungsbereich allein.
etisdew
2

Diese Nummer 1, könnte es eine andere Nummer sein? Könnte es 2 oder 3 sein, oder gibt es logische Gründe, warum es 1 sein muss? Wenn es 1 sein muss, ist die Verwendung von 1 in Ordnung. Ansonsten können Sie eine Konstante definieren. Nennen Sie diese Konstante nicht EINS. (Ich habe das getan gesehen).

60 Sekunden in einer Minute - brauchen Sie eine Konstante? Nun, es sind 60 Sekunden, nicht 50 oder 70. Und jeder weiß es. Das kann also eine Nummer bleiben.

Pro Seite werden 60 Elemente gedruckt - diese Zahl hätte leicht 59 oder 55 oder 70 betragen können. Wenn Sie die Schriftgröße ändern, wird sie möglicherweise zu 55 oder 70. Hier ist also eher eine aussagekräftige Konstante gefragt.

Es ist auch eine Frage, wie klar die Bedeutung ist. Wenn Sie "Minuten = Sekunden / 60" schreiben, ist das klar. Wenn Sie "x = y / 60" schreiben, ist das nicht klar. Es müssen einige aussagekräftige Namen irgendwo.

Es gibt eine absolute Regel: Es gibt keine absoluten Regeln. Mit etwas Übung werden Sie herausfinden, wann Sie Zahlen verwenden müssen und wann Sie benannte Konstanten verwenden müssen. Tu es nicht, weil es in einem Buch steht - bis du verstehst, warum es das sagt.

gnasher729
quelle
"60 Sekunden in einer Minute ... und jeder weiß es. Das kann also eine Zahl bleiben." - Für mich kein stichhaltiges Argument. Was ist, wenn mein Code 200 Stellen enthält, an denen "60" verwendet wird? Wie finde ich die Stellen, an denen Sekunden für Minuten verwendet werden, im Vergleich zu anderen möglicherweise ebenfalls "natürlichen" Verwendungszwecken? - In der Praxis wage ich es jedoch auch minutes*60, manchmal zu verwenden , selbst hours*3600wenn ich es brauche, ohne zusätzliche Konstanten zu deklarieren. Seit Tagen würde ich wahrscheinlich schreiben d*24*3600oder d*24*60*60weil 86400es nahe am Rand ist, wo jemand diese magische Zahl nicht auf einen Blick erkennt.
JimmyB
@JimmyB Wenn Sie "60" an 200 Stellen in Ihrem Code verwenden, ist es sehr wahrscheinlich, dass die Lösung eine Funktion umgestaltet, anstatt eine Konstante einzuführen.
klutt
0

Ich habe eine ganze Menge Code gesehen, wie im (modifizierten) OP bei DB-Abrufen. Die Abfrage gibt eine Liste zurück, aber die Geschäftsregeln besagen, dass es nur ein Element geben kann. Und dann änderte sich natürlich etwas für "nur diesen einen Fall" in eine Liste mit mehr als einem Element. (Ja, ich habe ein paar Mal gesagt. Es ist fast so, als ob sie ... nm)

Anstatt also eine Konstante zu erstellen, würde ich (in einer sauberen Codemethode) eine Methode erstellen, um einen Namen zu vergeben oder um zu verdeutlichen, was die Bedingung erkennen soll (und umzufassen, wie sie es erkennt):

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;
}
Kristian H
quelle
Weit vorzuziehen, um die Handhabung zu vereinheitlichen. Eine Liste von n Elementen kann einheitlich behandelt werden, unabhängig davon, was n ist.
Deduplizierer
Ich habe gesehen, wenn es sich bei dem einzelnen Fall nicht tatsächlich um einen anderen (Grenz-) Wert handelt, als wenn derselbe Benutzer Teil einer Liste mit n Größen gewesen wäre. In einem gut gemachten OO ist dies möglicherweise kein Problem, aber im Umgang mit Legacy-Systemen ist eine gute OO-Implementierung nicht immer durchführbar. Das Beste, was wir bekommen haben, war eine vernünftige OO-Fassade in der DAO-Logik.
Kristian H