"Globale Variablen" in Java

mmtzh

Cadet 3rd Year
Registriert
Nov. 2014
Beiträge
62
Hallo zusammen,

ich arbeite in einem Software-Team an einer Java-Anwendung. In unserem Code haben wir einen InstanceProvider. Dabei handelt es sich um eine Klasse mit vielen statischen Variablen und statischen Getter- und Setter-Methoden, um die Variablen zu befüllen. Die Klasse InstanceProvider dient dazu, auf Instanzen zuzugreifen, ähnlich wie bei globalen Variablen. Diese Objekte sind sehr unterschiedlich, aber sie haben alle gemeinsam, dass nur eine Instanz in der gesamten Software benötigt wird.

Ich bin eigentlich kein Fan von diesem InstanceProvider und würde ihn gerne loswerden. Ohne den InstanceProvider müsste man die Objekte per Dependency Injection an alle übergeben, die sie benötigen. Und wenn man mit Dependency Injections arbeitet, erhöht sich die Anzahl der Parameter im Konstruktor, was meinen Kollegen nicht gefällt.

Ich habe gerade gelernt, dass es schlechte Praxis ist, mit solchen "globalen" Variablen zu arbeiten. Deshalb würde ich den InstanceProvider gerne loswerden. Aber mir gehen die Argumente dagegen aus.

Wenn ich Google, kommen Argumente wie "man weiß nicht, wo im Code eine Instanz manipuliert wird, wenn man es so macht". Aber das kann auch bei Dependency Injection passieren, wo eine Instanz an einer vollkommen anderen Stelle im Code modifiziert wird.

Außerdem habe ich im Hinterkopf, dass so etwas problematisch sein kann, wenn man Unit-Tests schreiben möchte. Aber ich kann das Problem im Moment nicht genau benennen. Und wir arbeiten noch nicht mit Unit-Tests. Ich muss mein Team noch davon überzeugen, dass Unit-Tests sinnvoll sind.

Könnt ihr mir gute Argumente geben oder liege ich falsch und es spricht nichts dagegen, den InstanceProvider auf diese Weise zu verwenden?
 
Es scheint ja bei euch im Team gewissen Defizite zu geben. Hast du das den mal in der großen Runde besprochen? Bist du der einzige der diese Änderung will?
 
Zuletzt bearbeitet: (iOS Autokorrektur stinkt!)
  • Gefällt mir
Reaktionen: multipilz5, BeBur und madmax2010
Ich habe es noch nicht in der großen Runde angesprochen. Da ich weiß, dass es viele anders sehen als ich. (Es wurde ja schon immer so gemacht) Deshalb möchte ich erst einmal schauen ob ich denn richtig liege. Und welche guten Argumente es gäbe
 
Generell gilt das halt als schlechter Stil.
Je nach Umfang der Code Basis, wird es ja gern etwas unübersichtlich. Wenn du etwas am code verändern musst, willst du dir nicht jedes mal Gedanken machen ob du damit etwas veränderst, was fuer deine globalen variablen relevant ist. Musst du aber.
Du gibst dem kompletten Code ZUgriff auf den Inhalt der Variablen. Das fuehrst 1. dazu das du potentiell Inofrmationen an Stellen abrufen kann, die darauf keinen Zugriff haben duerfen und du gibst potentiellen Angreifern ein nettes Werkzeug an die Hand. Auditoren hassen globale variablen; Wird dir in jedem SIcherheitsaudit rot angestrichen. Saubere Tests fuer globale Variablen schreiben ist unglaublich nervig.
Dann bist du da in einer objektorientierten Programmiersprache. Kapselung ist da ein wichtiges paradigma, was du mit globalen Variablen verletzt.
Wenn du multithreading nutzt, hast du gern Probleme mit Nebenläufigkeit (ist das das richtige deutsche wort hier? dunno)
und so weiter. Nicht machen.
Haltet euch an die Spezifikationen der Sprache und nutzt nicht irgendwelche Hacks.
Ich schreibe ja auch liebend gern haesslichen code, aber den verkaufe oder publiziere ich dann nicht. Ihr macht das beruflich
 
Zuletzt bearbeitet:
  • Gefällt mir
Reaktionen: [ChAoZ], KeepCalm, BeBur und 3 andere
Ich bin jetzt zwar schon eine Weile aus der Software-Entwicklung auf beruflicher Ebene raus, aber meiner Meinung nach sollte man zwar möglichst seine Software an guten Design-Paradigmen ausrichten, aber nicht mit der Brechstange Dinge umwerfen, die funktionieren, nur weil etwas allgemein als eher unschön angesehen wird. Es kommt ja speziell auf eure Software an, was hier sinnvoll ist. Im Zweifelsfall ist das KISS (Keep It Simple Stupid) Prinzip besser, sofern dadurch nicht übelste Quellcode Grausamkeiten erzeugt werden.

Was ich sagen will: Schau dir ganz genau an, ob und wie sehr die Komplexität und damit auch der Wartungsaufwand erhöht wird. Mehr Komplexität = Mehr Fehlerquellen. Widerwillen ist auch eine Ressource, mit der man leider arbeiten muss. Du solltest mehr in Petto haben für deine Idee, als dass irgendwo steht, dass man es besser anders macht. Eure Software ist ja schon da, und damit arbeiten auch andere außer dir. Grundlegende Architekturänderungen sind kein Pappenstil.

Also bist du derjenige, der den Gewinn dahinter verargumentieren muss. Und den erhältst du nicht nur durch nachrecherchieren des Prinzips, sondern du musst das immer direkt mit eurem Code verargumentieren können. Im Zweifelsfall sind das ja auch Arbeitstunden, die faktoriert werden wollen. Und wenn du nicht darlegen kannst, inwiefern die Software dadurch unbestreitbar besser wird, ist der Arbeitgeber vermutlich weniger gewillt, diese Arbeitsstunden zu bezahlen.
 
Es geht mir weniger darum, bestehenden Code zu ändern, sondern ob wir für Code Änderungen das weiterhin so machen. Oder anders.
 
Naja, das hieße ja, dass ihr in einer Software dann beides habt. Es bleibt also an vielen Stellen das von dir ungewollte Prinzip der globalen Variablen, während an anderen Stellen anders gearbeitet wird, und zwar um des anders Arbeitens Willen. Ob das so sinnvoll ist?
 
  • Gefällt mir
Reaktionen: multipilz5 und madmax2010
Anders wird ja sowieso schon gearbeitet. Es wurde ja auch bisher nicht jede Instanz von allen Variablen global gespeichert. Sondern es wurden ja schon immer manche Objekte an den Konstruktor übergeben.
 
Dieser Instance Provider hört sich verdächtig nach Service Pattern an.
Ich verwende das Service Pattern bei mir auf der Arbeit für globale Objekte für die es nur eine Instanz gibt. Ich halte das nicht für so verkehrt. Kommt auf die Implementierung an. In meinem Fall wird intern ein Dictionary verwaltet, welches als Schlüssel ein Interface hat und als Wert die Referenz auf die Instanz. Die Instanzen werden dann über das Interface geholt.
C#:
IMyService svc = ServiceLocator.Instance.GetService<IMyService>();
So ähnlich geht das sicher auch mit JAVA.
Der Service Locator selbst ist ein Singleton. Den gibt es global nur einmal und er verwaltet die Referenzen auf die anderen Instanzen.

Der Vorteil: Für Unit Tests kann ich bestimmte Instanzen einfach durch Dummy Instanzen ersetzen ohne Änderungen am Code durchführen zu müssen, weil dort überall nur das Interface verwendet wird.

Edit: Hier ist ein Beispiel für Java. Da wird auch Dependency Injection diskutiert.
https://www.baeldung.com/java-service-locator-pattern
 
Zuletzt bearbeitet:
mmtzh schrieb:
Anders wird ja sowieso schon gearbeitet.
Naja, das Problem hier ist, es kann keiner hier in eure Software gucken. Du umschreibst es hier, aber solche Designentscheidungen trifft man ja nicht nur um des Patterns willen, zuerst einmal kommt es ja darauf an, was die Software tun soll bzw. tut, und danach wählt man das Pattern aus.

Und zum anderen ist hier schon eine ganze Menge Arbeit und Gehirnschmalz in die Software von anderen außer dir geflossen, die zum jetzigen Ist-Zustand geführt hat. Ob das bis hierhin optimal gelaufen ist, sei mal dahingestellt. Das kann hier wirklich niemand beurteilen.

Aber Fakt ist, das ist jetzt so, und wenn du das ändern möchtest, so musst du überzeugen.
Und überzeugen tust du nicht nur mit dem Prinzip an sich sondern einem klaren Benefit-Argument für eure Software. Und das hängt eben mit eurem speziellen Fall zusammen. In der Software inselweise verschiedene Wege für eine gleiche Problemstellung zu gehen, ist wohl eher wenig sinnvoll, das klingt nach gewolltem Wartungsunfall.
 
Zuletzt bearbeitet:
  • Gefällt mir
Reaktionen: madmax2010
Wenn du ein Argument für eine Designänderung haben willst, solltest du dich Fragen: Tut es irgendwo weh? Hast du in dem Projekt die Erfahrung gemacht, dass Code unübersichtlicher dadurch geworden ist? Gab es wegen der Designentscheidungen Schwierigkeiten beim Entwickeln der Software? Weil wenn nicht, weiß ich nicht ob man das ändern sollte.

Natürlich sollten Designentscheidungen im Vorfeld gut durchdacht werden, aber wenn man schon Bestandscode hat und Komponenten davon grundlegend umstrukturieren will, sollte das gut begründet sein. Das kostet ja viel Arbeit und Geld.

Am Rande würde ich bei professioneller Softwareentwicklung niemals auf Unittests verzichten. Damit spart man sich am Ende Zeit und Mühen.

Wenn jemand anderer Meinung ist, fände ich die super spannend. Bin jetzt auch noch nicht 10 Jahre lang Entwickler sondern nur 4 :)
 
Finde deine Eingangsfrage super und bisher immernoch unbeantwortet:
Sind geteilte Instanzen, die man zB per ctor allen gibt, besser als Singletons?

Ich weiß auch nicht, was pauschal besser wäre. Habe mich in einem größeren Projekt nach hin- und herprobieren beider Möglichkeiten bewusst für die „globalen“ Singletons entschieden.

War ne Desktop-Software, die manchmal an verschiedendsten Stellen Dinge über die einmalig existierende Serielle Schnittstelle zum angeschlossenen microcontroller (mc) Infos schicken sollte.
Anfangs habe ich allen Klassen, die das tun müssen, ne Ref/Pointer des mc-Kommunikators gegeben. Dadurch wurden viele Konstruktoren riesig groß, da es mit der Zeit viele andere, nur einmalig existierende Schnittstellen, Configs usw geben sollte.
Die wurden zum Start der SW alle angelegt und sauber instanziiert und nie zur Laufzeit zerstört.

Hab dann alles umgebaut und nen Owner aller Singletons erschaffen über den jeder „Global“ an alle Singletons rankommt. Alle modifizerenden Methoden haben pro Singleton-Instanz nen Mutex wie ihn zB „sendCommand“ bei meinem mc-Sender nutzt.

Das ist nicht deppensicher.. aber das war es vorher mit Referenzen/Pointer auf shared Ressourcen genau so wenig und mein Code is 100x einfacher zu lesen.

Wäre auch neugierig, ob noch jemand deine Frage mit einem Lösungsvorschlag beantwortet.
 
Ich programmiere aktuell in Matlab Script und in C++ und die Ausdrucksstärke und const Korrektheit von C++ vermisse ich schmerzlichst bei Matlab. Es ist einfach alles viel übersichtlicher, wenn man sofort sieht, welche Daten lesend und welche schreibend verwendet werden. Ich würde im Matlab Projekt am liebsten alle Methoden zu Einzeilern umbauen und in denen wird dann einfach jeweils eine Hilfsfunktionen aufgerufen, die kein member der Klasse ist, so das absolut unzweifelhaft ist, was lesend verwendet wird (die Parameter) und was verändert wird (die Ausgabe der Funktion).
 
Was ihr da macht nennt sich Service Locator Pattern und ist ein Anti-Pattern.
Hier wird bspw. gut erklärt, warum man es vermeiden sollte (verlinkte Artikel ebenfalls beachten).

Die Alternative dazu ist eben Dependency Injection, insbesondere Constructor-based, was seit etlichen Jahren in unzähligen Programmiersprachen und Frameworks State of the art ist.

Mit Singletons hat das an sich nichts zu tun. Sowohl SL als auch DI funktionieren mit beliebigen Lifetimes. Auch mit Interfaces vs. konkrete Typen hat es nichts zu tun, beides funktioniert ebenfalls mit beidem.

Wenn man schon DI verwendet, gibt es selten einen Grund einen Service Locator zu verwenden. Es ergibt eigentlich keinen Sinn, das zu mischen (außer in Ausnahmefällen wegen technischer Einschränkungen in Bezug auf Factories in manchen Frameworks).

Constructors mit zu vielen Argumenten sind in der Regel ein Anzeichen dafür, dass die Klasse zu viel macht (Single Responsibility Principle). Ansonsten können da auch das Mediator Pattern oder das Facade Pattern helfen.
 
Zuletzt bearbeitet:
  • Gefällt mir
Reaktionen: [ChAoZ], marcOcram, Ack der III und eine weitere Person
Ohne den Code oder die Applikation zu kennen, halte ich es aber trotzdem für gefährlich einen Großteil mit einer Klasse zu laden.

1. Risiko eines Memory-Leaks
2. Das verstößt ganz klar gegen die Code-Convention, jeder Neuling im Projekt könnte unvorteilhaften Code schreiben, wenn ich von überall gewisse Objekte mir holen kann. Kapselung hat auch den Vorteil, dass der Entwickler der null Ahnung hat was der Code will, sofort anhand von Zugriffsmodifier sehen kann wo er etwas instanziieren kann.
3. Die anderen haben es schon geschrieben, DI ist eigentlich für sowas da. Was passiert eigentlich wenn man bei euch eine dieser Klassen aus diesem Pool die statisch aufgerufen werden, grundlegend verändert werden müssen? Müsstet ihr dann in etlichen anderen Klassen Anpassungen machen?
4. Ich bin kein Experte in Sicherheit aber ich könnte mir gut vorstellen, dass man viel leichter an sensible Sachen kommen kann innerhalb des Rams die eigentlich nicht lesbar sein sollten.

Wie gesagt, ohne den Code zu kennen, schwer zu beurteilen warum die Kollegen daran festhalten. Für mich wäre Punkt 3 der schwerwiegendste. Ich hätte keine Lust komplexe Klassen an etlichen Stellen neu anzupassen, wenn es grundlegende Veränderungen in der Basis gibt.
 
DefconDev schrieb:
4. Ich bin kein Experte in Sicherheit aber ich könnte mir gut vorstellen, dass man viel leichter an sensible Sachen kommen kann innerhalb des Rams die eigentlich nicht lesbar sein sollten.
Wenn jemand Zugriff auf das System hat, dann ist das ja wohl dein geringstes Problem..
 
  • Gefällt mir
Reaktionen: madmax2010
ich erstelle seit gut 20 Jahre Software mit Java und seit ca. 8 Jahren mit dem Framework "Spring" .. wenn ich den Eingangpost lese bekomme ich Puls .. und mein spontaner Rat wäre, such' DIr ein anderes Team, denn DU hast gute Ansätze und wenn ein Team keine Unit-Test hat, dann krankt es woanders ..


In Spring ist jedes Bean ein Singelten und das Schlüsselwort @Autowired sorgt dafür, das es bei Properties (Klassenmember) und bei Variablen von Konstrukturen injected wird, das ersetzt so komplett den Instance-Provider; ebenso Getter / Setter (braucht man si unbedingt, hilft Lombok gegen Boilerplate)

Hier ist die Injection per Konstruktur zu nutzen .. warum ?? ganz einfach, eine Klasse, die über den Konstruktur erstellt wird, kann gut getestet werden, da jeder Parameter ein Mock sein kann, das bekommst Du anders nicht so einfach hin .. das Vorteil fällt weg, wenn man keine Unit-Tests hat

Es schwingt aber noch etwas anderes mit .. die beschreibene Klasse scheint eine Gott-Klasse zu sein .
.https://de.wikipedia.org/wiki/Gottobjekt
ein Anitpattern .. irgendwann traut sich da keiner mehr ran, hier wurde das Prinzip "speration of Concerns" verletzt oder wie bereits beschrieben Single Reponsibility
Einiges wurde ja schon erwähnt .. z.B. von @character ich empfehle hier auch ein DI-Framework .. und evtl. zuerst mal die Lektüre von dem Buch "Clean Code von Robert C Martin"... böse ausgedrückt scheint es, als ob die Kollegen auch schon seit 20 Jahren Java machen und leider auch noch wie vor 20 Jahren
 
Zuletzt bearbeitet:
  • Gefällt mir
Reaktionen: andy_m4, [ChAoZ], BeBur und eine weitere Person
Wie gut ich das kenne, bin zwar kein Java Dev aber die Pattern und Lösungen sind oft Sprachübergreifend.
Wir hatten das Registry Pattern in einer 60k Zeilen Code Applikation für einen Online Shop, das war sehr Ähnlich zu dem Service Locator... eine Gottesklasse also, die alles konnte aber die fast unmöglich war zu testen und noch schwieriger zu warten. Kurzum: wir wurden sie nie wieder los.

Nutze DI, das ist DAS Mittel der Wahl und ehrlich gesagt möchte ICH drauf nicht mehr verzichten.
Einmal implementiert musst du dir nie wieder Gedanken über die Abhängigkeiten machen und ja, zu viele Constructor Parameter sind ein Zeichen dass die Klasse zu viel macht, wurde schon gesagt... ja ist nicht schön, lässt sich manchmal schwer vermeiden. Es ist am Ende vor allem Clean Code welcher sich von selbst ließt.

SOLID....
Wer SOLID beherrscht entwickelt automatisch guten Code, das ist meine Erfahrung.
Google es.
 
  • Gefällt mir
Reaktionen: madmax2010
heulendoch schrieb:
Wenn jemand Zugriff auf das System hat, dann ist das ja wohl dein geringstes Problem..
Sofern es eine Webanwendung ist. Wenn es eine Desktop-Anwendung ist, die jeder installieren kann, sieht es schon anders aus.

Ist aber auch nicht so wichtig.
 
Zurück
Oben