Wird jede Zahl im Code als "magische Zahl" betrachtet?

21

Also wird jede Zahl im Code, die wir als Argument an eine Methode senden, als magische Zahl betrachtet? Für mich sollte es nicht. Ich denke, wenn irgendeine Zahl ist, sagen wir, es ist für die minimale Länge des Benutzernamens und wir beginnen, "6" im Code zu verwenden ... dann haben wir ja ein Wartungsproblem und hier ist "6" eine magische Zahl ... aber Wenn wir eine Methode aufrufen, bei der eines ihrer Argumente beispielsweise eine Ganzzahl als i-tes Mitglied einer Auflistung akzeptiert, und dann "0" an diesen Methodenaufruf übergeben, sehe ich in diesem Fall diese "0" nicht als Magie Nummer. Was denkst du?

Blake
quelle
4
Was bedeutet in Ihrem Beispiel die 0?
Aaron Kurtzhals
2
In dem Fall, den Sie veranschaulichen, hat "0" keinerlei magische Eigenschaften.
Tulains Córdova
4
Alles außer 0,1 und 42 ist Magie
Mawg

Antworten:

43

Wenn die Bedeutung der Zahl im Kontext sehr klar ist, halte ich es nicht für ein "magisches Zahlenproblem".

Beispiel: Angenommen, Sie versuchen, die Teilzeichenfolge einer Zeichenfolge vom Anfang bis zu einem Token abzurufen, und der Code sieht folgendermaßen aus (imaginäre Sprache und Bibliothek):

s := substring(big_string, 0, findFirstOccurence(SOME_TOKEN, big_string));

In diesem Zusammenhang ist die Bedeutung der Zahl 0 klar genug. Ich nehme an, Sie könnten es definieren START_OF_SUBSTRINGund auf 0 setzen, aber in diesem Fall wäre es ein Overkill (obwohl es der richtige Ansatz wäre, wenn Sie wüssten, dass der Anfang Ihrer Teilzeichenfolge möglicherweise nicht 0 ist, aber das hängt von den Besonderheiten von ab deine Situation).

Ein anderes Beispiel könnte sein, wenn Sie versuchen festzustellen, ob eine Zahl gerade oder ungerade ist. Schreiben:

isEven := x % 2;

ist nicht so seltsam wie:

TWO := 2;
isEven := x % TWO;

Negative Zahlen testen als

MINUS_ONE := -1;
isNegativeInt := i <= MINUS_ONE;

fühlt sich auch komisch an für mich, ich würde viel lieber sehen

isNegativeInt := i <= -1;
FrustratedWithFormsDesigner
quelle
6
Um ein weiteres Beispiel zu geben, wäre es in Code, in dem Sie explizit mit Grad auf einem Kreis arbeiten, fair, eine Zahl 360zu verwenden, die eine vollständige Rotation kennzeichnet, mit dem Verständnis, dass die meisten Leute wissen, was das bedeutet (obwohl dies der Fall ist) ist ein Fall, in dem es nicht schaden würde , eine Konstante bereitzustellen)
KChaloux
11
KChaloux: Wenn ich könnte, würde ich Ihren Kommentar -1. 360 ist eine magische Zahl. Wenn 360 zufällig ein Wert für eine andere Konstante ist, haben Sie 2 Sätze für 360, die nicht zusammenhängen und nicht unterscheidbar sind. Junior kommt vorbei, gehen Sie zu "That is a magic number", suchen Sie global und ersetzen Sie 360 ​​durch "Degrees_in_Circle", führen Sie alle Einheiten- und Regressionstests durch, bestehen Sie alle - und korrigieren Sie den Code. Code jetzt ein
Hundefrühstück
4
@mattnz: Hoffentlich wird diese Art von umfangreicher Codeänderung schnell erkannt (hoffentlich während der Codeüberprüfung, wenn sie noch so jung ist), lange bevor sie jemals in Produktion ging. Ich denke, jemand, der das in diesem Zusammenhang tun würde, würde es wahrscheinlich auch 0im Zusammenhang mit meinem Teilstring-Beispiel ersetzen . In diesem Fall ist dies möglicherweise der geringste Schaden, den sie verursachen können. Es ist lange her, dass ich eine Codierung durchgeführt habe, die geometrische Berechnungen durchgeführt hat, aber im Allgemeinen waren die Werte 15, 30, 45, 60, 90, 180, 360 Konstanten, die akzeptiert wurden. Ich habe noch nie jemanden definieren gesehen FIFTEEN_DEGREES, ...
FrustratedWithFormsDesigner
5
@KChaloux Das Beispiel kann tatsächlich auseinanderfallen, wenn von Grad zu Radiant gewechselt wird. Um 360 drücken Sie 1 vollständige Umdrehung aus. Da es für denselben Wert mehrere Darstellungen gibt, sollte er herausgezogen werden. Insbesondere wenn man bedenkt, dass 360PI genauso aussehen könnte wie 2PI (180 Umdrehungen, aber am Ende immer noch in die gleiche Richtung), oder 360 Umdrehungen genauso wie 1 Umdrehung, aber die Nebenwirkungen können unterschiedlich sein.
Chris
14
Ein bisschen wie ein Strohmann, TWO und MINUS_ONE sind absolut schlecht, weil das Ersetzen einer magischen Zahl durch das Rendern im Text natürlich idiotisch ist. Der Name der Konstante muss ihre Bedeutung vermitteln. Mit der Ausnahme, dass es sich bei Ihren Beispielen um grundlegende Fakten zu Zahlen handelt, die eng mit diesen spezifischen Zahlen verknüpft sind.
Michael Borgwardt
17
bool hasApples = apples > 0;

Es ist offensichtlich, dass Null Abwesenheit bedeutet. Ich finde 0 leichter zu verstehen als eine Variable mit dem Namen "absenceValue".


for(int i=0; i < arr.length; i++)

Es ist offensichtlich, dass 0 die Startposition ist. Ich würde durch eine Variable mit dem Namen "firstPosition" verwirrt. Eine solche Variable würde mich fragen lassen, ob sich die Ausgangsposition ändern könnte.

mike30
quelle
14

Ich würde drei Schlüsselfaktoren vorschlagen, um zu entscheiden, ob etwas eine konstante Deklaration sein soll:

  1. Ist die Zahl etwas, das genau und prägnant darstellbar ist
  2. Gibt es plausible Szenarien, in denen sich der Wert ändern müsste, der Code jedoch nicht neu geschrieben werden müsste?
  3. Wäre jemand, der die Zahl sieht, geneigt, sie schneller oder weniger schnell zu erkennen als jemand, der eine benannte Konstante sieht?

So etwas wie pi sollte wahrscheinlich als benannte Konstante und nicht als numerisches Literal geschrieben werden, da ein numerisches Literal dazu neigt, unnötig wortreich, unnötig ungenau oder beides zu sein. Etwa die Anzahl der Slots in einem Cache sollte wahrscheinlich eine benannte Konstante sein (siehe Hinweis unten), damit der Cache erweitert werden kann, ohne den gesamten Code ändern zu müssen, der ihn verwendet. Dinge wie die Zahlen "4", "28" und "29" in der Anweisung if ((year % 4)==0) FebruaryDays = 29; else FebruaryDays = 28;sollten wahrscheinlich nicht als Konstanten bezeichnet werden, da der Ausdruck mit ziemlicher Sicherheit lesbarer ist als if ((year % YearsBetweenLeapYears)==0) FebruaryDays = FebruaryDaysInLeapYear; else FebruaryDays = FebruaryDaysInNonLeapYear;. Beachten Sie, dass die Verantwortlichen der Standards angegeben haben, dass die Länge von Februar 2100 in diesem Jahr nicht mit der obigen Formel übereinstimmt. Hindernis für die korrekte Behandlung solcher Daten (dh der Code wird nicht durch einen Ganzzahlüberlauf oder andere derartige Probleme ausgelöst).

Eine wichtige Einschränkung bei Regel Nr. 2 ist, dass Code in einigen Fällen auf hartcodierte Zahlen zurückgreifen kann, die nicht ohne weiteres durch eine benannte Konstante dargestellt werden können. Beispielsweise ist eine Methode, die ein Kreuzprodukt von zwei als diskrete Parameter übergebenen Vektoren berechnet, nur bei dreidimensionalen Vektoren sinnvoll. Die erforderliche Anzahl von Dimensionen ist kein Wert, der sinnvoll geändert werden könnte, ohne die Routine vollständig neu zu schreiben. Selbst wenn man einen möglichen Bedarf für die Berechnung des Kreuzprodukts dreier vierdimensionaler Vektoren voraussah, würde die Verwendung einer benannten Konstante für den Wert "3" wenig dazu beitragen, diesen Bedarf leichter zu befriedigen.

Superkatze
quelle
4

Dies ist, wie alle Prinzipien, eine Frage des Grades. Im Allgemeinen sind Zahlenliterale im Quellcode umso verdächtiger, je größer sie sind. Eine maximale Länge von 10 oder eine Speicheradresse von 0x587FB0 sind offensichtlich eine schlechte Praxis - es ist fast sicher, dass Sie diese Werte früher oder später mehrmals wiederholen müssen, wodurch das Risiko von Inkompatibilität und subtilen Fehlern besteht, die an anderen Stellen auftreten geändert.

0 ist am anderen Ende der Skala; es ist immer noch verdächtig, aber nicht ganz so viel. Verwenden Sie 0 als Sentinel-Wert? Dann sollten Sie wahrscheinlich stattdessen eine symbolische Konstante verwenden, nur weil die Konstante erklären kann , was sie bedeutet. Handelt es sich um eine tief verwurzelte kulturelle Vereinbarung wie "0 bedeutet erfolgreichen Abschluss"? Das ist wahrscheinlich in Ordnung. Bedeutet das "der erste Gegenstand in einer Sammlung"? Das mag harmlos sein, aber wenn es eine alternative Methode gibt, first()würde ich das wahrscheinlich vorziehen.

Kilian Foth
quelle
1
"Verwenden Sie 0 als Sentinel-Wert?" <- Können Sie hier erklären, was Sie mit "Sentinel" meinen? Ich kann keine passende Definition finden.
rory.ap
3

Jede unbenannte Zahl, die aus dem Kontext nicht sofort ersichtlich ist, ist eine magische Zahl. Es ist ein bisschen albern, Zahlen zu definieren, die eine Bedeutung haben, die sich unmittelbar aus dem Kontext ergibt.

In Django (Python Web Framework) kann ich ein Datenbankfeld mit einer unformatierten Zahl wie der folgenden definieren:

firstname = models.CharField(max_length=40)
middlename = models.CharField(max_length=40)
lastname =  models.CharField(max_length=40) 

Was ist klarer (und die empfohlene Praxis ) als sagen

MAX_LENGTH_NAME = 40
...
firstname = models.CharField(max_length=MAX_LENGTH_NAME)
middlename = models.CharField(max_length=MAX_LENGTH_NAME)
lastname =  models.CharField(max_length=MAX_LENGTH_NAME) 

da ich wohl nie die länge ändern muss (und immer mit der max_lengthdes feldes vergleichen kann ). Wenn ich die Länge des Felds nach der ersten Bereitstellung der Anwendung ändern muss, muss ich es in meinem Django-Code an genau einem Ort pro Feld ändern und dann zusätzlich eine Migration schreiben, um das Schema der Datenbank zu ändern. Wenn ich jemals zu Referenz muß max_lengtheine definierten Feld einer Art von Objekt, kann es es direkt tun - wenn diese Felder einen wurden die Definition der PersonKlasse, kann ich Person._meta.get_field('firstname').max_lengthdas erhaltenmax_lengthverwendet werden (die an einer Stelle definiert ist). Die Tatsache, dass dieselbe 40 für mehrere Felder verwendet wurde, ist irrelevant, da ich sie möglicherweise unabhängig voneinander ändern möchte. Die Länge des Vornamens sollte niemals von der Länge des Zwischennamens oder des Nachnamens abhängen. Sie sind separate Werte und können sich unabhängig voneinander ändern.

Häufig können Array-Indizes unbenannte Zahlen verwenden. Zum Beispiel, wenn ich eine CSV-Datei mit Daten habe, die ich in ein Python-Wörterbuch einfügen möchte, mit dem ersten Element in der Zeile als dem Wörterbuch, das keyich schreiben würde:

mydict = {}
for row in csv.reader(f):
    mydict[row[0]] = row[1:]

Klar, ich könnte index_column = 0etwas benennen und tun wie:

index_col = 0
mydict = {}
for row in csv.reader(f):
    mydict[row[index_col]] = row[:index_col] + row[index_col+1:]

oder schlimmer definieren after_index_col = index_col + 1, um das loszuwerden index_col+1, aber das macht den Code aus meiner Sicht nicht klarer. Auch wenn ich index_coleinen Namen gebe , lasse ich den Code besser funktionieren, auch wenn die Spalte nicht 0 ist (daher der row[:index_col] +Teil).

Dr. Jimbob
quelle
7
Eigentlich ist max_lngth=40vs. max_length=MAX_LENGTH_NAMEein klassisches Beispiel für eine magische Zahl, die schreit , ein Symbol zu sein. Der Tag wird kommen, an dem Sie 45 Zeichennamen unterstützen möchten, und jetzt ist jede Verwendung von "40" verdächtig und muss sorgfältig geprüft werden.
Ross Patterson
1
@ RossPatterson - Dies ist nicht C, wo wir ständig mit einer globalen Variable MAX_ARRAY_SIZE vergleichen, sondern mit einem anständigen Webframework. Der einzige Ort, an dem die magische Zahl angezeigt wird, ist der Ort, an dem Sie das Datenbankmodell deklarieren. alles andere wird mit diesem Wert verglichen (z. B. 40 steht nirgendwo anders im Code). Beachten Sie auch, dass Sie diese Variable nicht einfach ändern können, ohne Schema-Migrationen durchzuführen, da sie an eine Datenbank gebunden sind. Wenn ich ändern wollte, um 1-stellige Zweitnamen zu sagen, ist es sofort offensichtlich, an welcher Stelle im Code dies geändert werden 40soll 1. Man muss an den Kontext denken.
Dr. Jimbob
2
Entschuldigung, Sie liegen in zwei Punkten falsch. Zunächst stellte das OP eine Frage zu "Programmierpraktiken", in der keine Sprache angegeben ist. Sie sagten "Methode", nicht "Funktion", also nehmen wir etwas objektorientiertes an, aber das führt uns nicht aus dem Bereich der magisch nummerierten Daten heraus. Zweitens, wenn die magische Zahl in die Datenbank ( z. B. das Schema) eingebrannt wird , ist es noch schlimmer, sie im Code zu haben. Das Richtige ist, die magische Fast-A-Konstante aus ihrer Quelle abzurufen - entweder aus der Datenbank selbst oder aus einem Schemamodul, das alle Konstanten zentralisiert, die über die gesamte Lebensdauer des Codes variieren.
Ross Patterson