Das Java Collections Framework liefert allgemeine Abstraktionen. Eine List beschreibt eine geordnete Sammlung, die auch doppelte Elemente enthalten kann. ArrayList ist eine konkrete Implementierung davon. Beide kennen aber nicht die fachliche Rolle der Werte in einer Anwendung.
Nehmen wir eine Gästeliste mit einer klaren Zusage: Jeder Gast darf höchstens einmal vorkommen. Eine List<GastId> kann die IDs speichern, setzt diese Regel aber nicht selbst durch. Die Gästeliste muss beim Einladen prüfen, ob die ID schon enthalten ist. Auch wenn die Liste diese Prüfung ermöglicht, sagt der Typ List nicht, was „einladen“ bedeutet oder welche Handlungen Aufrufer mit der Gästeliste ausführen dürfen.
Diagnose
Stell dir vor, du organisierst ein Abendessen. Der Anwendungscode speichert die Gäste direkt in einer List<GastId>. Damit kann dieselbe ID mehrfach in der Liste stehen. Um die Zusage „jeder Gast höchstens einmal“ einzuhalten, muss jeder Aufruf vor dem Hinzufügen prüfen, ob der Gast schon eingetragen ist.
Die Liste bietet allgemeine Operationen. Was bedeutet add in diesem Anwendungsfall? Was soll bei einer zweiten Einladung passieren? Ist der Gast eingeladen, hat er zugesagt oder ist er nur vorgemerkt? Die Datenstruktur beantwortet diese Fragen nicht. Die Gästeliste ist mehr als die Collection, in der ihre Einträge liegen.
Muster
Eine Liste beschreibt technische Eigenschaften und mögliche Operationen. Sie sagt nicht, welche Rolle ihre Werte im Anwendungsfall spielen.
Darum würde ich die Liste nicht durch die Anwendung reichen, sondern ein Objekt Gästeliste anbieten. Sein Name erklärt, wofür die Einträge stehen. Seine Operationen könnten einladen, istEingeladen und ausladen heißen.
Der übrige Code arbeitet dann mit Einladungen statt mit allgemeinen Listenoperationen. Der Aufruf beschreibt den Anwendungsfall, nicht die Speichertechnik.
Mechanik
Eine Gästeliste könnte beispielsweise diese Operationen anbieten:
einladen(gastId): den Gast auf die Gästeliste setzen.istEingeladen(gastId): prüfen, ob der Gast auf der Liste steht.ausladen(gastId): den Gast wieder von der Liste entfernen.
Innen verwendet die Gästeliste eine ArrayList<GastId>. Sie hält die Collection privat und prüft beim Einladen, ob der Gast schon eingetragen ist. Nach außen gilt: Ein Gast steht höchstens einmal auf der Liste. Eine erneute Einladung erzeugt keinen zweiten Eintrag. Wer ausgeladen wurde, gilt danach nicht mehr als eingeladen.
Der Aufruf bleibt fachlich lesbar:
final class Gästeliste {
private final List<GastId> gäste = new ArrayList<>();
boolean einladen(GastId gastId) {
if (gäste.contains(gastId)) {
return false;
}
gäste.add(gastId);
return true;
}
}
Der Aufrufer sagt gästeliste.einladen(gastId). Der Rückgabewert zeigt, ob die ID neu hinzugefügt wurde: true bei der ersten Einladung, false, wenn sie bereits auf der Liste steht. Der Aufrufer bekommt die Liste nicht selbst und kann sie nicht leeren, durchlaufen oder beliebig verändern.
Das ist mehr als ein anderer Name für dieselbe Collection. Die Schnittstelle lässt weniger zu und erklärt mehr. Der Aufrufer muss nicht selbst auswählen, wie Listenoperationen die Gästelistenregel umsetzen sollen.
Hier berührt der Gedanke das Interface Segregation Principle: Ein Aufrufer soll nur von den Operationen abhängen, die er braucht. Die Gästeliste verbirgt allgemeine Listenoperationen wie clear, removeAll oder einen Iterator, mit dem sich die gesamte Liste von außen verändern ließe. Wenn manche Aufrufer nur prüfen und andere Einladungen verwalten sollen, können dafür zusätzlich getrennte Lese- und Schreibschnittstellen sinnvoll sein.
Wenn sich die interne Struktur ändert
Die Gästeliste könnte zunächst mit ihrer internen Liste arbeiten und die Regel durch eine Prüfung vor jedem Hinzufügen sichern. Wenn die Liste später wächst, kann es sinnvoll sein, diese interne Struktur durch ein Set<GastId> zu ersetzen. Ein Set verhindert doppelte IDs anhand der Gleichheitsregeln von GastId; die fachliche Zusage und der Aufruf gästeliste.einladen(gastId) bleiben gleich.
Weil Aufrufer nur den Vertrag der Gästeliste kennen, bleibt dieser Umbau in der Gästeliste. Ohne diese Grenze könnten Aufrufer selbst Listenoperationen verwenden oder die Regel an mehreren Stellen nachbauen. Dann müssten diese Stellen bei einer späteren Strukturänderung gefunden und geprüft werden.
Die Abstraktion verspricht nicht, jede Änderung kostenlos zu machen. Sie bündelt aber die interne Struktur und die Regeln, die sie durchsetzt. Ändert sich auch der Vertrag – etwa weil nun eine bestimmte Reihenfolge garantiert werden soll –, müssen die Aufrufer diese neue Zusage berücksichtigen.
Was das mit KI zu tun hat
Selbst wenn ein KI-Agent ganze Programme erzeugen oder umfangreich umbauen kann, bleibt eine andere Frage: Wie kann er eine Änderung zuverlässig begrenzen und prüfen? Fähigkeit, viel Code zu erzeugen, macht die Beziehungen zwischen den Teilen nicht einfacher.
Wenn Anwendungscode direkt mit Listen arbeitet, muss ein Agent erkennen, welche Stellen die Gästelistenregel umsetzen und welche Listenoperationen davon abhängen. Eine Gästeliste gibt ihm einen kleineren, benannten Auftrag: einladen, istEingeladen, ausladen.
Das kann die kognitive Last eines Agenten reduzieren: Für eine Änderung muss er weniger mögliche Operationen und verstreute Regeln gleichzeitig berücksichtigen. Wird die interne Liste später durch eine andere Collection ersetzt, kann die Anpassung in der Gästeliste liegen. Der Agent muss dann nicht zuerst alle Aufrufer finden, die von der konkreten Collection abhängen. Menschen profitieren davon aus demselben Grund.
Das ist meine Entwurfsbegründung aus der Arbeit mit einem konkreten Codebestand, kein empirischer Nachweis darüber, wie jedes Modell intern denkt. Die praktische Frage bleibt überprüfbar: Muss ein Agent für eine lokale Änderung viele Aufrufstellen und Listenoperationen gleichzeitig rekonstruieren, oder reicht ihm der Vertrag der Gästeliste?
Gegenargument
Man kann aus jeder Variable eine Klasse machen. Das wäre keine Verbesserung. Wenn Gästeliste nur add, contains, remove, clear und alle weiteren Listenoperationen unverändert weiterreicht, hat sie die allgemeine Schnittstelle lediglich hinter einer zusätzlichen Datei versteckt.
Auch eine lokale Liste kann passend sein, wenn sie nur eine einfache Zwischenrechnung ausdrückt. Meine These richtet sich an Anwendungscode, in dem eine Liste eine konkrete fachliche Rolle übernimmt: Dort sollte ihre Bedeutung nicht unsichtbar bleiben.
Handlungsvorschlag
Wenn im Anwendungscode eine allgemeine Liste auftaucht, frage ich zuerst, wofür ihre Werte in diesem Ablauf stehen und welche Regel gelten soll. Wenn die Sammlung eine fachliche Rolle hat oder eine Regel durchsetzen muss, sollte ich erwägen, diese Rolle durch eine passende Schnittstelle auszudrücken.
Diese Schnittstelle sollte nur die erlaubten Handlungen anbieten und ihre Zusagen klar machen. Die interne Collection kann sich später ändern, solange die Gästeliste ihren Vertrag weiterhin erfüllt.
Prompt für einen Agenten
Für eine konkrete Codebasis kann dieser Prompt als Ausgangspunkt dienen:
Untersuche in diesem Codebereich die Verwendung allgemeiner Listen und anderer Collections.
Finde Stellen, an denen eine Liste eine fachliche Rolle oder konkrete Regeln ausdrückt. Prüfe dazu ihre Aufrufer und die tatsächlich benötigten Operationen. Schlage eine benannte, engere Abstraktion nur dann vor, wenn sie Bedeutung, Regeln oder erlaubte Handlungen klarer ausdrückt. Erzeuge keine Klasse, die lediglich die Listenmethoden unverändert weiterreicht.
Nenne für jeden Vorschlag:
- die betroffenen Stellen und die dort sichtbare fachliche Bedeutung,
- den Vertrag und die Regeln, die eine Abstraktion sichern würde,
- welche Komplexität dadurch entfällt und welche neue Komplexität entsteht,
- wie sich die interne Collection später ändern ließe, ohne den Vertrag zu verletzen.
Belege deine Einschätzung am vorhandenen Code. Wenn eine lokale Liste die klarste Lösung ist, sage das ebenfalls. Ändere noch keinen Code; liefere zuerst eine knappe Analyse und einen konkreten Vorschlag.
Prüffrage
Kann die interne Liste später geändert werden, ohne die Aufrufer anzupassen und ohne die Zusagen der Gästeliste zu verletzen?
Signal
Ein gutes Zeichen ist, wenn der Anwendungscode gästeliste.einladen(gastId) aufruft und eine spätere Änderung der internen Collection keine Anpassung dieser Aufrufer erfordert.
Ein Warnzeichen ist eine Hülle, die alle Listenmethoden weiterreicht und dem Aufrufer trotzdem dieselben Entscheidungen über die Speicherung und die Gästelistenregeln überlässt.