Artikel

Plugin-Funktionen in überschaubare Module aufteilen

Eine gute Modulgrenze macht Zuständigkeiten und Fehlerfolgen erkennbar. Der Artikel untersucht Lufox-Bausteine und entwickelt eine schrittweise Aufteilung von Darstellung, Fachregeln und Speicherung mit klaren Startbedingungen, geordnetem Beenden und aussagekräftigen Tests für die Übergänge.

BlackZackBlackZack

1581 Wörter · 8 Min. Lesezeit

  • lufox
  • minecraft
  • plugins
  • architektur
Plugin-Funktionen in überschaubare Module aufteilen

Schematisches Blockmodell als Entwurfsbeispiel; kein Screenshot der Lufox-Spielwelt.

Ein Minecraft-Plugin wächst häufig aus kleinen nützlichen Funktionen. Zuerst kommt ein Menü hinzu, danach eine Belohnung, später eine gemeinsame Datenbank und ein Webzugang. Jede Ergänzung kann für sich sinnvoll sein. Schwierig wird es, wenn eine Änderung an einem Teil plötzlich mehrere scheinbar unabhängige Funktionen beeinflusst. Eine brauchbare Modulstruktur soll solche Zusammenhänge sichtbar machen und begrenzen. Sie ist kein Selbstzweck und benötigt nicht zwangsläufig viele getrennte Plugin-Dateien.

Lufox besitzt bereits fachlich benannte Bereiche für Menüs, Quests, Datenhaltung, Weltwechsel und weitere Funktionen. Die Hauptklasse baut verschiedene Komponenten auf und beendet sie in einer festgelegten Reihenfolge. Diese Struktur ist ein konkreter Ausgangspunkt für die Betrachtung. Ein Verzeichnisname allein beweist jedoch noch keine saubere Grenze. Entscheidend ist, welche Informationen eine Komponente benötigt, welche Entscheidungen sie treffen darf und welche Arbeit beim Ende tatsächlich aufgeräumt wird.

Eine Grenze nach Verantwortung ziehen

Ein Modul sollte einen verständlichen Zweck besitzen. „Questfortschritt verwalten“ ist eine brauchbare Verantwortung. „Alles, was gerade nirgendwo anders hinpasst“ ist keine. Der Zweck erklärt, welche Änderungen gemeinsam auftreten und welche Ergebnisse andere Teile erwarten dürfen. Wenn eine Klasse sowohl Menüfelder zeichnet als auch Kontobuchungen und Datenbankmigrationen steuert, vereint sie sehr unterschiedliche Gründe für Änderungen.

Für einen Entwurf wird eine Belohnungsfunktion betrachtet. Die Oberfläche zeigt die verfügbare Belohnung und nimmt eine Absicht entgegen. Eine Fachkomponente entscheidet, ob die Voraussetzungen erfüllt sind und ob der Vorgang bereits abgeschlossen wurde. Ein Speicherzugang führt die erforderlichen dauerhaften Änderungen aus. Diese Aufteilung ist nicht zwingend drei große Frameworks wert. Oft reichen wenige klar benannte Klassen und eine schmale Schnittstelle.

Die Grenze sollte echte Entscheidungen trennen. Eine zusätzliche Hilfsklasse, die nur denselben Aufruf weiterreicht, macht das System nicht automatisch verständlicher. Umgekehrt kann eine kleine reine Berechnung sehr wertvoll sein, wenn sie eine wichtige Regel unabhängig prüfbar macht. Die Frage lautet daher, ob die Aufteilung einen Zusammenhang erklärt oder eine Abhängigkeit begrenzt. Die reine Anzahl von Dateien ist kein Qualitätsmaßstab.

Darstellung von fachlicher Wahrheit lösen

Ein Menü darf anzeigen, dass eine Belohnung verfügbar ist. Die verbindliche Entscheidung über die Ausgabe sollte jedoch nicht ausschließlich beim Zeichnen des Knopfes fallen. Zwischen Anzeige und Klick kann sich der Zustand ändern. Wenn die Fachlogik einen eigenen Einstieg besitzt, kann derselbe Vorgang von einem Befehl oder einer anderen Oberfläche aus konsistent verwendet werden.

Der vorhandene Lufox-Menülauscher verteilt erkannte Eingaben an Menüinstanzen. Diese gemeinsame Behandlung ist ein sinnvoller Ort für allgemeine Eingaberegeln. Die einzelne Fachaktion muss trotzdem ihre eigenen Voraussetzungen prüfen. Das Menü weiß beispielsweise, welches Angebot ausgewählt wurde; die Geldverwaltung weiß, wie eine Buchung verbindlich ausgeführt wird. Beide Verantwortlichkeiten ergänzen sich, ohne dieselbe Entscheidung mehrfach unabhängig nachzubauen.

Ein hilfreicher Vertrag beschreibt das Ergebnis in fachlichen Begriffen. Eine Belohnung kann ausgegeben, bereits abgeholt oder vorübergehend nicht entscheidbar sein. Die Oberfläche übersetzt diese Ergebnisse in passende Texte. So werden interne Fehler nicht ungefiltert als Spielernachricht verwendet. Gleichzeitig bleibt die fachliche Unterscheidung erhalten, statt sämtliche Fälle in einem allgemeinen Wahrheitswert verschwinden zu lassen.

Abhängigkeiten beim Aufbau sichtbar machen

Eine Komponente sollte möglichst erkennen lassen, welche Dienste sie benötigt. Wenn sie sich an beliebigen Stellen über eine globale Hauptklasse weitere Abhängigkeiten holt, wird ihr tatsächlicher Bedarf schwer überschaubar. Ein klarer Konstruktor oder eine andere ausdrückliche Übergabe macht dagegen sichtbar, ob sie etwa Speicherung, Zeitquelle oder Nachrichtenausgabe benötigt. Das erleichtert auch einen Test mit gezielt ersetzten Teilen.

Die Hauptklasse behält dabei eine wichtige Aufgabe: Sie verbindet die konkreten Implementierungen. Sie muss aber nicht jede Fachregel selbst kennen. Im Lufox-Quelltext sind viele solche Verbindungen im Startablauf erkennbar. Für eine schrittweise Verbesserung würde man zunächst besonders stark gekoppelte Funktionen auswählen und ihre Abhängigkeiten beschreiben, bevor man große Teile auf einmal verschiebt.

Papers Beschreibung des Pluginlebenszyklus ordnet Aufbau und Aufräumen den entsprechenden Phasen zu. Für die Modulplanung ist daraus die enge Konsequenz wichtig, dass ein Objekt nicht beliebig früh alle Serverdienste verwenden darf. Konstruktion, Registrierung und tatsächliche Betriebsbereitschaft können unterschiedliche Schritte sein. Paper: How plugins work

Startfehler als erkennbare Zustände behandeln

Eine benötigte Datenverbindung kann beim Start fehlen. Ein optionaler Modellanbieter kann nicht vorhanden sein. Ein Modul muss für solche Fälle eine bewusste Entscheidung besitzen: vollständig nicht verfügbar, eingeschränkt nutzbar oder später erneut verbindbar. Ein teilweise aufgebautes Objekt, dessen Methoden zufällig an fehlenden Feldern scheitern, ist keine verständliche Betriebsform.

Für die Belohnungsfunktion wäre eine nicht verfügbare Speicherung ein Grund, Ausgaben zunächst zu sperren. Eine rein dekorative Vorschau könnte dagegen ohne diese Speicherung weiterhin erscheinen. Diese Entscheidung wird fachlich getroffen und dann in der Oberfläche sichtbar gemacht. Das Modul meldet seinen Zustand, anstatt von jedem Aufrufer zu verlangen, dieselben internen Details erneut zu prüfen.

Der Test startet die Komponente deshalb mit fehlender Abhängigkeit. Anschließend werden ihre öffentlichen Einstiege aufgerufen. Erwartet wird ein definiertes Ergebnis und keine teilweise ausgeführte Änderung. Falls eine spätere Wiederverbindung vorgesehen ist, wird auch dieser Übergang getestet. Ein erfolgreiches zweites Verbinden darf nicht doppelte Ereignisregistrierungen oder mehrere gleichartige Hintergrundaufgaben erzeugen.

Das Beenden genauso entwerfen wie den Start

Jede gestartete Aufgabe benötigt einen Endpunkt. Dazu gehören wiederkehrende Aktualisierungen, offene Verbindungen und vorübergehende Darstellungen. Beim Pluginende ist außerdem die Reihenfolge wichtig. Eine Komponente kann noch Daten sichern müssen, während eine andere die zugrunde liegende Verbindung verwaltet. Wird diese zu früh geschlossen, scheitert das ansonsten korrekt geplante Aufräumen.

Im Lufox-Abschaltcode ist diese Abhängigkeit konkret kommentiert. Bestimmte Darstellungen und offene Inventarzustände werden vor den nachfolgenden Sicherungen beendet, und die Datenbank wird in einem abschließenden Pfad geschlossen. Das ist vorhandene Struktur, aber kein pauschaler Nachweis für sämtliche Fehlerfälle. Ein einzelner unerwarteter Fehler darf nicht unbemerkt verhindern, dass andere unabhängige Ressourcen ebenfalls beendet werden.

Für jedes Modul wird deshalb kurz festgehalten, was es besitzt und was es nur verwendet. Eine selbst gestartete Aufgabenwarteschlange gehört zu seiner Verantwortung. Eine gemeinsam bereitgestellte Datenverbindung möglicherweise nicht. Diese Unterscheidung verhindert sowohl vergessene Ressourcen als auch das versehentliche Schließen eines Dienstes, den andere Module noch benötigen. Klare Besitzverhältnisse machen die Abschaltreihenfolge erklärbar.

Eine Schnittstelle am Beispiel entwerfen

Die fiktive Belohnungskomponente erhält eine Spielerkennung und eine Aufgabenkennung. Sie liefert ein fachliches Ergebnis. Die Oberfläche übergibt keine frei erfundene Auszahlungshöhe, wenn diese durch die Aufgabe bestimmt werden soll. So bleibt die Regel an einer zentralen Stelle. Eine schematische Schnittstelle kann diesen Vertrag verdeutlichen, ohne eine konkrete Lufox-Implementierung zu behaupten.

belohnung_abholen(spieler, aufgabe, auftrag)
  -> ausgegeben
  -> bereits_abgeholt
  -> voraussetzung_fehlt
  -> pruefung_offen

Die Auftragskennung unterstützt die Zuordnung wiederholter Anfragen. Das Ergebnis „Prüfung offen“ ist bewusst kein allgemeiner Fehler. Es beschreibt, dass der maßgebliche Ausgang noch geklärt werden muss. Eine andere Oberfläche kann denselben Zustand wieder anzeigen. Dadurch hängt die Fortsetzung nicht von einer bestimmten Menüinstanz ab, die vielleicht längst geschlossen wurde.

Die Schnittstelle bleibt klein, weil sie die Absicht beschreibt. Ein Aufruf, der beliebige SQL-Fragmente oder ganze Spielerobjekte durch mehrere Schichten reicht, würde mehr Verantwortung verteilen als nötig. Für dauerhafte Speicherung genügt häufig eine stabile Kennung und ein klarer Datensatz. Spielobjekte bleiben dort, wo ihr aktueller Lebenszyklus sicher behandelt werden kann.

Speicherung als austauschbaren Zugang begrenzen

Eine Fachkomponente sollte nicht an jeder Stelle eigene Verbindungen öffnen und individuelle Abfragen zusammensetzen. Ein benannter Speicherzugang kann die erforderlichen Operationen zusammenfassen. Dabei werden zusammengehörige Änderungen nicht künstlich auf mehrere unabhängige Aufrufe verteilt. Die Grenze soll Konsistenz unterstützen, nicht bloß SQL-Text verstecken.

Paper beschreibt vorbereitete Anweisungen und den Umgang mit Datenbankverbindungen. Für den Entwurf ist vor allem wichtig, Werte als Daten zu übergeben und die Lebensdauer von Verbindungen kontrolliert zu halten. Die konkrete Datenbankwahl folgt den Anforderungen des Projekts. Eine abstrakte Schnittstelle macht eine ungeeignete Speicherstrategie nicht automatisch richtig. Paper: Using databases

Ein Test kann einen einfachen Speicherersatz verwenden, um Fachregeln schnell zu prüfen. Zusätzlich braucht es jedoch einen Integrationstest mit dem tatsächlichen Speicherverhalten. Ein Ersatz, der niemals konkurrierende Änderungen oder Verbindungsfehler zeigt, kann wichtige Grenzen übersehen. Beide Testarten beantworten unterschiedliche Fragen und werden entsprechend benannt.

Schrittweise aufteilen, ohne Verhalten zu verlieren

Bei einem bestehenden Plugin beginnt die Aufteilung mit einem begrenzten Ablauf. Zuerst wird dessen aktuelles Verhalten beschrieben und durch passende Prüfungen festgehalten. Danach wird eine Verantwortung verschoben, während die sichtbare Handlung gleich bleibt. So lässt sich beurteilen, ob die neue Grenze tatsächlich hilft. Ein großer gleichzeitiger Umbau von Menüs, Datenformaten und Spielregeln wäre wesentlich schwerer zu überprüfen.

Eine gute erste Grenze ist oft eine reine Entscheidung. Beispielsweise kann die Frage, ob eine Aufgabe zur aktuellen Handlung passt, von der Ereignisannahme getrennt werden. Diese Regel lässt sich mit einfachen Eingaben prüfen. Erst danach wird die Speicherung oder die Oberfläche weiter aufgeteilt. Jeder Schritt sollte einen konkreten Verständlichkeitsgewinn liefern, statt lediglich einen zukünftigen Idealzustand zu versprechen.

Nach der Änderung wird der vollständige Spielablauf erneut ausgeführt. Einzelne Modultests reichen nicht, wenn ihre Verbindung falsch aufgebaut wurde. Der Test umfasst Start, normale Handlung, Unterbrechung und Beenden. Besonders nach einer Auslagerung muss geprüft werden, ob Ereignisse genau einmal registriert und Ressourcen genau einmal aufgeräumt werden.

Optionale Funktionen nicht still voraussetzen

Ein Modul kann eine Erweiterung unterstützen, ohne sie zwingend zu benötigen. Diese Möglichkeit muss ausdrücklich modelliert sein. Ein fehlender Darstellungsanbieter könnte beispielsweise zu einer schlichten Textausgabe führen. Eine fehlende Buchungsinstanz darf dagegen nicht als erfolgreicher Nullpreis interpretiert werden. Die Ersatzhandlung richtet sich nach der Bedeutung der Abhängigkeit, nicht nach dem Wunsch, jeden Fehler möglichst unsichtbar zu machen.

Der Test entfernt deshalb jeweils eine optionale Verbindung und betrachtet die verbleibende Funktion. Die Oberfläche soll ihren eingeschränkten Zustand korrekt erklären. Wenn die Verbindung später wieder vorhanden ist, muss der Übergang ebenso sauber erfolgen. Eine ungenutzte Referenz auf die alte Instanz oder eine doppelte Registrierung würde die vermeintlich einfache Wiederherstellung sonst erneut unsicher machen.

Die Struktur mit kurzen Verträgen pflegen

Für ein Modul genügt häufig eine kleine Beschreibung: Zweck, benötigte Dienste, erlaubte Aufrufe und Abschlussverhalten. Diese Notiz hilft neuen Mitwirkenden mehr als ein umfangreiches Diagramm ohne klare Bedeutung. Änderungen an der Schnittstelle werden zusammen mit den betroffenen Aufrufern geprüft. So bleiben Abhängigkeiten sichtbar, auch wenn das Projekt wächst.

Lufox kann seine vorhandene fachliche Gliederung auf diese Weise weiter schärfen. Das Ziel ist ein Plugin, dessen einzelne Teile verständlich aufgebaut, geprüft und beendet werden können. Eine gute Modulgrenze macht Fehlerfolgen kleiner und Entscheidungen deutlicher. Sie erlaubt dem Team, eine Funktion zu verändern, ohne jedes Mal die gesamte Anwendung gedanklich neu zusammensetzen zu müssen.

Quellen