Meine Antwort auf "A Response to Hello World"
Kürzlich machte ein Blog-Eintrag mit dem Titel "A Response to Hello World" von Caleb Doxsey die Runde. Darin versucht er, die von Drew DeVault in dessen Artikel "Hello World" vorgebrachten Argumente für die Einfachheit von Software zu sezieren.
Veröffentlicht am von Philipp Trommler. Dieser Beitrag wurde außerdem übersetzt nach: en.
Eins vorneweg: Dies hier soll in keinster Weise ein persönlicher Angriff auf Caleb Doxsey sein. Die Punkte, die er vorbringt, sind alle vernünftig und – abhängig vor der Domäne und dem Hintergrund des Lesers – richtig. Ich möchte lediglich ein Licht auf Ansichten und Meinungen werfen, die meiner Meinung nach zunehmend an Beliebtheit und Akzeptanz verlieren, und ich möchte klarstellen, dass dies wahrscheinlich auch das einzige ist, worauf Drew hinweisen wollte.
In der Zwischenzeit hat Drew selbst ein "Follow-Up" zum Thema veröffentlicht, in dem er seine Motivation zum Schreiben des ursprünglichen Artikels genauer darlegt. Der wichtigste Punkt, den er hervor hebt, ist der, dass seine erste Veröffentlichung in keinster Weise als Vergleich oder – wie er es ausdrückt – Benchmark zwischen den verschiedenen aufgeführten Programmiersprachen gedacht war sondern ausschließlich als Verdeutlichung seines Hauptthemas: Komplexität. Und als Maßstab für ebendiese Komplexität hat er die Anzahl der Syscalls gewählt, die ein Programm für die Abarbeitung einer bestimmten Aufgabe benötigt.
Ich stimme Drew zu, dass die Komplexität von Software, die durch das Abstrahieren und Einziehen von Schichten durch nahezu alle Programmierer auf allen Ebenen in den letzten Jahren eingeführt worden ist, an allen Ecken und Enden Ressourcen frisst und dass nur Bewusstsein für dieses Problem vielleicht eine bessere und leistungsfähigere Zukunft für die Software-Entwicklung herbeiführen kann. Dies möchte ich näher erläutern, indem ich Calebs Blog-Eintrag kommentiere, weil ich denke, dass er ziemlich gute Antworten auf die von Drew aufgeworfenen Fragen gibt, nur aus einem völlig anderen Blickwinkel.
Wahrnehmbare Geschwindigkeitsverbesserungen¶
In seinem ersten Teil spricht Caleb über die drei Schattenseiten von Komplexität, die Drew erwähnt hat, und erläutert sie nacheinander.
Schwerer zu debuggende Programme¶
Hier muss ich Caleb voll und ganz zustimmen. High-Level-Programmiersprachen wie Go sind nicht notwendigerweise schwieriger zu debuggen als Low-Level-Sprachen, nur weil sie mehr Syscalls verwenden. Die Debugbarkeit wird viel mehr durch die Komplexität der Architektur des Programms, Multi-Threading und – am allerwichtigsten! – die verfügbaren Tools bestimmt.
Eine größere Anzahl an verfügbaren Stack-Overflow-Antworten bei der Verwendung einer Hochsprache ist jedoch ein fragwürdiges Argument.
Höherer Festplattenplatzverbrauch¶
Dieses Argument wird von Caleb heruntergespielt, aber ich muss deutlich widersprechen. Immer größer werdende Programme sind nicht nur ärgerlich, für mich als Embedded-Entwickler sind sie ein echtes Problem. eMMCs sind immer zu klein und sie werden nicht größer, nur weil man diese schicke, in Go geschriebene Anwendung mitliefern will. Die Größe von Binärdateien bestimmt maßgeblich die Startgeschwindigkeit von Embedded-Geräten – ja, all diese Bits und Bytes müssen beim Start von einer langsamen eMMC gelesen und in den Hauptspeicher geschrieben werden! Das Vorhalten von Build-Artefakten wird immer teurer, die Anforderungen an die Netzwerkkapazität der Build-Infrastruktur steigen stetig.
Hast du dich schon mal über den viel zu kleinen integrierten Speicher oder den prinzipiell vollen RAM deines Android-Geräts geärgert? Tja...
Schlechtere Benutzererfahrung¶
Drew weist darauf hin, dass komplexere Programme zwangsläufig zu längeren Ausführungszeiten und diese wiederum zu einer schlechteren Benutzererfahrung führen. Dem kann ich nur voll und ganz zustimmen.
Caleb versucht dieses Argument zu relativieren, indem er sagt, dass, obwohl manche der Programme, die Drew verwendet hat, tatsächlich länger laufen, alle Zeiten immer noch ausreichend kurz sind, um nicht als störend empfunden zu werden. Das mag in diesem speziellen Fall zwar stimmen, aber es ist definitiv keine allgemeingültige Wahrheit. Weiterhin versucht er das Argument zu entkräften, indem er stellvertretend das von Drew bereitgestellte Go-Programm verbessert. Dabei verfehlt er den eigentlichen Punkt gleich in mehrfacher Hinsicht:
- Er vergleicht seine optimierte Version des Go-Programms mit einer nicht im selben Maße optimierten Version des Assembly-Programms.
- Indem er das Programm überhaupt optimiert hat, bestätigt er Drews wesentliches Argument, dass es Komplexität enthält, die der Programmierer kennen und um die er herumarbeiten muss, um eine akzeptable Performance zu erreichen.
- Er stellt das Testszenario so um, dass die Startup-Performance nicht mehr ins Gewicht fällt und argumentiert so, dass diese vernachlässigbar ist. Dabei ist es gerade die Geschwindigkeit beim Starten, die für die gefühlte Performance entscheidend ist! (Hast du jemals vor deinem untermotorisierten Windows-Rechner gesessen und darauf gewartet, dass der erste Python-Aufruf an diesem Tag fertig wird?)
Er hat allerdings damit Recht, dass es mit Hochsprachen einfacher ist, Multi-Threading-Programme zu entwickeln und so die Hardware voll auszunutzen. Aber Multi-Threading ist nicht die Lösung aller Probleme, insbesondere nicht, wenn eine effizientere Umgebung das gleiche Problem mit nur einem Thread hätte lösen können.
Kompromisse beim Kompilieren¶
Im nächsten Teil spricht Caleb über die Kosten für das Kompilieren effizienterer Programme. Natürlich ist es wahr, dass Compiler-Optimierungen Zeit kosten und so den Arbeitsablauf stören können und hin und wieder verhindern sie auch das ein oder andere kleine Feature in einer Programmiersprache. Und in der Tat ist dies ein Argument, das ich ziemlich oft höre. Trotzdem bin ich anderer Meinung.
Der Code, den ich beruflich geschrieben habe, kann Millionen oder auch Milliarden mal laufen. Wenn eine Compiler-Optimierung ein oder zwei Sekunden braucht, aber die Performance bei jeder dieser unzähligen Aufrufe erhöht, ist das dann wirklich teuer? Vielleicht sollten die Programmierer vom Compiler-Driven-Development wegkommen und versuchen, von Anfang an (fast) fehlerfreien Code zu schreiben? Das würde wahrscheinlich die Kompilierzeiten weitaus mehr reduzieren als der Verzicht auf Optimierungen.
Analyse der Syscalls¶
Hier bricht Caleb die von Drew gefundene, durch Syscalls bedingte Komplexität herunter. Er kategorisiert und klassifiziert die vom Go-Programm verwendeten Syscalls und kommt dabei zu dem Schluss, dass sie alle nützlich sind. Natürlich hängt diese Bewertung davon ab, was man erwartet, aber dennoch möchte ich hier ein paar Kommentare hinterlassen.
Scheduling¶
Es ist zwar schön, dass Go im Kern mit Multi-Threading umgehen kann und es zu
einem Programmierparadigma erster Klasse erklärt, aber den Entwickler zu
zwingen, es zu nutzen, ist meiner Meinung nach nicht gut. Viele Entwickler wären
überrascht, was man mit einer Single-Thread-Anwendung erreichen kann, wenn man
nur die richtigen Werkzeuge und Pattern kennt. Multi-Threading hat seine eigenen
Kosten, seine eigene Komplexität und erhöht die Möglichkeiten, Fehler zu machen,
um ein Vielfaches. Und all das nur, damit anschließend die meisten Threads im
poll(2)
hängen?
Garbage Collection¶
Ja, es stimmt, Garbage Collection kann vor allen möglichen
Speicherzugriffsfehlern schützen. Aber sie hat auch ihre Kosten und der
Entwickler sollte das Recht haben, sich für oder gegen sie zu entscheiden.
Außerdem können viele der von der Garbage Collection behandelten Fehler durch
statische Analyse und/oder valgrind
gefunden werden, ohne die
Laufzeit-Performance negativ zu beeinflussen. Ja, die meisten Programmierer
nutzen diese Werkzeuge nicht, also wäre es vielleicht sinnvoller, sie davon zu
überzeugen, als diese Schulden auf den Endnutzer abzuwälzen?
Stdin, Stdout, Stderr¶
Ich verstehe nicht, was Caleb hier sagen will. Ja, diese Dateideskriptoren sind
non-blocking. Trotzdem können sie mit poll(2)
genutzt werden. Und wenn
blockierendes Verhalten unbedingt notwendig ist, kann man immer noch fcntl(2)
nutzen.
Signale¶
Die Umwandlung aller Signale in Laufzeitfehler klingt in der Tat sinnvoll, da richtige Signalbehandlung eine permanente Fehlerquelle darstellt und die Kosten angemessen erscheinen. Trotzdem sollte es optional sein (vielleicht als Opt-Out).
Executable Path¶
Hierbei scheint es sich für mich um die Lösung eines nicht vorhandenen Problems zu handeln.
Fazit¶
Wie ich eingangs sagte: Alles zuvor ausgeführte, alle verwendeten und widerlegten Argumente, all das hängt stark davon ab, wer man ist, in welchem Bereich man sich bewegt und welche Ziele man verfolgt. Ich will nicht sagen, dass all meine Punkte richtig sind, ich will nur darauf hinweisen, dass sie auch gültige Meinungen sind. Wenn man heutzutage über Software-Bloat spricht, gerät man schnell in eine Nische, aus der man nur schwer wieder heraus kommt. Man gehört nicht zu den coolen Kids. Aber das ist nicht gerechtfertigt: Nicht jeder Computer ist dein großer iMac, tatsächlich sind eher IoT-Geräte und Edge-Computing im Aufschwung. Software-Bloat ist ein echtes Problem für die Embedded-Entwicklung.
Der Hauptpunkt dieses Artikels ist der folgende: Wann auch immer du Komplexität einführst, tritt zurück und nimm dir einen Moment Zeit darüber nachzudenken, ob es wirklich notwendig ist, denk an deine Nutzer und an die Nebenwirkungen. Oft wird durch Software-Bloat nur die Rechnung für eine einfache Entwicklung an den Endnutzer weitergegeben und das ist nicht fair. Wenn dein Nutzer neue Hardware kaufen muss, nur, damit du das neueste Feature deiner Programmiersprache nutzen kannst, ist das gemein und destruktiv. Als Entwickler ist man Dienstleister, Softwareentwicklung ist kein Selbstzweck.
Abgelegt unter Opinion. Tags: developer expectations, efficiency, programming, user experience.
Willst du diesen Beitrag kommentieren? Schreib mir an blog [at] philipp-trommler [dot] me!