Eliminierung magischer Zahlen: Wann ist es Zeit, "Nein" zu sagen?

36

Uns allen ist bewusst, dass magische Zahlen (fest codierte Werte) in Ihrem Programm Chaos anrichten können, insbesondere wenn es an der Zeit ist, einen Codeabschnitt ohne Kommentare zu ändern, aber wo ziehen Sie die Linie?

Wenn Sie beispielsweise eine Funktion haben, die die Anzahl der Sekunden zwischen zwei Tagen berechnet, ersetzen Sie diese

seconds = num_days * 24 * 60 * 60

mit

seconds = num_days * HOURS_PER_DAY * MINUTES_PER_HOUR * SECONDS_PER_MINUTE

Ab wann entscheiden Sie, dass es völlig offensichtlich ist, was der fest codierte Wert bedeutet, und lassen Sie es in Ruhe?

Oosterwal
quelle
2
Warum nicht ersetzt diese Berechnung mit einer Funktion oder Makro so Ihrem Code Enden aussehen wieseconds = CALC_SECONDS(num_days);
FrustratedWithFormsDesigner
15
TimeSpan.FromDays(numDays).Seconds;
Niemand
18
@oosterwal: Mit dieser Einstellung ( HOURS_PER_DAY will never need to be altered) werden Sie niemals für Software programmieren, die auf dem Mars bereitgestellt wird. : P
FrustratedWithFormsDesigner
23
Ich hätte die Anzahl der Konstanten auf SECONDS_PER_DAY = 86400 reduziert. Warum etwas berechnen, das sich nicht ändert?
JohnFx
17
Was ist mit Schaltsekunden?
John

Antworten:

40

Es gibt zwei Gründe, symbolische Konstanten anstelle von numerischen Literalen zu verwenden:

  1. Vereinfachung der Wartung, wenn sich die magischen Zahlen ändern. Dies gilt nicht für Ihr Beispiel. Es ist äußerst unwahrscheinlich, dass sich die Anzahl der Sekunden pro Stunde oder die Anzahl der Stunden pro Tag ändert.

  2. Zur Verbesserung der Lesbarkeit. Der Ausdruck "24 * 60 * 60" ist für fast jeden offensichtlich. "SECONDS_PER_DAY" ist auch, aber wenn Sie einen Fehler suchen, müssen Sie möglicherweise überprüfen, ob SECONDS_PER_DAY richtig definiert wurde. In der Kürze liegt der Wert.

Bei magischen Zahlen, die genau einmal vorkommen und vom Rest des Programms unabhängig sind, ist es Geschmackssache, ein Symbol für diese Zahl zu erstellen. Im Zweifelsfall erstellen Sie ein Symbol.

Mach das nicht:

public static final int THREE = 3;
Kevin Cline
quelle
3
+1 @ Kevin Cline: Ich stimme mit Ihrem Punkt über die Kürze einer Fehlerjagd überein. Der zusätzliche Vorteil der Verwendung einer benannten Konstante, insbesondere beim Debuggen, besteht darin, dass Sie, wenn festgestellt wird, dass eine Konstante falsch definiert wurde, nur einen Codeteil ändern müssen, anstatt ein gesamtes Projekt nach allen Vorkommen der falsch implementierten zu durchsuchen Wert.
Osterwal
40
Oder noch schlimmer:publid final int FOUR = 3;
gablin
3
Oh je, du musst mit dem gleichen Kerl gearbeitet haben, mit dem ich einmal gearbeitet habe.
quick_now
2
@gablin: Um fair zu sein, ist es für Pubs sehr nützlich, Deckel zu haben.
Alan Pearce
10
Ich habe folgendes gesehen: public static int THREE = 3;... Hinweis - nein final!
Stephen C
29

Ich würde die Regel einhalten, niemals magische Zahlen zu haben.

Während

seconds = num_days * 24 * 60 * 60

Ist die meiste Zeit perfekt lesbar, nachdem Sie drei oder vier Wochen lang 10 Stunden am Tag im Crunch-Modus codiert haben

seconds = num_days * HOURS_PER_DAY * MINUTES_PER_HOUR * SECONDS_PER_MINUTE

ist viel einfacher zu lesen.

Der Vorschlag von FrustratedWithFormsDesigner ist besser:

seconds = num_days * DAYS_TO_SECOND_FACTOR

oder noch besser

seconds = CONVERT_DAYS_TO_SECONDS(num_days)

Dinge hören auf, offensichtlich zu sein, wenn Sie sehr müde sind. Defensiv codieren .

Vitor Py
quelle
13
In einen Crunch-Modus zu gelangen, wie Sie es beschreiben, ist ein kontraproduktives Gegenmuster, das vermieden werden sollte. Programmierer erreichen eine Spitzenproduktivität bei etwa 35 bis 40 Stunden pro Woche.
btilly
4
@btilly Ich stimme dir voll und ganz zu. Dies geschieht jedoch häufig aufgrund externer Faktoren.
Vitor Py
3
Ich definiere allgemein Konstanten für Sekunde, Minute, Tag und Stunde. Wenn nichts anderes '30 * MINUTE 'ist wirklich einfach zu lesen und ich weiß, es ist eine Zeit, ohne darüber nachdenken zu müssen.
Zachary K
9
@btilly: Peak ist 35-40 Stunden oder ein Blutalkoholspiegel zwischen 0,129% und 0,138%. Ich habe es auf XKCD gelesen , also muss es wahr sein!
Osterwal
1
Wenn ich eine Konstante wie HOURS_PER_DAY sehen würde, würde ich sie löschen und Sie dann öffentlich vor Ihren Kollegen demütigen. Ok, vielleicht würde ich auf die öffentliche Demütigung verzichten, aber ich würde sie wahrscheinlich löschen.
Ed S.
8

Die Zeit, nein zu sagen, ist fast immer. In Zeiten, in denen es für mich einfacher ist, nur fest codierte Zahlen zu verwenden, ist dies beispielsweise beim Layout der Benutzeroberfläche der Fall. Die Erstellung einer Konstanten für die Positionierung jedes Steuerelements im Formular ist sehr umfangreich und anstrengend macht nicht viel aus. ... es sei denn, die Benutzeroberfläche ist dynamisch angeordnet oder verwendet relative Positionen zu einem Anker oder wird von Hand geschrieben. In diesem Fall ist es besser, einige sinnvolle Konstanten für das Layout zu definieren. Und wenn Sie hier oder dort einen Fudge-Faktor benötigen, um etwas "Genau Richtiges" auszurichten / zu positionieren, sollte dies ebenfalls definiert werden.

Aber in Ihrem Beispiel denke ich, dass das Ersetzen 24 * 60 * 60durch DAYS_TO_SECONDS_FACTORbesser ist.


Ich gebe zu, dass hartcodierte Werte auch dann in Ordnung sind, wenn der Kontext und die Verwendung vollständig klar sind. Dies ist jedoch ein Urteilsspruch ...

Beispiel:

Wie @rmx hervorhob, ist die Verwendung von 0 oder 1, um zu überprüfen, ob eine Liste leer ist, oder möglicherweise in den Grenzen einer Schleife, ein Beispiel für einen Fall, in dem der Zweck der Konstante sehr klar ist.

FrustratedWithFormsDesigner
quelle
2
Es ist normalerweise in Ordnung, 0oder 1ich rechne damit. if(someList.Count != 0) ...ist besser als if(someList.Count != MinListCount) .... Nicht immer, aber allgemein.
Niemand
2
@Dima: VS Forms Designer kümmert sich um all das. Wenn es Konstanten erzeugen will, ist das in Ordnung für mich. Aber ich gehe nicht auf den generierten Code ein und ersetze alle hartcodierten Werte durch Konstanten.
FrustratedWithFormsDesigner
4
Lassen Sie uns nicht den Code verwechseln, der von einem Tool generiert und verarbeitet werden soll, dessen Code jedoch für den menschlichen Gebrauch geschrieben wurde.
Biziclop
1
@FrustratedWithFormsDesigner wie @biziclop betonte, ist generierter Code ein ganz anderes Tier. Benannte Konstanten müssen unbedingt in Code verwendet werden, der von Personen gelesen und geändert wird. Generierter Code sollte, zumindest im Idealfall, überhaupt nicht verändert werden.
Dima
2
@FrustratedWithFormsDesigner: Was passiert, wenn ein bekannter Wert in Dutzenden von Dateien in Ihrem Programm fest programmiert ist und plötzlich geändert werden muss? Wenn Sie beispielsweise den Wert, der die Anzahl der Taktstriche pro Mikrosekunde für einen eingebetteten Prozessor darstellt, fest codieren, wird die Software auf ein Design portiert, bei dem die Anzahl der Taktstriche pro Mikrosekunde unterschiedlich ist. Wenn Ihr Wert etwas Allgemeines wie 8 gewesen wäre, könnte das Durchführen eines Suchens / Ersetzens für Dutzende von Dateien zu weiteren Problemen führen.
Osterwal
8

Stoppen Sie, wenn Sie der Zahl keine Bedeutung oder keinen Zweck zuordnen können.

seconds = num_days * HOURS_PER_DAY * MINUTES_PER_HOUR * SECONDS_PER_MINUTE

ist viel einfacher zu lesen als nur die Zahlen zu verwenden. (Es könnte zwar durch eine einzelne SECONDS_PER_DAYKonstante besser lesbar gemacht werden , aber es ist ein völlig separates Problem.)

Angenommen, ein Entwickler, der sich den Code ansieht, kann sehen, was er tut. Aber nehmen Sie nicht an, dass sie auch wissen warum. Wenn Ihre Konstante hilft, das Warum zu verstehen, versuchen Sie es. Wenn nicht, dann nicht.

Wenn Sie am Ende zu viele Konstanten haben, wie in einer Antwort vorgeschlagen, sollten Sie stattdessen eine externe Konfigurationsdatei verwenden, da Dutzende von Konstanten in einer Datei die Lesbarkeit nicht gerade verbessern.

biziclop
quelle
7

Ich würde wahrscheinlich zu Dingen wie "Nein" sagen:

#define HTML_END_TAG "</html>"

Und würde definitiv "nein" sagen zu:

#define QUADRATIC_DISCRIMINANT_COEF 4
#define QUADRATIC_DENOMINATOR_COEF  2
dan04
quelle
7

Eines der besten Beispiele, die ich gefunden habe, um die Verwendung von Konstanten für offensichtliche Dinge zu fördern, HOURS_PER_DAYist:

Wir haben berechnet, wie lange sich die Dinge in der Job-Warteschlange einer Person befanden. Die Anforderungen wurden lose definiert und der Programmierer 24an mehreren Stellen fest programmiert . Schließlich stellten wir fest, dass es nicht fair war, die Benutzer dafür zu bestrafen, dass sie 24 Stunden lang auf einem Problem saßen, obwohl sie eigentlich nur 8 Stunden am Tag arbeiteten. Als die Aufgabe kam, dieses Problem zu beheben UND herauszufinden, welche anderen Berichte das gleiche Problem haben könnten, war es ziemlich schwierig, den Code nach 24 zu durchsuchen. Es wäre viel einfacher gewesen, nach diesen zu suchenHOURS_PER_DAY

bkulyk
quelle
Oh nein, die Stunden pro Tag variieren je nachdem, ob Sie sich auf die Arbeitsstunden pro Tag beziehen (ich arbeite 7,5 BTW) oder auf die Stunden pro Tag. Um die Bedeutung der Konstante auf diese Weise zu ändern, möchten Sie den Namen der Konstante durch einen anderen Namen ersetzen. Ihr Punkt zur Suche leicht gemacht ist jedoch gültig.
gbjbaanb
2
Es scheint, dass in diesem Fall HOURS_PER_DAY auch nicht wirklich die gewünschte Konstante war. Die Möglichkeit, nach Namen zu suchen, ist jedoch ein großer Vorteil, selbst wenn (oder insbesondere wenn) Sie es an vielen Stellen in etwas anderes ändern müssen.
David K
4

Ich denke, solange die Zahl völlig konstant ist und sich nicht ändern kann, ist sie vollkommen akzeptabel. Also in Ihrem Fall seconds = num_days * 24 * 60 * 60ist es in Ordnung (vorausgesetzt natürlich, dass Sie nichts Dummes tun, wie diese Art der Berechnung in einer Schleife) und für die Lesbarkeit wahrscheinlich besser als seconds = num_days * HOURS_PER_DAY * MINUTES_PER_HOUR * SECONDS_PER_MINUTE.

Es ist schlecht, wenn man so etwas macht:

lineOffset += 24; // 24 lines to a page

Verwenden Sie stattdessen eine konstante Variable, auch wenn Sie keine Zeilen mehr in die Seite einfügen konnten oder nicht beabsichtigen, sie zu ändern, da sie eines Tages zurückkehren wird, um Sie zu verfolgen. Letztendlich geht es um Lesbarkeit und nicht um die Einsparung von 2 Berechnungszyklen auf der CPU. Dies ist nicht mehr 1978, als kostbare Bytes für alle ihren Wert gequetscht wurden.

Neil
quelle
2
Würde es für Sie akzeptabel sein, den unveränderlichen Wert 86400 fest zu codieren, anstatt die benannte Konstante SECONDS_PER_DAY zu verwenden? Wie können Sie sicherstellen, dass alle Vorkommen des Werts korrekt sind und beispielsweise keine 0 fehlen oder die 6 und 4 vertauschen?
Osterwal
Warum nicht: Sekunden = num_days * 86400? Das wird sich auch nicht ändern.
JeffO
2
Ich habe SECONDS_PER_DAY in beide Richtungen ausgeführt - mit dem Namen und der Nummer. Wenn Sie 2 Jahre später auf den Code zurückkommen, ist die genannte Nummer IMMER sinnvoller.
quick_now
2
Sekunden = num_days * 86400 ist mir nicht klar. Darauf kommt es letztendlich an. Wenn ich "seconds = num_days * 24 * 60 * 60" sehe, würde ich mich sofort fragen, warum ich sie getrennt habe und die Bedeutung wird offensichtlich, weil ich gegangen bin Sie sind Zahlen (daher sind sie konstant) und keine Variablen, für die ich bei weiteren Untersuchungen ihre Werte verstehen müsste, und wenn sie konstant sind.
Neil
1
Was die Leute oft nicht merken: Wenn Sie diesen lineOffset-Wert von 24 auf 25 ändern, müssen Sie Ihren gesamten Code durchgehen, um zu sehen, wo 24 verwendet wurde, und wenn es geändert werden muss, und dann alle Tage zu Stunden-Berechnungen Multiplikation mit 24 wirklich in die Quere kommen.
gnasher729
3
seconds = num_days * 24 * 60 * 60

Ist vollkommen in Ordnung. Dies sind keine magischen Zahlen, da sie sich nie ändern werden.

Alle Zahlen, die sich vernünftigerweise ändern können oder keine offensichtliche Bedeutung haben, sollten in Variablen eingegeben werden. Das bedeutet so ziemlich alles.

Carra
quelle
2
Wäre das seconds = num_days * 86400noch akzeptabel? Wenn ein solcher Wert mehrmals in vielen verschiedenen Dateien verwendet würde, wie würden Sie überprüfen, ob jemand nicht versehentlich seconds = num_days * 84600an einer oder zwei Stellen getippt hat ?
Osterwal
1
Das Schreiben von 86400 unterscheidet sich stark vom Schreiben von 24 * 60 * 60.
Carra
4
Natürlich wird sich das ändern. Nicht jeder Tag hat 86.400 Sekunden. Betrachten Sie beispielsweise die Sommerzeit. Einmal im Jahr haben einige Orte nur 23 Stunden an einem Tag, und an einem anderen Tag werden sie haben 25. puff Ihre Zahlen sind gebrochen.
Dave DeLong
1
@ Dave, exzellenter Punkt. Schaltsekunden existieren - en.wikipedia.org/wiki/Leap_second
Gutes Argument. Das Hinzufügen einer Funktion wäre ein Schutz, wenn Sie diese Ausnahmen jemals abfangen müssen.
Carra
3

Ich würde vermeiden, Konstanten (magische Werte) zu erstellen, um einen Wert von einer Einheit in eine andere umzuwandeln. Bei der Konvertierung bevorzuge ich einen sprechenden Methodennamen. In diesem Beispiel wäre dies zB DayToSeconds(num_days)intern. Die Methode benötigt keine magischen Werte, da die Bedeutung von "24" und "60" klar ist.

In diesem Fall würde ich niemals Sekunden / Minuten / Stunden verwenden. Ich würde nur TimeSpan / DateTime verwenden.

KayS
quelle
1

Verwenden Sie den Kontext als Parameter, um zu entscheiden

Wenn Sie beispielsweise eine Funktion mit dem Namen "berechne Sekunden zwischen: einem Tag und: einem anderen Tag" haben, brauchen Sie nicht viel zu erklären, was diese Zahlen tun, da der Funktionsname ziemlich repräsentativ ist.

Und eine andere Frage ist, welche Möglichkeiten gibt es, es anders zu berechnen? Manchmal gibt es viele Möglichkeiten, das Gleiche zu tun, um zukünftige Programmierer anzuleiten und ihnen zu zeigen, welche Methode Sie verwendet haben. Die Definition von Konstanten könnte dabei helfen, dies herauszufinden.

Guiman
quelle