Was ist eine magische Zahl?
Warum sollte es vermieden werden?
Gibt es Fälle, in denen dies angemessen ist?
Was ist eine magische Zahl?
Warum sollte es vermieden werden?
Gibt es Fälle, in denen dies angemessen ist?
Antworten:
Eine magische Zahl ist eine direkte Verwendung einer Zahl im Code.
Zum Beispiel, wenn Sie (in Java) haben:
public class Foo {
public void setPassword(String password) {
// don't do this
if (password.length() > 7) {
throw new InvalidArgumentException("password");
}
}
}
Dies sollte überarbeitet werden, um:
public class Foo {
public static final int MAX_PASSWORD_SIZE = 7;
public void setPassword(String password) {
if (password.length() > MAX_PASSWORD_SIZE) {
throw new InvalidArgumentException("password");
}
}
}
Es verbessert die Lesbarkeit des Codes und ist einfacher zu warten. Stellen Sie sich den Fall vor, in dem ich die Größe des Kennwortfelds in der GUI festgelegt habe. Wenn ich eine magische Zahl verwende, muss ich mich bei jeder Änderung der maximalen Größe an zwei Codepositionen ändern. Wenn ich einen vergesse, führt dies zu Inkonsistenzen.
Das JDK ist voll von Beispielen , wie in Integer
, Character
und Math
Klassen.
PS: Statische Analysetools wie FindBugs und PMD erkennen die Verwendung magischer Zahlen in Ihrem Code und schlagen das Refactoring vor.
TRUE
/ FALSE
) sind
Eine magische Zahl ist ein fest codierter Wert, der sich zu einem späteren Zeitpunkt ändern kann, der jedoch schwer zu aktualisieren ist.
Angenommen, Sie haben eine Seite, auf der die letzten 50 Bestellungen auf einer Übersichtsseite "Ihre Bestellungen" angezeigt werden. 50 ist hier die magische Zahl, da sie nicht durch Standard oder Konvention festgelegt wurde, sondern eine Zahl, die Sie aus den in der Spezifikation angegebenen Gründen erfunden haben.
Jetzt haben Sie die 50 an verschiedenen Stellen - Ihr SQL-Skript ( SELECT TOP 50 * FROM orders
), Ihre Website (Ihre letzten 50 Bestellungen), Ihr Bestell-Login (for (i = 0; i < 50; i++)
) und möglicherweise vielen anderen Orten.
Was passiert nun, wenn jemand beschließt, 50 auf 25 zu ändern? oder 75? oder 153? Sie müssen jetzt die 50 an allen Stellen ersetzen, und es ist sehr wahrscheinlich, dass Sie sie verpassen. Suchen / Ersetzen funktioniert möglicherweise nicht, da 50 für andere Zwecke verwendet werden kann und das blinde Ersetzen von 50 durch 25 andere schlimme Nebenwirkungen haben kann (z. B. IhreSession.Timeout = 50
Anruf, der ebenfalls auf 25 eingestellt ist und Benutzer beginnen, zu häufige Zeitüberschreitungen zu melden).
Außerdem kann der Code schwer zu verstehen sein, dh " if a < 50 then bla
" - wenn Sie mitten in einer komplizierten Funktion darauf stoßen, fragen sich andere Entwickler, die mit dem Code nicht vertraut sind, möglicherweise "WTF ist 50 ???"
Deshalb ist es am besten, solche mehrdeutigen und willkürlichen Zahlen an genau einer Stelle zu haben - " const int NumOrdersToDisplay = 50
", weil dadurch der Code besser lesbar wird (")if a < NumOrdersToDisplay
", bedeutet dies auch, dass Sie ihn nur an einer genau definierten Stelle ändern müssen.
Orte, an denen magische Zahlen angemessen sind, sind alles, was durch einen Standard definiert wird, dh SmtpClient.DefaultPort = 25
oder TCPPacketSize = whatever
(nicht sicher, ob dies standardisiert ist). Auch alles, was nur innerhalb einer Funktion definiert ist, ist möglicherweise akzeptabel, dies hängt jedoch vom Kontext ab.
SmtpClient.DefaultPort = 25
ist möglicherweise deutlich er als SmtpClient.DefaultPort = DEFAULT_SMTP_PORT
.
25
gesamten Anwendung nach etwas suchen und sicherstellen, dass Sie nur die Vorkommen ändern 25
, die für den SMTP-Port gelten, nicht die 25er, die z. B. die Breite einer Tabellenspalte oder die Zahl sind von Datensätzen, die auf einer Seite angezeigt werden sollen.
IANA
.
Haben Sie sich den Wikipedia-Eintrag für magische Zahlen angesehen?
Es wird ein wenig detailliert auf alle Arten eingegangen, wie auf die magische Zahl Bezug genommen wird. Hier ist ein Zitat über magische Zahlen als schlechte Programmierpraxis
Der Begriff magische Zahl bezieht sich auch auf die schlechte Programmierpraxis, Zahlen ohne Erklärung direkt im Quellcode zu verwenden. In den meisten Fällen ist es dadurch schwieriger, Programme zu lesen, zu verstehen und zu warten. Obwohl die meisten Hilfslinien eine Ausnahme für die Zahlen Null und Eins machen, ist es eine gute Idee, alle anderen Zahlen im Code als benannte Konstanten zu definieren.
Magie: Unbekannte Semantik
Symbolische Konstante -> Bietet sowohl die richtige semantische als auch den richtigen Kontext für die Verwendung
Semantik: Die Bedeutung oder der Zweck einer Sache.
"Erstellen Sie eine Konstante, benennen Sie sie nach der Bedeutung und ersetzen Sie die Zahl durch diese." - Martin Fowler
Erstens sind magische Zahlen nicht nur Zahlen. Jeder Grundwert kann "Magie" sein. Grundwerte sind manifestierte Entitäten wie Ganzzahlen, Real, Doubles, Floats, Datumsangaben, Zeichenfolgen, Boolesche Werte, Zeichen usw. Das Problem ist nicht der Datentyp, sondern der "magische" Aspekt des Werts, wie er in unserem Codetext erscheint.
Was meinen wir mit "Magie"? Um genau zu sein: Mit "Magie" wollen wir auf die Semantik (Bedeutung oder Zweck) des Wertes im Kontext unseres Codes verweisen; dass es unbekannt, unerkennbar, unklar oder verwirrend ist. Dies ist der Begriff "Magie". Ein Grundwert ist keine Magie, wenn seine semantische Bedeutung oder sein Zweck schnell und einfach aus dem Surround-Kontext ohne spezielle Hilfswörter (z. B. symbolische Konstante) schnell und einfach bekannt, klar und verständlich (nicht verwirrend) ist.
Daher identifizieren wir magische Zahlen, indem wir die Fähigkeit eines Codelesers messen, die Bedeutung und den Zweck eines Grundwerts aus seinem umgebenden Kontext zu kennen, klar zu sein und zu verstehen. Je weniger bekannt, weniger klar und verwirrter der Leser ist, desto "magischer" ist der Grundwert.
Wir haben zwei Szenarien für unsere magischen Grundwerte. Nur die zweite ist für Programmierer und Code von vorrangiger Bedeutung:
Eine übergreifende Abhängigkeit von "Magie" besteht darin, dass der einzelne Grundwert (z. B. Zahl) keine allgemein bekannte Semantik (wie Pi) hat, sondern eine lokal bekannte Semantik (z. B. Ihr Programm), die aus dem Kontext nicht ganz klar hervorgeht oder missbraucht werden könnte in guten oder schlechten Kontexten.
Die Semantik der meisten Programmiersprachen erlaubt es uns nicht, einzelne Grundwerte zu verwenden, außer (vielleicht) als Daten (dh Datentabellen). Wenn wir auf "magische Zahlen" stoßen, tun wir dies im Allgemeinen in einem Kontext. Daher die Antwort auf
"Ersetze ich diese magische Zahl durch eine symbolische Konstante?"
ist:
"Wie schnell können Sie die semantische Bedeutung der Zahl (ihren Zweck, dort zu sein) in ihrem Kontext beurteilen und verstehen?"
Mit diesem Gedanken können wir schnell erkennen, dass eine Zahl wie Pi (3.14159) keine "magische Zahl" ist, wenn sie in den richtigen Kontext gestellt wird (z. B. 2 x 3.14159 x Radius oder 2 * Pi * r). Hier wird die Nummer 3.14159 ohne die symbolische Konstantenkennung mental als Pi erkannt.
Aufgrund der Länge und Komplexität der Zahl ersetzen wir 3.14159 im Allgemeinen durch einen symbolischen Konstantenbezeichner wie Pi. Die Aspekte der Länge und Komplexität von Pi (verbunden mit einem Bedürfnis nach Genauigkeit) bedeuten normalerweise, dass die symbolische Kennung oder Konstante weniger fehleranfällig ist. Das Erkennen von "Pi" als Name ist einfach ein bequemer Bonus, aber nicht der Hauptgrund für die Konstante.
Wenn wir gängige Konstanten wie Pi beiseite lassen, konzentrieren wir uns hauptsächlich auf Zahlen mit speziellen Bedeutungen, die jedoch auf das Universum unseres Softwaresystems beschränkt sind. Eine solche Zahl könnte "2" sein (als ganzzahliger Grundwert).
Wenn ich die Nummer 2 alleine benutze, könnte meine erste Frage sein: Was bedeutet "2"? Die Bedeutung von "2" an sich ist unbekannt und ohne Kontext nicht erkennbar, so dass seine Verwendung unklar und verwirrend bleibt. Obwohl nur "2" in unserer Software aufgrund der Sprachsemantik nicht vorkommen wird, möchten wir sehen, dass "2" für sich genommen keine spezielle Semantik oder einen offensichtlichen Zweck hat, allein zu sein.
Stellen wir unsere einsame "2" in einen Kontext von: padding := 2
, wobei der Kontext ein "GUI-Container" ist. In diesem Zusammenhang bietet uns die Bedeutung von 2 (als Pixel oder andere grafische Einheit) eine schnelle Einschätzung der Semantik (Bedeutung und Zweck). Wir könnten hier anhalten und sagen, dass 2 in diesem Zusammenhang in Ordnung ist und wir nichts anderes wissen müssen. Vielleicht ist dies in unserem Software-Universum jedoch nicht die ganze Geschichte. Es steckt noch mehr dahinter, aber "padding = 2" als Kontext kann es nicht offenbaren.
Stellen wir uns weiter vor, dass 2 als Pixelauffüllung in unserem Programm in unserem gesamten System von der Sorte "default_padding" ist. Daher ist das Schreiben der Anweisung padding = 2
nicht gut genug. Der Begriff "Standard" wird nicht offenbart. Nur wenn ich schreibe: padding = default_padding
als Kontext und dann anderswo: default_padding = 2
Erkenne ich eine bessere und umfassendere Bedeutung (Semantik und Zweck) von 2 in unserem System vollständig.
Das obige Beispiel ist ziemlich gut, da "2" für sich genommen alles sein kann. Nur wenn wir den Bereich und den Bereich des Verstehens auf "mein Programm" beschränken, wobei 2 default_padding
in den GUI-UX-Teilen von "mein Programm" steht, machen wir endlich Sinn für "2" in seinem richtigen Kontext. Hier ist "2" eine "magische" Zahl, die default_padding
im Kontext der GUI UX von "my program" zu einer symbolischen Konstante herausgerechnet wird , damit sie default_padding
im größeren Kontext des einschließenden Codes so schnell wie möglich verwendet wird.
Somit ist jeder Grundwert, dessen Bedeutung (Semantik und Zweck) nicht ausreichend und schnell verstanden werden kann, ein guter Kandidat für eine symbolische Konstante anstelle des Grundwerts (z. B. magische Zahl).
Zahlen auf einer Skala können auch semantisch sein. Stellen Sie sich zum Beispiel vor, wir machen ein D & D-Spiel, in dem wir die Vorstellung eines Monsters haben. Unser Monsterobjekt hat eine Funktion namens life_force
, die eine ganze Zahl ist. Die Zahlen haben Bedeutungen, die ohne Worte zur Bedeutung nicht erkennbar oder klar sind. Wir beginnen also damit, willkürlich zu sagen:
Aus den obigen symbolischen Konstanten erhalten wir ein mentales Bild der Lebendigkeit, des Todes und der "Untotheit" (und möglicher Konsequenzen oder Konsequenzen) für unsere Monster in unserem D & D-Spiel. Ohne diese Wörter (symbolische Konstanten) bleiben uns nur die Zahlen von -10 .. 10
. Allein der Bereich ohne die Wörter lässt uns an einem Ort mit möglicherweise großer Verwirrung und möglicherweise mit Fehlern in unserem Spiel zurück, wenn verschiedene Teile des Spiels davon abhängen, was dieser Bereich von Zahlen für verschiedene Operationen wie attack_elves
oder bedeutet seek_magic_healing_potion
.
Daher möchten wir bei der Suche nach und dem Ersetzen von "magischen Zahlen" sehr zweckmäßige Fragen zu den Zahlen im Kontext unserer Software stellen und sogar darüber, wie die Zahlen semantisch miteinander interagieren.
Lassen Sie uns überprüfen, welche Fragen wir stellen sollten:
Sie könnten eine magische Zahl haben, wenn ...
Untersuchen Sie eigenständige manifest konstante Grundwerte in Ihrem Codetext. Stellen Sie jede Frage langsam und nachdenklich über jede Instanz eines solchen Wertes. Betrachten Sie die Stärke Ihrer Antwort. Oft ist die Antwort nicht schwarz und weiß, sondern hat Schattierungen von missverstandener Bedeutung und Zweck, Lerngeschwindigkeit und Geschwindigkeit des Verstehens. Es muss auch überprüft werden, wie die Verbindung zur umgebenden Softwaremaschine hergestellt wird.
Am Ende ist die Antwort auf das Ersetzen die Antwort auf das Maß (in Ihrem Kopf) der Stärke oder Schwäche des Lesers, um die Verbindung herzustellen (z. B. "bekommen"). Je schneller sie Sinn und Zweck verstehen, desto weniger "Magie" haben Sie.
SCHLUSSFOLGERUNG: Ersetzen Sie Grundwerte nur dann durch symbolische Konstanten, wenn die Magie groß genug ist, um schwer zu erkennende Fehler aufgrund von Verwirrungen zu verursachen.
Eine magische Zahl ist eine Folge von Zeichen am Anfang eines Dateiformats oder eines Protokollaustauschs. Diese Nummer dient als Überprüfung der geistigen Gesundheit.
Beispiel: Öffnen Sie eine beliebige GIF-Datei, die Sie ganz am Anfang sehen: GIF89. "GIF89" ist die magische Zahl.
Andere Programme können die ersten Zeichen einer Datei lesen und GIFs richtig identifizieren.
Die Gefahr besteht darin, dass zufällige Binärdaten dieselben Zeichen enthalten können. Aber es ist sehr unwahrscheinlich.
Mit dem Protokollaustausch können Sie schnell feststellen, dass die aktuelle Nachricht, die an Sie weitergeleitet wird, beschädigt oder ungültig ist.
Magische Zahlen sind immer noch nützlich.
Bei der Programmierung ist eine "magische Zahl" ein Wert, der einen symbolischen Namen erhalten sollte, aber stattdessen als Literal in den Code eingefügt wurde, normalerweise an mehr als einer Stelle.
Es ist aus demselben Grund schlecht, aus dem SPOT (Single Point of Truth) gut ist: Wenn Sie diese Konstante später ändern möchten, müssen Sie Ihren Code durchsuchen, um jede Instanz zu finden. Es ist auch schlecht, weil anderen Programmierern möglicherweise nicht klar ist, was diese Zahl darstellt, daher die "Magie".
Menschen gehen manchmal die Eliminierung magischer Zahlen weiter, indem sie diese Konstanten in separate Dateien verschieben, um als Konfiguration zu fungieren. Dies ist manchmal hilfreich, kann aber auch zu mehr Komplexität führen, als es wert ist.
(foo[i]+foo[i+1]+foo[i+2]+1)/3
viel schneller als eine Schleife ausgewertet werden. Wenn man 3
den Code ersetzen würde, ohne den Code als Schleife neu zu schreiben, könnte jemand, der als ITEMS_TO_AVERAGE
definiert angesehen wird, 3
ihn ändern 5
und den Code durchschnittlich mehr Elemente haben lassen. Im Gegensatz dazu würde jemand, der den Ausdruck mit dem Literal betrachtete 3
, erkennen, dass dies 3
die Anzahl der Elemente darstellt, die zusammen summiert werden.
Eine magische Zahl kann auch eine Zahl mit einer speziellen, fest codierten Semantik sein. Zum Beispiel habe ich einmal ein System gesehen, in dem Datensatz-IDs> 0 normal behandelt wurden, 0 selbst "neuer Datensatz" war, -1 "dies ist der Stamm" und -99 "dies wurde im Stamm erstellt". 0 und -99 würden dazu führen, dass der WebService eine neue ID bereitstellt.
Das Schlechte daran ist, dass Sie ein Leerzeichen (das von vorzeichenbehafteten Ganzzahlen für Datensatz-IDs) für spezielle Fähigkeiten wiederverwenden. Vielleicht möchten Sie nie einen Datensatz mit der ID 0 oder mit einer negativen ID erstellen, aber selbst wenn nicht, könnte jede Person, die entweder den Code oder die Datenbank betrachtet, darauf stoßen und zunächst verwirrt sein. Es versteht sich von selbst, dass diese besonderen Werte nicht gut dokumentiert waren.
Wohl 22, 7, -12 und 620 zählen auch als magische Zahlen. ;-);
Ein Problem, das bei der Verwendung magischer Zahlen nicht erwähnt wurde ...
Wenn Sie sehr viele davon haben, stehen die Chancen ziemlich gut, dass Sie zwei verschiedene Zwecke haben , für die Sie magische Zahlen verwenden, bei denen die Werte zufällig gleich sind.
Und dann müssen Sie den Wert ändern ... für nur einen Zweck.
Ich gehe davon aus, dass dies eine Antwort auf meine Antwort auf Ihre frühere Frage ist. Bei der Programmierung ist eine magische Zahl eine eingebettete numerische Konstante, die ohne Erklärung erscheint. Wenn es an zwei verschiedenen Stellen angezeigt wird, kann dies dazu führen, dass eine Instanz geändert wird und keine andere. Aus diesen beiden Gründen ist es wichtig, die numerischen Konstanten außerhalb der Orte, an denen sie verwendet werden, zu isolieren und zu definieren.
Ich habe den Begriff "magische Zahl" immer anders verwendet, als einen obskuren Wert, der in einer Datenstruktur gespeichert ist und als schnelle Gültigkeitsprüfung überprüft werden kann. Zum Beispiel enthalten gzip-Dateien 0x1f8b08 als ihre ersten drei Bytes, Java-Klassendateien beginnen mit 0xcafebabe usw.
In Dateiformaten sind häufig magische Zahlen eingebettet, da Dateien ziemlich promisku gesendet werden können und keine Metadaten darüber verloren gehen, wie sie erstellt wurden. Manchmal werden magische Zahlen jedoch auch für speicherinterne Datenstrukturen wie ioctl () -Aufrufe verwendet.
Eine schnelle Überprüfung der magischen Zahl vor der Verarbeitung der Datei oder Datenstruktur ermöglicht es, Fehler frühzeitig zu signalisieren, anstatt die möglicherweise langwierige Verarbeitung zu durchlaufen, um anzukündigen, dass die Eingabe vollständig war.
Es ist erwähnenswert, dass Sie manchmal nicht konfigurierbare "fest codierte" Nummern in Ihrem Code haben möchten. Es gibt eine Reihe berühmter Methoden , darunter 0x5F3759DF, die im optimierten inversen Quadratwurzel-Algorithmus verwendet werden.
In den seltenen Fällen, in denen ich die Notwendigkeit finde, solche magischen Zahlen zu verwenden, setze ich sie in meinem Code als Konstante und dokumentiere, warum sie verwendet werden, wie sie funktionieren und woher sie stammen.
Was ist mit der Initialisierung einer Variablen am Anfang der Klasse mit einem Standardwert? Zum Beispiel:
public class SomeClass {
private int maxRows = 15000;
...
// Inside another method
for (int i = 0; i < maxRows; i++) {
// Do something
}
public void setMaxRows(int maxRows) {
this.maxRows = maxRows;
}
public int getMaxRows() {
return this.maxRows;
}
In diesem Fall ist 15000 eine magische Zahl (laut CheckStyles). Für mich ist es in Ordnung, einen Standardwert festzulegen. Ich möchte nicht tun müssen:
private static final int DEFAULT_MAX_ROWS = 15000;
private int maxRows = DEFAULT_MAX_ROWS;
Erschwert das das Lesen? Ich habe das erst in Betracht gezogen, als ich CheckStyles installiert habe.
static final
Konstanten sind übertrieben, wenn Sie sie in einer Methode verwenden. Eine final
Variable, die oben in der Methode deklariert ist, ist meiner Meinung nach besser lesbar.
@ eed3si9n: Ich würde sogar vorschlagen, dass '1' eine magische Zahl ist. :-)
Ein Prinzip, das mit magischen Zahlen zusammenhängt, ist, dass jede Tatsache, mit der sich Ihr Code befasst, genau einmal deklariert werden sollte. Wenn Sie in Ihrem Code magische Zahlen verwenden (wie das Beispiel für die Kennwortlänge, das @marcio angegeben hat, können Sie diese Tatsache leicht duplizieren, und wenn Sie diese Tatsache verstehen, haben Sie ein Wartungsproblem.
factorial n = if n == BASE_CASE then BASE_VALUE else n * factorial (n - RECURSION_INPUT_CHANGE); RECURSION_INPUT_CHANGE = 1; BASE_CASE = 0; BASE_VALUE = 1
Was ist mit Rückgabevariablen?
Ich finde es besonders schwierig, gespeicherte Prozeduren zu implementieren .
Stellen Sie sich die nächste gespeicherte Prozedur vor (falsche Syntax, ich weiß, nur um ein Beispiel zu zeigen):
int procGetIdCompanyByName(string companyName);
Es gibt die ID des Unternehmens zurück, wenn es in einer bestimmten Tabelle vorhanden ist. Andernfalls wird -1 zurückgegeben. Irgendwie ist es eine magische Zahl. Einige der Empfehlungen, die ich bisher gelesen habe, besagen, dass ich wirklich so etwas entwerfen muss:
int procGetIdCompanyByName(string companyName, bool existsCompany);
Was sollte es übrigens zurückgeben, wenn das Unternehmen nicht existiert? Ok: es wird existesCompany als false setzen , aber auch -1 zurückgeben.
Eine weitere Option besteht darin, zwei separate Funktionen zu erstellen:
bool procCompanyExists(string companyName);
int procGetIdCompanyByName(string companyName);
Voraussetzung für die zweite gespeicherte Prozedur ist also, dass eine Firma existiert.
Aber ich habe Angst vor Parallelität, weil in diesem System ein Unternehmen von einem anderen Benutzer erstellt werden kann.
Das Fazit lautet übrigens: Was halten Sie von der Verwendung dieser Art von "magischen Zahlen", die relativ bekannt und sicher sind, um zu sagen, dass etwas nicht erfolgreich ist oder dass etwas nicht existiert?
Ein weiterer Vorteil des Extrahierens einer magischen Zahl als Konstante bietet die Möglichkeit, die Geschäftsinformationen klar zu dokumentieren.
public class Foo {
/**
* Max age in year to get child rate for airline tickets
*
* The value of the constant is {@value}
*/
public static final int MAX_AGE_FOR_CHILD_RATE = 2;
public void computeRate() {
if (person.getAge() < MAX_AGE_FOR_CHILD_RATE) {
applyChildRate();
}
}
}
const myNum = 22; const number = myNum / 11;
könnten meine 11 Personen oder Flaschen Bier oder ähnliches sein. Stattdessen würde ich 11 in eine Konstante ändern wie Einwohner.