So findest du die perfekte Programmiersprache für dein Projekt

Programmiersprachen gibt es fast so viele wie Kaffeesorten. Doch welche passt am besten zu meinem Projekt? Dieser Artikel bietet eine Sammlung von Fragen, die sich Entscheider und Entwickler beantworten sollten, bevor sie ihre Wahl treffen.

Holger Balow-Schott
Bitgrip

--

In den letzten zwei Jahrzehnten entstanden über 20 neue Sprachen, darunter so bekannte wie Groovy (2003), Go (2009), Kotlin (2011), TypeScript (2012) und Swift (2014). Viele laufen unter Open Source-Lizenz und nur wenige sind kommerziell. Mögliche Einsatzszenarien einer Sprache lassen sich schnell an der Laufzeitumgebung und den sprachlichen Paradigmen erkennen:

  • Nativ oder Virtuell
  • Deklarativ oder Imperativ
  • Prozedural oder Objekt orientiert
  • Natürlich Sprachlich oder Mathematisch.

Reizvolle Features in einer neuen Sprachen verleiten schnell dazu, diese als Heilsbringer oder Heiligen Gral der Programmierung zu betrachten. Keine Frage: Sprachliche Features sind wichtig, denn sie erhöhen die Produktivität. Niemand will sich noch mit dem Sprachumfang von Java 1.4 quälen, wenn auch der Einsatz von Java 8 oder neuerer Versionen möglich ist. Doch allein innovative Features machen eine Sprache nicht automatisch zur richtigen Wahl für das nächste Projekt.

Neu ist nicht gleich besser

Um die Einsatzfähigkeit einer neuen Sprache für ein Projekt zu bewerten, müssen sehr viele Kriterien berücksichtigt werden.

Um keine Romane zu schreiben, musste ich mich bei der Auswahl der hier behandelten Kriterien einschränken. Es gilt, eine Hochsprache mit vielseitigem Einsatzzweck zu bewerten. Nischenprodukte für den Einsatz in eingebetteten Systemen oder wissenschaftlichen und mathematischen Projekten werden hier nicht betrachtet. Die Kriterien gliedern sich in drei Blöcke: Systemnahe Funktionen, Frameworks und Bibliotheken sowie Ökosysteme.

Systemnahe Funktionen (photo by nathan dumlao on unsplash.com)

Systemnahe Funktionen

Bei den systemnahen Funktionen geht es darum festzustellen, wie gut die Sprache den Entwickler dabei unterstützt, die zur Verfügung stehenden Ressourcen zu nutzen.

Speichermanagement

Automatisches dynamisches Speichermanagement ist aus modernen Sprachen nicht mehr wegzudenken und daher obligatorisch.

  • Wie effizient funktioniert das?
  • Lässt es sich konfigurieren?
  • Wie groß ist der Speicherhunger einer Anwendung?
  • Wie groß ist der Overhead, um den Speicher zu organisieren?
  • Welche Möglichkeiten gibt es, um ein Speicherleck zu entdecken?

Nebenläufigkeit

Die Programmierung von nebenläufigen Anwendungen ist für viele Entwickler schwierig. Dabei gibt es bereits Sprachen oder Laufzeitumgebungen, die einen direkten Umgang mit Threads und konkurrierenden Zugriffen vereinfachen oder sogar verhindern.

Dennoch sollte die Sprache dokumentieren, wie konkurrierende Zugriffe atomar und transaktional sichtbar ermöglicht werden oder welches andere Konzept zu tragen kommt.

  • Ist Concurrency vorgesehen?
  • Gibt es zu Threads und Mutex abweichende Konzepte wie Channels oder Event Loop?
  • Welche Möglichkeiten gibt es einen Deadlock zu erkennen oder ist dieser gar nicht erst möglich?

Ausnahmebehandlung

Das Erzeugen und Fangen von Ausnahmen (Exceptions) ist eines der wichtigsten Konzepte zum Übermitteln von Fehlerzuständen zur Laufzeit einer Anwendung. Fast alle Sprachen unterstützen Exceptions, da diese bereits in moderne Prozessorarchitekturen integriert sind.

  • Können Ausnahmen als beliebiges oder vererbtes Objekt erzeugt werden?
  • Kann eine gefangene Ausnahme auf Typ oder Inhalt analysiert werden?

Eingabe und Ausgabe

Alle Betriebssysteme steuern die Eingabe und Ausgabe von Daten. Sei es der Zugriff auf das Dateisystem oder das Netzwerk. Eine Sprache sollte diese Funktionen des Betriebssystem effizient und wenn möglich für den Entwickler einfach oder sogar elegant bereitstellen.

  • Werden alle Dateisystem-Funktionen unterstützt (auch z.B. Seek, Watch, Move, Permissions)?
  • Lassen sich größere Datenmengen als Stream lesen oder schreiben?
  • Wird IPv6 unterstützt?
  • Wird UDP unterstützt?
  • Wird IPC Socket unterstützt?
Frameworks und Bibliotheken (photo by nathan dumlao on unsplash.com)

Frameworks und Bibliotheken

Fast jede moderne imperative Hochsprache wird es ermöglichen, Anwendungen in sauberen Software-Architekturen zu erstellen. Aber das Rad muss nicht jedes Mal neu erfunden werden. Wie steht es also um die Unterstützung in diesem Thema durch Bibliotheken oder Frameworks? Architekturmuster sind ein weites Feld. Deshalb habe ich mich hier auf typische Muster für Webanwendungen beschränkt.

Model View Controller

Serverseitige Webanwendungen basieren fast immer auf dem MVC-Mustern. Eine eigene Implementierung ist möglich, aber nicht in jedem Fall notwendig. Ein MVC-Framework sollte mehr können als seine drei Buchstaben versprechen. Hinzu kommen meist Funktionen für das Binding und die Validierung des Input-Models und Funktionen für die Authentifizierung und Autorisierung von Nutzern mit Basic, Digest oder Bearer.

MVC-Frameworks sollten eine einfache Erweiterung ihrer Funktionen erlauben. Oft ist hier von Interceptor, Handler oder Middleware die Rede.

  • Gibt es Unterstützung für MVC durch Frameworks?
  • Ist das Framework gut erweiterbar?

Object Relational Mapping

Viele zu programmierende Anwendungen verwalten Daten. Diese wollen gespeichert werden. Wer nicht auf Dokument-basierte Datenbanken zurückgreifen kann oder möchte, sondern eine Relationale Datenbank bevorzugt, der wird in größeren Projekten auch ein ORM verwenden wollen.
Ein ORM-Framework sollte mindestens folgende Eigenschaften aufweisen:

  • Datenbank-Technologie agnostisch
  • Relationen: One-to-Many, Many-to-One, One-to-One, Many-to-Many
  • Laden von Relationen bei Bedarf
  • Transaktionen und Isolations Level
  • Optimistische Sperren
  • Kombinierte Primärschlüssel

SQL-Abstraktion und -Treiber

Es muss nicht immer gleich ein ORM-Framework sein. Doch auch ein einfacher Zugriff auf eine relationale Datenbank sollte Technologie agnostisch erfolgen. Hierfür ist eine Abstraktion erforderlich. Diese Abstraktion wird dann vom Technologieanbieter als Treiber implementiert.

  • Gibt es eine Abstraktion und implementierte Treiber für gängige relationale Datenbanken?

NoSQL und Messaging

Die meisten NoSQL-Datenbanken und Messaging-Systeme nutzen proprietäre Protokolle für die Datenübertragung. Hierzu ist es notwendig auf eine Client-Implementierung des jeweiligen Anbieters zurückzugreifen. Typische Anbieter sind z.B. MongoDB, CouchDB, Cassandra, etcd, Kafka oder Redis.

  • Gibt es eine Client-Implementierung für den gewünschten Anbieter?

Kryptographie

Verschlüsselte Kommunikation oder Persistenz ist für öffentliche Anwendungen eine Kernfunktionalität. Auch hierfür sollte eine Bibliothek oder ein Framework gefunden werden, da die Implementierung komplex und fehleranfällig ist. Im besten Fall wird die crypto API des Kernels verwendet.

  • Gibt es Unterstützung für Kryptographie in der Sprache oder als Bibliothek?
  • Werden aktuelle Hash- und Verschlüsselungsalgorithmen unterstützt?

Aspektorientierung

Aspektorientierung ist kein Paradigma der Sprache als solches. Vielmehr ist es die Möglichkeit über Annotationen/Tags und Reflektion in einer objekt-orientierten Sprache generische Funktionen abzubilden und hiermit eine Entkopplung der funktionalen Komponente von technischen oder anderen fachlichen Details herzustellen. Das ist besonders nützlich für die Implementierung der Autorisierung des Zugriffs auf Objekte oder Methoden.

  • Gibt es Frameworks für Aspekte?
  • Gibt es Reflektion von Objekten und Annotation von Strukturen der Sprache?
Ökosysteme (photo by crew on unsplash.com)

Ökosystem

Die Features einer Sprache und die Unterstützung durch Bibliotheken sind nur eine Seite der Medaille. Wie sieht es mit unterstützenden Werkzeugen aus, die für Komfort in der Entwicklung sorgen oder die entwickelte Software testen, verpacken oder verteilen?

IDE Unterstützung

Egal ob leichtgewichtiger Editor im Terminalfenster oder vollausgestattete IDE, es gibt Features auf die ich beim Entwickeln nicht verzichten könnte. Da seien genannt:

  • Syntax-Highlight
  • Code-Completion
  • Code-Navigation
  • Code-Format
  • Debugger

Damit die IDE eine Refactoring von Code unterstützt, welches über ein einfaches Suchen und Ersetzen hinaus geht, muss die IDE den Syntaxbaum der Sprache verstehen und validieren können. Das ist gerade für sehr neue Sprachen nicht immer der Fall.

Ein Verständnis des Syntaxbaum setzen auch Werkzeuge für die statische Code-Analyse voraus. Manche Sprachen analysieren den Code bereits während des Compile- oder Transpile-Vorgangs und monieren Verstöße gegen Regeln für sicheren oder wartbaren Codes oder gegen den guten Stil.

Bauen und Verteilen

Der fertig entwickelte Code im Versionskontrollsystem nützt nur begrenzt. Für den Build, die Paketierung und die anschließende Verteilung der Software-Pakete braucht es unterstützende Werkzeuge. Oft sind diese nur als einzelne Kommandozeilen-Werkzeuge verfügbar und es sollte zusätzlich nach einer Orchestrierung geschaut werden, welche auch in einem Continuous Integration System funktioniert.

  • Gibt es Werkzeuge, um abhängige Bibliotheken revisionssicher zu verwalten?
  • Gibt es Repositories, um Bibliotheken oder ausführbare Anwendungen zu verwalten?

Unit Tests

Unit Tests lassen sich ohne Umwege direkt in der jeweiligen Sprache schreiben. Aber auch hier lohnt ein Blick, ob es Bibliotheken für die Testausführung und das Reporting oder für das Mocking von abhängigen Services oder für Assertions gibt.

Und sonst noch …

Ein Blick in die bekannten Foren und Source Code-Repositories zeigt, wie aktiv die Community ist. Ich weiß, das kann sehr ermüdend sein. Doch eine oder zwei mit der Dokumentation oder einem Tutorial verbrachte Stunden sind Gold wert und definitiv keine Zeitverschwendung. Eventuell gibt es bereits aufgeschriebene Best Practices als Anregung für das eigene Arbeiten.

  • Wie viele Projekte gibt es in der neuen Sprache bereits?

Im Umfeld mancher Sprachen, die innerhalb einer virtuellen Laufzeitumgebung laufen, sind weitere entstanden. Diese können als Plug-in-replacement fungieren, da das bestehende Ökosystem und die Laufzeitumgebung fast vollständig genutzt werden. Hier fällt ein Umstieg natürlich leichter. Bekannte Beispiele hierfür sind Groovy oder Kotlin statt Java oder auch TypeScript statt JavaScript.

Grundsätzlich sollt man sich die Frage stellen, warum ein Umstieg auf eine neue Sprache überhaupt notwendig erscheint.

  • Was kann die bisherige Sprache nicht leisten?
  • Lohnt es sich nur Teilsysteme eines Projekts mit einer neuen Sprache zu erstellen?
Perfekte Lösung (photo by waranya mooldee on unsplash.com)

Zusammenfassung

Dieser Artikel liefert viele wichtige Fragen, die man sich ehrlich beantworten sollte, bevor man sich für die passende Programmiersprache für ein Softwareprojekt entscheidet. Neu heißt nicht automatisch besser. Manchmal hilft es schon, sich bei anderen Projekten in der eigenen Firma umzusehen oder mit anderen Architekten zu sprechen.

Trotzdem kann auch eine neue Programmiersprache die genau richtige Lösung für das Projekt sein. Deshalb ist es wichtig, immer auf dem aktuellen Stand zu bleiben. Bei meiner Firma BITGRIP, wo ich als Software-Architekt arbeite, machen wir das so.

In diesem Beitrag findet ihr ein aktuelles Ranking der beliebtesten Programmiersprachen. Auch hier wird nochmals deutlich, wie dynamisch dieser Bereich ist.

Welche Erfahrungen habt ihr bei der Auswahl der richtigen Programmiersprache gesammelt? Ich freue mich über Meinungen, Fragen, Vorschläge und Kritik.

Wenn du mehr über BITGRIP, unsere Projekte und unseren Tech-Stack erfahren willst, folge uns auf Twitter @bitgrip_berlin, LinkedIn oder hier auf Medium.

--

--