Mit RESTful aus der Legacy Code Hölle – Resümee meiner Erfahrungen mit unwartbarem Code

Standard

Nein, ich bin nicht der Ansicht, dass RESTfull alle Probleme, die wir klassischerweise mit Legacy Code haben, beseitigen kann. RESTful jedoch ist ein hilfreiches und effizientes Werkzeug, mit dem Legacy Code gut gekapselt werden kann. Zudem können unverzichtbare Funktionen von außen über das Netzwerk, und somit über Architekturgrenzen hinweg, erreichbar gemacht werden. Dadurch können alte System mit neuen Systemen kommunizieren, die in einer modernen und aufgeräumten Weise reimplementiert wurden.

Zunächst möchte ich aufzeigen, was an Legacy Code problematisch ist und worin die Ursachen liegen.

Riesige Code-Base

Legacy Code hat eine lange Geschichte von vielleicht 20 Jahren oder mehr hinter sich und wurde von mehren Generationen von Programmierern gepflegt. Von vielen Code-Teilen sind die Autoren schon längst nicht mehr im Unternehmen. Aufgrund mangelnder Dokumentation und Code-Verständlichkeit kann niemand genau sagen, welche Code-Teile obsolete sind. Weil sich niemand traut, Code zu entfernen, wächst die Code-Base immer weiter.

Die Kultur bestimmter Programmiersprachen

Bei bestimmten Programmiersprachen wie C/C++ kommt erschwerend hinzu, dass sich Entwickler besonders schwer tun, Code weg zuwerfen und neu zuschreiben. Auf einem Vortrag in München, der sich speziell mit C++-Test-Frameworks beschäftige, wurde definiert, „Testgetrieben“ meine, man schreibe erst den Test und dann die Implementierung, um den Test zu bestehen. Zum existierendem Code nachträglich Tests zu schreiben, sei wenig aussagekräftig und ineffizient. Anschließend wurde diskutiert, ob und wie Tests beim Refactoring von Legacy Code helfen können. Deutlich wurde, dass C/C++-Programmierer überdurchschnittlich viel Zeit damit verbringen, über altem Code zu brüten.

Wieso ist das bei C/C++ so? Möglicherweise liegt es daran, dass es generell aufwendiger ist, in C++ zu implementieren, als z.B. in Python. Durch die Typisierung von C++ hat eine API-Änderung einen Rattenschwanz an Änderungen quer durch den ganzen Code zur Folge. Was tut man also? Richtig, man schreibt Proxy-Klassen und Casting-Funktionen, um alte APIs nicht ändern zu müssen. Das führt jedoch zu noch mehr Code…

Wie ist das bei anderen Programmiersprachen, z.B. Perl ? Vermutlich verwerfen die meisten Programmierer unverständlichen Perl-Code schnell und schreiben ihn neu. Gerade Skript-Sprachen, wie Python, Ruby und Perl, haben einen riesigen Pool an Bibliotheken, mit denen man auch anspruchsvolle Aufgaben mit einer überschaubaren Anzahl Zeilen Code umsetzen kann.

Auch unter C/C++ gibt es viele Bibliotheken, jedoch sind sie nicht Bestandteil der Standardbibliothek. Der Pogrammierer muss verschiedene Lösungen evaluieren und für sich zusammenstellen. Dazu müssen die verschiedenen Komponenten untereinander kompatibel sein. Hierbei hilft kein Paket-Management wie pip(Python), gem (Ruby), npm (Node.js) etc. Wenn man nicht ausschließlich Pakete aus seiner Linux-Distribution verwendet, sondern sich alles selber beschaffen und aus den Quellen übersetzen muss, ist die Pflege sehr aufwändig.

Das Make-or-Buy-Erbe

Das Problem der aufwändigen Pflege von 3rd-Party-Libraries in C++ und anderen Sprachen hat auch Auswirkungen auf die Make-or-Buy-Frage. Wobei „Buy“ im Falle von Open Source nicht immer eine finanzielle Frage ist, da bei der Verwendung z.B. von Boost keine Lösung gekauft werden muss. Der „Preis“, den man hier zahlt, sind die z.T. hohen Kosten der Integration und Pflege von 3rd-Party-Libraries ins eigenen Projekt/Produkt. Und so lautet dann die Antwort auf die Frage „Make or Buy?“ häufig: „Make!“ statt „Buy“. Mit jeder Make-Entscheidung wächst der eigene Code immer weiter.

Unterschiedliche Paradigma

In Legacy Code finden sich meist unterschiedlichste Paradigmen. Dies ist zum einen den Vorlieben der vielen beteiligten Entwickler(-Generationen) geschuldet, aber zum anden im Laufe der Zeit entstandenen Konzepten, die wieder verschwunden sind oder verdrängt wurden. Dies wiederum verhindert meistens einheitliche APIs.

Teile des Codes sind noch 32bit-Only

Nicht nur Paradigmen ändern sich sondern auch Hardwarearchitekturen. Gerade bei der Hardware-Nähe wie bei C/C++ ist das ein Problem. Als das Projekt gestartet wurde, war vielleicht von 64bit noch nichts zu sehen. Es ist sehr aufwändig oder gar unmöglich den Build-Prozess so anzupassen, dass nur die Teile des Codes mit 32bit übersetzt werden, die das benötigen, um zu funktionieren. So ist Projekt gezwungen, den gesamten Code in 32bit zu übersetzen, mit den Nachteilen, die das mit sich bringt.

Inkompatibel Compiler

Das ist eigentlich eine thematische Überschneidung mit „Unterschiedliche Paradigma“ und „32bit-Only“. Man möchte gerne C++11-Feature nutzen, kann es aber nicht, weil Teile des alten Codes sich nicht mit einem modernen Compiler übersetzen lassen. Den Build-Prozess aufschnüren kann oder will man nicht, weil man die Nebendefekte oder den Aufwand fürchtet. Einige Sachen lassen sich in C++11 eleganter und kompakter ausdrücken als noch mit C++98. Auch das führt zu einem Anstieg des Codes.

Alte Plattformen

Manchmal sind nicht die Entwickler schuld. So kann es sein, dass es vorgeschrieben ist, das uralte Betriebssysteme unterstützt werden müssen. So stellt z.B. NCR erst jetzt seine Geldautomaten von Windows XP auf Android/Linux um. Windows XP kam am 25. Oktober 2001, also für 14 Jahren. Das ist jedoch kein Windows-spezifisches Problem. Ich kenne Fälle, in denen noch RHEL4 (2005 erschienen) und SLES11 (2006 erschienen) produktiv eingesetzt werden. Wer in einen solchen Umfeld arbeitet, fühlt sich in manchen Momenten wie im Computer-Museum. Nicht selten macht man dann die Erfahrung, dass eine Bibliothek, die man gerne einsetzen würde, zu dieser Umgebung nicht kompatibel ist, so dass man Funktionen implementieren muss, für die es eigentlich schon fertige Lösungen gibt. Und auch das lässt die Code-Base weiter wachsen.

Das Dateisystem wird als Middleware missbraucht

Sprachen wie C/C++ können sehr umständlich sein. Gerade wenn man auf altem Code sitzt und nicht auf beliebige 3rd-Party-Libraries zurückgreifen kann. Also ist man versucht die Dinge, die sich in C/C++ sich nur umständlich und aufwendig realisieren lassen, an andere Programme in andern Sprachen zu delegieren. Gegen den Lösungsansatz ist prinzipiell nichts einzuwenden, wenn man sich ein konsistentes und schlüssiges Konzept überlegt. Das ist ein klassisches Middleware-Thema.

Es gelang mir nicht herausfinden, seit wann das Konzept der Middleware Einzug in die IT-Welt hatte. In Legacy Code wird man nicht immer eine durchdachte und flexible Middleware-Schicht finden. Zum Projektstart waren diese Konzepte entweder nicht bekannt oder man glaubte noch, das Projekt bliebe so klein, dass man darauf verzichten könne.

Was tut man also, wenn man von einem Programm aus ein anderes Programm starten will, um ihm eine (Teil-)Aufgabe zu delegieren? Richtig, es werden Programme mit Parametern aufgerufen und die Rückgabe Werte werden weiterverarbeitet. Das geht eine Weile ganz gut.

Dann werden die übergebenen Daten und die zurückgegebenen Zwischenergebnisse komplexer. Es entstehen tiefe Kaskaden von von sich gegenseitig aufrufenden Programmen.  Mit der Zeit wird es zunehmend schwieriger Quellen von auftretenden Fehlern zu finden. Das führt zum Wunsch, die Zwischenergebnisse, die die Programme untereinander austauschten, analysieren zu können. Also lässt man die Programm ihre Zwischenergebnisse in Datein schreiben, die andere Programme auslesen, um sie weiterzuverarbeiten. Auf diese Weise lassen sich Zwischenergebnisse leichter analysieren. Die Programme können umfangreichere und komplexere Datenstrukturen ausgetauschen.

Dieser Lösungsansatz hat jedoch in der Praxis erhebliche Nachteile:

  • In aller Regel wird man einen unüberschaubaren Wildwuchs von kleinen Programmen haben, von den nach einiger Zeit niemand mehr sagen kann, wofür sie mal gedacht waren und in welchem in welchen Abhängigkeitsverhältnis Programme und Skript stehen.
  • Weil niemand mehr weiß, was die vielen kleinen Helfer-Programme tun, werden sie auch nicht mehr angefasst oder wiederverwertet. Stattdessen schreibt man sich lieber seine eigenen kleinen Hilfsskripte. Das sorgt auch wieder dafür, dass der Codeberg weiter wächst.
  • Ob die Hilfsskripte und der Datenaustausch über das Filesystem funktionieren, lässt sich nur schwer bis gar nicht überwachen.
  • Bei der langen Verarbeitungskette über das Filesystem und Programmaufrufen sind Fehler schwer abfangbar. Die Programmierung der nötigen Fehlerbehandlungen sorgt für weitern Codezuwachs.
  • Filesysteme haben Limits. Nicht jedes Netzwerk-Filesystem unterstützt exklusive Schreibzugriffe, die für ein Programm eventuell erforderlich sind, um die Konsistenz der Daten zu bewaren. Das kann einige Einsatzszenarien von Legacy Code unmöglich machen.

Nebenläufigkeit

Eigentlich verwundert es nicht, wenn es mit Legacy Code Probleme mit Nebenläufigkeit gibt. 2003 war Red Hat Linux 9 die erste Linux-Distribution, in der die Native POSIX Thread Library in einem gepatchten 2.4er-Kernel verwendet wurde. Wer Code aus den 1990er Jahren zu pflegen hat, wird höchstwahrscheinlich vor großen Problemen stehen, da zu diesem Zeitpunkt Nebenläufigkeit kein großes Thema war und beim Code nicht darauf geachtet wurde, das er thread safe ist.

Das Problem wird u. a. dadurch verschärft, dass man es früher für guten Stil hielt, in Klassen, wann immer möglich und sinnvoll, member static zu deklarieren, um den Arbeitsspeicher zu schonen. Zum Daten-/Nachrichten-Austausch wurden auch static Variablen genutzt, z.T. sogar in einem globalen Namensraum.

Fat Clients und proprietäre Protokolle

Es gab zwar schon ab Anfang der 2000er Jahre Webapplikationen, jedoch konnten diese von ihrer Usability nicht an Desktop-Anwendungen heran reichen. Da gab es z.B. von 1995 bis 1998 den so genannten Browserkrieg, der es Webentwicklern auch viele Jahre danach noch nicht leicht machte, gut gemachte Webanwendungen zu schreiben, da eine große Inkompatibilität herrschte. Heute hat sich die Lage etwas entzerrt. So hat z.B. MicroSoft seine proprietären Lösungen Silverlight und ActiveX abgekündigt und rät seinen Kunden html5-Techologie, einen offenen Standard, einzusetzen.

Wenn man eine Code-Base hat, die älter als 20 Jahre ist und die eine Server-Client-Architektur hat, ist zu erwarten dass die Clients Desktop-Anwendungen sind und das verwendete Protokoll (zwischen Server und Client) etwas proprietär- selbsgestricktes ist. Wenn man Glück hat, findet man XML-RPC, CORBA oder SOAP vor. Aber das ist eher unwahrscheinlich

Die Nachteile von (Fat-)Clients gegenüber Webinterfaces, brauch ich nicht im Detail zu erörtern( da ich sie als bekannt voraussetze). Deshalb hier nur ein paar Stichworte:

  • der (Fat-)Client muss überall installiert werden (ein Browser hingegen, ist fast immer vorhanden)
  • (Fat-)Clients auf verschiedene Plattformen zu portieren ist aufwändig
  • eigene (Fat-)Clients sind in aller Regel nicht so gut getestet, wie Standard-Browser
  • Einarbeitung von Personal dauert länger, wenn keine Standardtechnologien (wie HTML) verwendet werden
  • Deployment fällt seitens des Client weg, wenn ein Browser verwendet wird

Es gibt sicher noch mehr Argumente gegen (Fat-)Clients. Natürlich gibt es auch Argumente, die für (Fat-)Clients sprechen. Bedienkomfort  z.B. ist ein Grund, warum sich Web-Clients auf SmartPhons (noch) nicht wirklich durchgesetzt haben.

Wirtschaftliche Auswirkungen

Die Liste der Probleme mit Legacy Code kann man sicher noch weiter verlängern. Alle bisher aufgezählten Probleme, sind nicht nur Herausforderungen der Entwicklungsabteilung. Aus Sicht des Kunden bzw. Vertriebs schlagen sie in Form eines „Time-to-Marke-Problems“ bei ihnen durch:

  • es dauert immer länger neue Funktionen zu implementieren
  • es dauert immer länger Bugs zu fixen
  • es dauert immer länger die Software auszurollen
  • das Testing wird immer aufwändiger und unbefriedigender

Mit all diesen Problemen gehen die Entwicklungskosten durch die Decke. Die Software, die früher mal „Schlachtschiff“ und „Cashcow“ des Unternehmens war, wirft immer weniger Gewinn ab. Je nach wirtschaftlichen Umfeld, lässt sich dieser Zustand noch eine Weile aufrecht erhalten. Der Kunde zahlt für ein zunehmend schlechter werdendes Produkt immer mehr Geld. Irgendwann sind die Entwicklungskosten höher als die Einnahmen.

Ob mangelnde Qualität oder zu hoher Preis, irgendwann kommt der Punkt, an dem der Kunde oder die Konkurrenz eine bessere Alternativen findet. Die einstige „Cashcow“ nicht mehr konkurrenzfähig.

Lösungsansatz mit RESTful-Achitektur

Was also kann man  tun, um die Code-Base wieder unter Kontrolle zu bekommen? Sich in über 1.000.000 Zeilen Code einzulesen, Diagramme zu zeichnen, APIs (nach-)zudokumentieren und anfangen die schlimmsten Code-Sünden umzuschreiben? Nicht selten kommt man zu dem Ergebnis, das dies aussichtslos ist. Spätestens wenn man schon viele Stunden damit verbracht hat, ohne dass sich die Situation signifikant verbessert hat, ist es Zeit zu erkennen, das es zu spät ist für die Minimal-invasive Lösung ist. Hier muss ein Cut her.

Am Erfolgversprechendsten ist eine Art „Salami-Taktik“ , bei der der Code Scheibchenweise zerlegt wird, und zwar von hinten nach vorne. Man schaut sich die „Verwertungskette“ bzw. „Nachrichtenkette“ an und lokalisiert das letzte Glied, das keine anderen Kettenglieder mehr aufruft. Dann versucht man alle Nachrichten/Daten-Kanäle zu lokalisieren. Diese Nachrichten/Daten-Kanäle (Seien es übergebene Kommandozeilenparameter, gelesen und geschriebene Dateien, oder ähnliches) sollte man durch RESTful-Aufrufe ersetzen. Der Teil der Software, den man herausschneiden und ersetzen will, ist durch die RESTful-API über Netzwerk zu erreichen. Die Neuimplementierung kann dadurch auf einem ganz anderem System mit modernen Voraussetzungen laufen, wie z.B. aktuellen Bibliotheken, Compilern, Betriebssystemen und allem, was das Entwicklerherz begehrt.

Es ist sogar möglich eine andere Programmiersprache zu wählen, um die Produktivität zu steigern. Anfang der 1990er Jahre war C++ sicher eine interessante Option etwas moderner zu programmieren, ohne die Möglichkeit zu verlieren, C-Bibliotheken verwenden zu können. Open Source war noch nicht so verbreitet und die proprietären Compiler, Frameworks und C-Bibliotheken teuer. Man wollte nicht alles wegwerfen und neu einkaufen müssen.

Jetzt, ein 1/4 Jahrhundert später sieht die Welt etwas anders aus. Es gibt eine Vielzahl an Open Source-Technologien die frei verfügbar sind. Durch die hohe Produktivität, die in anderen Programmiersprachen möglich ist, wurden in den letzten Jahren C/C++ Marktanteile streitig gemacht. Es ist z.B. sehr bezeichnend, dass es keinen Applikation-Server für C/C++ gibt, der nennenswerte Marktanteile hat, im Gegensatz zu Java, für das es gleich ein ganzes Dutzend Alternativen gibt.

Die Schwäche von C/C++ haben auch andere schon erkannt. So gibt mehrere Projekte, die eine Alternative zu C/C++ schaffen möchten, die eine höhere Produktivität zulässt. Da wären z.B. D, Rust (von Mozilla ) oder Go (von Google). Jede Programmiersprache hat ihre Stärken und Schwächen. Alles mit einer Technologie erschlagen zu wollen, wird sicher gehen, jedoch erneut auf Kosten der Produktivität. Deshalb gilt es gut abwägen, ob der initiale Auffand der Einarbeitung in eine neue Technologie, am Ende nicht billiger ist, als eine Programmiersprache zu vergewaltigen.

Alles neu schreiben? Ich bin doch nicht wahnsinnig!

Der Leser mag sich zwischendurch schon gefragt haben, wie ich eigentlich a priori davon ausgehe kann, dass man die komplett Anwendung neu schreiben könne. Da stecken doch etliche Mannjahre Arbeit drin, die man nicht einfach so wegwerfen könne. Doch man kann, -vielleicht nicht alles und nicht sofort.

Der Wert einer Software besteht nicht in sich selbst, sondern in dem was sie kann, also der ihr innewohnenden Geschäftslogik. Wenn ich also etwas retten will, muss das Geschäftslogik sein und nicht der Code, um seiner selbst willen. Das Problem mit Legacy Code ist jedoch, dass man die Geschäftslogik im Code gar nicht mehr wiederfinden kann. Im Code ist noch erkennbar, an welchen Stellen Komponenten mit andern Komponenten Daten austauschen. Diese sind noch lesbar und nachvollziehbar. Aber die Verarbeitung versteht oft niemand mehr.

Die Richtigkeit eines Resultat oder die Ausgabe eines Moduls, lässt sich meist leichter und testbarer überprüfen als das Innere eines Moduls, das man nicht mehr versteht. Ein Modul kann ein (Hilfs-)Programm, oder eine Klasse sein, etwas, das eine Teilaufgabe erledigt, dessen (Zwischen-)Resultat in sich konsistent und verständlich ist. Konkretes Beispiel dafür ist der Warenkorb in einem Webshop. Das Modul hat nur wenige Schnittstellen:

  • Artikel hinzufügen
  • Artikel entfernen
  • Liste ausgeben

Der ursprüngliche Code ist unverständlich und unwartbar. Ich versuche zu lokalisieren, welche anderen Teile des Codes mit dem Modul sprechen. Mich interessiert nur, was rein und raus geht an Daten. Nur das muss ich verstehen. Dann implementiere ich einen neuen Warenkorb mit REST-API. Jetzt bringe ich den Alten Code dazu, mit der REST-API zu sprechen. Meistens gibt es schon fertige Libs für REST. Dann versuche ich die Geschäftslogik  reimplementieren. REST hilft mir dabei, mich von allen Abhängigkeiten des alten Systems zu lösten und alle Freiheiten bei der Wahl der richtigen Technologie zu haben.

Dadurch, dass ich auf nichts mehr im altem Code Rücksicht nehmen muss, kann ich maximale Produktivität entfalten, um möglichst schnell das alte Modul abzulösen. Ich kann mich voll auf den wichtigsten und wertvollsten Teil der Software konzentrieren, die Geschäftslogik. Auch das Deployment wird einfacher. Ich kann durch die lockere Koppelung jede REST-Komponente einzeln verteilen (solang die REST-API stabil bleibt). Dadurch werde ich dem Leitsatz „Release early, release often“ leichter gerecht.

…Und der ganze Rest des Universums.

Natürlich habe ich ein bisschen im Internet gestöbert, was andere Entwickler für Erfahrungen gesammelt haben. Erstaunlicherweise lässt sich in Wikipedia wenig zu diesem Thema finden. In der IT gibt es zu allen möglichen Dingen völlig aufgeblasene Theorien und Konzepte, zu denen umfangreiche Bücher geschrieben werden. Nicht selten bekommt man den Eindruck, es nicht mit einer neuen Technologie zu tun zu haben, sondern mit einer neuen Glaubensgemeinschaft. So machte sich schon 2007 Scott Berkun in seinem dem Artikel Asshole driven development darüber lustig.

Der Artikel von Patrick Koglin: „Legacy Code – Hier liegt die Herausforderung – TDD & Legacy – Best practices & Lessons learned“. verweist zurecht darauf, dass zum Legacy Code auch die Legacy Entwicklern gehören.

Ich bin unschlüssig, ob man Legacy Entwickler als Segen oder als Fluch betrachten soll. Oft wird man die Situation vorfinden, dass es nur einen Legacy Entwickler gibt, der einen bestimmten Teil des Codes noch so gut versteht, dass er darin Änderungen vornehmen kann. Meine Erfahrung ist, dass Zusammenarbeit und Wissenstransfer mit Legacy Entwicklern sehr schwierig ist. Mein Eindruck ist, sie betrachten sie ihr exklusives Wissen als Job-Garantie. In diesem Fall haben sie kein Interesse, verständlichen, wartbaren und dokumentierten Code zu schreiben. Ein Leser des Webblocks von Scott Berku nannte das in seinem Kommentar Job Security Development.

Selbst wenn der Code nicht mutwillig unverständlich gehalten wurde, weist Patrick Koglin zu Recht darauf hin: „Und meist, Nein – immer sind diese Leute stolz auf, das was sie geleistet haben. Das darf man nicht vergessen.“. Auch wenn man es sich als Dazugekommener nicht vorstellen kann, aber der Legacy Entwicklern hat eine emotionale Beziehung zu seinem Code. Egal wie grausam und misslungen sein Baby auch ist. Deshalb warnt Patrick Koglin auch: „Schnell kann man es sich mit dem Kollegen verscherzen, wenn man die falsche Frage gestellt.“

Der dritte Aspekt im Umgang mit Legacy Entwicklern ist, dass man leider nicht erwarten darf, das sie sich auf moderne Arbeitsweisen und Technologien einlassen. Hierzu noch einmal Patrick Koglin: „TDD Entwickler und Nicht-TDD-Entwickler arbeiten völlig gegensätzlich. Das ist wie Motorrad fahren mit Flip Flops ohne Helm vs. Motorrad fahren mit Rückenpanzer, Helm und Lederkombi. Du kannst nur geringe Unterstützung und wenig Verständnis von Legacy Entwicklern erwarten, die nicht testgetrieben arbeiten. Schau dich nach anderen Code Buddies um.“

Zum Abschluss noch eine Buchempfehlung : Buchempfehlung: “REST und HTTP”

Werbung

Python 3.x auf CentOS 7.x und Systemd mit Ansible

Standard

Bevor ich meine TUXERJOCH-Software auf meinen Server aufgespielt habe, lief sie nur auf meinem Desktop mit Fedora 21. Ich war überrascht, dass ich unter CentOS 7 auf Probleme gestoßen bin, mit denen ich nicht gerechnet habe.

Unter CentOS 7 stand zwar schon ein Python-3-Paket zur Verfügung, aber darüber hinaus gibt es nichts. Die meisten Libs sind noch (ausschließlich) für Python 2.x. Alles was es für Python 3 gibt ist:

  • python3-pkgversion-macros
  • python34
  • python34-debug
  • python34-devel
  • python34-libs
  • python34-test
  • python34-tkinter
  • python34-tools

So musste ich mir überlegen, wie ich an meine benötigten Python-3-Pakete komme. Ich habe keine RPM-Repos mit CentOS 7-Paketen gefunden, die ich hätte einbinden können. Also wollte ich den Weg über pip gehen. Das ist natürlich nicht so schön, weil pip ein Fremdkörper ist und die Installationen am System vorbei gehen. Ich kann nicht mit einem Befehl (yum -y update) das gesamte System aktualisieren. Aber durch meinen Einsatz von Ansible halte ich das Problem noch
für beherrschbar, da ich jetzt das System mit einem einzigen Ansible-Befehl aktualisieren kann. Ansible bedient dann seinerseits rpm und pip.

Leider musste ich feststellen, dass auch pip keine Python 3-Pakete kannte. Ich wusste aber von Fedora, dass es sie geben muss. Was mir fehlte, war also ein pip für Python 3, den es leider nicht für CentOS 7 fertig gibt. Man muss sich diesen selbst herunterladen und installieren:

curl "https://bootstrap.pypa.io/get-pip.py" -o "get-pip.py"
python3.4 ./get-pip.py

Danach habe ich die gleiche pip-Version installiert, wie für den Python-Interpreter, den ich dafür benutzt habe, also in diesem Fall: pip3.4

Jetzt konnte ich endlich die passenden Libs für mein Python 3 installieren:

pip3.4 install bottle
pip3.4 install simplejson
pip3.4 install requests
pip3.4 install cherrypy

Was noch bei der Verwendung mit Ansible zu beachten ist

Damit ich den den Weblog als Service in Hintergrund laufen lassen kann, griff ich auf Systemd zurück. Damit Systemd auch tatsächlich Python 3.x verwendet und nicht 2.x, muss ich Datei /usr/lib/systemd/system/tuxerjoch.service anpassen. Und die sieht jetzt so aus:

[Unit]
Description=tuxerjoch server
After=syslog.target
After=network.target

[Service]
Type=simple
User=root
Group=root
WorkingDirectory=/usr/local/lib/tuxerjoch/
ExecStart=/usr/bin/python3.4 /usr/local/lib/tuxerjoch/tuxerjoch.py

# Give a reasonable amount of time for the server to start up/shut down
TimeoutSec=300

[Install]
WantedBy=multi-user.target

Wie bereits erwähnt, editierte ich die Datei nicht direkt, sondern verteilte meine Konfiguration über Ansible. Bei der Konfiguration von Systemd war aber noch zu beachten, das Systemd selbst benötigt einen reload, um die veränderte Konfiguration zu berücksichtigen. Dazu habe ich mir zwei Handlers eingerichtet:

---
- name: restart systemd
  command: /usr/bin/systemctl daemon-reload

- name: restart tuxerjoch
  service:
    name: "tuxerjoch"
    state: "restarted"

Der erste veranlasste den Reload von Systemd. Der Zweite startete den Service für TUXERLOCH neu. Bei den Tasks konfigurierte ich nun, das bei jeder Änderung an der Datei /usr/lib/systemd/system/tuxerjoch.service erst systemd neu gestartet wird und dann tuxerjoch selbst:

- name: set file tuxerjoch.service
  copy:
    src: "../files/tuxerjoch.service"
    dest: "/usr/lib/systemd/system/tuxerjoch.service"
    owner: "root"
    group: "root"
    mode: 644
  notify:
    - restart systemd
    - restart tuxerjoch

Proof of Concet mit Python3 und CouchDB

Standard

Seit geraumer Zeit ist mein Weblog the-independent-friend.de schon abgeschaltet. Grund sind technische Schwierigkeiten bei der Migration der Dateien in ein anderes System. Da ich gerade ein bisschen Zeit habe, nehme ich jetzt die Baustelle mal in Angriff. Zuerst habe ich mein Server von CentOS 6 auf 7 gehoben. Dabei habe ich gleich konsequent Ansible verwendet.

Als nächstes arbeite ich an einer eigenen Weblog-Software. Ich hab einfach keine Lust mehr, auf einer Software zu sitzen, aus der ich meine Artikel nicht einfach exportieren kann. Die paar Funktionen, die ich brauche, können nicht so schwer zu implementieren sein. Erst mal ist es nur ein Proof of Concet namens TUXERJOCH. Über meine Erfahrungen werde ich unter the-independent-friend.de schreiben. Durch die Benutzung von TUXERJOCH werde ich dann auch gleich reelle Erfahrungen mit dem PoC sammeln. Nach dem Motto: „Eating your own dog food“.

Buchempfehlung: „REST und HTTP“

Standard

REST-CoverIch lese gerade „REST und HTTP: Entwicklung und Integration nach dem Architekturstil des Web“ (ISBN 3864901200). Es gibt tolle Frameworks, die dem User viel Arbeit bei dem Erstellen von REST-Services abnehmen. Jedoch können diese einem in aller Regel nicht das API-Design abnehmen.  Um besser verstehen zu können, was ein gutes REST-Design ist, muss man sich mit den Konzepten und Ideen von REST beschäftigen. Hierzu ist das Buch von Stefan Tilkov, Martin Eigenbrodt, Silvia Schreier und Oliver Wolf sehr gut geeignet.

Es ist völlig unabhängig von einer bestimmten Programmiersprache oder Technologie, was ja auch Sinn macht, da RES genau das können soll: über Programmiersprachen und Technologiegrenzen hinweg zu kommunizieren. Auf den ca. 300 Seiten werden konkurrierende Konzepte wie SOAP dem REST-Konzept gegenübergestellt. Begrifflichkeiten wie „Ressource“, „URI“, „Verben“ usw. werden erklärt, Präsentationsformate werden erläutert. Thematisiert wird Session-Handling, Skalierbarkeit und Sicherheit ebenso wie Architektur, Werkzeuge, Bibliotheken und immer wieder Fallstudien.

Das Ganze ist zudem sehr kompakt und didaktisch durchdacht aufgebaut.

The Silicon Web Framework

Standard

Jens Weller machte mich auf ein weiteres C++-Web-Framwork aufmerksam: The Silicon Web Framework

Voraussetzung ist allerdings ein C++14 kompatibler Compiler. Das Projekt gibt als Vorteile unter Anderem an:

  • Leichte Benutzbarkeit
  • Sicherheit
  • Gesschwindigkeit
  • Automatic depndency injection
  • Automatische Validation, Serialization und Deserialization von Nachrichten
  • Sessions

Dem Projekt fehlt allerdings noch eine Template-Engine für MVC. Dafür hat es Object relational mapping. Somit ist dieses Framework, meiner Einschätzung nach, ohne Template-Engine aktuell nur für RESTFull zu gebrauchen, – aber ansonsten ein erfrischend moderner Ansatz.

Template-Engines für C++

Standard

Ich habe in den letzten Tage nach einer Alternative für NL::Template gesucht, weil der Support für C++98 in NL::Template nicht optimal zu sein scheint. Es gibt zwar ein speziellen Branch für C++98, wenn ich diesen verwende, bekomme ich jedoch einen Speicherzugriffsfehler.

Im englischsprachigen Wikipedia gibt es eine tabellarische Auflistung von Template-Engines, ebenso wie für C++: http://en.wikipedia.org/wiki/Comparison_of_web_template_engines

Google ctemplate scheint von Google fallen gelassen worden zu sein und wird noch von einer Person auf GitHub weiter gepflegt. Es gib noch zwei Frameworks, die zum Großteil nur auf Russisch dokumentiert sind (auch in den Sourcen). Aber irgendwie habe ich nichts wirklich Überzeugendes gefunden.

Ich habe auch ein Wenig zu den (relativ) neuen Programmiersprachen Go und Rust quer gelesen. Beide möchten eine Alternative zu C++ sein und wollen es besser machen, als C/C++. Ich war sehr erstaunt zu sehen, dass es für beide Programmiersprachen schon mehrere ausgereift wirkende Web Frameworks gibt.

Mein Eindruck ist: C++ kann sich im Web-Umfeld warm anziehen. Der einzige Vorteil, den ich noch bei C++ sehe, ist dass es mehr Compiler zur Auswahl gibt und dass diese für mehr Plattformen zur Verfügung stehen, als für Rust und Go ist. Im Gegensatz zu node.js, gibt es in Rust und Go echte Nebenläufigkeit (zu dem noch auch einen interessanten Ansatz). Ich denke, das wird die nächsten Jahren noch recht spannend werden.

Ich lese gerade: „Testgetriebene Entwicklung mit C++“

Standard

tdd-book

Von Jeff Langr (ISBN-10: 3864901898). In Vorbereitung auf ein Projekt mit Legacy-Code habe ich mich nach einem Buch umgeschaut, mit dem ich mich in das Thema „Testgetriebene Entwicklung“ (TDD) einarbeiten kann. Nachdem ich jetzt etwa in der Mitte des Buches bin, kann ich sagen: ich bin begeistert! Es ist didaktisch hervorragend aufgebaut. Die Texte sind gut verständlich und die Code-Beispiele sind sehr gut geeignet, die Theorie mit Leben zu füllen.

Verwendet werden die Test-Frameworks Google Mock und CppUTest. Ersteres ist auch Bestandteil von einigen Linux-Distributionen wie bei Ferora z.B.. Was mir sehr gut gefällt, ist dass TDD nicht nur an Neuimplementierungen demonstriert wird, sondern auch an Refactoring-Themen – also Legacy-Code. Es ist spannend zu sehen, dass TDD auch und gerade mit „Altlasten“ funktioniert und nicht nur unter Laborbedingungen auf der „grünen Wiese“. Völlig neu war für mich auch die so genannte „Mikado-Methode“. Ich war anfänglich etwas skeptisch, aber ich glaube, es könnte tatsächlich ein vielversprechender Ansatz sein, um tief ergreifende Änderungen anzugehen.

static-Eigenschaften in JavaScript

Standard

Ich spiele gerade ein wenig mit Node.js herum. Es dauerte eine Weile, bis ich verstanden habe, wie man in JavaSrcipt static Eigenschaften einer Klasse abbilden kann. Der Hintergrund war, dass ich Werte für Konfigurationen der Applikation in einer Klasse speichern wollte, um sie überall im Programm-Code zur Verfügung zu haben. In C++ hätte ich das so abgebildet, indem ich eine Klasse definiere, deren Eigenschaften „static“ sind. Wo immer ich dann im Code eine solche Klasse initialisiere, sind ihre Werte die selben.

In JavaScript lassen sich Klassen-Eigenschaften jedoch nicht mit „static“ deklarieren. Stattdessen arbeitet man mit so genannten „prototype“. Jedes SavaScript hat eine Eigenschaft „prototype“. Wird in diesen Namensraum eine Eigenschaft angelegt, gilt sie für alle Objekte die von der selben Klassen-Type sind. Zur Verdeutlichung ein kurzes Beispiel:

var AppConfig = function AppConfig(){};
AppConfig.prototype.filename = "config-file.json"
AppConfig.prototype.db = {
    host: "localhost",
    port: "9999"
};

var app_config = new AppConfig();
module.exports = app_config;

Zunächst wird mit der ersten Zeile ein Constructor für die Klasse „AppConfig“ definiert. Das würde in C++ so ausgedrückt.

class AppConfig {
    public:
         AppConfig(){};
}

Der nächste Schritte fügt der Klasse eine neue Static-Eigenschaft hinzu. Das wiederum würde in C++ etwa so aussehen:

class AppConfig {
    public:
        AppConfig(){};
        static std::string filename;
};

AppConfig::AppConfig(): filename(„config-file.json“){ }

In der Zeile darunter sieht man, wie komplexere Daten-Strukturen angelegt werden. In der vorletzten Zeile wird die Klasse instantiiert. Die letzte Zeile ist ein Konstrukt das benötigt wird, um die Klasse in einem Node.js-Projekt zu verwenden. Mit dieser Zeile kann die Klasse überall hin importiert werden:

var app_config = require('./app_config.js');

Wo immer eine Instanz dieser Klasse verwendet wird, haben deren Eigenschaften die gleichen Werte (jedenfalls alle Prototype-Eigenschaften). Auf diese Weise kann man sich das mühsame Durchreichen von Werten über Methoden-Parameter ersparen.

Buchtip: JavaScript für C++-Programmierer

Standard

cover_javascript-oop Vermutlich bin ich nicht der Einzige, der aus der C++-, Python- oder Java-Welt kommt und das Konzept von JavaScript für objektorientierte Programmierung verwirrend findet. Warum sich C++-Entwickler überhaupt mit JavaScript beschäftigen könnten, habe ich bereitsl HIER thematisiert. Im Urlaub las ich ein Buch gelesen, das bei mir ein Aha-Erlebnis ausgelöst hat: „JavaScript objektorientiert: Verständlicher, flexibler, effizienter programmieren„, von Nicholas Zakas; erschienen 2014 im dpunkt.verlag. Es erklärt sehr gut die Stärken von JavaScript. Auch wenn C++ eine  Multiparadigmen-Sprache ist, lassen sich einige Sachen in JavaScript doch eleganter umsetzen, als in C++. Deshalb halte ich die Kombination aus C++ und JavaScript tatsächlich für sinnvoll in Node.js. Das Buch zeigt an leicht verständlichen Beispielen, was mit JavaScript möglich  und was best practices ist. Andere Beispiele zeigen, was zwar möglich, aber besser zu unterbleiben ist (Anti-Pattern). Der winzigste Wermutstropfen ist der Preis. Die gerade mal 120 Seiten kosten stolze 19,95 Euro. Zum Vergleich: Das „JavaScript kurz & gut“ vom O’Reilly Verlag hat 277 Seiten zum Preis von 12 Euro.