Object-oriented programming is bad

Grad zufaellig entdeckt: Object-Oriented Programming is Bad - YouTube

Ziemlich radikal zwar, aber dennoch interessante Sichtweise. Glaub, da muss ich erstmal drueber nachdenken :slight_smile:

OK, hab’s mir mal reingezogen und ich muss sagen, es war sehr gut für meine Fitness weil ich andauernd den Kopf schütteln musste.

Sein wesentlicher Kritikpunkt ist ein (aus meiner Sicht) falsches Verständnis von “Encapsulation”: Er behauptet, dass ein Objekt nicht nur für seinen eigenen State verantwortlich ist, sondern auch für den seiner Abhängigkeiten, und dass es eine Verletzung der “Encapsulation” ist, wenn andere Objekte den State seiner Abhängigkeiten ändern. Auch die These, das die Weitergabe von Objekten als Methodenparameter nicht dem “Nachrichtenparadigma” entspricht ist IMHO ziehmich weit her geholt. Und mehr hat er ja gar nicht gegen OOP.

Und das eine Klasse mit Namen X nicht die Funktionalität X (komplett) enthält ist IMHO auch nicht ein Problem von OOP, sondern von schlechten Programmierern.

Richtig absurd wird’s dann ab 40:00, wo er dann seine Gegenvorschläge bringt.
Erst erzählt er, dass “composed methods” blöd sind, weil es schwer ist, gute Methodennamen zu finden, und man doch lieber lange Methoden mit “Abschnittskommentaren” schreiben sollte (Wahrscheinlich ist er der einzige Programmierer Weltweit, der diese Kommentare korrekt pflegt, wenn sich was ändert…), um dann nach einem neuen Feature zu rufen, dass ihm “private scope” innerhalb der langen Methode ermöglicht, also eine Lösung für ein Problem, dass er mit “composed methods” nicht hätte…

Also nein, mich hat er nicht überzeugt.

bye
TT

Naja, das is doch im Kern einfach nur eine Argumentation fuer functional programming - also es ist egal, ob du nun ein internes Feld direkt aendern kannst (z.b. wenn es pubic waere) oder ob du einen Setter dazu aufrufen musst; entscheidend ist, DASS du den internen Zustand eines Objektes aendern kannst. In der funktionalen Welt geht das einfach nicht. Da legt man eine Kopie mit genau dieser Aenderung an.

Bei der Kommentargeschichte geh ich mit, das is ziemlich wirr.

Gescopte Methoden (bzw functions) waeren trotzdem nett, leider ist hier auch Java 8 ziemlich eingeschraenkt (uebrigens koennte er sich diese use Sache mit JavaScript (was er ja da verwendet) relativ easy nachbauen).

Beim Thema Vererbung bin ich mir auch nich so ganz sicher. Jedenfalls achte ich inzwischen schon darauf, moeglichst kleine oder besser noch gar keine Hierarchien zu benutzen und eher mit Interfaces zu arbeiten. Ich hab einfach schon Vererbungshierarchien erleben duerfen, da will ich lieber nicht drueber reden :slight_smile:

[quote=schalentier]Naja, das is doch im Kern einfach nur eine Argumentation fuer functional programming - also es ist egal, ob du nun ein internes Feld direkt aendern kannst (z.b. wenn es pubic waere) oder ob du einen Setter dazu aufrufen musst; [/quote]Diese Aussage geht leider am Wesen der OOP vorbei: Den State über Setter zu ändern ist nicht OOP.
Aber ich will mal nicht Erbsenzählen, denn es geht Dir wohl um die Zustandsänderung des Objekts selbst.

Das ist in der OO-Welt aber OK, denn Objekte haben nun mal einen State, der über Nachrichten verändert werden kann.

[quote=schalentier;131358]entscheidend ist, DASS du den internen Zustand eines Objektes aendern kannst. In der funktionalen Welt geht das einfach nicht. Da legt man eine Kopie mit genau dieser Aenderung an.[/quote]Für mich ist die Frage, ob das immer sinnvoll ist, noch nicht beantwortet. Auch in dem Video ist ja das Problem angesprochen worden, dass eine Anwendung irgendwie einen State verwalten muss. Sein Vorschlag war globale Variablen einzusetzen. “Back to the roots” so zu sagen… ;o)

[quote=schalentier;131358]Beim Thema Vererbung bin ich mir auch nich so ganz sicher.[/quote]Das Video hat Vererbung gar nicht so im Fokus gehabt.
Aus meiner Sicht gibt es 2 Probleme: Equals-Implementierungen und Testbarkeit.
Daher bilde ich Vererbung über Interfaces ab und bei den Implementierungen halte ich mich an das “Favour Composition over Inheritance” Pattern.

Aber solche Design-Pattern sind ja laut Video nur Pflaster für die ach so kaputte OO-Welt…

BTW: Falls ich das Video richtig verstanden habe scheint er ja TDD an sich ganz OK zu finden. Da möchte ich doch mal sehen, wie mal für funktionale Programmteile Unittests so schreibt, dass die die RTFM-Bedingungen erfüllen, und wie das dann mit “private scoped inline functions” harmoniert…

bye
TT

Ja, hab’s auch mal angeschaut, und stimme einigen der oben schon genannten Punkte zu. Wo er da über „Encapsulation“ redet bin ich dann gedanklich kurz abgedriftet, weil es einfach ZU abstrus erschien (z.B. das was er über die „Zuständigkeiten für den State“ sagt usw). Nur auf der höchsten Abstaktionsebene (sic!) kann ich einigem davon zustimmen: Wenn man alles immer bis zum Anschlag Schulbuch-OO-mäßig durchplanen will, ist es schwer, ein Ende zu finden, an dem nicht jemand sagt: „Du könntest da aber noch diesen Teil in einen X-er auslagern“.

Ingesamt ist da viel Kritik hinter einem provokativen Titel versteckt. („Papers whose title contains ‚considered harmful‘ are considered harmful“), aber finde auch, dass einiges der Kritik auf ein „falsches“ (oder etwas verzerrtes/überzogenes) Verständnis hindeutet.

Wieder recht abstrakt: Der Schönheit von „Funktionen (als Building Blocks)“ stimme ich zu, mit der Konsequenz dass ich mir schon gelegentlich von durch andere Paradigmen indoktrinierten Rosinenproduzenten anhören durfte: „Ieeeh, static methods, die sind schlecht, die kann man nicht mocken!!!11eins“ (Ja. Und? Das ist egal!).

Das Ende war dann aber wieder etwas wirr: Nicht in Funktionen auslagern. Also dann doch. Aber wieder nicht. Oder doch, aber nur speziell gescopt. Mit super-spezial-Funktionen, die es in keiner Programmiersprache gibt. (Außer in C++ - mit lambda captures, die GENAU seinem „use“ entsprechen…).

Tja, wie auch immer: Scheißcode kann man in jeder Sprache und mit jedem Paradigma schreiben :smiley:

[quote=Marco13]finde auch, dass einiges der Kritik auf ein „falsches“ (oder etwas verzerrtes/überzogenes) Verständnis hindeutet.[/quote]Genau mein Eindruck. Er sagt zwar, dass er schon lange (OO-) programmiert, aber man kann eine Sache auch lange falsch machen… ;o)

[quote=Marco13;131368]„Ieeeh, static methods, die sind schlecht, die kann man nicht mocken!!!11eins“ (Ja. Und? Das ist egal!).[/quote]Nur solange du keine echten (im Sinne von RTFM) Unittests schreibst.

[edit SlaterB: weiter dazu abgespalten hier: https://forum.byte-welt.net/java-forum/allgemeine-themen/19089-test-mocken-statischer-methoden.html ]

[quote=Marco13;131368]Tja, wie auch immer: Scheißcode kann man in jeder Sprache und mit jedem Paradigma schreiben[/quote]Da hast Du sowas von Recht…:kiss:

bye
TT

Ich versteh eigentlich nicht, was wirklich allgemein gegen objektorientiertes Programmieren spricht. Schließlich ist es ein Paradigma, welches die Eleganz unserer deutschen/englischen Sprache bietet. Was jedoch Java aus OOP gemacht, finde ich gruselig, wie etwa AbstractNamingConventionProxyFactory. In C++ oder Python ist wenigstens keine Funktion gezwungen sich in einer Klasse zu befinden, was dann eben nicht zu solchen Conventions führt.

Die Aussage, dass es ~“nicht schön ist, dass in Java alles in einer Klasse stehen muss, und es keine ‘freien’ Funktionen gibt”, hat mich nie ganz überzeugt. Solche Funktionen gibt es in C++ “praktisch” auch nicht - und wer glaubt, dass es sie DOCH gibt, der möge sich in Java eine Klasse definieren, die “std” heißt, und nur statische Methoden enthält, damit er weiterhin std::max (bzw. dann eben std.max) aufrufen kann.

Das Argument, dass OOP “die Eleganz unserer … Sprache bietet” stimmt einerseits. Aber das kritisiert bzw. hinterfragt er ja auch - und IMHO, auch zu Recht. Es ist zwar einerseits schön und erstrebenswert, wenn sich Code wie “Prosa” liest. Aber oft stellen sich dann Fragen zur Modellierung (die mich, zugegeben, manchmal zur Verzweiflung bringen). Sein Beispiel war ja grob (hier leicht erweitert), welches hiervon die “richtige” Modellierung ist:

message.send(); 
sender.send(message);
sender.send(message, receiver);
receiver.receive(message);
receiver.receive(message, sender);
transferrer.transfer(message, sender, receiver);
MessageTransfers.create(message, sender, receiver).exeucte();

Das sind zumindest die Fragen, die mich Nachts nicht schlafen lassen, weil alles Vor- und Nachteile hat, es immer Dinge gibt die mit dem einen Ansatz “schön” und mit dem anderen “schlecht” (oder sogar: gar nicht) abgebildet werden können, und über allem das Damoklesschwert der unbenutzbaren Über-Abstraktion schwebt, wenn man wirklich alles “sauber durchmodellieren” will…

Ich meinte natürlich, dass sich die Funktionen sich nicht zwangweise in Klassen befinden müssen. In Python befinden sie sich natürlich in Module, wo man auch Klassen stecken kann. In C++ befinden sich diese zu meist in Namespaces.

Zu dem Beispiel: Ich selbst wüsste auch nicht, wie ich so etwas in Java abbilden sollte. Wenn dann würde ich mal schauen, was andere APIs machen. Ich würde aber dazu neigen, einen Mittelweg zwischen Abstrahierung und nicht Abstrahierung wählen, weil alle Lösungen irgendwie nicht besonders gut aussehen. Ich glaube ich würde am ehesten message.send() wählen, auch wenn es auch nicht gut aussieht, weil dadurch die Message class mit dem Rest unnötig verdrahtet wird. Auch sieht es blödsinnig aus, eine Klasse mit dem $Verb + Präfix -er $Verb zu befehlen und die statische Methode sieht dort auch nicht gut aus. In Python jedenfalls würde ich versuchen ein Modul namens msgs erstellen und dort alle Funktion hineinzustecken, die das senden und empfangen von Nachrichten betreffen.

Ja, das ist genau der Konflikt, den ich da sehe (und den er auch (soweit ich das verstehe, zumindest indirekt) kritisiert). Auch wenn die genauen Klassen und Bedeutungen und use-cases bei dieser sehr suggestiv gestellten Frage nicht klar sind, würde man hinter einem „Message“-Objekt ja intuitiv irgendein Datenpaket verstehen - und zwar ein ziemlich „dummes“. Es wäre zwar „eher OOP“, wenn man message.send() schreiben würde, als wenn man auf MyStaticUtilityMethods.send(message, sender, receiver) zurückgreifen würde (scnr :D). Aber wie du schon angedeutet hast, würde das bedeuten, dass eine „Message“ IHREN „Sender“ und IHREN „Receiver“ kennen müßte. Auch wenn es sicher Szenarien geben kann, wo das sinnvoll ist, würde es erstmal, für mich persönlich und aus abstrakter Modellierungssicht zumindest ungünstig wirken. (Ganz oberflächlich: Es würde sich zumindest sofort die Frage der Multiplizität stellen. Hat jede Message nur EINEN Receiver, oder kann nicht dieSELBE Message auch mal an ZWEI Receiver geschickt werden? Etc. Ich finde, es gehört einfach nicht da rein). Eine Modellierung, von der ich glaube, dass sie „OK“ sein könnte, wäre die angedeutete (aber IIRC nicht (und nichtmal ähnlich) im Video vorkommende) „MessageTransfer“-Klasse. Keiner muss irgendwas kennen, keine unnötigen Abhängigkeiten. Aber es gibt eben einen MessageTransfer, wo alles zusammenkommt, und eine Message von einem Sender zu einem Receiver geschickt wird.

(Es ist wie immer schwierig, so ein vermeintlich konkretes Bespiel auf so einer oberflächlichen, nicht-ausspezifizierten Ebene zu besprechen, und damit Gefahr zu laufen, andere Messages (sic) aus dem Video unter den Tisch fallen zu lassen…)

Ich habe das Video nicht angesehen, ist wahrscheinlich nicht gut für meinen Blutdruck.

Aber natürlich lässt sich aus einem schlechten Video nicht schließen, dass im OO-Land alles eitel Sonnenschein wäre. Das Hauptproblem, das ich mit OO-Programmierung habe, ist wenig überraschend Vererbung. Dieses **eine **Konzept muss für (mindestens) **drei **Ziele herhalten, die nicht immer miteinander kompatibel sind, und keine Sprache hat es bisher geschafft, diese Ziele flexibel und orthogonal zueinander abzubilden. Die Ziele sind:

  1. Typing: Wenn ich ein Foo x habe, ist es legal, x einer Variable vom Typ Bar zuzuweisen? Fast alle OO-Sprachen sind sich darin einig, dass man das darf, wenn Foo von Bar erbt (*). Ich kann mir durchaus vorstellen, dass es Fälle gibt, wo es nützlich wäre zu “vererben”, aber die entsprechende Zuweisung trotzdem verboten sein sollte (etwa Timestamp von Date).

  2. Schnittstelle: Welche Operationen kann ein Foo x ausführen? Aber die Vermischung mit der Typing-Frage erlaubt es nicht, das z.B. Teile der Schnittstelle (oder die gesammte Schnittstelle) wieder “versteckt” werden können. Wäre es nicht cool, aus einer mutablen Collection eine immutable abzuleiten, in dem man auch bestimmte Methoden beim Vererben weglässt (und dafür entsprechende “kopierende” Varianten anbietet)?

  3. Implementierung: “Don’t repeat yourself” - Übernahme von vorhandenen Implementierungen. Ein Quadrat könnte durchaus Implementierungen von einem Rechteck übernehmen, aber nicht dessen Schnittstelle, die eine unterschiedliche Länge und Breite erlaubt.

(*) Was auch immer das im jeweiligen Kontext heißen mag. Schon die Einführung parametrischer Typen (a.k.a. “Generics”) zeigt, dass die Frage nicht ganz so einfach zu beantworten ist, wie es scheint (z.B. Kovarianz, Kontravarianz, Invarianz)

[quote=Landei]1) Typing:[/quote]Mehrfachimplementierung von (von einander unabhängigen) Interfaces macht genau das.

[quote=Landei;131607]2) Schnittstelle: Welche Operationen kann ein Foo x ausführen? Aber die Vermischung mit der Typing-Frage erlaubt es nicht, das z.B. Teile der Schnittstelle (oder die gesammte Schnittstelle) wieder “versteckt” werden können.[/quote]Also eigentlich doch, wenn die Schnittstellen eben nicht von einander geerbt haben.

[quote=Landei;131607]Wäre es nicht cool, aus einer mutablen Collection eine immutable abzuleiten, in dem man auch bestimmte Methoden beim Vererben weglässt (und dafür entsprechende “kopierende” Varianten anbietet)?[/quote]Viel cooler wäre es doch aus einer Immutable Collection eine Mutable Collecton durch Hinzufügen der notwendigen Methoden abzuleiten…

[quote=Landei;131607]3) Implementierung: “Don’t repeat yourself” - Übernahme von vorhandenen Implementierungen. Ein Quadrat könnte durchaus Implementierungen von einem Rechteck übernehmen, aber nicht dessen Schnittstelle, die eine unterschiedliche Länge und Breite erlaubt.[/quote]Ich sag nur “Favour Composition over Inheritance”.

Also in den seltensten Fällen ist der Hammer schuld, wenn man seinen Daumen trifft…

bye
TT

@Timothy_Truckle : ich stimme dir in allen Punkten zu. Beim Lesen von @Landei s Beitrag gingen mir dieselben Lösungsansätze durch den Kopf - das sind ja auch die (zumindest in Java) etablierten Wege.

Zu 1) fiel mir spontan auch noch das Konzept von dynamisch typisierten Sprachen ein. Auch wenn ich die Sprache im Detail nicht kenne, soll smalltalk den Typ einer Klasse aus den vorhandenen Methoden ableiten und nicht aus einer Deklaration.
Zu 2) noch ein Anwendungsbeispiel, das auch in Java funktioniert: Read-Only-Interfaces.

Zum Thema aus einer mutablen eine immutable Collection (oder umgekehrt) abzuleiten: widerspricht das nicht dem Grundsatz der “is-a”-Beziehung? Das funktioniert zwar (zumindest in eine Richtung), aber die Eigenschaft “Veränderbar” oder “Unveränderbar” ist elementar für die Klasse und dürte daher mMn innerhalb einer Vererbungshierarchie nicht verändert werden. Auch wenn durch Vererbung an dieser Stelle Code wiederverwendet werden könnte, wäre Komposition der “richtige” Weg.

Einerseits erinnert das dann an das Ars technica experiment, andererseits: Schon OK, sooo sehenswert ist das nicht. Recht wirr und länglich… man kann seine Zeit sinnvoller nutzen.

Die genannten drei Punkte sind eine Sichtweise. Ich finde, dass die Punkte schon stark zusammenhängen, aber … das ist ja anscheinend das, was du kritisierst, und letztlich ist das vielleicht nur das Ergebnis der „Prägung“ durch klassiche Sprachen wie Java und C++. Deren Typsysteme haben ihre Schwächen, die man teilweise mit „Altlasten“ rechtfertigen kann, aber auch noch mit vielen anderen Dingen. Beide Sprachen wurde nicht mit dem Ziel entwickelt, eine „Typtheoretisch einwandfreie Sprache“ zu entwickeln, sondern eine Sprache zum „getting things done“. (Zumindest Java. Zu C++ sage ich da jetzt mal nichts :rolleyes: hucrapst ). Die Schwierigkeiten und das Glatteis, auf das man sich da leicht begibt, erkennt man, wie du ja schon sagtest, bei den Generics. Die sind zwar „typtheoretisch schön“, aber immernoch nicht einwandfrei, und haben selbst eingefleischte Java-Fans an den Rand der Verzweiflung gebracht. Sie haben auch viel „häßlichen Code“ verursacht. Sowas wie Angelika Langers FAQ-Beitrag dazu spricht schon Bände, die Abschnitte zu Typinferenz in der JLS ebenso, die (gefühlt) meisten Regression-Bugs zwischen den Major-Java-Versionen (und Unterschiede zwischen dem Eclipse-Compiler und Javac) hängen damit zusammen, dass es die Typinferenz bei irgendwelchen absurden Schachtelungen raushaut, und für mich persönlich war es schon fast erschütternd, zu lesen, wass man mit Wildcards Typen bauen kann, die es nicht geben kann (und wo es eigentlich jeden Compiler bei dem Versuch, sie aufzulösen, raushauen müßte). (Erschüternd, aber nicht wirklich überraschend, wenn man sieht, wie absurd komplex vermeintlich einfache Dinge sind).

Trotzdem denke ich, dass die drei genannten Punkte mit Java schon „relativ sauber“ getrennt werden können. Nicht perfekt, ohne sowas wie Duck-Typing, und natürlich ist Java nicht JavaScript, wo man Funktionen eher an Objekte hängt und sie weniger mit Klassen zu tun haben, aber … es liefert einen ganz guten Trade-off zwischen Generizität, Sauberkeit und Einfachheit (wobei letzteres sehr wichtig ist - auch wenn es nicht cool klingt, wenn man das sagt).

Und ich sag nur:

public double getArea() {
    return MyStaticUtilityMethods.computeArea(width, height);
}

:smiley:

Ich habe nicht gesagt, dass sich für die angesprochenen Probleme keine Lösungen finden lassen, der Punkt ist vielmehr, dass viele der Lösungsvorschläge eben weg von “OO” gehen. So stimme ich der alten Weisheit “Composition over Inheritance” ja zu, nur muss sich dann “Inheritance” die Frage gefallen lassen, welches Problem sie denn eigentlich löst.

Auch was eine “is-a”-Beziehung ist, ist in der Praxis nicht ganz klar. In der Theorie gibt es das LSP als Kriterium, nur wird das eben im richtigen Leben nicht immer eingehalten, weil es zu restriktiv ist. Und sobald die Typen komplizierter werden, ist der “ist-ein” Ansatz sowieso zu simplistisch (“ist eine” List<Timestamp> denn eine Collection<Date>? - nein, ist es nicht, aber erkläre das einmal einem Neuling)

Und manchmal möchte man auch nur das gleiche Verhalten, aber keine Vererbungsbeziehung (etwa physikalische Einheiten, die sich exakt so wie BigDecimal verhalten sollen, aber mit diesem nicht einfach verrechnet werden können). Haskell realisiert diese Funktionalität über newtype. Sicher kann man hier auch “wrappen”, aber das heißt in Sprachen ohne Delegation (was ein zu OO orthogonales Konzept ist), jede einzelne Methode manuell “weiterzuleiten”.

[ot][quote=Timothy_Truckle;131353]Richtig absurd wird’s dann ab 40:00[/quote]
So lange hast du das durchgehalten? :o)[/ot]

[quote=L-ectron-X]So lange hast du das durchgehalten?[/quote][OT]Bin verheiratet, also weitgehend leidensfähig…
:D[/OT]

bye
TT