Warum Java das bessere C# ist…
Okt 29th, 2008 by hendrik
Der große Hype ist vorbei. Ich kann auf 2 Jahre professionelle Softwareentwicklung mit .NET unter C# zurück blicken und ein Resüme ziehen. Entgegen dem, was vielerorts behauptet wird, und was ich zu Beginn und für lange Zeit auch gerne geglaubt habe, ist C# eben kein besseres Java. Im Gegenteil, es löst viele Dinge nur vermeintlich elegant, und offenbart nicht selten einige eklatante Designschnitzer, die im normalen Tagesbetrieb nicht auffallen:
- Properties: Properties sind ein nettes Sprachschmankerl. Wirklich Zeit sparen sie aber nicht, denn das Implementieren dauert genau so lange wie bei normalen getter/setter Methoden. Zudem erkennt man im Produktionscode keine Fehler mehr, die das Data-Hiding-Prinzip verletzen, weil sich der Zugriff auf Datenattribute und der Zugriff über Properties optisch nicht voneinander unterscheiden. Das an sich wäre kein Problem, wenn man Datenmember nur als protected oder private deklarieren könnte – so allerdings ist das Ganze recht missverständlich.
- Structs: Eigentlich ist die Verwendung von benutzerdefinierten Werttypen eine schöne Sache, aber auch hier wurde meiner Meinung nach das Konzept nicht bis zum Ende durchgedacht. Ich lege Structs in C# mit new an – wie einen Referenztypen! Habe dann aber einen Werttyp. Wie soll ich das Leuten mit wenig Programmiererfahrung klar machen? Wie vermeide ich Fehler, sobald noch weitere Sprachen mit ins Spiel kommen (lege ich structs in C++/CLI mit gcnew an, habe ich nämlich plötzlich Referenztypen, die anstandslos kompiliert werden. Der ganze Mist funktioniert dann aber nicht mehr – und das bedeutet dann wieder stundenlanges Debuggen, wenn jemand mal kurz nicht aufgepasst hat!).
- Attribute: Sind zugegebenermassen toll. Aber die habe ich Form von Annotations in Java seit Version 1.5 auch.
- Lambda-Funktionen: Warum unterminiere ich eine objektorientierte Sprache durch funktionale Elemente, die dort eigentlich nicht das geringste verloren haben? Die Syntax ist Anfängern nicht begreiflich zu machen und verwendet einen Operator, der an anderer Stelle eine völlig andere Bedeutung hat. Zudem brauche ich die Dinger hier garnicht – wenn ich funktional entwickeln will, dann nehme ich F# oder IronPython – damit geht das dann nämlich auch komfortabel. Dazu kommt noch, dass ich denselben Effekt sowieso schon mit anonymen Delegaten erreichen kann. Und – wozu kann ich denn die Sprachen im CLI nach belieben mischen, wenn ich doch wieder alle Konzepte in eine Sprache packe? Was kommt als nächstes – prozedurale Erweiterungen für C#?
- Der neueste Gag: Dynamische Typisierung in C# 4.0. Jetzt wird also auch noch die statische Typisierung von C# ausgehebelt – die in kompilierbaren Sprachen durchaus ihre Berechtigung hat, da sie schließlich eine Vielzahl von Flüchtigkeitsfehlern verhindert.
- Konvertierungsoperatoren: In C# kann ich diese Operatoren überladen. Das implizite Konvertierungen böse (weil extrem fehleranfällig) sind, wissen wir aber schon seit langem aus C++. Anlegen kann ich sie hier trotzdem. Explizite Konvertierungen könnte ich auch prima über normalen Methoden abhandeln. C# benutzt dafür dagegen ein eigenes Sprachkonstrukt – und das auch nicht durchgängig – oder warum hat jedes Objekt eine Methode ToString? Sinnvoll dagegen wären implizite Konvertierungen dagegen bei der Umwandlung von primitiven Typen in Strings – genau das unterstützt C# aber wieder nicht.
- In eine ähnliche Kategorie fällt das Überladen von arithmetischen Operatoren. Das ist in beinahe allen Fällen unnütz (einzige Ausnahme – Zahlentupel aller Art (z.B. komplexe Zahlen oder Vektoren) oder Tensoren höherer Ordnung (z.B. Matrizen)), kann aber, da man die Operatoren beliebig implementieren kann, natürlich sehr schnell nach hinten los gehen. Auch hier bezahlt man ein klein wenig Bequemlichkeit mit einem großen zusätzlichen Risiko.
- Grobe Designschnitzer in der Sprache. Bestes Beispiel: Arrays implementieren die Schnittstelle einer Liste. Das heißt ich kann Arrays implizit in Listen konvertieren und darf mich dann wundern, warum meine Listenoperationen zur Laufzeit Ausnahmen werfen. In Java dagegen ist für eine Konvertierung einen expliziter Aufruf von as_list() notwendig. Da weiß man schon eher, woran man ist.
- Erweiterungsmethoden: Dieses neue Feature von C# 3.0 erlaubt es uns, nachträglich zu Typen, die eigentlich als sealed deklariert worden sind, neue Methoden hinzu zu fügen. Das führt natürlich das gesamte Konzept der Kapselung ad absurdum.
- Delegaten und Events: …sind eigentlich nur abgekürzte Schreibweisen für Funktionsreferenzen oder Funktoren. Während Events damit quasi dem Listener-Mechanismus in Java entsprechen (und eigentlich auch so verwendet werden könnten – da bräuchte man kein separates Schlüsselwort für), sind erstere extrem gefährlich, dank ihres =-Operators. Damit kicke ich nämlich ganz schnell registrierte Delegaten wieder heraus und wundere mich dann, warum meine dynamischen Aufrufe nicht mehr funktionieren.
Letztendlich komme ich zu folgendem Fazit: C# ist durch die ständige (und meiner Meinung nach überflüssige) Integration neuer Features auf dem besten Wege, ein zweites C++ zu werden. Das Orthogonalitätsprinzip wird von den Sprachdesignern immer wieder erneut verletzt, was die Fehleranfälligkeit erhöht, den Einstieg in die Sprache erschwert und das finden von Lösungen unnötig kompliziert macht. Java verzichtet auf diesen ganzen Schnickschnack; und dies ist nur oberflächlich ein Nachteil. Die Entwicklungen komplexerer Sprachkonstrukte erfolgt in Java traditionell durch automatisierte Funktionen in den gängigen IDE’s (Eclipse, z.T. auch NetBeans), weshalb in der Sprache selbst auf Bequemlichkeitsfunktionen wie Properties und LINQ verzichtet werden kann. Traurigerweise bietet auch das Visual Studio mittlerweile viele dieser Funktionen an (Implement interface, Refactoring, Create getter/setter), so dass diese Erweiterungen eigentlich garnicht nötig wären.
Eigentlich finde ich diese Entwicklung sehr schade; in vielen Belangen scheint Microsoft endlich im 21. Jahrhundert anzukommen (Endlich ein Visual Studio mit UML-Unterstützung, nach 20 Jahren hat man auch bei MS endlich begriffen, was ein Model-View-Controller-Pattern ist; man entwirft ein Framework, dessen Architektur nichts taugt, komplett neu) zu sein. Warum man dann beim Design der neuen Vorzeigesprache so grundlegende Fehler macht, die jeder Informatikstudent in Theorie von Programmiersprachen vermeiden lernt, ist mir persönlich ein Rätsel.
Letztendlich muss ich natürlich beruflich damit leben, weiterhin mit C# zu entwickeln (besser als Desktopprodukte mit C++ zu entwickeln ist es allemal) – dabei werde ich mich aber wie bisher ans K.I.S.S.-Prinzip halten und im Team darauf setzen, dass wir nur Techniken einsetzen, die auch alle nachvollziehen können. Keine Lambafunktionen, kein LINQ, so lange keine Datenbanken im Spiel sind, sparsamer Einsatz von structs und kein nachträgliches Erweitern von sealed-Typen. Auf das der Debugger nicht zu heiß läuft und es bei einer einstelligen Zahl von Abstürzen des Visual Studios am typischen Arbeittag bleibt…

