Ihr ahnt nicht, was es so gibt in der schönen Welt des wissenschaftlichen Rechnens. Viele Programmierer werfen ihren Nutzern einfach so ihre Software vor die Füsse und kümmern sich danach einfach nicht mehr darum. Schließlich funktioniert die fragliche Software ja. Und wer nachfragt, wie man diese ☠@✴#-Software ans Laufen bekommt mitunter zurück: “Bei mir funktioniert es doch!” – daher auch der Name “runs-on-my-system”-Software.
Es kann aber auch noch ein Wenig mehr gemeint sein. Natürlich zunächst mal, das ein Programm wirklich nur auf bestimmten Systemen lauffähig ist. Aber auch, wenn Leute wirklich der Meinung sind jede Mindestregel zur Veröffentlichung von Software verletzen zu wollen. Wollen? Ja, wollen! Denn manche Macher schaffen Zumutungen: Da wird irgendwas hingerotzt, Erwartungen werden geweckt, Leute planen damit ihre Projekte und werden schwer enttäuscht.
Ihr merkt, ich schiebe gerade etwas Installierfrust … Also, falls ihr je (als WissenschaftlerIn) eure Software veröffentlichen wollt und euren Nutzern das Leben schwer machen mögt, um ein paar anti-Karma-Punkte sammeln, dann habe ich hier einige Vorschläge:
Die Ziele
- hier werden einige Techniken zusammengefasst, die es schwierig machen (wissenschaftliche) Software zu installieren – egal welche Paket Manager (also: Leute) euer Ding aufgreifen werden und egal für welches Paket Managing System oder ob sie dies “per Hand” erledigen
- ich nenne einige Entschuldigungen, wie ihr damit davon kommen werdet
- und zeige wie man in diesen Punkten zu einem wirklichen Profi wird
Und wozu? Na, klar:
- ihr stellt so sicher, dass weniger Leute eure Software nutzen
- sie könnten schließlich Bugs finden, die ihr fixen müsst
- sie werden Fragen stellen, die nach einer Antwort verlangen
- oder im schlimmsten Fall sogar Code beisteuern, den ihr einarbeiten müsst
- ihr verhindert überhaupt Beiträge zu eurer Software zu erhalten
- das frisst einfach zu viel Zeit für eure Wissenschaft, muss man sich ja anschauen und testen
- und ihr müsst das auf lange Sicht auch noch warten!
Ok, die Ziellinie ist klar. Wie vorgehen?
1. Kreativer Umgang mit Versionierung und Veröffentlichung
- bloß keine semantische Versionierung nutzen!
- Shiet, ihr habt gerade veröffentlicht und danach einen kleinen Fehler bemerkt? Kein Problem, fixen und erneut unter derselben Versionsnummer veröffentlichen – merkt sowieso niemand!
- besser noch, ihr macht gar keine bugfix releases:
- einfach den NutzerInnen sagen, bei github ein checkout zu machen, um alle updates zu erhalten
- oder eine Webseite aufsetzen, mit einer Anleitung zum selber fixen.
- oder erst gar keine Releases/Veröffentlichungen machen
- ein Masterbranch auf github ohne tags oder Versionen tut es auch
- man auch auch ein eigenes Versionsschema erfinden – Verwirrung stiften hilft, wenn die eigene Software nicht genutzt sehen will!
- wenn ihr doch versioniert, unbedingt die alten Versionen löschen! Kein Archiv der alten Versionen führen! Am Ende könnte noch jemand nachvollziehen, was ehedem bei einer Datenanalyse schief lief, das darf nicht sein!
Wenn sich doch mal jemand von den Old Boys (wie ich) beschwert, hier ein paar mögliche Gründe, die ihr angeben könnt:
- “War bloß eine winzige Änderung, kein Grund ‘ne neue Version raus zu hauen.”
- “Geh! Versionierung ist heute sowieso unwichtig geworden!”
- “He, Du solltest sowieso nur die aktuelle ‘Version’ nutzen!”
- “Die alte Version hatte Bugs, bitte nicht mehr verwenden.”
Oh, und wenn ihr keine Versionierung verwendet, könnt ihr euch besonders beliebt machen mit
- strikte Versionsanforderungen für Bibliotheken von denen eure Software abhängt (dependencies)
- auf eurer Homepage schreiben warum euch Versionierung so ankotzt und ihr eure Alternative bevorzugt.
Beispiele gefällig? Wie wäre R mit sein hunderten Paketen, die zwar zu > 99 % abwärtskompatibel sind, aber immer schreien “ich kann nur mit der neuesten Version!” (Es gibt einen Weg drum herum, aber automatisiert für potentiell sehr viele Nutzer, muss alles stimmen). Oder Bioconductor? Bioconductor ist seinerseite eine R-Paketsammlung, die häufig in der Bioinformatik eingesetzt wird. Nutzt strikte Versionierung bei allen Abhängigkeiten. Ein x-beliebiges Paket kann mit der Vorgängerversion laufen? Egal! Ein Update wird erzwungen. Ein Update bei einer Abhängigkeit? Her damit! (Man muss aber nicht unbedingt die Version von Bioconductor ändern.)
2. Geschichte eurer Software
Ihr könnt
- Leute raten lassen, was sich geändert hat, beim Update eurer Software
- oder wenigstens vage Angabe machen (“minor updates & bug fixes”)
Die Entschuldigung hierfür ist offensichtlich: “Hey, schau einfach in die commit-Historie, da steht alles drin!”
3. Abhängigkeiten mitschiffen
Immer wieder beliebt: Alle Abhängigkeiten (Softwarebibliotheken oder Programme, die eure Software benötigt um lauffähig zu werden) sind im Download eurer Software mit dabei. (Ok, dann müsstet ihr eigentlich alle Updates in den Abhängigkeiten immer wieder nachvollziehen und in eurem Download einbauen, aber wer wird denn so kleinlich sein?)
Auch hier sind die Entschuldigungen offensichtlich:
- “So ist die Installation einfach einfacher!”
- “Wir wissen einfach besser, wie unsere Software zu installieren ist!”
Noch besser natürlich, wenn ihr Änderungen an euren Abhängigkeiten anbringt (z. B. ein Bugfix), das aber den ursprünglichen Entwicklern nicht mitteilt. (“Jeder ist sich selbst der Nächste! Und das ganze Gerede über Reproduzierbarkeit, ach geh!”)
Was auch geht: Nur einige, nicht alle Abhängigkeiten mitschiffen.
4. Magische Installation von Abhängigkeiten
Richtig cool ist es Abhängigkeiten während der Installation aus den Netz zu laden und irgendwie(!) zu installieren.
Auch hier gilt:
- “So ist die Installation einfach einfacher!”
- “Das Internet ist doch auf jedem System immer erreichbar, oder?”
- und natürlich auch: “Oh, das bisschen Redundanz auf Deinem System ist schon nicht so schlimm, oder?”
Oder ihr macht es so richtig schwer Abhängigkeiten irgendwie anders zu installieren, als auf eurem System. Beispiel gefällig?
app: app.o g++ -static -o app app.o \ /usr/lib/libgsl.a \ /usr/lib/libgslcblas.a \ /usr/lib/liblapack.a \ $(LOCAL)/lib/libopenblas.a \ -lgfortran -lpthread
Dies ist ein Ausschnitt aus einem sogenannten “Makefile”, vor ein paar Tagen gefunden. Man verwendet solche Dateien, um Programme (hier mal “app” genannt) unter Linux zu kompilieren und installieren. Und was ihr seht ist ein sog. Compiler-Aufruf (g++
heißt er in diesem Fall) – etwas um Code in Maschinencode zu übersetzen und (in diesem Fall) ein auführbares Programm zu erstellen.
Das Problem an dieser Stelle? Seht ihr die vielen /usr/lib
-Einträge? Sie stehen für bestimmte Verzeichnisse. Und wenn es genau diese auf dem Zielsystem nicht gibt, dann lässt sich diese Software nur installieren, wenn man diese Dinge korrigiert (für Fachleute: einen sog. patch schreibt). Herrlich! So muss man es machen!11!! Und da ist noch was: Seht ihr das -static
? Das zwingt zur Erzeugung eines sogenannten statischen Programms wo alle Bibliotheken mit in das Programm geschrieben werden, wodurch dieses sehr groß wird (und noch weitere Probleme erzeugt). Fantastisch!
5. Je mehr Abhängigkeiten desto besser
Wenn der Blogschreiber es mal abgelehnt hat wegen so ‘ner Pillepalle überhaupt auf einer Publikation dabei zu sein, ist dies kein Grund es ihm gleich zu tun. Es ist wirklich gut viele Abhängigkeiten zu haben. Am besten welche, die selber schwer zu installieren sind!
Begründung ist natürlich: “Ich möchte schlicht nicht das Rad neu erfinden müssen.”
Was für tolle Auswirkungen das haben kann, sieht man an NPM. Ein paar Abhängigkeiten entfernt, das halbe Internet fing an zu röcheln. Wissenschaftlich geht auch: Homer, wirklich ein großes Vorbild – hakt viele Punkte unserer Checkliste hier ab. Mit zusätzlichen anti-Karma-Punkten obendrauf.
6. Möglichst viel zur Installation hardcoden
Wie das geht? Siehe Punkt Nr. 3.
Entschuldigungen:
- “Wir erwarten eine Standardumgebung!”
- “Wir können einfach nicht alles mögliche supporten!”
7. Wahl des Installationstools
- möglichst exotisch, wenn es bekannter wird: Wechseln!
- irgendwas, was “besonderes” Verhalten aufweist
- z. B. die Umgebung kontrolliert
- oder es unmöglich macht irgendwas zu korrigieren, falls es mal nicht klappt
- am Allerbesten: Ein Skript schreiben, zu einem Tool, dass es ohnehin schon gibt. Auf diese Weise ist es richtig schwer sauber zu arbeiten!
Aber klar, ihr wisst es besser:
- “Diese modernen tools arbeiten einfach besser!”
- “Wir können nicht in der Vergangenheit verharren!”
- “Ich schreibe einfach bessere Installationsskripte!”
Richtig gruselig wird es für Anwender, wenn ihr eure Super-Duper-Spezialscripte so benennt wie ein Standardtool – es aber etwas völlig anderes macht. Beispiel: ./configure
.
8. Nur teilweise installieren oder interaktive Skripte
Wenn ihr schon irgendwas bereitstellt, dann sollte es die Arbeit nicht ganz machen. Wäre ja noch schöner, oder?!
- am besten nichts konfigurierbar halten, alles hardcoden (s.o.)
- interaktive Installationsskripte sind das Nonplusultra, sie verhindern zuverlässig, dass ein Paketmaintainer ein einfaches Leben hat!
Für beide Fälle gilt: “So ist es intuitiver!”
Noch ein paar Beispiele gefällig?
Ok, aber werden wir erst mal ernst! Warum sollte man überhaupt Quellcode selber übersetzen wollen? Tensorflow, eine Programmierplattform für maschinelles Lernen, ist so ein Kandidat. Kann man sehr einfach vorkompiliert installieren. Wenn man es selber macht ist es allerdings wesentlich schneller. Aber – ihr könnt ja mal versuchen Tensorflow “von Hand” zu bauen – die Leute hinter Tensorflow decken viele oben genannten Punkte der Checkliste supergut ab und bekommen folglich viele extra anti-Karma-Punkte.
Hier wäre noch ein Beispiel aus der Bioinformatik. Gar nicht besonders, solche Webseiten gibt es häufiger. Was findet man da? Ein unversioniertes Script, ein unversioniertes Quellcodebündel und noch mehr davon. Warum machen die das (sie wissen es besser: auf derselben Seite gibt Projekte, die auf github versioniert werden)? Solcherlei Sorglosigkeit findet man häufiger, z. B. auch hier. Letzte Veröffentlichung ist eine Version 2.4.0, die Seite verrät aber gleich
Unfortunately the 2.4.0 release has a buggy interaction with the boost C++ libraries that causes frequent crashes, therefore it is suggested that the development snapshots linked below be used until the next release.
Den next release gibt es aber nicht, weshalb Bioconda beispielsweise auf einen snapshot von 2015 (also eine bestimmten, aber nicht versionierten und offiziell unterstützten Entwicklungszustand) zurückfällt. Ein Übersetzungsversuch: “Uns ist der Zustand egal, der Entwickler hat irgendwann aufgehört. Es funktioniert oder auch nicht.” (Kleiner Hinweis für Nicht-Programmierer: “boost” sind aktiv entwickelte C++-Bibliotheken, die seit 2015 große Sprünge gemacht haben. Hätte man aktuelle Versionen, man hätte das Problem nicht – bzw. man könnte es nicht auf die Bibliothek schieben. Im Grunde steht da auch: “Wir haben keine Ahnung, wie man das richtig installiert auf einem Multiusersystem (sonst könnten wir ja updaten) und deshalb müssen unsere Nutzer damit leben lernen!” Ich vermute auch: Die Bibliotheken, die auf einem System X statisch eingebunden werden, ergeben nicht immer ein lauffähiges Programm auf System Y. Und dann schreibt man folglich: “has a buggy interaction with the boost C++ libraries that causes frequent crashes”. Ein deftiger Facepalm ist an der Stelle das Mindeste. Und das bei einer Gruppe, “die was mit Informatik macht”.
Im Grunde ist dieser Beitrag eine Bestätigung eines sehr frühen Beitrag im Blog über das Problem der Softwarefinanzierung. Es kommt die Gleichgültigkeit der Entwickler hinzu, weshalb solche Software häufig genug nicht Eingang in öffentliche Quellecodemanagementsysteme wie github & Co findet. Und natürlich weil Anfänger gewisse mentale Hürden zu überwinden haben, was auch damit zusammenhängt, dass sie schlecht betreut werden. Oft jedoch sind informatische Arbeitsgruppen am Werk, die es besser wissen sollten. Manchmal zeigen sie das auch, wandert doch die eine oder andere Software aus derselben Arbeitsgruppe, die auch solchen Mist auf der Homepage hat, doch zu github und wird besser betreut.
Und was passiert, wenn die/der Chef in Rente geht oder eine andere Position annimmt mit der nicht betreuten Software, deren Homepage dann auch früher oder später verschwindet? Genau! Der ultimative Reproduzierbarkeitsgau.
Ein großer Spaß ist auch wenn eine Software eine umständliche Bauanleitung gibt (“so habe ich’s gemacht, sollte bei Dir auch funktionieren” – hier ein Beispiel, es gibt verschiedene Spielarten, in neuerer Zeit auch gerne mit einer shiny Webseite, oft ohne sinnvolle Funktionalität, die andere Lösungen nicht bieten), die so ganz natürlich annimmt alles manuell zusammen zu kramen. Kein wissenschaftliches Problem, aber großer Quatsch. Kommt immer wieder vor.
Zusammengefasst …
… lässt sich sagen:
- runs-on-my-system ist manchmal wörtlich zu nehmen. Und das kann man dann in die Tonne kloppen: Alles was damit an Daten analysiert wurde, kann nur vielleicht von den Machern reproduziert werden.
- ist häufig als big-binary erhältlich mit viel Ballast oder reine Bloatware. Egal. In jedem Fall erschwert es Reproduzierbarkeit: Wer sagt denn, dass das auf einer CPU von morgen wirklich laufen wird? Immer? Mit allen (teils unnötigen) Abhängigkeiten? Da hilft die Archivierung in Containern oder virtuellen Maschinen nur sehr, sehr bedingt.
- gibt es sehr viele Wege der Nutzercommunity das Leben wirklich schwer zu machen.
Meine Wette: Aus der letzten Reproducibility Challenge und anderen Maßnahmen wurde so wenig gelernt, dass sich wenigstens deren deprimierenden Ergebnisse in zehn Jahren wiederholen lassen.
Achtung: Dieser Beitrag ist in großen Teilen inspiriert/abgekupfert durch einen Vortrag von Kenneth Hoste (sehenswert).
Kommentare (59)