Funktionsneid kontra Vermischung von Objekt und Datenstruktur

Nach den Empfehlungen in Clean Code sollte man die Vermischung einer Datenklasse mit einer Objektklasse vermeiden:

Hybride Strukturen, die halb Objekt und halb Datenstruktur sind, haben Funktionen, die wichtige Aufgaben erfüllen und sie haben öffentliche Variablen oder öffentliche Accessoren und Mutatoren, die private Variablen praktisch enthüllen und andere externe Methoden dazu verleiten, diese prozedural zu verwenden. Dies wird manchmal als Feature Envy bezeichnet.

Solche hybriden Strukturen erschweren sowohl das Hinzufügen neuer Methoden wie auch das Hinzufügen neuer Datenstrukturen und repräsentieren das Schlimmste aus beiden Welten!

Wenn ich das nun auflösen will und zur inhaltlichen Bearbeitung eines solchen Datenobjektes eine andere Klasse anlege, kann ich den Funktionsneid (Feature Envy) ja gar nicht vermeiden, weil die Verarbeitungsklasse natürlich auf die Accessoren zugreifen und auch Änderungen am Datenobjekt vornehmen muss.

Dennoch wäre es vermutlich der bessere Weg, als eine solche Verquickung?

Es ist schwer, zu so einem isolierten Punkt etwas konkretes zu sagen. Aber … wenn ein Objekt wirklich nur Teil einer Datenstruktur ist, spricht ja “weniger” etwas dagegen, dass es seine internen Teile freigibt. Wo man da konkret die Trennlinie zieht, hängt wohl von der Situation ab (nur suggestiv: Z.B. als eine Datenstruktur, deren Accessoren package-private sind, und Klassen mit public Funktionen, die auf diesen Datenstrukturen arbeiten).

@Crian
immer gruselig wenn wer (sehr oft du, glaube ich :wink: ) solche Halbweisheiten aus Büchern zitiert und daraus irgendwelche Code-Standards entwickelt/ vermutet

Funktionsneid/ Feature Envy nennst du ja auch als Fachbegriff und ist dir demnach sicherlich bekannt,
Beispiele wie
https://elearning.industriallogic.com/gh/submit?Action=PageAction&album=recognizingSmells&path=recognizingSmells/featureEnvy/featureEnvyExample&devLanguage=Java
mit mobilePhone.toFormattedString() statt "(" + mobilePhone.getAreaCode() + ") " + mobilePhone.getPrefix() + ..
lassen ja wohl kaum Fragen offen, kann jeder direkt aus eigener Überzeugung nachvollziehen,
auch ohne den Begriff zu kennen ein klares Konzept dem sicher niemand widerspricht,
Methoden auf Daten gehören möglichst in die Klassen rein statt das andere von außen alle Daten abfragen, fertig

die Idee, zum reinen Phone-Datenobjekt noch ein Phone-Objekt mit Logik-Methoden dazu zu haben
ist doch unrealistisch und ohne erkennbaren Vorteil, schon irgendwo gesehen, irgendwo dazu ausführlicher nachzulesen?


das entsprechende Kapitel, wie man über Suchmaschine auch schnell in ganzen Seiten angezeigt bekommt, ist (jedenfalls für mich auch) etwas unklar,

es ist ja durchaus ein interessanter Punkt angesprochen wenn eine Klasse Zugriff direkt auf die Felder erlaubt (insbesondere set statt get) + höhere Methoden darauf,
aber zu Ende gedacht ist es dort anscheinend nicht, also nicht viel davon zu gewinnen,

insbesondere deine Folgerung steht dort ja nicht, für das ctxt-Beispiel ist die Lösung, eine neue Methode anzubieten,
wohl in der Hoffnung dass die verwendeten internen Daten auch sonst nie gebraucht werden, dauerhaft versteckt werden können…

in den meisten Fällen wie bei Phone aus meinem Link illusorisch, getter, oft auch setter, und trotzdem Methoden darauf


nun, die idealen Lösungen hinsichtlich Hybrid-Klassen stehen noch aus, von mir keine,
aber deine ist es nun gewiss nicht, soviel glaube ich bezeugen zu können

Das wichtigste ist, dass man für such selbst eine gewisse Konsequenz entwickelt, wenn es darum geht Daten und Funktionalität zu trennen.

DTOs sollen ausschließlich der Datenhaltung dienen, Für diese ist das „nach Außen geben“ der interna föllig ok. Der Punkt ist, dass diese Klassen keine Geschäftslogik enthalten, die über simple Validierungen (also prüfen von „not Null“-Constrainst und ähnlichem) hinaus gehen.

Alle „höherwertigen“ Verarbeitungen der Daten wird in anderen Klassen gemacht, die dann sehr wohl via Getter-/Setter-Methoden auf die Interna der DTOs zugreifen. Auf die Interna der „Verarbeitungsklassen“ dagegen darf keine andere Klasse zugreifen.

Aber wie gesagt ist das in erster Linie eine Frage der Disziplin, Datenhaltung und Geschäftslogik sauber zu trennen.

bye
TT

Schon lustig, am Anfang der OO-Zeit hat man gesagt: “Toll Objekte können Daten UND Funktionen gleichzeitig!”. Dann hat man es mit den Funktionen übertrieben (siehe z.B. Swing-Klassen) und jetzt ist es eine Clean Code Empfehlung, Datenobjekte und Funktionsobjekte zu trennen… War OO also ein Fehler? Antwort lasse ich mal offen…

Im Prinzip schließe ich mich Marco’s und TT’s Meinung an. Ergänzend möchte ich noch auf funktionale Alternativen zu Gettern Hinweisen. Anstatt mit einem Getter den internen Zustand eines Objekts zu “exposen” und damit 1. zur prozeduralen Programmierung zu verführen und sich 2. dem Zwang zu unterwerfen, immer etwas returnen zu müssen (Stichwort enge Koppelung und Responsibillity), kann man auch Methoden anbieten, die als Argument Funktionale Interfaces entgegennehmen und diese mit dem internen Zustand bspw. einem Member des Objekts aufrufen.

Vielleicht habe ich es nicht klar genug formuliert gehabt, aber es ging mir darum, eine Trennung in dem Sinne zu erreichen, wie sie Timothy_Truckle beschrieben hat. Und der Punkt

beantwortet die Frage ausreichend.

Ich hatte eine Klasse, die erst als reines DTO angelegt war und die nun zu bestimmten Zeiten modifiziert werden muss. Zuerst schrieb ich dafür eine Methode in die Klasse und schuf damit genau so ein Mischobjekt, wie man sie nicht haben möchte.

Also ist die Trennung zwischen reinen Datenobjekten und der Verarbeitungsogik durchaus in Ordnung, im Gegensatz zu dem, was ich aus SlaterBs Posting herauslese.

in einzelnen strukturell hochgezüchteten Gebieten wie bestimmte Datenbankentities und Logikklassen dazu mag man sich den Aufwand gönnen,
teils vielleicht besser aus anderen Gründen:

  • eine einfache Person-Klasse braucht nichts über ihre DB-Speicherung zu wissen -> Repository dazu,

  • wenn die Formatierung von Dates & Co. (auch Phone…) in extra Klassen liegt, dann auch wegen höherer Funktionalität, Frage nach gewählten Formaten, Spracheinstellungen usw.,
    sicher aber auch wenn es eine zu große Arbeit wird, die allein im Umfang die Originalklasse sprengt

aber nie den Blick fürs Weite verlieren,
es gibt so endlos viele Klassen, die negativ genannten aus Swing mit Panel, Button, Events, einfache wie Swing, Listen, endlose eigene neue Klassen,
eine jeweils zweite Klasse dazu für simple Logik-Methoden wie List.isEmpty() ist reichlich undenkbar,
nur die Trennung von Daten zu Logik an sich ist normalerweise kein Faktor, aber bei richtigen Gründen vereinzelt sicher möglich

freilich ist die Regel vielleicht auch nur auf die wenigen kritischen komplexen Klassen bezogen, die einfachen außer Acht gelassen

Um auf das Folgende einzugehen:

Ich refakturiere meinen Code immer mal wieder und mir ist sehr daran gelegen, dass er gut verständlich und elegant ist und diverse über die Jahre entwickelte Richtlinien erfüllt. Natürlich tut er das in der Regel nicht sofort beim entwickeln, aber es ist - denke ich - nicht verkehrt, sich manchmal zu fragen, wie man bestimmte Dinge am besten trennt oder so ausdrückt, dass sie klar, einfach und naheliegend wirken.

Dazu über den eigenen Tellerrand zu schauen - wie zum Beispiel hier im Forum oder mit dem Lesen von Literatur, die sich mit dem Thema befasst - finde ich eigentlich wenig gruselig.

Da hier einige Entwickler mit viel Erfahrung und auch Leute mit verschiedenen Ansichten und Gewohnheiten versammelt sind, ist eine solche Diskussion für mich meist sehr bereichernd. Du musst nicht befürchten, dass ich mich dogmatisch und blind an irgendwelche fremden, halb verstandenen Richtlinien halte.

jaja, auf schnippische Anmerkungen auch entsprechend zurück, habs verdient :wink:


ich habe gewiss besonders noch

in Erinnerung mit später auch noch

hättest du auf normalen Wege final verwendet (bzw. auch nicht verwendet) wärst du ganz natürlich zum selben Ergebnis gekommen,
ohne von einer Richtlinie zur nächsten hin und her gestoßen zu werden mit vielleicht Unsicherheit, wem nun zu folgen

hier ähnlich bzw. noch wichtiger: Klassen mit Daten und Logik, viel zentraler kann es kaum gehen in der Programmierung allgemein,
lerne lieber vor allem selber kennen, was man alles machen kann,

wenn du dabei zu unpraktischen Code kommst, übervolle Klassen, überlange Methoden, Auftrennung sinnvoll oder nicht,
gleiche Zugriffe auf X von drei Stellen die durch eine Methode in X zusammengefasst werden können,
dann lernst du ganz natürlich sinnvolle Vorgehensweisen und besonders wichtig auch dabei praktisch anschaulich, wann und warum die Dinge nötig sind

aus Büchern dasselbe, teils vielleicht mit verschiedenen Philosophien unterschiedlicher Autoren, das kann kritisch enden,
Befolgen von Regeln ohne wirkliches Verständnis für das Warum (auch wenn genannt, kann praktische Erfahrung wertvoller sein)

aber nun gut, genug dazu, und im Forum zur Diskussion stellen auf jeden Fall nur richtiger Schritt :daumen:

[ot]
:slight_smile:

Vielleicht wirke ich ja irgendwie komisch? Ich programmiere inzwischen seit 33 Jahren und verdiene mein Geld damit (erst neben dem Studium, dann in Vollzeit). Das allein ist natürlich keinerlei Merkmal für Qualität, aber deine Anmerkungen wirken so ein wenig wie an jemand unerfahreneren gerichtet. Wirke ich so?

Dennoch versuche ich immer noch dazuzulernen und mich zu verbessern. Ich denke in unserer Branche ist das auch absolut notwendig. Wer aufhört, besser zu werden, wird schlechter oder so.

Und im Moment habe ich hier einen in drei Projekte aufgeteilten Code, der zum einen in einer frei wiederverwendbaren Basis vorhanden ist und für den es zwei Anwendungsprojekte gibt, beide mit vielen Klassen und Ebenen. Wenn soetwas erstmal einen gewissen Umfang hat, muss man einfach eine bestimmte Sorgfalt und Disziplin an den Tag legen, was Trennung, Klarheit und Ausdrucksstärke angeht.

So verwende ich inzwischen sogar Dinge wie das abstract factory pattern, das ich beim ersten Lesen damals als für mich zu theoretisch beiseite gelegt habe, und das plötzlich von ganz alleine Sinn machte, als es darum ging, Dinge zu entkoppeln, um sie unabhängig testen zu können, oder eben um sie in verschiedene, unabhängige Programmpakete aufzuteilen.

Ich argwöhne halt immer, weiter Ungeschicklichkeiten oder unschöne Dinge in meinem Code zu haben - was sicher auch der Fall ist - und versuche nach Möglichkeit, die Ecken zu verbessern, an denen ich gerade aus anderen Gründen arbeite. Daher kommen hier vielleicht ab und an so eher theoretische Nachfragen von mir. Die stammen aber nicht aus dem Elfenbeinturm.

Und sei ruhig schnippisch, auch aus deinen Beiträgen in diesem und dem alten Forum habe ich viel gelernt! Denn das Erlernen einer neuen Programmiersprache ist ja nicht mit der Syntax erledigt - das fällt nach so vielen Jahren und so vielen verschiedenen Sprachen leicht - es ist immer das Drumherum und die richtige Verwendung der Sprachmittel, die die Dinge elegant werden lassen oder eben auch nicht.

Und wenn ich an meine Anfänge denke, haben wir heute einen Haufen toller Möglichkeiten mehr. In meiner ersten Programmiersprache, Basic auf dem C64, gab es weder Methoden noch Funktionen, da gab es nur goto und gosub. So musste man sich also Zeilennummern als Methodennamen merken, und wehe die freien Zeilennummern dazwischen wurden zu knapp … eine Refakturierungshölle! Das wäre heute wahrlich eine Strafe, damit produktiv arbeiten zu müssen… es war damals zum Glück aber auch nur Hobby. Aber auch damit haben mein Vater und ich ein Schachprogramm geschrieben, das die Regeln korrekt abbildete und Matt in zwei Zügen finden konnte (auch wenn das dauerte…).
[/ot]

[ot]
für mich in der Tat dieser Eindruck nach ständigen Themen die eher Neulinge posten,
immerhin durchaus mit etwas anderer Sprache, und andere zitieren nicht unbedingt aus Clean Code, wenn auch mal final entdeckt usw.

und noch ein positiver Punkt:
besser so rum (erfahren sein und (evtl. für manche) nach Neuling wirken, viel fragen)
als andersrum (nur erfahren reden, dabei ohne Ahnung), wie manche User im Forum, mich würde da bestimmt auch der ein oder andere zu zählen :wink:
[/ot]

[ot]
Dazu passt:

Ich finde „solche“ Fragen machen einen wichtigen Teil eines Forums aus (Mit „solche“ meine ich welche, die woanders downgevotet und als „Primarily opinion-based“ geclosed werden würden)
[/ot]

Ich koennt jetzt mal noch einwerfen, dass Getter und Getter fuer genau diese Datenklassen voellig sinnlos sind und man deshalb gleich public Felder verwenden koennte (aber ich lass es mal :-P).

In Kotlin gibts uebrigens eine spezielle Art, solche Klassen zu definieren:

data class User(val name: String, val age: Int)

Macht eine Klasse mit zwei immutable Feldern und generiert (unsichtbar) Haufen nuetzlichen Kram dafuer (equals, toString, copy, …).

Ich find auch, das widerspricht nicht dem OOP Gedanken. Bei dieser User-Klasse handelt es sich einfach nicht um eine “normale” Klasse. Es ist einfach nur eine zusammengesetzte Datenstruktur. Eine Datenstruktur “kann” (== hat Methoden) per Definition nichts, ausser eben Daten zu strukturieren.

Ja, zugegeben, diese Trennung zwischen “Datenklassen” und “Funktionsklassen” war mir nicht “präsent bewußt” und ich habe sie nie bewußt forciert. Aber wenn ich eine Klasse “Person” habe, und nicht weiß, ob die in Zukunft aus einer Datenbank, einer Binärdatei oder einer XML-Datei gelesen wird, dann würde ich einen Teufel tun und da eine
static Person readFrom(String databaseName) throws SQLException
reinschreiben (das ist IMHO schon durch andere “Prinzipien” vorgegeben). Auch an anderen, weniger offensichtlichen Stellen tendiere ich dann eher zu (meinetwegen in manch anderer Hinsicht wieder fragwürdigen) Klassen, die AUF den eigentlichen Datenhalteklassen operieren (und seien es nur (hemdsärmelige) “Utils”-Klassen :rolleyes: )

(Kurz zu den settern+gettern: Es MACHT Sinn. Ein mutable public field ist ein no-go (einzige Ausnahme: Innere Klassen, wo die Sichtbarkeit durch die Umgebung schon ausreichend eingeschränkt ist). Selbst bei einem immutable field würde ich noch einen getter drumwickeln. Allein für debugging und introspection. (Andere sind der Meinung, “public final” wäre OK, aber darüber kann man streiten)).

Man koennte sowas schreiben:

public class User {
  private static class UserData {
    public String name;
    public Integer age;
  }
  private static final UserData NULL = new UserData();
  private UserData data = NULL;

  public String getName() { return data.name; }
  ...
  "Logik"
}

Das wuerde der Sichtbarkeitsregel entsprechen und man haette trotzdem die Trennung. Zusaetzlich isses trivial, mehrere Zustaende eines Users zu verwalten (fuer History, DB, etc).

Das nutze ich in Einzelfällen auch (mit äußerst überschaubarem Scope).

Oft sind allerdings die Setter nur Paketsichtbar, die Getter public. Das gibt ein wenig mehr Sicherheit, wer die Daten ändert und wer nicht.

Wenn möglich benutze ich auch für DTOs Klassen ohne Setter.