3 Punkte von GN⁺ 2024-01-05 | 1 Kommentare | Auf WhatsApp teilen
  • Go wurde am 10. November 2009 als Open Source veröffentlicht und entwickelte sich in den folgenden 14 Jahren mit dem Ziel weiter, nicht einfach eine neue Sprache zu sein, sondern Produktionssoftware leichter erstellen zu können
  • Eine formale Spezifikation, mehrere Compiler-Implementierungen, einfaches Cross-Compiling, die Kompatibilitätsgarantie seit Go 1.0, die Standardbibliothek und gofmt unterstützten die Verbreitung
  • Nebenläufigkeit als Mittel zur Strukturierung von Server-Software war eine Stärke, doch anfangs wurde der Unterschied zu Parallelität und die passenden Einsatzbereiche nicht ausreichend erklärt, was Verwirrung verursachte
  • Interfaces wurden zur Grundlage für Bibliotheken, Tests, Dependency-Management und ein auf Komposition ausgerichtetes Design; das Fehlen von Generics erschwerte anschließend über mehr als zehn Jahre polymorphe Entwürfe
  • Bei Paketverwaltung, Dokumentation und Zusammenarbeit mit der Community gab es viele Umwege, doch Go strebt eine Umgebung an, in der Code unabhängig von Autor und Zeitpunkt ähnlich aussieht und weiterhin gebaut werden kann

Das Ziel von Go: eine Art der Softwareentwicklung, nicht nur eine Sprache

  • Der 10. November 2023 markierte den 14. Jahrestag der Veröffentlichung von Go als Open-Source-Projekt
  • Bei der Veröffentlichung 2009 waren unter anderem Ken Thompson, Robert Griesemer, Russ Cox, Ian Taylor, Adam Langley und Jini Kim dabei
  • Das ursprüngliche Ziel von Go war nicht eine neue Programmiersprache an sich, sondern hochwertige Software auf bessere Weise zu schreiben
  • Der damalige Engpass lag weniger in Sprachfeatures selbst als in der Komplexität, moderne Server-Software bei Google zu entwickeln
    • Kontrolle von Abhängigkeiten
    • große Teams und sich verändernde Personalstrukturen
    • Wartbarkeit
    • effizientes Testen
    • Nutzung von Multicore-CPUs und Networking
  • Go definiert sich auch heute noch als Projekt, das den Aufbau von Produktionssoftware einfacher und produktiver machen soll

Die Community-Identität durch das Gopher-Maskottchen

  • Der Go Gopher war einer der frühen Erfolgsfaktoren von Go
  • Der von Renee French geschaffene Gopher wurde zu einem gemeinsamen Symbol der Go-Programmierer und zur Fahne der Community
  • Wie bei der Konferenz namens GopherCon prägte der Gopher eine Projektatmosphäre, in der technische Exzellenz und Spaß zusammenkommen
  • Die Entscheidung, ihn unter einer Creative-Commons-Attribution-Lizenz zu veröffentlichen, hatte Vor- und Nachteile
    • Sie förderte Remixes und Variationen und stärkte den Community-Geist
    • Die Attribution-Bedingung führte aber auch zu Debatten oder dazu, dass Arbeiten, die nicht von Renee French stammten, fälschlich ihr zugeschrieben wurden
  • Es bleibt eine schwierige Aufgabe, das Maskottchen so zu betreuen, dass sein ursprünglicher Geist erhalten bleibt

Technische Entscheidungen, die Go richtig getroffen hat

  • Zuerst wurde eine formale Spezifikation geschrieben, um das Verhalten von Compiler-Implementierungen festzulegen und mehreren Implementierungen zu ermöglichen, sich auf dasselbe Verhalten zu einigen
    • Dahinter stand die Einschätzung, dass ein einzelner Compiler keine Spezifikation sein kann
    • Der erste Entwurf der Spezifikation entstand im 18. Stock eines Gebäudes am Sydney Darling Harbour
  • Mehrere Compiler-Implementierungen halfen dabei, Sprache und Spezifikation zu verfeinern
    • Ian Taylor schrieb am 7. Juni 2008 eine E-Mail, dass er den Entwurf der Go-Spezifikation gelesen und ein gcc-Frontend gebaut habe
    • Auch später entstanden verschiedene kompatible Implementierungen
  • Das einfache Cross-Compiling ermöglichte Entwicklern, auf der gewünschten Plattform zu arbeiten und für die benötigte Plattform auszuliefern
  • Die Entscheidung, die Sprache mit Go 1.0 aufzuräumen und danach eine Kompatibilitätsgarantie einzuführen, hatte großen Einfluss auf die Verbreitung von Go
    • Starke Kompatibilität hat ihren Preis, reduziert aber die Sorge, dass ein neues Go-Release Projekte kaputtmacht
  • Die Standardbibliothek bot die meisten Funktionen, die zum Schreiben von Server-Code im 21. Jahrhundert nötig waren, und sorgte dafür, dass die Community mit demselben Werkzeugkasten arbeitete
    • Ein Grund für die Größe der Bibliothek war auch, dass es anfangs keinen anderen Ort gab, um Go-Code zu installieren
    • Sie half, eine Zersplitterung in Variantenbibliotheken zu verringern und die Community zu bündeln

Die Developer Experience durch Tools und gofmt

  • Go wurde als leicht zu parsende Sprache entworfen, was das Erstellen von Tools erleichterte
  • Anfangs ging man davon aus, dass eine IDE nötig sein würde, doch weil Tooling einfacher wurde, unterstützten IDEs mit der Zeit Go, und auch gopls entstand
  • Zusammen mit dem Compiler wurden Hilfswerkzeuge für automatisierte Tests, Coverage und Code-Vetting bereitgestellt
  • Der go-Befehl integrierte den gesamten Build-Prozess, und viele Projekte konnten damit den Großteil dessen erledigen, was zum Bauen und Warten von Go-Code nötig war
  • gofmt wurde zu einem Tool, das nicht nur Go, sondern die gesamte Programmier-Community beeinflusste
    • Robert Griesemer bestand von Anfang an darauf, gofmt zu bauen
    • Vor gofmt war die Qualität automatischer Formatter nicht hoch, weshalb sie kaum genutzt wurden
    • Die Zeit für Debatten über Leerzeichen und Zeilenumbrüche nahm ab
    • Als das Innere von gofmt zu einer Bibliothek wurde, konnten ASTs geparst, verändert und wieder in menschenlesbare Ausgabe umgewandelt werden
    • Dadurch wurden auch andere Tools wie simplifier, analyzer und Code-Coverage-Tools möglich

Nebenläufigkeit: mächtig, aber zu spät erklärt

  • Um 2002 herum vermied Google-Software Threads weitgehend; unter Verweis auf John Ousterhouts Position „threads were bad“ herrschte eine Atmosphäre, die ihre Nutzung unterband
  • Go ging von der Erfahrung aus, dass Nebenläufigkeit auf bessere Weise nutzbar ist als mit Low-Level-Paketen wie pthread
  • Go spielte eine wichtige Rolle dabei, dass die Programmierwelt Nebenläufigkeit als mächtiges Werkzeug akzeptierte
    • Besonders in Multicore- und Netzwerkumgebungen war der Effekt groß
    • Heute unterstützen die meisten Mainstream-Sprachen Nebenläufigkeit
  • Go verwendete keine Bezeichnungen wie coroutine, task oder thread, sondern prägte den neuen Begriff goroutine
  • async/await ist gegenüber pthread eine große Verbesserung, kann aber im Vergleich zu goroutine, channel und select zwar für Implementierer einfacher und kleiner sein, zugleich jedoch einen Teil der Komplexität zurück an die Programmierer geben
    • Als Beispiel dient Bob Nystroms Problem „What Color is Your Function?
    • Das CSP-Modell von Go passt gut zu prozeduralen Sprachen, erfordert für eine korrekte Implementierung aber Runtime-Komplexität
    • Da mehrere Nebenläufigkeitsimplementierungen in einer Umgebung problematisch sein können, nahm Go sie nicht in eine Bibliothek, sondern in die Sprache auf

Zwei Fehler bei Nebenläufigkeit

  • Das Go-Team gab anfangs nicht genügend Orientierung dazu, wo Nebenläufigkeit eingesetzt werden sollte
    • Der wichtigste Einsatzbereich war, Server-Software in zentralen Bibliotheken wie net/http einfacher zu machen
    • Es war kein Feature, das überall in jedem Programm verwendet werden sollte
  • Auch die späte Erklärung des Unterschieds zwischen Parallelität und Nebenläufigkeit war ein Problem
    • Viele Entwickler versuchten, Code mit goroutines zu parallelisieren und schneller zu machen, waren dann aber überrascht, dass er langsamer wurde
    • Nebenläufiger Code kann durch Parallelisierung nur schneller werden, wenn das Problem selbst parallel ist, etwa bei der Verarbeitung von HTTP-Anfragen
  • Um das zu korrigieren, wurde 2012 auf der Waza der Vortrag „Concurrency is not Parallelism“ gehalten
  • Diese Erklärung hätte früher kommen müssen
  • Trotzdem trug Go dazu bei, Nebenläufigkeit als Methode zur Strukturierung von Server-Software populär zu machen

Interfaces und ein auf Komposition ausgerichtetes Design

  • Interfaces sind neben Nebenläufigkeit eine Kernidee, die Go auszeichnet
  • Go-Interfaces kommen der Antwort von Go auf das ursprüngliche, verhaltensorientierte objektorientierte Design nahe
  • Der dynamische Ansatz, bei dem nicht vorab deklariert werden muss, welcher Typ welches Interface implementiert, wurde anfangs kritisiert, war aber wichtig für Programmierung im Go-Stil
  • Die Standardbibliothek, Tests und Dependency-Management hängen stark von der offenen Natur der Interfaces ab
  • sort in Go ist als Funktion implementiert, die auf einem Interface arbeitet
    • Das unterschied sich vom objektorientierten Stil, den viele gewohnt waren, war aber eine starke Idee
  • Kleine Interfaces wie Reader, Writer und das empty interface wurden zur Grundlage der Go-Bibliotheken
    • Diese drei berühmten Interfaces haben im Durchschnitt jeweils 2/3 Methoden
    • Kleine Methodenmengen wurden zu einem Go-typischen Idiom

Der lange Umweg um Generics

  • Das Go-Team hatte beobachtet, dass generische Programmierung dazu neigt, Typen vor Algorithmen zu denken
    • Es bevorzugte organisches Design gegenüber früher Abstraktion
    • Funktionen wurden wichtiger genommen als Container
  • Go nahm generische Container wie map, slice, array und channel in die Sprache auf, stellte diese Generizität den Nutzern aber nicht direkt bereit
  • Man ging davon aus, dass diese Typen für die meisten einfachen Programmieraufgaben ausreichen, doch das galt nicht immer, und diese Grenze war für einige Nutzer unbequem
  • Da Interfaces die Grundlage der Go-Programmierung waren, musste ein neues Polymorphie-Modell unbedingt gut zu Interfaces passen
    • Es gab mehrere Versuche und abgebrochene Implementierungen
    • Nach vielen Diskussionen wurde auch Hilfe von Typentheoretikern um Phil Wadler hinzugezogen
  • Am Ende entwarf Go Generics, indem Interfaces von „Methodenmengen“ zu „Typmengen“ verallgemeinert wurden
  • Der Begriff „generics“ stammt aus der datenstrukturorientierten Polymorphie und bezeichnet die von Go bereitgestellte parametric polymorphism nicht ganz exakt, in der Praxis setzte sich aber die Bezeichnung generics durch

Compiler: zuerst C, später Go

  • Dass die frühen Go-Compiler in C geschrieben waren, störte Teile der Sprach-Community
    • Es gab die Meinung, man solle Toolkits wie LLVM verwenden oder mit Go selbst self-hosting betreiben
  • Die Gründe, den ersten Compiler in C zu schreiben, waren pragmatisch
    • Um eine neue Sprache zu bootstrappen, braucht man eine bestehende Sprache
    • Ken Thompson hatte bereits einen C-Compiler geschrieben, und dessen interne Struktur konnte als Grundlage für den Go-Compiler dienen
    • Wenn man eine Sprache entwirft und gleichzeitig einen Compiler in dieser Sprache schreibt, kann sie sich zu stark in Richtung einer guten Compiler-Schreibsprache verschieben
  • Der frühe Compiler erzeugte gewöhnlichen Code und war intern nicht besonders sauber, war aber klein, vertraut und schnell änderbar
  • Das Hinzufügen automatisch wachsender segmented stacks zeigte die Vorteile dieser Entscheidung
    • Bei einem Toolkit wie LLVM hätte man auch ABI- und Garbage-Collector-Unterstützung ändern müssen, was die Integration erschwert hätte
  • Die ursprünglich aus der Plan-9-Compilerfamilie stammende Struktur passte auch gut zum Cross-Compiling
  • In Go 1.5 schrieb Russ Cox ein Tool, das den Compiler halbautomatisch von C nach Go übersetzte
    • Zu diesem Zeitpunkt war die Sprache fertig, und die Sorge vor einem compilerzentrierten Sprachdesign war verschwunden
    • Nach dem Wechsel zu Go konnten die Vorteile von Go wie Tests, Tools, automatische Umschreibungen und Performance-Analyse auch auf die Compilerentwicklung angewandt werden
  • Heute gibt es auch einen LLVM-basierten Go-Compiler und mehrere andere Compiler

Betrieb als Open-Source-Projekt und Community

  • Von Anfang an galt: Damit Go erfolgreich sein kann, muss es ein Open-Source-Projekt sein
  • Zugleich hielt man private Entwicklung für produktiver, bis die Kernideen und die Umsetzung des Verhaltens geordnet waren
    • Die ersten zwei Jahre vor der Veröffentlichung waren wichtig, um die Ziele zu klären
  • Nach der Umstellung auf Open Source war der Input der Community überwältigend; insbesondere Ian Taylor verbrachte viel Zeit damit, Fragen zu beantworten
  • Der Windows-Port wurde unter Anleitung von Alex Brainman vollständig von der Community umgesetzt
  • Das Go-Team brauchte lange, um die beste Art der Zusammenarbeit mit der Community zu verstehen
    • Man glaubte, gut zu kommunizieren, doch Missverständnisse und nicht übereinstimmende Erwartungen kosteten Zeit
  • Das Go-Projekt entschied sich nicht dafür, Code schnell anzunehmen und danach aufzuräumen, sondern vor dem Mergen hohe Codequalität sicherzustellen
    • verpflichtende Code-Reviews
    • akribische Aufmerksamkeit für Details
  • Dieser Ansatz belastet die Community stärker; wenn Mitwirkende seinen Wert nicht verstehen, können sie sich unerwünscht fühlen
  • Die Repository-Historie des Go-Projekts führte über SVN, Perforce, Mercurial und Git, doch Russ Cox erhielt die Historie, sodass auch im heutigen Git-Repository frühe SVN-Änderungen erhalten sind
  • Google unterstützt Go, bestimmt aber nicht die Agenda des Go-Teams
    • Die große interne Go-Codebasis bei Google wird verwendet, um Releases zu testen und zu validieren
    • Der Fluss geht vom öffentlichen Repository zu Google, nicht umgekehrt
    • Das Kernteam von Go wird von Google bezahlt, agiert aber unabhängig

Irrwege beim Paketmanagement

  • Das package-Design der Go-Sprache selbst war hervorragend, doch die Entwicklung des Paketmanagements verlief nicht gut
  • Pfade in import-Statements als gewöhnliche Strings anzugeben, war eine wichtige und flexible Entscheidung
  • Das Problem war, dass der Übergang von einem Zustand mit nur der Standardbibliothek hin zu einem Modell, bei dem Code aus dem Web geholt wird, holprig war
  • Das frühe Go-Team war an Googles Monorepo und das Bauen vom head gewohnt
    • Es hatte nicht genug Erfahrung mit Paketmanagern, die mehrere Versionen von Paketen verwalten
    • Die technische Komplexität der Auflösung von Dependency-Graphen wurde nicht von Anfang an angegangen
  • Die Arbeit an deps.dev wurde als eine Art Wiedergutmachung für dieses Problem behandelt
  • Der Versuch, das Dependency-Management gemeinsam mit der Community zu lösen, war gut gemeint, doch als das finale Design erschien, fühlten sich viele Community-Mitglieder ausgeschlossen
  • Diese Erfahrung wurde zu einer Lehre darüber, wie Community-Beteiligung tatsächlich ablaufen sollte, und seitdem hat sich vieles verbessert
  • Heute ist das Design stabil, technisch hervorragend und scheint für die meisten Nutzer gut zu funktionieren

Dokumentation und ausführbare Beispiele

  • Anfangs wurde viel Dokumentation geschrieben, sie entsprach aber nicht dem Niveau, das die Community erwartete
  • Am meisten fehlte, dass selbst die einfachsten Funktionen Anwendungsbeispiele brauchen
    • Zu zeigen, wie etwas verwendet wird, war wertvoller, als zu sagen, was es tut
  • Die heutige Dokumentation enthält viele Beispiele, die größtenteils von Open-Source-Mitwirkenden beigesteuert wurden
  • Go machte Beispiele schon früh im Web ausführbar
    • In einer Google-I/O-Präsentation 2012 konnten Code-Snippets zur Demonstration von Nebenläufigkeit im Browser ausgeführt werden
    • Andrew Gerrand schrieb die Webtechnologie, die das ermöglichte
    • Später wurde sie auch in Blogs und Online-Paketdokumentation ausgerollt
  • The Go playground wird als öffentliche Sandbox bereitgestellt, in der Menschen frei Code ausprobieren und entwickeln können

Wo Go nach 14 Jahren steht

  • Dank der Entscheidungen im Sprachdesign und Entwicklungsprozess wurde Go über eine einfache Programmiersprache hinaus zu einer Art, Software zu schreiben
  • Zu der Position, die Go erreicht hat, trugen folgende Faktoren bei
    • eine starke Standardbibliothek, die die meisten Grundfunktionen für Server-Code implementiert
    • Nebenläufigkeit als First-Class-Baustein der Sprache
    • ein Ansatz auf Basis von Komposition statt Vererbung
    • ein Packaging-Modell, das Dependency-Management klar macht
    • integrierte, schnelle Build- und Test-Tools
    • striktes und konsistentes Formatting
    • eine Haltung, die Lesbarkeit höher bewertet als Cleverness
    • die Kompatibilitätsgarantie
  • Die Go-Community besteht aus vielfältigen und aktiven Gophern und spielt eine Schlüsselrolle für den Erfolg des Projekts
  • Im Ergebnis sieht Go-Code ähnlich aus und verhält sich ähnlich, unabhängig davon, wer ihn geschrieben hat; es gibt vergleichsweise wenige Fraktionen, die unterschiedliche Teilmengen der Sprache verwenden, und es wird garantiert, dass Code auch im Lauf der Zeit weiter kompiliert und ausgeführt werden kann
  • Diese Eigenschaften können als seltenes Ergebnis unter großen Programmiersprachen gelten

1 Kommentare

 
GN⁺ 2024-01-05
Meinungen auf Hacker News
  • Es ist wirklich dankenswert, wenn sich zentrale Personen die Zeit nehmen, Rückblicke zu schreiben, und es hilft Menschen sehr, die jetzt tatsächlich Veränderungen bewirken wollen.
    Allerdings glaube ich nicht, dass Rob Pike klar genug gesagt hat, was Go richtig gemacht hat. Go hat nicht nur die Sprache selbst gut gestaltet, sondern auch die Kräfte, die auf das Projekt einwirkten: Der Zielbereich wurde auf Systemprogrammierung begrenzt, Sprache und Prinzipien wurden klar definiert, man hielt an einer qualitätsorientierten Haltung fest, Probleme vor der Veröffentlichung zu beheben, und während Sprache, Releases und Kernbotschaften streng kontrolliert wurden, ließ man in untergeordneten Bereichen Raum dafür, dass die Community führte.
    Ein unterschätzter Punkt ist, dass Google selbst sich nicht stark eingemischt hat. Vielleicht lag das daran, dass Go tatsächlich zu Googles Zielen beitrug und wichtig war. Ob das bei neuen Projekten heute noch möglich wäre, ist fraglich.
    Dart wurde außerhalb von Flutter kaum übernommen, was interessant ist, wenn man es mit der Realität vergleicht, dass es viel mehr Anwendungscode als Systemcode gibt. Go war eine Schlüsseltechnologie, um serverseitige Software aus der aufgeblähten Java-Umgebung in native Container zu verlagern, und wurde zur Grundlage eines Großteils der Webanwendungs-Infrastruktur der letzten zehn Jahre. Trotz dieses Wachstums blieb das Team klein und die zentralen Personen blieben dabei; ob das so bleiben wird, weiß ich nicht.

    • Go hat Garbage Collection, und wegen der schweren Runtime einschließlich Green Threads wird die Interoperabilität mit C umständlich und langsam.
      Daher ist es vom Design her nicht als Systemprogrammiersprache geeignet. Weil es sich anfangs selbst so bezeichnete, hielt sich ein seltsamer Mythos lange, aber diese Formulierung scheint vor einigen Jahren entfernt worden zu sein.
      Go ist weniger ein Konkurrent von C oder Rust als vielmehr einer aus der Java-Familie, und das zeigt sich an den tatsächlichen Einsatzbereichen. Wegen des eingeschränkten Typsystems und der hervorragenden Unterstützung für Nebenläufigkeit landet es allerdings häufig etwas tiefer im Stack.
    • Dem Punkt, dass die Community führt, kann ich schwer zustimmen. Es fühlt sich immer noch stark so an, als würde Google entscheiden.
      Besonders bei der Versionsverwaltung war das so. Anfangs erkannte Go das Problem selbst nicht einmal an, und als der Konsens in der Community größer wurde, vergaß man die bisherigen Diskussionen und entschied: Jetzt gehen wir mit modules weiter.
      Ich erinnere mich auch daran, dass man monotonic time nicht zu einer öffentlichen API machen wollte, bis Cloudflare wegen der Sommerzeit eine Störung hatte.
    • Ich war Java-Entwickler und mag Go inzwischen, bin mir aber nicht sicher, ob meine früheren Java-Kollegen Go mögen würden. Go war selbst dann, als es eine neue Sprache war, eine eher langweilige Sprache.
      In der Java-Welt hat man Spaß daran, neue Features zu verwenden, etwa großartige streams, und damit Codebasen zu refaktorisieren. In Go liegt der Spaß darin, die einfachste und gewöhnlichste Abstraktion zu finden.
    • Ich arbeite an Dart. Die Kritik, dass Dart außerhalb von Flutter nicht übernommen wird, halte ich nicht für so vernichtend.
      Jede Sprache braucht Bibliotheken und Frameworks, die zu bestimmten Bereichen passen. Flutter ist ein Framework, um Client-Apps in Dart zu schreiben; ohne Flutter müsste man, selbst wenn man die Sprache Dart noch so sehr mag, enorm viel Zeit darauf verwenden, überhaupt erst Pixel auf Android und iOS auf den Bildschirm zu zeichnen.
      Jeder, der Anwendungen schreibt, baut auf einem Stack aus Bibliotheken und Frameworks auf. Der Unterschied zwischen Go und Dart mit Flutter besteht ungefähr darin, dass die für Server nötigen Bereichsbibliotheken wie Networking, Serialisierung und Kryptografie bei Go in der Standardbibliothek enthalten sind.
      Dart hat grundlegende Elemente wie Collections und Asynchronität als eingebaute Bibliotheken, verlässt sich für bereichsspezifische Funktionen aber auf externe Pakete wie Flutter. Das liegt zu einem großen Teil daran, dass Dart schon sehr früh ein solides Paketmanagement hatte. Viele zentrale Bibliotheken, die vom Dart-Team geschrieben und gepflegt werden, werden ebenfalls nicht eingebaut, sondern über den Paketmanager verteilt, weil sie sich so viel leichter weiterentwickeln lassen.
      UI-Frameworks haben tendenziell eine kürzere Lebensdauer als Sprachen, daher halte ich es für besser, dass Flutter nicht in die Dart-Standardbibliothek eingebettet ist. Flutter ist hervorragend, aber es wäre nicht überraschend, wenn in 20 Jahren etwas Besseres auftaucht; wenn Flutter dann nicht eingebaut ist, können Dart-Nutzer leichter wechseln.
    • Bei den Entlassungen wurden zentrale FOSS-Teams gestrichen, und das Management mag echtes FOSS nicht. In Ruths Tabellen sieht es nämlich nicht gut aus.
      Pseudo-FOSS wie Android wird strategisch toleriert, aber abgesehen davon scheint die Haltung zu sein: Wenn es nicht zu Anzeigenklicks führt, ist es nutzlos.
  • 2012 habe ich als Python-Entwickler Go ausprobiert, weil ich Bitmanipulation für Modbus machen musste. Diesen Code habe ich nie ausgeliefert, aber ich war schockiert, wie einfach das Arbeiten mit Bits und Bytes einfach funktionierte.
    Zehn Jahre später, nach mehreren Phasen fast vollzeitnaher Arbeit mit Go, überrascht es mich immer noch, dass das meiste einfach funktioniert.
    Ich mag die Go-Sprache und die Go-Community. Ich bin Rob, Ian, Russ und den anderen Menschen, die Go entwickeln, dankbar für ihre Arbeit, und ich schätze auch, dass dieser Vortrag bzw. Text die „holprigen Abschnitte“ mit der Community offen anspricht.
    Aus der Perspektive von jemandem, der diese Zeit miterlebt hat, fällt es mir allerdings sehr schwer zu glauben, dass sie besonders beim Chaos um Paketmanagement nicht genau wussten, wie sie sich verhielten. Trotzdem stimmt auch die Einschätzung des Textes, dass man inzwischen bei einer guten Lösung angekommen ist.
    Ich hoffe, dass Go in den nächsten zehn Jahren weiter wächst, ebenso wie ähnliche, davon inspirierte Sprachen wie Zig und Deno, und zwar mit gesunden Communities.

    • Was ich am Go-Kernteam am meisten mag, ist, dass es zu allen möglichen Anfragen „Nein“ sagen konnte und bei Generics sagte: „Warten wir auf die richtige Implementierung.“
      Ich bin eher Computational Biologist als Programmierer und nutze Go daher unregelmäßig, aber wenn ich zu Go zurückkehre, kompiliert mein alter Code und die Sprache verhält sich so, wie ich es erwartet habe.
      Gleichzeitig finde ich es gut, dass Rob Pike zwar nicht alle glänzenden Features übernommen hat, aber die Fehler anerkennt, die beim Lernen von Community-Beteiligung passiert sind.
    • Ich komme von Python. Heutzutage verwende ich KI-Tools wie Bard und GPT-4, um alte Python-Utilities direkt nach Go zu konvertieren.
      Einige funktionierten sofort, bei einigen musste ich etwas nacharbeiten, weil die KI-Modelle API-Informationen in leicht veraltetem Zustand kannten.
      Trotzdem ist es erstaunlich, ein Programm per scp auf einen Server zu kopieren und es läuft einfach – verglichen mit dem Tanz, den man bei Python jedes Mal aufführen musste: „Welches venv-System ist gerade angesagt?“
    • Zig und Deno scheinen mir keine besonders guten Vergleichsobjekte zu Golang zu sein. Eher sind Elixir und ReasonML ähnlicher, und wenn man innerhalb von Google schaut, auch Dart.
  • Ein großartiger Text, wirkt aber auch ziemlich selbstbeweihräuchernd. Ich hätte erwartet, dass die tieferliegenden Probleme der Sprache anerkannt werden; vermutlich läuft es darauf hinaus, dass die Sprache für sie perfekt ist und ich das Problem bin
    Deshalb fällt es mir schwer, Go zu empfehlen. Es gibt Probleme mit dem Typsystem, der Fehlerbehandlung, unsicherer Nebenläufigkeit, einer übermäßig simplen Syntax, nil, Standard-Nullwerten und vielen vernachlässigten Mainstream-Paketen
    Derzeit nutze ich Rust als Hauptsprache. Das Ökosystem ist aktiv und hat in vielerlei Hinsicht eine Vision, die Go fehlt
    Schärfer formuliert: Go hatte wohl seine Zeit, als es als Backend-Sprache mit PHP konkurrierte

    • Das trägt meiner Meinung nach nicht viel zur Diskussion bei. Es liest sich ohne konkrete Punkte wie Rust-Advocacy
      Der erste Absatz ist „ich mag es nicht“, der zweite Sarkasmus, der dritte auch „ich mag es nicht“, der vierte „Rust ist besser“, der fünfte wieder Sarkasmus
    • Die Wissenschaftler aus der Plan-9-Ecke mögen ihre eigene Marke viel zu sehr. Ich habe 2012 angefangen, Go zu verwenden, habe aber aufgegeben, nachdem deps.dev abgeschafft wurde
      Ein paar Jahre später wollte ich in einer Firma Go im Team einführen, um Arbeit fertigzubekommen; ein anderer Engineer nahm sich Zeit, die Sprache anzuschauen, listete auf, warum sie schlecht sei, und er hatte recht
      Der Kern war: Sie ist zwar einfach, aber Kleinigkeiten wie Fehlerbehandlung und unbenutzte Imports machen die Nutzung schmerzhaft. Persönlich mochte ich die Fehlerbehandlung, aber das Typsystem mochte ich nicht
    • Ich habe Go benutzt, bevor ich Rust gelernt habe, und der Unterschied zwischen beiden hat mein Denken stark verändert
      Da ich früher OCaml verwendet hatte, kannte ich den Wert von Option- und Result-Typen gegenüber try/catch oder nil, nutzte aber Go, weil es einfach war. Diese Einfachheit bringt jedoch langfristige Wartungskosten mit sich. Es ist besser, es von Anfang an richtig zu machen und später nicht mit den Schwierigkeiten konfrontiert zu werden
      Ich will es nicht leichtfertig sagen, aber ich habe oft die Beschreibung gehört, Go sei eine Sprache, die die Fortschritte der Programmiersprachen der letzten 50 Jahre genommen und weggeworfen hat
    • Bei der Einschätzung zu Programmiersprachen stimme ich zu, aber zur Verteidigung von Go: Go ist eher eine Sprache von Leuten und für Leute, die sich nicht für Programmiersprachen an sich interessieren
      Das ist nicht böse gemeint. Rob Pike kümmert es nicht, dass Nebenläufigkeit unsicher ist oder nil der Milliarden-Dollar-Fehler ist, und den meisten Go-Nutzern geht es genauso
      Heißt das, dass Go eine gute Sprache ist? Nein. Aber das ist nicht der Punkt. Go ist eine bequeme und hinreichend ordentliche Sprache, kombiniert mit überzeugenden Tools, die sie leicht nutzbar machen. Sie ist so etwas wie die Crocs unter den Programmiersprachen
    • Ich habe Rust gelernt, aber wegen der Rust-Community ist es unmöglich, die Sprache zu mögen. Auch nach Jahren hat sich daran nichts geändert; wenn überhaupt, ist es schlechter geworden, nicht besser
      Wenn hohe Integrität und Sicherheit nötig sind, würde ich Ada oder Ada/SPARK mit formaler Verifikation verwenden. Für alles andere ist Go wesentlich produktiver als Rust
      Persönlich sehe ich Rust wie C++ als Paradebeispiel für Overengineering
  • Es mag verbittert klingen, aber vor 10 Jahren wurde ich auf go-nuts verspottet und bekam auch von Rob Pike eine herablassende Antwort. Ich hatte es gewagt zu sagen, dass die Art und Weise, wie go get und Modul-Imports über das Netzwerk funktionieren, in der Einsteiger-Dokumentation breit beworben und in der gesamten Community so verwendet werden, letztlich schädlich und kurzsichtig sei

    • Das Paketsystem von Go, insbesondere die Zeit vor modules, wirkt wie ein Hack, bei dem externes Verhalten auf ein stärker eingeschränktes System aufgepfropft wurde, das nur für die Nutzung innerhalb des Google-Monorepos gedacht war
      Der seltsame globale Namespace-Baum ergibt dort Sinn, und die Betonung auf eingecheckten generierten Artefakten ist ebenfalls nachvollziehbar, wenn man bedenkt, dass Google in seinem Monorepo sogar Build-Artefakte enthält
    • Interessant ist trotzdem, dass das, was Go hervorgebracht hat, besser ist als das anderer Sprachen
      Natürlich gibt es das v2-Problem und das ewige v0-Problem. Aber es ist immer noch besser, als npm zu benutzen oder sich mit irgendetwas in Python herumzuschlagen, bei dem man ein Kreuzzeichen machen möchte
    • Rob Pike hat damit der Welt gezeigt, wie unwissend er gegenüber der seit 2000 entdeckten Programmiersprachentheorie ist
  • Dieser Text wirkt wie eine persönlichere Version des Artikels [1] in der ACM. Beide Texte erkennen an, dass sie aus Sicht der Sprachspezifikation keine großartige neue Programmiersprache geschaffen haben, sondern das Drumherum um die Programmiersprache sehr gut gebaut haben, und dass das vielleicht sogar wichtiger ist
    Im eingereichten Text heißt es, man habe den Ansatz zur Nutzung von Interfaces und den Ansatz für Nebenläufigkeit erfunden. Aber Goroutines sind dasselbe wie Haskell-Threads, und Interfaces ähneln, sobald sie wie heute generische Argumente unterstützen, sehr stark Haskell-Typeclasses. Da Haskell Go vorausging, ist es interessant zu sehen, wie imperative Programmierer unabhängig die Stärke funktionaler Programmierideen entdecken
    Eine echte Sprachinnovation von Go ist, dass die Implementierung eines Interfaces nicht deklarieren muss, welches Interface sie implementiert. Aus Sicherheitsperspektive ist das furchtbar, in der Praxis aber kaum ein Problem und beseitigt die schrecklichen zirkulären Abhängigkeitsprobleme, die man in Haskell und Rust erlebt
    [1] https://cacm.acm.org/magazines/2022/5/260357-the-go-programm...

    • Ich glaube, es wird unterschätzt, wie wichtig die Tatsache, dass Interfaces strukturelle Typen sind, für die gesamte Wirkung dieses Features auf die Sprache und darüber hinaus auf Idiome und Ökosystem war
      Wenn Typen im Voraus hätten deklarieren müssen, welche Interfaces sie implementieren, wäre Go eine völlig andere Sprache gewesen. Das war einer der wichtigsten Unterschiede von Go. Zumindest bis TypeScript aus anderen Gründen Interfaces mit struktureller Typisierung aufbrachte
    • Strukturelle Typisierung ist bereits ein mehr als 25 Jahre altes Konzept und wurde auch schon in Sprachen wie OCaml und Scala verwendet
    • Ich sehe nicht, inwiefern das innovativ sein soll. Alle mir einfallenden Mainstream-Sprachen vor Go funktionieren so
      Kann man auch nur eine Programmiersprache nennen, die uns interessieren sollte und die erzwingt, dass man bei der Definition eines Interfaces auch zwingend dessen Implementierung bereitstellt?
  • Gos Kryptografie-Probleme scheinen bisher noch nicht viel erwähnt worden zu sein. Nachdem OpenSSH SHA1 abgeschafft hatte, brauchte das Go-Team ein ganzes Jahr, um x/crypto/ssh SHA2-Unterstützung hinzuzufügen [1]. Gitea war ein prominentes Opfer [2]
    Außerdem schafft es kein Vertrauen, wenn ein Kryptografie-Maintainer GnuPG angreift [3] und versucht, Dan Bernsteins Glaubwürdigkeit zu untergraben [4]
    [1]: https://github.com/golang/go/issues/49269
    [2]: https://github.com/go-gitea/gitea/issues/17798
    [3]: https://twitter.com/FiloSottile/status/1127643698676797441
    [4]: https://twitter.com/FiloSottile/status/1555669786826244096

    • Das Problem ist, dass die x/-Pakete ziemlich oft vernachlässigt werden und einem Entwicklungsmodell folgen, das es neuen Beitragenden schwer macht, Patches erfolgreich einzureichen
      Dagegen sehe ich die crypto-Pakete der Standardbibliothek als einen der großen Erfolge von Go. OpenSSL war lange Zeit eine Katastrophe, und Go, insbesondere agl, hat eine sehr weit verbreitete alternative Implementierung gebaut und ausgeliefert, mit im Schnitt hoher Qualität. Das gilt besonders im Hinblick darauf, dass es in der öffentlichen API deutlich weniger Fallstricke gibt, mit denen man sich selbst ins Bein schießen kann
  • Dieser Artikel nennt gofmt als etwas, das „richtig gemacht“ wurde, und das verdient besondere Hervorhebung
    Für viele Sprachdesigner und Befürworter mag das nebensächlich wirken, aber es bringt enormen Wert, weil es vom ersten Tag des Go-Projekts an eine der üblichen Bikeshedding-Debatten vollständig eliminiert
    Ich habe gesehen, dass mehrere neuere Sprachen das bewusst kopieren oder es übernehmen, weil sie denken: Wenn Go das hat, sollten wir es auch bauen
    Ich habe auch ziemlich seltsame Formatierungsregeln gesehen, aber kaum Regeln, an die man sich nicht gewöhnen könnte. Umgekehrt war Code in Codebasen ohne erzwungene Formatierungsregeln dadurch oft schwerer zu verstehen

    • Ich neige dazu, an Codeformatierung herumzufeilen, und finde es gut, dass go fmt das größtenteils sinnlos macht
  • Ich stimme der Aussage zu, dass „die Existenz einer soliden, gut gemachten Library, die das meiste abdeckt, was man zum Schreiben von Server-Code im 21. Jahrhundert braucht, ein großer Vorteil war“
    Go wurde von Google finanziert, weil Google ein Problem hatte. Man musste viel serverseitigen Application-Code schreiben, Python war zu langsam und C++ zu fragil. Go passte sehr gut in diese Lücke
    Ein großer Bonus war, dass Google-Leute die Libraries schrieben, die man für solche Aufgaben brauchte, und sie auch intern nutzten. Wenn man die Libraries verwendete, war daher auch viel Code auf den Fehlerpfaden tatsächlich schon ausgeführt worden
    Technisch habe ich Einwände gegen die Sprache. Besonders gegen die frühe Betonung, für alles Queues zu verwenden, sogar Mutexes mit Queues zu bauen. Aber die green threads für den Use Case haben sie richtig getroffen. Das Problem der Funktionsfarben ist real, und die obskuren Tricks, mit denen Rust einen dazu bringt, Async und Threads zusammen zu verwenden, sind einfach zu viel. Go gibt etwas Performance auf und gewinnt dafür viel Einfachheit
    Interessant ist auch die frühere Abneigung gegen Threads. Ich habe auf einem UNIVAC-Mainframe angefangen; dort gab es seit 1967 Threads, damals „activities“ genannt. 1972 gab es nicht nur User-Space-Threads, sie liefen auch auf symmetrischen Multiprozessoren. Es gab asynchrone Ein-/Ausgabe und User-Space-Completion-Funktionen, eingebaute Instruktionen für Locks sowie ein intern threaded Betriebssystem
    Threads und Multiprozessoren galten als normal, und beim Wechsel zu UNIX hatte man das Gefühl, sie zu verlieren. UNIX/Linux brauchte Jahrzehnte, um in diesem Bereich aufzuholen, und mehrere Generationen von Programmierern wussten nicht, wie man Shared-Memory-Multiprozessoren nutzt
    Dijkstras frühe Concurrency-Theorie wurde mit anderen Begriffen und oft schlechteren Designs neu erfunden. Die Go-Leute verstanden Dijkstras bounded buffer und warum bounded buffer der Länge 0 und 1 nützlich sind. Das wiederzusehen, war gut. Mit den richtigen primitiven Operationen ist Concurrency nicht so schwierig. Wenn man versucht, es wie im klassischen pthreads-Denken zu lösen, indem man überall Locks verteilt, funktioniert es nicht richtig. Dass die CPU-Scheduler in der frühen UNIX/Linux-Zeit wirklich miserabel waren und das Aufheben von Thread-Blockierungen schrecklich funktionierte, half auch nicht

    • Zufällige HN-Kommentatoren sagen oft Dinge wie „Go wurde finanziert, weil Google ein Problem hatte“, aber das liegt daran, dass sie nicht wirklich wissen, wie Google funktioniert oder wie groß das Unternehmen ist
      Go entstand, weil Rob Pike ein Problem hatte. Er mochte C++, Java und Python nicht, und weil er Rob Pike war, erlaubte Google ihm, ein paar Jahre lang an einer neuen Sprache zu arbeiten. Es war nicht so, dass Larry, Sergey, Sundar oder Eric zu Rob gegangen wären und ihn gebeten hätten, etwas zu tun
      Im Großen und Ganzen wurde Go finanziert, weil Rob Pike um Unterstützung bat, Google Rob Pike halten wollte und man davon ausging, dass er mit sehr hoher Wahrscheinlichkeit etwas Interessantes machen würde. Bei Google Engineering bedeutet Firmengeld im Grunde, Menschen zu erlauben, an etwas zu arbeiten
      Weil Rob Pike ein hervorragender Ingenieur war, wurde Go in einigen Aspekten ziemlich gut, und es passte gut zu seinem anfänglichen Problem, dass man für Google-Betriebsanwendungen C++, Python oder Java verwenden musste. Einige Leute innerhalb von Google stimmten dem zu und begannen, Go zu nutzen, und einflussreiche darunter „ermutigten“ andere, Go statt Python zu verwenden
    • Go ist nicht einfach ein Google-Projekt, sondern im Grunde die Compiler-Philosophie von Plan 9 für Unix/Linux plus CSP
    • Das passt nicht zu meiner Erinnerung an die Zeit, in der ich bei Google gearbeitet habe. Wie das Meme „Go war für Systemprogrammierung gedacht“ ist es eher eine Überzeugung, die bei genauerem Hinsehen nicht wirklich Sinn ergibt
      2009 wurde Python bei Google, abgesehen vom Fall der YouTube-Übernahme, kaum für Server jenseits einiger interner Utilities verwendet, daher kann man diese Idee sofort verwerfen. Der größte von Google selbst geschriebene Python-Server war Mondrian, ein Code-Review-Tool von Guido van Rossum, das später durch das in Java geschriebene Critique ersetzt wurde
      Damals hatte Google eine ziemlich strikte Drei-Sprachen-Policy, um Programmiersprachenkämpfe zu vermeiden. Man konnte C++, Java und Python verwenden; Python wurde für Skripte genutzt, C++ für Infrastruktur und Java für Webserver
      Es gab auch Überschneidungen. Java wurde später als C++ eingeführt, und einige Webserver blieben in C++, weil sie schwer neu zu schreiben waren. Es gab auch Versuche, sie schrittweise nach Java zu migrieren, aber das setzte sich nicht großflächig durch. Einige Infrastruktur-Server wie Megastore wurden in Java geschrieben, und Kern-Libraries waren meist C++ mit JNI- und Python-Bindings
      Es gab auch Schlupflöcher in der Policy, sodass einige Teams eigene Sprachen für interne Infrastruktur bauten, und in der tatsächlichen Codebase wurden auch Custom-Konfigurationssprachen sowie das von Rob Pike geschaffene Sawzall verwendet. Die Syntax ähnelte ein wenig Go
      Insgesamt hatten die Leute viel Entscheidungsfreiheit; neue Webserver wurden meist in Java geschrieben, neue Datenbank-Engines und Ähnliches meist in C++
      Async-Code zu schreiben war definitiv schwierig, und es stimmt, dass Go diesen Teil richtig gelöst hat. Aber Go war kein Projekt, das vom Topmanagement gestartet wurde, um Googles Probleme zu lösen. Nicht weil das Management solche Projekte gescheut hätte, sondern weil es enorm viele interne Infrastrukturprojekte gab, die von den zuständigen Teams geschaffen und besetzt wurden, um Entwicklerprobleme direkt zu lösen. Die damals vorangetriebenen großen Networking-Upgrades sind ein Beispiel dafür. Solche Projekte wurden vorher angekündigt
      Ich erinnere mich nicht, dass es über diesen Zustand große Beschwerden gab. Build-Zeiten waren ein Problem, bis Bazel/Blaze zusammen mit Remote-Build-Clustern funktionierte; danach wurde alles remote gecacht, sodass selbst riesige Codebases in wenigen Sekunden kompiliert werden konnten. Die Geschwindigkeit des lokalen Compilers wurde fast irrelevant, und auch javac war sehr schnell
      Die Ankündigung von Go war ziemlich überraschend. Wenn es wirklich entwickelt worden wäre, um Googles Probleme zu lösen, wäre der übliche Google-Weg gewesen, es zuerst intern herauszubringen, es einige Jahre mit internen Nutzern weiterzuentwickeln und es erst danach öffentlich zu machen. Aber Go wurde zuerst öffentlich gemacht. Die Leute in meinem Umfeld fragten sich, wofür das gedacht war – eine Reaktion, die man kaum erwarten würde, wenn es wirklich von interner Nachfrage getrieben gewesen wäre
  • Interessant ist der Abschnitt über die Entscheidung, Ken Thompsons C-Compiler statt LLVM zu verwenden. Die Leute beschwerten sich, und gerade in frühen Versionen führte das auch zu weniger optimiertem generiertem Code.
    Im Gegenzug konnte man Split Stacks schnell umsetzen. Hätte man das in LLVM implementieren und an das LLVM-ABI anpassen müssen, hätte man es vielleicht gar nicht gemacht.
    Im Artikel wird das nicht als einziger Vorteil dieser Entscheidung genannt, sondern als ein Beispiel für einen Vorteil.

    • Die Aussagen zu LLVM im Interview sind nicht korrekt. Für Rust wurden Split Stacks in LLVM implementiert, und das war tatsächlich ziemlich einfach.
      In X86FrameLowering gab es bereits Unterstützung dafür, und sie existierte auch schon, als Go veröffentlicht wurde. Wenn Stack Splitting aktiviert ist, gibt LLVM im Funktionsprolog eine Prüfung aus und ruft bei Bedarf __morestack auf, um mehr Stack zuzuweisen. Auch das Windows-MSVC-ABI benötigt für die Unterstützung von _chkstk sehr ähnlichen Code, daher fügte sich die Unterstützung von __morestack ganz natürlich ein.
      GDB dazu zu bringen, Split Stacks zu verstehen, war im Gegenteil schwieriger als jeder Teil der Compiler-Implementierung. Das ist unabhängig vom Backend.
      Vermutlich verwechselt der Autor das mit verschiebbaren Stacks. Damals war dafür eine präzise Garbage Collection nötig, was die Implementierung schwierig machte; inzwischen hat Azul das jedoch in LLVM implementiert. Der damals einfachste Weg, präzise Garbage Collection umzusetzen, bestand darin, über Funktionsaufrufe hinweg alle Register zu spillen. Das hätte mehr Implementierungsaufwand bedeutet, wäre aber nicht übermäßig gewesen. Da der Plan-9-Compiler das offenbar ohnehin so macht, wäre es gegenüber 6g/8g wohl auch keine Performance-Regression gewesen. Jedenfalls hat Azuls Garbage-Collection-Unterstützung inzwischen eine saubere Implementierung, bei der Roots in Registern gespeichert werden können.
    • Ist Go am Ende nicht von Split Stacks weggegangen? Das war in Go 1.3, das 2014 veröffentlicht wurde. Grund war das „Hot Spot“-Problem [1].
      Dass die Fähigkeit zum Experimentieren wertvoll war, stimmt also, aber dieses konkrete Beispiel ist kein perfekter Fall.
      [1]: https://go.dev/doc/go1.3#:~:text=Go%201.3%20has%20changed%20...
  • Während ich eine statisch typisierte Hobby-Sprache entwerfe, versuche ich mich an etwas, das Interfaces und Generics zusammenbringt, und ich habe selbst gemerkt, dass Rob recht hat. Beides gut ineinandergreifen zu lassen, ist wirklich schwierig.
    Trotzdem halte ich es für lohnenswert. Persönlich fände ich es wenig befriedigend, eine statisch typisierte Sprache zu verwenden, in der Nutzer keine eigenen generischen Typen definieren können.
    Früher habe ich in einer Umgebung programmiert, in der BASIC zwar GOSUB für Subroutinen hatte, es aber keine Möglichkeit gab, Subroutinen mit Parametern zu schreiben. Diese Erfahrung möchte ich nicht auf Ebene des Typsystems wiederholen.
    Daher verstehe ich, dass das Go-Team lange gebraucht hat, um ein gutes Design zu finden. Eine gute Sprache zu entwerfen ist schwer; eine gute Sprache mit Typsystem zu entwerfen ist zehnmal schwerer; und ein Typsystem mit Generics zu entwerfen ist noch einmal zehnmal schwerer.

    • Dem stimme ich nicht zu. Der schwierigste Teil bei der Implementierung von Generics entsteht, wenn man Implementierungsvererbung unterstützt, und Go unterstützt das nicht.
      Go hatte bei der Implementierung von Generics also den einfachsten Teil vor sich. Der Grund, warum man es nicht tat, war nicht technischer Natur. Er war ideologisch und beruhte schlicht auf Unwissenheit, weil die meisten Go-Designer die Entwicklungen in der Programmiersprachentheorie seit Ende der 90er nicht mehr verfolgt hatten.
    • Die ML-Familie und funktionale Sprachen scheinen dieses Problem nicht zu haben, und viele von ihnen besitzen deutlich ausgefeiltere und mächtigere Typsysteme als Go.
    • Scala wurde vor Golang veröffentlicht und war zu der Zeit, als Golang herauskam, bereits populär. Dahinter standen auch nicht Ressourcen im Umfang von GOOG.