Darf sich der Wert einer Konstanten im Laufe der Zeit ändern?

28

Während der Entwicklungsphase gibt es bestimmte Variablen, die im selben Durchlauf korrigiert werden müssen, aber möglicherweise im Laufe der Zeit geändert werden müssen. Zum Beispiel ein boolean, um den Debug-Modus zu signalisieren, damit wir Dinge im Programm tun, die wir normalerweise nicht tun würden.

Ist es ein schlechter Stil, diese Werte in einer Konstanten zu enthalten, dh final static int CONSTANT = 0in Java? Ich weiß, dass eine Konstante während der Laufzeit gleich bleibt, aber soll sie auch während der gesamten Entwicklung gleich sein, außer natürlich ungeplanten Änderungen?

Ich habe nach ähnlichen Fragen gesucht, aber nichts gefunden, was genau zu meinen passt.

GregT
quelle
11
Ich bin neugierig, warum glaubst du, dass es ein schlechter Stil ist, diese zu ändern?
Vincent Savard
36
Sofern Sie physikalische Eigenschaften nicht mit Konstanten modellieren, deren mathematische Werte bekannt sind, kann sich alles zu einem bestimmten Zeitpunkt ändern.
Berin Loritsch
19
Software ist weich .
Erik Eidt
10
@ GregT Ich würde nicht zustimmen. finalgibt Ihnen eine vom Compiler erzwungene Garantie, dass das Programm den Wert nicht ändert. Darauf würde ich nicht verzichten, nur weil der Programmierer möglicherweise den im Quellcode zugewiesenen Wert ändern möchte.
Alexander - Reinstate Monica
10
Es ist nicht viel Zeit, eine vollständige Antwort zu formulieren, aber ich vermute, dass sich Ihre Kollegen nicht so sehr mit Konstanten befassen, sondern mit dem Code-Inlining von Konfigurationswerten, die sich möglicherweise als Konstanten manifestieren. ... Konstanten schützen Sie vor dummen Fehlern wie dem versehentlichen Zuweisen während gravitydes Spiels. Sie bedeuten nicht notwendigerweise, dass sie gravityauf jedem Planeten gleich sind ... Die gesunde Lösung besteht darin, gravityeine Konstante zu planeterstellen , diese jedoch zu Beginn des relevanten Bereichs aus einer Datei oder Datenbank zu ziehen .
Svidgen

Antworten:

6

In Java können statische Endkonstanten vom Compiler als ihre Werte in den Code kopiert werden, der sie verwendet . Infolgedessen wird die Konstante in diesem Code nicht aktualisiert, wenn Sie eine neue Version Ihres Codes freigeben und es eine Downstream-Abhängigkeit gibt, die die Konstante verwendet, es sei denn, der Downstream-Code wird neu kompiliert. Dies kann ein Problem sein, wenn sie dann diese Konstante mit Code verwenden, der den neuen Wert erwartet, da der Binärcode zwar richtig ist, der Quellcode aber nicht.

Dies ist eine Warze im Design von Java, da dies einer der wenigen Fälle (möglicherweise der einzige Fall) ist, in denen Quellkompatibilität und Binärkompatibilität nicht identisch sind. Mit Ausnahme dieses Falles können Sie eine Abhängigkeit durch eine neue API-kompatible Version ersetzen, ohne dass Benutzer der Abhängigkeit neu kompilieren müssen. Offensichtlich ist dies angesichts der Art und Weise, in der Java-Abhängigkeiten im Allgemeinen verwaltet werden, äußerst wichtig.

Erschwerend kommt hinzu, dass der Code im Stillen das Falsche tut und keine nützlichen Fehler erzeugt. Wenn Sie eine Abhängigkeit durch eine Version mit inkompatiblen Klassen- oder Methodendefinitionen ersetzen, erhalten Sie Klassenlade- oder Aufruffehler, die zumindest gute Hinweise auf das Problem liefern. Sofern Sie den Typ des Werts nicht geändert haben, wird dieses Problem nur als mysteriöses Laufzeitfehlverhalten angezeigt.

Ärgerlicher ist, dass die heutigen JVMs zur Laufzeit problemlos alle Konstanten inline setzen können, ohne dass Leistungseinbußen entstehen (abgesehen von der Notwendigkeit, die Klasse zu laden, die die Konstante definiert, die wahrscheinlich ohnehin geladen wird), leider die Semantik des Sprachdatums aus den Tagen vor den JITs . Und sie können die Sprache nicht ändern, weil dann mit früheren Compilern kompilierter Code nicht korrekt ist. Die Abwärtskompatibilität schlägt wieder zu.

Aus diesem Grund raten einige Leute, niemals einen statischen Endwert zu ändern. Für Bibliotheken, die zu unbekannten Zeiten weit verbreitet und auf unbekannte Weise aktualisiert werden könnten, ist dies eine gute Praxis.

In Ihrem eigenen Code, insbesondere an der Spitze der Abhängigkeitshierarchie, werden Sie wahrscheinlich damit durchkommen. Überlegen Sie in diesen Fällen jedoch, ob die Konstante wirklich öffentlich (oder geschützt) sein muss. Wenn die Konstante nur Paketsichtbarkeit ist, ist es abhängig von Ihren Umständen und Codestandards angemessen, dass das gesamte Paket immer auf einmal neu kompiliert wird und das Problem dann verschwindet. Wenn die Konstante privat ist, haben Sie kein Problem und können sie jederzeit ändern.

flauschiger Haufen
quelle
85

Alles in Ihrem Quellcode, einschließlich constdeklarierter globaler Konstanten, kann sich mit einer neuen Version Ihrer Software ändern.

Die Schlüsselwörter const(oder finalin Java) sollen dem Compiler signalisieren, dass sich diese Variable nicht ändert, während diese Instanz des Programms ausgeführt wird . Nichts mehr. Wenn Sie Nachrichten an den nächsten Betreuer senden möchten, verwenden Sie einen Kommentar in der Quelle. Dafür sind sie da.

// DO NOT CHANGE without consulting with the legal department!
// Get written consent form from them before release!
public const int LegalLimitInSeconds = ...

Ist ein viel besserer Weg, um mit Ihrem zukünftigen Selbst zu kommunizieren.

nvoigt
quelle
11
Ich mochte diesen Kommentar wirklich für das zukünftige Ich.
GregT
4
TaxRateSein publicmacht mich nervös. Ich möchte sicher wissen, dass nur der Vertrieb von dieser Änderung betroffen ist und nicht auch unsere Lieferanten, die uns eine Steuer berechnen. Wer weiß, was in der Codebasis passiert ist, seit dieser Kommentar geschrieben wurde.
candied_orange
3
@IllusiveBrian kritisierte die Verwendung von Konstanten nicht. Warnte davor, einem Kommentar zu vertrauen, der aktuell ist. Stellen Sie immer sicher, wie etwas verwendet wird, bevor Sie es ändern.
candied_orange
8
Dies ist ein guter Rat für Java . In anderen Sprachen kann es anders sein. Aufgrund der Art und Weise, wie const-Werte an die Aufrufsite in C # gebunden sind, public constsollten Felder nur für Dinge verwendet werden, die sich niemals ändern werden, wie z. B. Math.pi. Wenn Sie eine Bibliothek erstellen, sollten Dinge, die sich während der Entwicklung oder mit einer neuen Version ändern können public static readonly, so sein , dass keine Probleme mit Benutzern Ihrer Bibliothek auftreten.
GrandOpener
6
Sie sollten ein anderes Beispiel wählen ... Geldwerte sollten niemals Gleitkommazahlen sein!
CorsiKa
13

Wir müssen zwei Aspekte von Konstanten unterscheiden:

  • Namen für Werte, die zur Entwicklungszeit bekannt waren und die wir zur besseren Wartbarkeit einführen, und
  • Werte, die dem Compiler zur Verfügung stehen.

Und dann gibt es eine verwandte dritte Art: Variablen, deren Wert sich nicht ändert, dh Namen für einen Wert. Der Unterschied zwischen diesen unveränderlichen Variablen und einer Konstanten besteht darin, dass der Wert bestimmt / zugewiesen / initialisiert wird: Eine Variable wird zur Laufzeit initialisiert, der Wert einer Konstanten ist jedoch während der Entwicklung bekannt. Diese Unterscheidung ist etwas trübe, da ein Wert möglicherweise während der Entwicklung bekannt ist, aber tatsächlich nur während der Initialisierung erstellt wird.

Wenn der Wert einer Konstante jedoch zur Kompilierungszeit bekannt ist, kann der Compiler Berechnungen mit diesem Wert durchführen. Zum Beispiel hat die Java-Sprache das Konzept der konstanten Ausdrücke . Ein konstanter Ausdruck ist ein Ausdruck, der nur aus Literalen von Primitiven oder Zeichenfolgen, Operationen für konstante Ausdrücke (z. B. Casting, Addition, Zeichenfolgenverkettung) und konstanten Variablen besteht. [ JLS §15.28 ] Eine konstante Variable ist eine finalVariable, die mit einem konstanten Ausdruck initialisiert wird. [JLS §4.12.4] Für Java ist dies eine Konstante zur Kompilierungszeit:

public static final int X = 7;

Dies wird interessant, wenn eine konstante Variable in mehreren Kompilierungseinheiten verwendet wird und dann die Deklaration geändert wird. Erwägen:

  • A.java:

    public class A { public static final int X = 7; }
  • B.java:

    public class B { public static final int Y = A.X + 2; }

Wenn wir diese Dateien kompilieren B.class, deklariert der Bytecode ein Feld, Y = 9da B.Yes sich um eine konstante Variable handelt.

Wenn wir jedoch die A.XVariable auf einen anderen Wert ändern (z. B. X = 0) und nur die A.javaDatei neu kompilieren , wird B.Yweiterhin auf den alten Wert verwiesen. Dieser Status A.X = 0, B.Y = 9stimmt nicht mit den Deklarationen im Quellcode überein. Viel Spaß beim Debuggen!

Dies bedeutet nicht, dass Konstanten niemals geändert werden sollten. Konstanten sind definitiv besser als magische Zahlen, die ohne Erklärung im Quellcode vorkommen. Doch der Wert ist der öffentlichen Konstanten Teil Ihrer öffentlichen API . Dies ist nicht spezifisch für Java, tritt jedoch auch in C ++ und anderen Sprachen mit separaten Kompilierungseinheiten auf. Wenn Sie diese Werte ändern, müssen Sie den gesamten abhängigen Code neu kompilieren, dh eine Neukompilierung durchführen.

Abhängig von der Art der Konstanten können sie zu falschen Annahmen der Entwickler geführt haben. Wenn diese Werte geändert werden, können sie einen Fehler auslösen. Beispielsweise könnte ein Satz von Konstanten so gewählt werden, dass sie bestimmte Bitmuster bilden, z public static final int R = 4, W = 2, X = 1. Wenn diese geändert werden, um eine andere Struktur wie zu bilden, wird R = 0, W = 1, X = 2vorhandener Code wie boolean canRead = perms & Rfalsch. Und denken Sie nur an den Spaß, der sich daraus ergeben würde, wenn Sie sich Integer.MAX_VALUEändern würden! Hier gibt es keine Fehlerbehebung. Es ist nur wichtig zu wissen, dass der Wert einiger Konstanten wirklich wichtig ist und nicht einfach geändert werden kann.

Aber für die Mehrheit der Konstanten ist das Ändern in Ordnung, solange die obigen Einschränkungen berücksichtigt werden. Eine Konstante kann sicher geändert werden, wenn die Bedeutung und nicht der spezifische Wert wichtig sind. Dies ist z. B. der Fall für Tunables wie BORDER_WIDTH = 2oder TIMEOUT = 60; // secondsoder Vorlagen wie API_ENDPOINT = "https://api.example.com/v2/"- obwohl einige oder alle davon möglicherweise in Konfigurationsdateien und nicht in Code angegeben werden sollten.

amon
quelle
5
Ich mag diese Analyse. Ich habe es so gelesen: Es steht Ihnen frei, eine Konstante zu ändern, solange Sie verstehen, wie sie verwendet wird.
candied_orange
+1 C # "leidet" auch unter dem gleichen Problem mit öffentlichen Konstanten.
Reginald Blue
6

Eine Konstante ist nur für die Laufzeit der Anwendung garantiert konstant . Solange dies zutrifft, gibt es keinen Grund, die Sprachfunktion nicht zu nutzen. Sie müssen nur wissen, welche Konsequenzen es hat, wenn Sie Konstanten- oder Compiler-Flags für denselben Zweck verwenden:

  • Konstanten belegen Anwendungsraum
  • Compiler-Flags nicht
  • Durch Konstanten deaktivierter Code kann mit modernen Refactoring-Tools aktualisiert und geändert werden
  • Code, der durch Compiler-Flags deaktiviert wurde, kann nicht

Nachdem wir eine Anwendung gepflegt hatten, die das Zeichnen von Begrenzungsrahmen für Formen einschaltete, damit wir debuggen konnten, wie sie gezeichnet wurden, stießen wir auf ein Problem. Nach dem Refactoring wurde der gesamte Code, der durch Compiler-Flags deaktiviert wurde, nicht kompiliert. Danach haben wir unsere Compiler-Flags absichtlich in Anwendungskonstanten geändert.

Ich sage das, um zu demonstrieren, dass es Kompromisse gibt. Das Gewicht einiger Boolescher Elemente würde nicht dazu führen, dass der Arbeitsspeicher der Anwendung erschöpft war oder zu viel Speicherplatz in Anspruch genommen wurde. Dies ist möglicherweise nicht der Fall, wenn Ihre Konstante wirklich ein großes Objekt ist, das im Wesentlichen alles in Ihrem Code verarbeitet. Wenn nicht alle Verweise auf ein Objekt entfernt werden müssen, nachdem es nicht mehr benötigt wird, ist das Objekt möglicherweise die Ursache für einen Speicherverlust.

Sie müssen den Anwendungsfall auswerten und erklären, warum Sie die Konstanten ändern möchten.

Ich bin kein Fan von einfachen Pauschalaussagen, aber im Allgemeinen hat Ihr älterer Kollege Recht. Wenn sich etwas zwangsläufig häufig ändert, muss es möglicherweise ein konfigurierbares Element sein. Zum Beispiel könnten Sie Ihren Kollegen wahrscheinlich für eine Konstante überzeugen, IsInDebugMode = truewenn Sie einen Code vor Beschädigung schützen möchten. Einige Dinge müssen jedoch möglicherweise häufiger geändert werden, als Sie eine Anwendung freigeben. In diesem Fall müssen Sie diesen Wert zum richtigen Zeitpunkt ändern. Sie können das Beispiel eines TaxRate = .065. Dies mag zum Zeitpunkt der Kompilierung Ihres Codes zutreffen, kann sich jedoch aufgrund neuer Gesetze ändern, bevor Sie die nächste Version Ihrer Anwendung veröffentlichen. Das ist etwas, das entweder von einem Speichermechanismus (wie einer Datei oder einer Datenbank) aktualisiert werden muss.

Berin Loritsch
quelle
Was meinst du mit "Compiler Flags"? Vielleicht der C-Präprozessor und ähnliche Compiler-Funktionen, die Makros / Defines und #ifdefs unterstützen? Da diese auf einer textuellen Ersetzung des Quellcodes basieren , sind sie nicht Teil der Programmiersprachensemantik. Beachten Sie, dass Java keinen Präprozessor hat.
amon
@amon, Java vielleicht nicht, aber mehrere Sprachen. Ich meine #ifdefFlaggen. Während sie nicht Teil der C-Semantik sind, sind sie Teil von C #. Ich habe für den größeren Kontext des Sprachagnostizismus geschrieben.
Berin Loritsch
Ich denke, das Argument "Speicherverschwendung" ist umstritten. Inlining und Tree Shaking sind ein universeller Schritt in jedem Release-Modus-Optimierer.
Alexander - Wiedereinsetzung von Monica
@Alexander, ich stimme zu. Es ist jedoch etwas, dessen man sich bewusst sein muss.
Berin Loritsch
1
"Konstanten belegen Speicherplatz" - Wenn Sie keine eingebettete Anwendung für einen Mikrocontroller mit nur ein oder zwei Kilobyte Speicher entwickeln, sollten Sie nicht einmal über solche Dinge nachdenken.
vsz
2

Das const, #defineoder finalist ein Compiler-Hinweis (beachten Sie, dass das #definenicht wirklich ein Hinweis ist, sondern ein Makro und bedeutend leistungsfähiger). Es zeigt an, dass sich der Wert während der Ausführung eines Programms nicht ändert und verschiedene Optimierungen vorgenommen werden können.

Als Compiler-Hinweis führt der Compiler jedoch Dinge aus, die vom Programmierer nicht immer erwartet werden. Insbesondere wird javac a inline setzen, static final int FOO = 42;so dass FOOder tatsächlich kompilierte Bytecode an jeder Stelle gelesen wird, an der er verwendet wird 42.

Dies ist keine allzu große Überraschung, bis jemand den Wert ändert, ohne die andere Kompilierungseinheit (.java-Datei) neu zu kompilieren - und die 42Reste im Bytecode (siehe, ist es möglich, das Inlining von statischen Endvariablen in javac zu deaktivieren? ).

Etwas zu machen static finalbedeutet, dass es das ist und für immer mehr, und es zu ändern, ist eine wirklich große Sache - besonders wenn es nicht alles ist private.

Konstanten für Dinge wie final static int ZERO = 0sind kein Problem. final static double TAX_RATE = 0.55(Abgesehen davon, dass es sich um Geld und Double handelt, ist es schlecht und sollte BigDecimal verwenden, aber dann ist es kein Primitiv und daher nicht inline), ist ein Problem und sollte mit großer Sorgfalt darauf untersucht werden, wo es verwendet wird.

user292808
quelle
für kleine Werte von NULL.
3
is a problem and should be examined with great care for where it is used.Warum ist das ein Problem?
Alexander - Reinstate Monica
1

Wie der Name schon sagt, sollten sich Konstanten während der Laufzeit nicht ändern, und meiner Meinung nach werden Konstanten so definiert, dass sie sich langfristig nicht ändern. ( Weitere Informationen finden Sie in dieser SO-Frage .)

Wenn Sie Flags benötigen (z. B. für den Entwicklungsmodus), sollten Sie stattdessen eine Konfigurationsdatei oder einen Startparameter verwenden (viele IDEs unterstützen die Konfiguration von Startparametern auf Projektbasis; siehe die entsprechende Dokumentation), um diesen Modus zu aktivieren. Auf diese Weise behalten Sie die Flexibilität, einen solchen Modus zu verwenden, und Sie können nicht vergessen, ihn jedes Mal zu ändern, wenn der Code produktiv wird.

DMuenstermann
quelle
0

Die Möglichkeit, zwischen den Läufen gewechselt zu werden, ist einer der wichtigsten Punkte, um eine Konstante in Ihrem Quellcode zu definieren!

Die Konstante gibt Ihnen einen genau definierten und dokumentierten Ort, an dem Sie den Wert jederzeit während der Lebensdauer Ihres Quellcodes ändern können. Es ist auch ein Versprechen, dass das Ändern der Konstante an diesem Ort tatsächlich alle Vorkommen dessen, wofür es steht, ändert.

Als negatives Beispiel: Es wäre nicht sinnvoll, eine Konstante zu haben, TRUEdie truein einer Sprache ausgewertet wird, die tatsächlich das trueSchlüsselwort enthält. Sie würden niemals, niemals, nicht einmal erklären, TRUE=falseaußer als grausamer Witz.

Natürlich gibt es auch andere Verwendungen von Konstanten, zum Beispiel das Kürzen von Code ( CO_NAME = 'My Great World Unique ACME Company'), Vermeiden von Duplizierungen ( PI=3.141), Festlegen von Konventionen ( TRUE=1) oder was auch immer, aber eine definierte Position zum Ändern der Konstante ist mit Sicherheit eine der bekanntesten.

AnoE
quelle