Kolumne: Olis bunte Welt der IT

Tolle Typen – Wie statisch muss Typisierung sein?
Kommentare

Wenn es quakt wie eine Ente und schwimmt wie eine Ente, dann ist es eine Ente. Sie haben diesen Spruch schon einmal gehört – er wird von Verfechtern der dynamischen Programmierung seit Jahren immer wieder vorgetragen. Die Ente ist sinnbildlich ein Objekt, ein Element, mit dem anderer Code arbeitet, und an das dieser Code Erwartungen hat: quaken soll sie können, und schwimmen, und wenn das gegeben ist, dann ist alles in bester Ordnung.

Im Mainstream der Softwareentwicklung, besonders im großen Bereich der Businesssoftware, ist man in Hinsicht auf Entenprobleme gern anderer Meinung. Statisch typisierte Sprachen haben in diesem Bereich lange dominiert, und diese Sprachen mögen eine Ente nur, wenn sie klar definiert ist. Gefieder- und Schnabelfarbe, Gewicht und Schwanzlänge müssen sorgsam in einem Objektmodell abgelegt und notwendige Schnittstellen wie ISchwimmfähigesTier definiert und implementiert werden. Irgendwann darf dann auch gequakt und geschwommen werden, aber nur unter penibler Kontrolle der Compiler- und Laufzeitsysteme.

Statische Typisierung – eine Evolution

Viele Programmierer sind der Auffassung, dass diese statische Typisierung sich aus einem evolutionären Vorgang entwickelt hat und letztlich grundlegend „besser“ ist als eine offenere Anschauung der Entenproblematik. In einer bestimmten Gruppe von Sprachen ist diese Ansicht durchaus nachvollziehbar. In Sprachen wie C und später Pascal gab es zunächst nur eine trügerische Art von Typsicherheit: Es gab zwar Typen, aber es war sehr einfach, dem Compiler seinen Irrtum in Hinsicht auf eine bestimmte Typisierung mit einem simplen Cast verständlich zu machen. Alles basierte auf Zeigern, und letztlich zählte das Verständnis des Programmierers mehr als das des Compilers. Datentypen wie die Unions in C und die varianten Records in Pascal machten sich dies schamlos zunutze, und große Speichereffizienz und Performance konnte auf dieser Basis erzielt werden – wenn auch mit beträchtlichem Risiko.

Schnell und überall: Datenzugriff mit Entity Framework Core 2.0

Dr. Holger Schwichtenberg (www.IT-Visions.de/5Minds IT-Solutions)

C# 7.0 – Neues im Detail

Christian Nagel (CN innovation)

 

In strikt objektorientierten Sprachen, zunächst in erster Linie Java, wurde Typen ein ganz neuer Stellenwert eingeräumt. Der Cast wurde vom Befehl zur Anfrage degradiert, und ohne Zustimmung von Compiler und Runtime konnte der Programmierer nicht mehr beliebig mit Typen umgehen. Drei Schritte wurden hier gemacht, um Typsicherheit zu erzwingen. Erstens musste der Programmierer nun sorgsam spezifische Typen definieren, und seinen Code so strukturieren, dass Typkonversionen außerhalb des Systems nicht mehr notwendig waren. Zweitens, und darauf aufbauend, konnten Compiler und Runtime die korrekte Verwendung dieser Typen erzwingen und so die gewünschte Sicherheit herbeiführen. Drittens war es notwendig, das Verständnis des Programmierers immer wieder durch die klare Nennung der Typbezeichnungen im Code zu versichern – eigentlich ein erheblicher Aufwand, aber im Vergleich zu komplexen Zeigern in C oder C++ sahen die Typnamen auf den ersten Blick so übersichtlich aus, dass man damit zunächst ziemlich glücklich war.

In der .NET-Welt wurde die Idee der strikten Typisierung ebenfalls umgesetzt und tief in den Sprachen der Plattform verankert. C# unterstützt aus Kompatibilitätsgründen mit der Windows-Plattform auch Strukturen, die ein bestimmtes statisches Speichermapping anwenden. Mit dem var-Schlüsselwort in C# versuchte man, den Aufwand etwas zu mindern, der dem Programmierer durch die wiederholte Verwendung der Typnamen entsteht. Typherleitung ist in C# allerdings sehr rudimentär, und jeder verwendete Typ muss zumindest einmal beim Namen genannt werden. Mit einem anderen Schlüsselwort, dynamic, sollte eine Anbindung an dynamische Welten vereinfacht werden. Das machte C# zu einer relativ flexiblen statisch typisierten Sprache, schaffte aber auch Verwirrung bei Anwendern, die von der Microsoft-Welt klare Strategien erwarteten und gewohnt waren.

Typen ohne Aufwand

Wie wäre es, wenn der Compiler mit Typen arbeiten und deren korrekte Verwendung garantieren könnte, ohne dass dem Programmierer dadurch syntaktischer Aufwand entsteht? Mithilfe extrem leistungsfähiger Typherleitung ist dies tatsächlich möglich, wie etwa das Typsystem der Sprache Haskell beweist. Wertzuweisungen müssen in Haskell nur in Konfliktfällen mit Typannotationen versehen werden, sodass der Code im Allgemeinen ganz ohne die Erwähnung expliziter Typen auskommt – der Compiler leitet die Typen her und prüft sie, aber der Programmierer wird damit nicht belastet. Was Haskell allerdings zusätzlich besonders interessant macht, ist die Fähigkeit zur automatischen Verallgemeinerung. Dies funktioniert sogar auf der Basis von implementiertem Verhalten: Der Compiler leitet etwa automatisch her, dass Typen, mit denen Addition betrieben wird, zu einer gemeinsamen Typklasse gehören, die genau diese Fähigkeit verallgemeinert.

Auf der .NET-Plattform bietet F# sehr gute Unterstützung für Typherleitung, die sich allerdings nicht in jedem Detail in den objektorientierten Bereich erstreckt und, mangels der entsprechenden Basis in der CLR, auch keine Typklassen kennt. Somit bleibt generell die Tatsache bestehen, dass statische Sprachen Mehraufwand für den Programmierer erzeugen, da kontinuierlich sorgfältige Pflege der Typen und aller Anwendungspunkte betrieben werden muss. Natürlich soll damit nicht gesagt sein, dass dem kein positiver Wert gegenübersteht, aber die Arbeit zur Pflege von strikt typisiertem Code kann oft beträchtlich sein.

Dynamischer entwickeln

Tatsache ist, dass die Welt der Programmierung heutzutage dynamischer ist denn je. Wenn vor einigen Jahren die Frage gestellt wurde, was für unmittelbare Vorteile die Verwendung dynamischer Sprachen vorzuweisen habe, dann wurden als Antwort gewöhnlich bestimmte Anwendungsfälle beschrieben. Ich selbst habe immer gern ein Szenario einer Tabellenkalkulationssoftware beschrieben, die in Python implementiert wurde, und ein unglaublich einfach verwendbares API hatte, basierend auf der Tatsache, dass mittels dynamischer Mechanismen extrem einfache Syntax für Zelladressierung möglich war. MySheet.K5 liest sich eben besser als MySheet.Cells[„K5“], um ein simples Beispiel zu nennen. Auch im Bereich von Object-relational Mapping gab es beeindruckende Beispiele von ähnlicher Natur. Heute hingegen ist Dynamik ein elementarer Bestandteil von Applikationsarchitekturen und Entwicklungsmethoden, und ihr muss deshalb eine wesentlich größere Bedeutung beigemessen werden.

Dynamische Sprachen im .NET-Umfeld

In verteilten Systemen kommt es immer wieder vor, dass APIs externalisiert werden, die in der Vergangenheit elementarer Bestandteil des eigenen Systems waren. Das passiert zum Beispiel, wenn zur Datenablage oder zu anderen Zwecken Cloud-Dienste verwendet werden oder ähnliche Integrationen mit Diensten von Drittanbietern stattfinden. REST-Zugriff und Datenübertragung im JSON-Format führen in solchen Fällen leicht zu sehr dynamischen APIs, die der Programmierer nicht selbst beeinflussen kann, mit denen der Programmcode aber möglichst flexibel arbeiten muss, um dauerhaft stabil zu bleiben.

Die Problematik ist bei der Nutzung von offensichtlich externen Diensten nicht neu. Allerdings bringt die Idee der Microservices dieselben Umstände direkt ins eigene Haus. Modularisierung wird bei Anwendung dieses Schemas zur Disziplin, und eine große Anzahl eigenständiger Dienste wird unabhängig von mehreren Teams gepflegt und sogar betrieben, um ein Anwendungssystem aufzubauen. Diese Dienste müssen sich natürlich austauschen, was gewöhnlich mit ähnlichen Mitteln geschieht wie bei der Einbindung externer Dienste. Daraus entsteht natürlich eine beeindruckende Konsistenz, aber auch die Notwendigkeit, dort dynamisch zu denken, wo es um die Schnittstellen zwischen einzelnen Komponenten geht. Auch Systeme zur bidirektionalen Kommunikation oder zum Message-basierten Austausch von Informationen sind gewöhnlich in diesem Sinne dynamisch. Letztlich halten dynamische Komponenten sogar im allgemeinen .NET-Umfeld Einzug, wie etwa in ASP.NET MVC, wo anonyme Objekte zur Konfiguration und zur Übertragung von Daten zwischen Views und Controllern seitens Microsoft favorisiert werden.

Die Historie dynamischer Sprachen ist ebenso lang und interessant wie die der statisch typisierten. Lisp, eine der ältesten Programmiersprachen, ist dynamisch und bildet die Basis vieler anderer Sprachen. Clojure ist eine Lisp-basierte Sprache, die im Java-Umfeld sehr beliebt ist und sich auch auf .NET verbreitet. In Objective-C gibt es Message Passing in der Sprache selbst, wodurch Dynamik erreicht werden kann. Erlang ist dynamisch und wird immer wieder aufgrund seiner beeindruckenden Fähigkeiten in der Parallelisierung als Beispiel herangezogen, und das Schema der Aktoren, das in Erlang von großer Bedeutung ist, kann mittlerweile auf allen wichtigen Plattformen angewandt werden. In Python wurden und werden große Anwendungssysteme entwickelt, und dann gibt es natürlich JavaScript, dessen Bedeutung heute kaum überschätzt werden kann.

Pflegeleichte Typsicherheit

Der Vorteil dynamischer Sprachen in der beschriebenen dynamischen Umgebung besteht hauptsächlich darin, dass der Aufwand zur Pflege bestimmter Elemente einer Codebasis wesentlich niedriger ist als bei der Anwendung statisch typisierter Sprachen. Wenn ein externes API geändert wird, kann der eigene Code womöglich ohne Änderungen weiter arbeiten, und wenn Änderungen notwendig werden, müssen sie an weniger Codestellen durchgeführt werden.

Der Programmierer statisch typisierter Sprachen verlässt sich gern darauf, dass Compiler und Runtime einen Teil der Prüfungen durchführen, die sicherstellen, dass Programmcode in Hinsicht auf Datentypen korrekt ist. Daraus entsteht die größte Skepsis in Bezug auf dynamische Sprachen: Wie kann ein stabiles Ergebnis erzielt werden, wenn keine Typsicherheit besteht?

Auf diese Frage gibt es drei wesentliche Antworten. Zunächst gibt es bei dynamischen APIs gewisse Patterns, die direkt der langfristigen Stabilität von Anwendungssystemen dienen. APIs können auf diese Weise versioniert werden, sodass Clients nicht sofort mit jeder Änderung kompatibel sein müssen. Natürlich wollen solche parallelen Versionen eines API gepflegt werden, und Regeln etabliert, um gleichzeitig den Anwendern des API Sicherheit zu geben und den Pflegeaufwand überschaubar zu halten.

Zweitens verwenden auch dynamische Programmierer gern Softwarewerkzeuge, etwa zur statischen Codeanalyse, deren Mechanismen oft ähnliche Resultate erzielen können wie ein Compiler einer statisch typisierten Sprache. In diesen Bereich fällt auch Microsofts TypeScript, das mithilfe einer eigenen Syntax und eines Compilers eine gewisse Typsicherheit für JavaScript herbeiführt.

Die dritte Antwort, und wohl die wichtigste, besteht aus der Erzeugung von automatisierten Tests. Aus Statistiken geht hervor, dass die Anzahl von Tests in Projekten auf Basis dynamischer Sprachen oft wesentlich größer ist als bei statisch typisierten. Im dynamischen Umfeld wird die Erzeugung von Tests gewissenhafter betrieben, und die Abdeckung bestimmter Szenarien ist größer, um Fehler aufgrund der falschen Verwendung von Typen auszuschließen.

Gerade dieser letzte Teil wird von manchen Programmierern leider als Problem angesehen. Obwohl die Erzeugung einer möglichst vollständigen Testbasis seit Langem eine anerkannte Best Practice ist, hat diese sich in vielen Projekten und Entwicklerteams bisher nicht durchsetzen können. Oft wird als Grund der Aufwand genannt, der als zu hoch empfunden wird. Realistisch und korrekt ist dies meiner Erfahrung nach nicht. Selbst in bestehenden Projekten, die statische Typisierung verwenden, lohnt sich die Einführung einer guten Testbasis gewöhnlich schnell, da die Stabilität des Codes steigt, Fehlerbehebung sich vereinfacht und Regression ausgeschlossen werden kann. In modernen Anwendungssystemen, die mit der zuvor beschriebenen Dynamik der umgebenden Welt umgehen müssen, steht dem Aufwand der Testerzeugung außerdem der Pflegeaufwand statischer Typsysteme gegenüber, der unter Umständen sehr erheblich sein kann.

Fazit

Statisch und dynamisch typisierte Sprachen haben Vor- und Nachteile, wie auch jede einzelne Sprache im Vergleich mit jeder anderen. In der heutigen Zeit können Sie deutlich Zeit gewinnen und konzeptionell leistungsfähigere Anwendungen bauen, wenn Sie sich die Fähigkeiten dynamischer Sprachen zunutze machen und die Ideen der Dynamik akzeptieren. Sie haben die Wahl, welche Methoden zur Absicherung Sie einsetzen möchten, aber letztlich hilft vor allem eine Maßgabe, die in der Softwareentwicklung eine der ältesten ist: testen, testen und nochmal testen!

Unsere Redaktion empfiehlt:

Relevante Beiträge

Meinungen zu diesem Beitrag

X
- Gib Deinen Standort ein -
- or -