Was ist los mit magischen Saiten?

164

Als erfahrener Softwareentwickler habe ich gelernt, magische Fäden zu meiden.

Mein Problem ist, dass es so lange her ist, seit ich sie benutzt habe, dass ich die meisten Gründe dafür vergessen habe. Infolgedessen kann ich meinen weniger erfahrenen Kollegen nicht erklären, warum sie ein Problem sind.

Welche objektiven Gründe gibt es, sie zu vermeiden? Welche Probleme verursachen sie?

Kramii
quelle
38
Was ist eine magische Saite? Das Gleiche wie magische Zahlen ?
Laiv
14
@Laiv: Sie ähneln den magischen Zahlen, ja. Mir gefällt die Definition unter deviq.com/magic-strings : "Magische Zeichenfolgen sind Zeichenfolgenwerte, die direkt im Anwendungscode angegeben werden und sich auf das Verhalten der Anwendung auswirken." (Die Definition bei en.wikipedia.org/wiki/Magic_string ist nicht das, was ich überhaupt im Sinn habe)
Kramii
17
das ist lustig, ich habe gelernt zu verabscheuen ... später Mit welchen Argumenten kann ich meine Junioren überzeugen ... Die unendliche Geschichte :-). Ich würde nicht versuchen zu "überzeugen", ich würde lieber alleine lernen lassen. Nichts ist länger als eine Lektion / Idee, die Sie aus eigener Erfahrung gewonnen haben. Was Sie versuchen zu tun, ist indoktrinieren . Tun Sie das nicht, es sei denn, Sie möchten ein Team von Lemmingen.
Laiv
15
@Laiv: Ich würde gerne Menschen aus ihren eigenen Erfahrungen lernen lassen, aber das ist leider keine Option für mich. Ich arbeite für ein öffentlich finanziertes Krankenhaus, in dem subtile Fehler die Patientenversorgung beeinträchtigen können und in dem wir uns keine vermeidbaren Wartungskosten leisten können.
Kramii
6
@DavidArno, genau das macht er, indem er diese Frage stellt.
user56834

Antworten:

212
  1. In einer kompilierten Sprache wird der Wert einer magischen Zeichenfolge beim Kompilieren nicht überprüft . Wenn die Zeichenfolge mit einem bestimmten Muster übereinstimmen muss, müssen Sie das Programm ausführen, um sicherzustellen, dass es zu diesem Muster passt. Wenn Sie beispielsweise eine Aufzählung verwendet haben, ist der Wert zum Zeitpunkt der Kompilierung mindestens gültig, auch wenn es sich möglicherweise um den falschen Wert handelt.

  2. Wenn eine magische Zeichenfolge an mehreren Stellen geschrieben wird , müssen Sie sie alle ohne Sicherheit ändern (z. B. Fehler bei der Kompilierung). Dem kann entgegengewirkt werden, indem nur an einer Stelle deklariert und die Variable erneut verwendet wird.

  3. Tippfehler können schwerwiegende Fehler werden. Wenn Sie eine Funktion haben:

    func(string foo) {
        if (foo == "bar") {
            // do something
        }
    }
    

    und jemand tippt versehentlich:

    func("barr");
    

    Dies ist umso schlimmer, je seltener oder komplexer die Zeichenfolge ist, insbesondere wenn Sie Programmierer haben, die mit der Muttersprache des Projekts nicht vertraut sind.

  4. Magic Strings sind selten selbstdokumentierend. Wenn Sie eine Zeichenfolge sehen, sagt dies nichts darüber aus, was die Zeichenfolge sonst sein könnte / sollte. Sie müssen wahrscheinlich in die Implementierung schauen, um sicherzugehen, dass Sie die richtige Zeichenfolge ausgewählt haben.

    Diese Art der Implementierung ist undicht und erfordert entweder externe Dokumentation oder Zugriff auf den Code, um zu verstehen, was geschrieben werden soll, zumal er zeichengenau sein muss (wie in Punkt 3).

  5. Neben den Funktionen zum Suchen von Zeichenfolgen in IDEs gibt es eine kleine Anzahl von Tools, die das Muster unterstützen.

  6. Sie können zufällig dieselbe magische Schnur an zwei Stellen verwenden, wenn es sich wirklich um verschiedene Dinge handelt. Wenn Sie also ein Suchen und Ersetzen durchgeführt und beide geändert haben, könnte eine davon brechen, während die andere funktioniert.

Erdrik Ironrose
quelle
34
Zum ersten Argument: TypeScript ist eine kompilierte Sprache, mit der Zeichenfolgenliterale typüberprüft werden können. Dies macht auch Argument zwei bis vier ungültig. Daher sind nicht die Zeichenfolgen selbst das Problem, sondern die Verwendung eines Typs, der zu viele Werte zulässt. Dieselbe Überlegung kann auf die Verwendung magischer Ganzzahlen für Aufzählungen angewendet werden.
Yogu
11
Da ich keine Erfahrung mit TypeScript habe, werde ich dort auf Ihr Urteil zurückgreifen. Was ich dann sagen würde, ist, dass ungeprüfte Zeichenfolgen (wie bei allen Sprachen, die ich verwendet habe) das Problem sind.
Erdrik Ironrose
23
@Yogu Typescript benennt nicht alle Zeichenfolgen für Sie um, wenn Sie den erwarteten statischen Zeichenfolgenliteraltyp ändern. Es werden Kompilierungsfehler angezeigt, die Ihnen helfen, alle zu finden, aber das ist nur eine teilweise Verbesserung gegenüber 2. Ich sage nicht, dass es alles andere als absolut erstaunlich ist (weil es das ist und ich die Funktion liebe), aber definitiv nicht Beseitigen Sie den Vorteil von Aufzählungen. Wann wir in unserem Projekt Aufzählungen verwenden und wann nicht, ist eine Art offene Frage, bei der wir uns nicht sicher sind. Beide Ansätze haben Ärger und Vorteile.
KRyan
30
Eine große, die ich nicht für Strings gesehen habe, sondern für Zahlen, die mit Strings passieren kann, ist, wenn Sie zwei magische Werte mit demselben Wert haben. Dann ändert sich einer von ihnen. Jetzt werden Sie durch den Code geführt, der den alten Wert in den neuen Wert ändert. Dies ist eine eigenständige Aufgabe. Sie führen jedoch auch EXTRA-Arbeiten aus, um sicherzustellen, dass Sie nicht die falschen Werte ändern. Mit konstanten Variablen müssen Sie nicht nur nicht manuell durchgehen, sondern Sie müssen sich auch keine Sorgen machen, dass Sie das Falsche geändert haben.
corsiKa
35
@Yogu Ich würde weiter argumentieren, dass wenn der Wert eines String-Literal zur Kompilierungszeit überprüft wird, es keine magische Zeichenfolge mehr ist . Zu diesem Zeitpunkt ist es nur ein normaler const / enum-Wert, der auf witzige Weise geschrieben wurde. Angesichts dieser Perspektive würde ich tatsächlich argumentieren, dass Ihr Kommentar Erdriks Punkte tatsächlich unterstützt , anstatt sie zu widerlegen.
GrandOpener
89

Der Gipfel dessen, was die anderen Antworten verstanden haben, ist nicht, dass "magische Werte" schlecht sind, sondern dass sie sein sollten:

  1. erkennbar als Konstanten definiert;
  2. innerhalb ihres gesamten Verwendungsbereichs nur einmal definiert werden (sofern architektonisch möglich);
  3. zusammen definiert, wenn sie eine Reihe von Konstanten bilden, die irgendwie miteinander verwandt sind;
  4. in der Anwendung, in der sie verwendet werden, auf einer angemessenen Ebene allgemein definiert werden; und
  5. so definiert werden, dass ihre Verwendung in unangemessenen Kontexten eingeschränkt wird (z. B. für die Typprüfung geeignet).

Was normalerweise akzeptable "Konstanten" von "magischen Werten" unterscheidet, ist ein Verstoß gegen eine oder mehrere dieser Regeln.

Konstanten erlauben es uns einfach, bestimmte Axiome unseres Codes auszudrücken.

Was mich zum Schluss bringt, dass eine übermäßige Verwendung von Konstanten (und damit eine übermäßige Anzahl von Annahmen oder Einschränkungen, die in Form von Werten ausgedrückt werden), auch wenn sie ansonsten die oben genannten Kriterien erfüllen (aber insbesondere wenn sie von diesen abweichen), Dies kann bedeuten, dass die zu entwickelnde Lösung nicht allgemein genug oder nicht gut strukturiert ist (und daher sprechen wir nicht mehr wirklich über die Vor- und Nachteile von Konstanten, sondern über die Vor- und Nachteile von gut strukturiertem Code).

Sprachen auf hoher Ebene haben Konstrukte für Muster in Sprachen auf niedrigerer Ebene, die Konstanten verwenden müssten. Die gleichen Muster können auch in der höheren Sprache verwendet werden, sollten es aber nicht sein.

Aber das kann ein Expertenurteil sein, das auf einem Eindruck aller Umstände beruht und wie eine Lösung aussehen sollte, und genau, wie dieses Urteil gerechtfertigt sein wird, wird stark vom Kontext abhängen. In der Tat kann es nicht nach einem allgemeinen Grundsatz gerechtfertigt sein, außer zu behaupten, "ich bin alt genug, um diese Art von Arbeit, mit der ich vertraut bin, schon besser gesehen zu haben"!

BEARBEITEN: Nachdem ich eine Bearbeitung akzeptiert, eine andere abgelehnt und jetzt meine eigene Bearbeitung durchgeführt habe, kann ich jetzt in Betracht ziehen, dass die Formatierung und der Interpunktionsstil meiner Regelliste ein für alle Mal festgelegt werden müssen, haha!

Steve
quelle
2
Ich mag diese Antwort. Immerhin ist "struct" (und jedes andere reservierte Wort) eine magische Zeichenfolge für den C-Compiler. Es gibt gute und schlechte Möglichkeiten, sie zu codieren.
Alfred Armstrong
6
Wenn beispielsweise jemand in Ihrem Code "X: = 898755167 * Z" sieht, weiß er wahrscheinlich nicht, was es bedeutet, und noch weniger wahrscheinlich, dass es falsch ist. Wenn sie jedoch "Speed_of_Light: constant Integer: = 299792456" sehen, schlägt jemand den korrekten Wert (und möglicherweise sogar einen besseren Datentyp) vor.
WGroleau
26
Einige Leute verpassen den Punkt vollständig und schreiben COMMA = "," anstelle von SEPARATOR = ",". Ersteres macht nichts klarer, während Letzteres die beabsichtigte Verwendung angibt und es Ihnen ermöglicht, das Trennzeichen später an einer einzigen Stelle zu ändern.
Marcus
1
@ Marcus, in der Tat! Es gibt natürlich einen Grund, einfache Literalwerte direkt zu verwenden. Wenn eine Methode beispielsweise einen Wert durch zwei dividiert, ist es möglicherweise klarer und einfacher, einfach zu schreiben value / 2, als wenn dieser Wert anderswo value / VALUE_DIVISORdefiniert 2ist. Wenn Sie eine Methode zur Verarbeitung von CSVs verallgemeinern möchten, möchten Sie wahrscheinlich, dass das Trennzeichen als Parameter übergeben und überhaupt nicht als Konstante definiert wird. Aber im Kontext ist alles eine Frage des Urteils - @ WGroleaus Beispiel für das SPEED_OF_LIGHTist etwas, das Sie explizit benennen möchten, aber nicht jedes Wort benötigt dies.
Steve
4
Die beste Antwort ist besser als diese Antwort, wenn Sie davon überzeugt werden müssen, dass magische Saiten eine "schlechte Sache" sind. Diese Antwort ist besser, wenn Sie wissen und akzeptieren, dass sie eine "schlechte Sache" sind und den besten Weg finden müssen, um die Bedürfnisse, denen sie dienen, auf wartbare Weise zu erfüllen.
corsiKa
34
  • Sie sind schwer zu verfolgen.
  • Um alle zu ändern, müssen möglicherweise mehrere Dateien in mehreren Projekten geändert werden (schwer zu pflegen).
  • Manchmal ist es schwer zu sagen, wozu sie gut sind, wenn man nur ihren Wert betrachtet.
  • Keine Wiederverwendung.
Jason
quelle
4
Was bedeutet "keine Wiederverwendung"?
Tschüss
7
Anstatt eine Variable / Konstante usw. zu erstellen und sie in Ihrem gesamten Projekt / Code wiederzuverwenden, erstellen Sie in jeder eine neue Zeichenfolge, die eine unnötige Duplizierung verursacht.
Jason
Punkt 2 und 4 sind also gleich?
Thomas
4
@ThomasMoors Nein, er spricht davon, wie man jedes Mal eine neue Saite bauen muss, wenn man eine bereits vorhandene magische Saite verwenden möchte. Punkt 2 handelt davon, die Saite selbst zu ändern
Pierre Arlaud
25

Beispiel aus dem wirklichen Leben: Ich arbeite mit einem Drittsystem, in dem "Entitäten" mit "Feldern" gespeichert sind. Grundsätzlich ein EAV- System. Da es ziemlich einfach ist, ein weiteres Feld hinzuzufügen, können Sie auf eines zugreifen, indem Sie den Namen des Felds als Zeichenfolge verwenden:

Field nameField = myEntity.GetField("ProductName");

(Beachten Sie die magische Zeichenfolge "ProductName")

Dies kann zu mehreren Problemen führen:

  • Ich muss auf externe Dokumentation verweisen, um zu wissen, dass "ProductName" überhaupt existiert und die genaue Schreibweise
  • Außerdem muss ich mich auf dieses Dokument beziehen, um den Datentyp dieses Felds zu ermitteln.
  • Tippfehler in dieser magischen Zeichenfolge werden erst abgefangen, wenn diese Codezeile ausgeführt wird.
  • Wenn sich jemand entscheidet, dieses Feld auf dem Server umzubenennen (schwierig, um Datenverlust zu verhindern, aber nicht unmöglich), kann ich meinen Code nicht einfach durchsuchen, um zu sehen, wo ich diesen Namen anpassen soll.

Daher bestand meine Lösung darin, Konstanten für diese Namen zu generieren, die nach Entitätstypen organisiert sind. So, jetzt kann ich verwenden:

Field nameField = myEntity.GetField(Model.Product.ProductName);

Es ist immer noch eine String-Konstante und kompiliert genau die gleiche Binärdatei, hat aber mehrere Vorteile:

  • Nachdem ich "Modell" eingegeben habe, zeigt meine IDE nur die verfügbaren Entitätstypen an, sodass ich einfach "Produkt" auswählen kann.
  • Dann liefert meine IDE nur die Feldnamen, die für diese Art von Entität verfügbar sind und auch auswählbar sind.
  • Die automatisch generierte Dokumentation zeigt die Bedeutung dieses Felds sowie den Datentyp, der zum Speichern seiner Werte verwendet wird.
  • Ausgehend von der Konstante kann meine IDE alle Stellen finden, an denen diese exakte Konstante verwendet wird (im Gegensatz zu ihrem Wert).
  • Tippfehler werden vom Compiler abgefangen. Dies gilt auch, wenn ein neues Modell (möglicherweise nach dem Umbenennen oder Löschen eines Feldes) zum Regenerieren der Konstanten verwendet wird.

Als nächstes auf meiner Liste: Verstecke diese Konstanten hinter generierten stark typisierten Klassen - dann ist auch der Datentyp gesichert.

Hans Keing
quelle
+1 Sie sprechen viele gute Punkte an, die sich nicht nur auf die Codestruktur beschränken: IDE-Unterstützung und Tools, die bei großen Projekten
lebensrettend sein können
Wenn einige Teile Ihres Entitätstyps statisch genug sind, dass es sich lohnt, einen konstanten Namen dafür zu definieren, ist es meiner Meinung nach geeigneter, nur ein geeignetes Datenmodell dafür zu definieren, damit Sie dies einfach tun können nameField = myEntity.ProductName;.
Lie Ryan
@LieRyan - Es war viel einfacher, einfache Konstanten zu generieren und vorhandene Projekte zu aktualisieren, um sie zu verwenden. Das heißt, ich bin arbeiten auf der Generierung von statischen Typen so kann ich genau , dass
Hans Ke st ing
9

Magische Saiten sind nicht immer schlecht , daher kann es sein, dass Sie keinen pauschalen Grund finden, sie zu vermeiden. (Mit "magische Zeichenkette" meine ich Zeichenkettenliteral als Teil eines Ausdrucks und nicht als Konstante definiert.)

In bestimmten Fällen sollten magische Zeichenfolgen vermieden werden:

  • Dieselbe Zeichenfolge wird mehrmals im Code angezeigt. Dies bedeutet, dass Sie an einer der Stellen einen Rechtschreibfehler haben könnten. Und es wird ein Ärger mit den Saitenwechseln. Wenn Sie die Zeichenfolge in eine Konstante umwandeln, vermeiden Sie dieses Problem.
  • Die Zeichenfolge kann sich unabhängig vom angezeigten Code ändern. Z.B. Wenn die Zeichenfolge dem Endbenutzer als Text angezeigt wird, ändert sie sich wahrscheinlich unabhängig von einer logischen Änderung. Das Trennen eines solchen Strings in ein separates Modul (oder eine externe Konfiguration oder Datenbank) erleichtert das unabhängige Ändern
  • Die Bedeutung der Zeichenfolge ist aus dem Kontext nicht ersichtlich. In diesem Fall erleichtert die Einführung einer Konstante das Verständnis des Codes.

Aber in einigen Fällen sind "magische Saiten" gut. Angenommen, Sie haben einen einfachen Parser:

switch (token.Text) {
  case "+":
    return a + b;
  case "-":
    return a - b;
  //etc.
}

Hier gibt es wirklich keine Magie, und keines der oben beschriebenen Probleme trifft zu. Es wäre meiner Meinung nach kein Vorteil, string Plus="+"usw. zu definieren . Halten Sie es einfach.

JacquesB
quelle
7
Ich denke, deine Definition von "magischer Kette" ist unzureichend, es muss ein Konzept des Versteckens / Verschleierns / Mysteriösen Machens geben. Ich würde das "+" und "-" in diesem Gegenbeispiel nicht als "Magie" bezeichnen, genauso wenig wie ich die Null als Magie in bezeichnen würde if (dx != 0) { grad = dy/dx; }.
Rupe
2
@Rupe: Ich stimme zu, aber das OP verwendet die Definition " Zeichenfolgenwerte, die direkt im Anwendungscode angegeben werden und sich auf das Verhalten der Anwendung auswirken. ", Bei der die Zeichenfolge nicht geheimnisvoll sein muss die Antwort.
JacquesB
7
In Bezug auf Ihr Beispiel habe ich switch-Anweisungen gesehen, die "+"und "-"mit TOKEN_PLUSund ersetzt haben TOKEN_MINUS. Jedes Mal, wenn ich es las, hatte ich das Gefühl, dass es schwieriger war, es zu lesen und zu debuggen. Auf jeden Fall ein Ort, an dem ich der Meinung bin, dass die Verwendung einfacher Zeichenfolgen besser ist.
Cort Ammon
2
Ich stimme zu, dass es Zeiten gibt, in denen magische Zeichenfolgen angebracht sind: Sie zu vermeiden, ist eine Faustregel, und für alle Faustregeln gelten Ausnahmen. Hoffentlich , wenn wir klar darüber , warum sie kann eine schlechte Sache sein, werden wir in der Lage sein , um intelligente Entscheidungen zu treffen, anstatt Dinge zu tun , weil (1) wir haben das nie verstehen es kann eine bessere Art und Weise sein, oder (2) wir Ein leitender Entwickler oder ein Kodierungsstandard hat uns angewiesen, die Dinge anders zu machen.
Kramii
2
Ich weiß nicht, was hier "Magie" ist. Diese sehen für mich wie einfache String-Literale aus.
Tchrist
6

So fügen Sie vorhandene Antworten hinzu:

Internationalisierung (i18n)

Wenn der auf dem Bildschirm anzuzeigende Text fest codiert und in Funktionsebenen eingebettet ist, wird es sehr schwierig, Übersetzungen dieses Textes in andere Sprachen bereitzustellen.

Einige Entwicklungsumgebungen (z. B. Qt) verarbeiten Übersetzungen durch Nachschlagen von einer Basissprachentextzeichenfolge in die übersetzte Sprache. Magic Strings können dies im Allgemeinen überstehen - bis Sie sich entscheiden, denselben Text an einer anderen Stelle zu verwenden, und einen Tippfehler erhalten. Selbst dann ist es sehr schwer zu finden, welche magischen Zeichenfolgen übersetzt werden müssen, wenn Sie Unterstützung für eine andere Sprache hinzufügen möchten.

Einige Entwicklungsumgebungen (z. B. MS Visual Studio) verwenden einen anderen Ansatz und erfordern, dass alle übersetzten Zeichenfolgen in einer Ressourcendatenbank gespeichert und anhand der eindeutigen ID dieser Zeichenfolge für das aktuelle Gebietsschema zurückgelesen werden. In diesem Fall kann Ihre Anwendung mit magischen Zeichenfolgen einfach nicht ohne größere Überarbeitung in eine andere Sprache übersetzt werden. Für eine effiziente Entwicklung müssen alle Textzeichenfolgen in die Ressourcendatenbank eingegeben und beim ersten Schreiben des Codes mit einer eindeutigen ID versehen werden. Danach ist die Eingabe relativ einfach. Der Versuch, dies nachträglich nachzufüllen, erfordert normalerweise einen sehr großen Aufwand (und ja, ich war dabei!). Es ist also viel besser, die Dinge von Anfang an richtig zu machen.

Graham
quelle
3

Dies ist nicht für alle eine Priorität, aber wenn Sie jemals in der Lage sein möchten, Kopplungs- / Kohäsionsmetriken für Ihren Code automatisiert zu berechnen, machen magische Zeichenfolgen dies nahezu unmöglich. Eine Zeichenfolge an einer Stelle verweist auf eine Klasse, eine Methode oder eine Funktion an einer anderen Stelle, und es gibt keine einfache, automatische Möglichkeit, durch einfaches Parsen des Codes festzustellen, ob die Zeichenfolge mit der Klasse / Methode / Funktion gekoppelt ist. Nur das zugrunde liegende Framework (z. B. Angular) kann feststellen, dass eine Verknüpfung besteht - und das nur zur Laufzeit. Um die Kopplungsinformationen selbst zu erhalten, muss Ihr Parser über die Basissprache hinaus, in der Sie codieren, alles über das verwendete Framework wissen.

Aber auch dies ist für viele Entwickler nicht wichtig.

user3511585
quelle