Lügen, die wir uns selbst erzählen, um Golang weiter zu verwenden (2022)
(fasterthanli.me)- Die bei Kritik an Go wiederkehrenden Gegenargumente „große Unternehmen nutzen es auch“, „es hat auch gute Seiten“ und „Korrektheit ist teuer“ können verschleiern, dass sich Mängel im Sprachdesign langfristig als Kosten aufsummieren
- Go hat Stärken bei asynchroner Runtime, GC sowie Tools für Paketverwaltung, Refactoring und Cross-Compilation, aber man muss auch eine eigene Toolchain, ein Build-System, Calling Conventions, einen einzigen GC und das Ökosystem mit übernehmen
- Weil sum types fehlen, Mutability nur unzureichend kontrollierbar ist und zero values sowie
nilslices, maps und channels jeweils unterschiedlich funktionieren, kann der Compiler viele Invarianten nicht absichern - Ohne cgo ist die Integration mit anderen Sprachen und Tools schwierig; mit cgo entstehen Belastungen durch GC- und Pointer-Regeln, die Einbindung der Runtime und große statische Libraries, sodass die Netzwerkgrenze der realistischste Integrationspunkt wird
- Die Erwartung „wir bauen nur den Prototyp in Go und schreiben später neu“ kann in der Praxis wegen vermiedener Rewrites, Kopplung an bestehenden Go-Code und Aufwand für Linter, Dokumentation und manuelle Prüfung die Betriebskosten erhöhen
Wiederkehrende Verteidigungsmuster bei Kritik an Go
- Seit dem Artikel von 2020 I want off Mr Golang’s Wild Ride wiederholen sich die Reaktionen auf Kritik an Go immer wieder
- Die Reaktion, dass der Windows-Fall kein Bereich sei, in dem Go gut ist
- Die Reaktion, dass die guten Seiten von Go nicht erwähnt würden
- Die Reaktion, dass die von Go gewählten Kompromisse nicht verstanden würden
- Die Reaktion, dass Go nicht so schlecht sein könne, weil große Unternehmen es verwenden
- Die Reaktion, dass Diskussionen über Korrektheit sinnlos seien, weil es teuer sei, ein Problem „richtig“ zu modellieren
- Die Reaktion, dass Kritik an Go ungültig sei, weil auch Rust Nachteile habe
- Entscheidend ist nicht, ob Go Vorteile hat, sondern ob die Verteidigungslogik, die zur weiteren Nutzung von Go immer wieder bemüht wird, die tatsächlichen Kosten verdeckt
Zuerst die Qualifikation der Kritiker angreifen
- Wenn Feedback kommt, das man nicht hören möchte, muss man sich nicht mit dem Inhalt befassen, wenn man die sprechende Person für unfähig hält oder ihr versteckte Motive unterstellt
- Um Probleme zu erkennen, braucht man nicht zwingend hohe Expertise; Senior-Entwickler können Unbequemlichkeiten, an die sie sich gewöhnt haben, sogar eher übersehen
- Junior-Entwickler sind an merkwürdige Dinge noch nicht gewöhnt und können deshalb schneller Unbehagen spüren und Fragen stellen
- Der Wert von Feedback sollte danach geprüft werden, ob tatsächlich ein Problem besteht, nicht danach, wer es äußert
Die Grenzen des Arguments „andere Unternehmen nutzen es auch“
- Bei Technologieentscheidungen ist es zur Entdeckung nützlich zu schauen, was andere Unternehmen verwenden, als Begründung für eine Einführung reicht es aber nicht aus
- Technikblogs von Unternehmen können die Kosten einer Einführung meist nicht vollständig offenlegen
- Sie werden häufig geschrieben, nachdem die Entscheidung bereits gefallen ist
- Sie dienen dem Zweck, das Unternehmensimage zu verbessern und beim Recruiting zu helfen
- Scharfe Technologiekritik kommt eher von Einzelpersonen als von Unternehmen
- Tailscales Nutzung von Go ist eher ein Beispiel für ein Team von Go-Experten, das die Kosten kennt und tragen kann
- netaddr.IP: a new IP address type for Go zeigt die Einschränkungen von Go beim Darstellen einer IPv4- oder IPv6-Adresse
- Hey linker, can you spare a meg? behandelt ein Problem, das tiefes Wissen über den Go-Linker erfordert
- Die in
netaddr.IPsichtbaren Schmerzen hängen mit mehreren Einschränkungen von Go zusammen- Da Go keine sum types hat, ist es umständlich, einen Typ „IPv4 oder IPv6“ auszudrücken
- Slices werden als allgemeine Datenstruktur verwendet, die auf 64-Bit-Maschinen 24 Byte kostet
- Da es kein Operator-Overloading gibt, entstehen Unterscheidungen wie
a == bunda.equals(b) - Da unveränderliche Daten nicht unterstützt werden, muss man Kopien übergeben und interne Änderungen beachten
- Da es schwierig ist, ein opakes
newtypezu erstellen, sind Umwege über separate Packages und Interfaces nötig
- Teams wie Tailscale können sich intensiv mit Go-Interna und Grenzfällen befassen, aber nicht jede Organisation verfügt wie Google oder Tailscale über Go-Experten und die entsprechenden Engineering-Kosten
Worin Go tatsächlich gut ist
- Go bietet eine ziemlich gute asynchrone Runtime, meinungsstarke Defaults, einen modernen GC mit zwei Stellschrauben und eine gute Toolchain
- Weil die asynchrone Runtime im Zentrum der Sprache steht, gibt es Tools, um die Rust-Entwickler Go beneiden könnten
- Backtraces aller laufenden goroutines lassen sich leicht dumpen
- Deadlocks können erkannt werden
- Es gibt einen eigenen Profiler
- Man muss sich weniger mit function coloring befassen
- Paketverwaltung, Refactoring- und Cross-Compilation-Tools wirken im Vergleich zu
pkg-config, autotools und CMake im C-Ökosystem anfangs wie eine Verbesserung - Allerdings kann man nicht nur die bequeme Runtime von Go herauspicken
- Eigene Toolchain
- Build-System
- Calling Conventions
- Einzelner GC
- Enthaltene Standardkomponenten und Ökosystem
- Sogar das zufällig entstandene Sprachdesign übernimmt man mit
Lücken im Typsystem und Sprachdesign
- Go stellt eine Runtime für TCP, HTTP, TLS, HTTP/2, DNS und die Implementierung von Webservices in den Mittelpunkt und nimmt eine Form an, die Google-Entwicklern vertraut ist, die Java, C, C++ oder Python gelernt haben
- Wie C behandelt Go Error Handling selbst nicht besonders umfassend; viele mutable Zustände müssen Entwickler direkt mit
ifundelseverwalten - Wie Java verwischt Go die Unterscheidung zwischen Werten und Referenzen, sodass am Aufrufort schwer zu erkennen ist, ob ein Wert verändert wird
- Je nachdem, ob der Method receiver
Aoder*Aist, kann die lokale Variableainmainverändert werden oder nicht
- Je nachdem, ob der Method receiver
- Mutability und Immutability lassen sich schwer eindeutig über Typen beschränken; beim Übergeben von Referenzen besteht das Risiko, dass interne Werte verändert oder lange gehalten und dadurch nicht freigegeben werden
- Go verhindert die folgenden Fehlerklassen nicht ausreichend
- Ein mutex wird versehentlich kopiert und dadurch wirkungslos gemacht
- Struct-Felder werden nicht initialisiert, sodass der zero value eingetragen wird
- Falsche
nil-, zero- oder empty-string-Werte verbreiten sich tief im System
zero value und die Grenzen von „man muss nur aufpassen“
- Selbst wenn durch ein neues Feld in einem struct die Eingabeform einer Funktion faktisch geändert wird, verhindert der Compiler nicht, dass dieses neue Feld in einem composite literal ausgelassen wird
- Im Beispiel wurde
Paramsumb int32ergänzt, aber wenn die Aufrufstelle nura: 47übergibt, läuft der Code mitb=0 - Fügt man dagegen direkt ein Funktionsargument hinzu, führt eine zu geringe Argumentzahl zu einem Compilerfehler
- Im Beispiel wurde
- Einer der Grundsätze von Go ist, dass der zero value nützlich ist, seine tatsächliche Bedeutung unterscheidet sich aber je nach Typ
- Ruft man
lenauf einemnilslice auf, ist das Ergebnis 0, sodass es sich wie ein leerer slice verhält - Fügt man in eine
nilmap einen Wert ein, entstehtpanic: assignment to entry in nil map
- Ruft man
- Auch für channels gibt es eigene Regeln
- Ein send auf einen
nilchannel blockiert für immer - Ein receive von einem
nilchannel blockiert für immer - Ein send auf einen geschlossenen channel löst eine panic aus
- Ein receive von einem geschlossenen channel liefert sofort den zero value zurück
- Ein send auf einen
- Da Go keine sum types hat, ist es schwierig, Eingaben, bei denen „nur eine der bekannten Optionen erlaubt“ ist, als Typ auszudrücken; es ähnelt eher dem C-Ansatz „pass einfach auf“
- Die Haltung, dass man einzelne Probleme nicht verhindern müsse, weil man ohnehin nicht alle Probleme verhindern könne, ist eine falsche Dichotomie und verschiebt selbst Probleme, bei denen ein Typsystem helfen könnte, in manuelle Vorsicht
Der Vergleich mit Rust ist keine Debatte über den „perfekten Ersatz“
- Es geht nicht darum, dass Rust perfekt wäre; Go und Rust lassen sich als Erfolgsgeschichten unterschiedlicher Art betrachten
- Der Erfolg von Go hängt stark von den enthaltenen Komponenten und meinungsstarken Defaults ab
- Der Erfolg von Rust hängt stark von schrittweiser Einführung und einfacher Integration mit anderen Sprachen ab
- Reale Rust-Einführungen ähneln eher einer schrittweisen Portierung als einem vollständigen Austausch
- Firefox enthält mehrere zentrale Rust-Komponenten innerhalb einer großen C++-Codebasis
- Android hat den Bluetooth stack in Rust neu implementiert
- Rust-Kryptocode ist in Python eingeflossen, und Rust-HTTP-Code wurde zu einem von mehreren Backends in curl
- Rust-Patches für den Linux kernel werden wiederholt verbessert
- Auch Rust-Nutzer sprechen aktiv über Build-Zeiten, fehlende Sprachfeatures und andere Nachteile
- Der Punkt ist nicht, sich bedingungslos zwischen Go und Rust zu entscheiden, sondern zu sehen, welche Probleme Tools reduzieren, die Mutability, Lifetime und Abstraktionen in Typen ausdrücken können
- Auch in Rust muss nicht jeder Fall streng modelliert werden
- Wenn es beispielsweise nicht um ein Low-Level-Tool wie
lsgeht, kann man camino verwenden und sich darauf beschränken, nur UTF-8 paths zu behandeln - Beim Error Handling kann man nur einige wichtige variants auflisten und den Rest als
Other,InternaloderUnknownbelassen - Der Aufruf von unwrap() auf
Option<T>ist eine Art, explizit zu behaupten, dass ein Wert vorhanden ist
- Wenn es beispielsweise nicht um ein Low-Level-Tool wie
Go ist eher eine Insel, getrennt von anderen Ökosystemen
- Ohne cgo ist Go weit entfernt von gängigen Assemblers, Linkern, Debuggern, Memory Checkern und Calling Conventions
- Go ist eher eine closed-world-Sprache als C/C++ und FFI-feindlicher als Node.js, Python oder Ruby
- Dieser Unterschied hat auch Vorteile
- Interna des TLS- und HTTP-stacks lassen sich im Profiler genauso betrachten wie Business-Logik
- Das steht im Gegensatz zu dynamischen Sprachen, bei denen der stack trace in OpenSSL endet
- Die Runtime kann non-blocking I/O und Scheduling übernehmen
- Viele bestehende Tools und institutionelles Wissen lassen sich jedoch nur schwer unverändert nutzen, und auch die Integration von Go mit anderen Sprachen ist schwierig
- Die Arten der Go-Integration bringen jeweils Kosten mit sich
- Wenn Go C aufruft, entstehen neben den Kosten der FFI-Grenze auch manuelle descriptor-Tracking-Aufgaben, damit der GC nicht beschädigt wird
- Wenn andere Sprachen Go aufrufen, muss die gesamte Go runtime samt GC eingebunden werden, was eine große static library und betrieblichen Aufwand verursacht
- Die beste Grenze zu Go ist eher die Netzwerkgrenze
- RPC over TCP wie REST-ish HTTP/1, JSON-RPC oder gRPC ist vergleichsweise weniger schmerzhaft, wenn man die Latenzkosten tragen kann
- Trotzdem müssen auf beiden Seiten Invarianten aufrechterhalten werden, und auf der Go-Seite garantiert der Compiler das nicht, wenn man nicht kontinuierlich Validierungspackages verwendet
Die Falle der Erwartung, Go nur für Prototypen zu nutzen
- Weil Go leicht zu lernen ist und Recruiting einfach wirkt, gibt es die Vorstellung, zuerst einen Prototyp zu bauen und später, wenn es schwierig wird, neu zu schreiben oder Experten hinzuzuholen
- Doch „Wegwerfcode“ existiert in der Realität kaum
- Rewrites brauchen Zeit
- Ein reibungsloser Übergang ist schwer zu koordinieren
- Details können verloren gehen
- Die Entwicklung neuer Features kommt zum Stillstand
- Menschen müssen für neue Technologien erneut geschult werden
- Je mehr Go-Komponenten entstehen, desto mehr Gründe gibt es, weiterhin Go zu wählen, um mit bestehendem Go-Code zu interagieren
- Linter helfen teilweise, können aber nicht so viel leisten wie der Compiler einer Sprache, die das Problem von Anfang an ernsthaft behandelt, und sie verlangsamen zudem die Entwicklung
- Komplexität, die in der Sprache nicht abgebildet wird, wandert in die Codebasis
- Invarianten, die nicht als Typen ausgedrückt sind, müssen direkt im Code geschrieben werden
- Allein anhand der Funktionssignatur ist schwer zu erkennen, ob Daten verändert oder behalten werden, ob zero values erlaubt sind, ob eine goroutine gestartet wird, ob ein
nilchannel möglich ist oder welche Typen tatsächlich aninterface{}übergeben werden dürfen - Man wird von Dokumentation abhängig, und Dokumentation verursacht sowohl Aktualisierungs- als auch Nichtaktualisierungskosten
Abschließend zusammengefasste „Lügen“
- Die Dinge, die man sich sagt, um Go weiter zu verwenden, lassen sich wie folgt zusammenfassen
- Andere nutzen es, also wird es auch für uns gut sein
- Menschen, die Bedenken äußern, sind Elitisten oder unhöflich
- Die attraktive asynchrone Runtime und der GC gleichen alle anderen Nachteile aus
- Jede einzelne Designschwäche der Sprache ist isoliert und auch in Kombination unproblematisch
- Man kann sie durch „Aufpassen“, Linter und mehr Reviewer überwinden
- Weil Go leicht zu schreiben ist, ist auch die Entwicklung von Produktionssoftware leicht
- Weil die Sprache einfach ist, ist auch der Rest einfach
- Man kann Go nur ein wenig oder nur am Anfang verwenden und leicht wieder herauskommen
- Man kann später jederzeit neu schreiben
- Wenn das gewählte Werkzeug Komplexität nicht beseitigt, verlagert sich diese Komplexität auf andere Entwickler, Betreiber und Kunden
- Wenn ein
nil-Wert an eine Stelle gelangt, an die er nicht gehört, und man nachts im Betrieb geweckt wird, führt das zur Wiederholung des Billion Dollar Mistake
2 Kommentare
Ich weiß nicht, ob ich als Amateur, der sich nur für einen augenblicklich kurzen Zeitraum intensiv mit Go beschäftigt hat, überhaupt etwas dazu schreiben sollte, aber ... Bei Go sind die Vor- und Nachteile wirklich sehr klar, deshalb scheinen sowohl die, die sich dafür entscheiden, als auch die, die es meiden, jeweils gute Gründe zu haben. Persönlich finde ich, dass man es nicht mit Rust vergleichen sollte, sondern eher mit Kotlin (Java).
Die Goroutines von Go sind wirklich hervorragend, aber sie sind keine Magie. Gerade im Backend ist diese Nebenläufigkeit in kleinen Projekten, die nur ein einziges MySQL verwenden, sehr schwer zu beherrschen. Dinge wie die Erschöpfung von MySQL-Ressourcen oder das Management des Pools, um die man sich in JS/TS-Runtimes nicht allzu sehr kümmern muss, sind schwieriger als gedacht. Am Ende wird in dieser Situation die DB zum Flaschenhals, sodass der Vorteil von Gos Nebenläufigkeit teilweise verblasst. (Asynchrones I/O oder der Event Loop von JS/TS-Runtimes könnten sogar passender sein.) Das merkt man sofort, wenn man mit einem Tool wie
heyeinfach mal-c 100ausprobiert.Und obwohl es einen hervorragenden GC gibt, heißt das nicht, dass man leichtfertig nur Pointer auf Objekte weiterreichen und sich dann nicht mehr um das Aufräumen kümmern sollte. Alles ist ein Trade-off, aber auch in Go ist es besser, wenn möglich kleinere Objekte einfach per Wertkopie zu übergeben, damit sie direkt verarbeitet werden, wenn die Funktion endet. Vielleicht hänge ich da in einer veralteten Denkweise fest, aber man sollte Pointer nicht so leicht aus einer Effizienzperspektive betrachten wie in C/C++.
Dass man
errorbeim Rückgabewert von Funktionen fast jedes Mal zurückgeben und dann jedes Mal mitif err != nil {}prüfen muss, ist wirklich lästig, aber das ist auch ein Vorteil. Es ist kostengünstiger alstry catch. Und das Schlüsselwortdefer, das eine ähnliche Rolle wiefinally {}übernimmt, ist ebenfalls großartig. Es ist gut, dass man sich keine Gedanken über den Zeitpunkt der Freigabe von Ressourcen machen muss. Schön ist auch, dass man allein mit der Standardbibliothek sofort einen hervorragenden Backend-Server aufbauen kann (ab 1.23). Vor allem ist es am besten, dass man nach dem Build für das Ziel-OS keine andere Runtime und keine Vorinstallation braucht.Ich habe Go nicht besonders lange benutzt, aber ich glaube, ich schreibe schon zu lang über eine allzu persönliche Meinung, also höre ich hier auf. Haha, ich mag Go, und ich mag auch andere Sprachen!
Hacker-News-Meinungen
Der Artikel benennt viele Schwächen von Go treffend, aber ich denke nicht, dass explizite Fehlerbehandlung dazugehört.
Exception-Handling mochte ich nie, weil es eine „magische“ Schicht hinzufügt, die man viel zu leicht kaputtmachen kann. Auch wenn das technisch kein perfektes Argument ist: Nach Jahrzehnten Erfahrung bevorzuge ich, dass Fehler genau dort behandelt werden, wo sie auftreten.
Man kann sagen, dass Rust das eleganter macht, und für persönliche Projekte mag ich Rust lieber. Aber in großen Projekten, in denen Entwickler mit sehr unterschiedlichem Erfahrungsstand kommen und gehen, halte ich Gos Philosophie für einen recht vernünftigen modernen Ansatz zur Fehlerbehandlung.
In meiner Region wird Go dank seiner Einfachheit deutlich häufiger eingeführt als andere „neue“ Sprachen. Es ist nicht die beste Sprache, aber weil sie viele eingebaute Leitplanken hat, die einen davon abhalten, sich selbst zu verletzen, ist sie als Allzwecksprache oft die beste Wahl.
Es gibt keinen Stacktrace, und man muss Fehler wrappen. Dadurch muss man nicht nur eindeutige Fehlermeldungen erstellen, sondern auch an jeder Aufrufstelle eindeutige Wrapper-Meldungen, damit man per
grepaus den Meldungen etwas Stacktrace-Ähnliches ableiten kann.„Rückgabe-Tupel“ sind ein seltsames Konstrukt, das nur wegen Fehlern existiert; anderswo in der Sprache kann man Tupel nicht einmal verwenden. Wegen der unbeholfenen Regeln zur Variableninitialisierung wird man irgendwann die falsche
err-Variable benutzen.Go hätte Enums haben sollen, oder statt der merkwürdigen, nur für Fehler gedachten „Rückgabe-Tupel“ besseren syntaktischen Zucker wie Rusts
?. Die aktuelle Lösung ist mühsam und fehleranfällig.Go ist eigensinnig, aber allzu oft wirkt diese Eigensinnigkeit so, als solle sie die Sprache vor Kritik schützen. Leider spielen beim Erfolg einer Programmiersprache Marketing — also auch Lügen — eine wichtige Rolle, und das funktioniert.
Exceptions sind ein schreckliches
goto, das jede Library in meinen Code werfen kann, und sie werden fast nie richtig behandelt. Selbst nachdem eine Exception „behandelt“ wurde, befindet sich die Anwendung meist in einem unbekannten Zustand und lässt sich nicht mehr sinnvoll durchdenken.Go-Fehler können selbst Junior Engineers meistens relativ einfach debuggen; die wahre Ursache von Java-Exceptions herauszufinden, fällt dagegen selbst erfahrenen Principals schwer.
Umgekehrt habe ich noch niemanden gesehen, der mit Summentypen wie Maybe, Option oder Result vertraut ist und Gos Ansatz auch nur halbwegs gut findet.
Wenn man die Logik einer bestimmten Methode lesen will, aber 75 % davon Fehlerrauschen sind und nur 25 % der Zeilen für das Verständnis des eigentlichen Verhaltens nötig sind, ist das schwer lesbar.
Wenn ich eine Codebasis zum ersten Mal sehe, möchte ich zuerst verstehen, was sie erreichen soll, und erst danach, wie sichergestellt wird, dass das korrekt ist — nicht zuerst die Fehler-Randfälle.
In dieser Hinsicht ist Gos Fehlerbehandlung deutlich gescheitert. Nicht explizite Fehlerbehandlung ist schlecht, sondern das Beharren darauf, Erfolgspfad und Fehlerbehandlung zeilenweise zu vermischen. Auch in einem eigenen Fehlerbehandlungsbereich kann man Fehler explizit behandeln.
Worin ich zustimme: Sie zwingt einen dazu, über alle Möglichkeiten nachzudenken, die der Code erzeugen kann. Allerdings ist das eher eine C-nahe Frage als eine Frage anderer Sprachen.
Wenn man Code abstrahiert, muss man Fehler manchmal nicht sofort behandeln, oder man möchte sie weiter unten im Call Stack behandeln.
Man opfert also viel Lesbarkeit, nur damit die Sprache einen zu einer bestimmten Vorgehensweise zwingt.
Rust und Go sind sehr unterschiedlich, und viele scheinen sich einen Mittelweg zu wünschen, den es derzeit nicht gibt
Gemeint ist eine Sprache mit Garbage Collection, die relativ einfach ist und zu statisch gelinkten Binaries kompiliert, aber ein Typsystem ähnlich wie Rust und Dinge wie Resttypen hat
Syntaktisch kommen Gleam und Kotlin dem ein Stück weit nahe, aber eben nicht vollständig. Ich mag Rust, halte es aber für zu komplex für viele Menschen, die zwar in der Lage sind, etwas zu bauen, aber weder Informatik studiert haben noch als Programmierer arbeiten
Wenn man eine Sprache nur gelegentlich nutzt, erinnert man sich nicht daran, was eine vtable ist oder wann und wie ein Wert freigegeben wird. Es gibt keine „perfekte Sprache“, aber sowohl Go als auch Rust haben Großartiges eingebracht, und ich hoffe, dass sich jemand von beiden inspirieren lässt und eine einfache, breit nutzbare Sprache baut
In der Praxis ist Kotlin sehr komplex, und man verliert Zeit damit, besonders clever sein zu wollen. Es gibt 1000 Arten, Dinge zu erledigen, dazu Operator-Overloading, Proxys, Properties, Companion Objects, Exceptions und Result-Typen
Wenn man von Gradle ausgeht, ist das Build-System für jemanden, der
go buildgewohnt ist, fast schon FolterDie Coroutine-API fühlt sich gleichzeitig komplexer und eingeschränkter an als Goroutines. Sie ist stärker strukturiert, aber weniger flexibel und erfordert mehr Aufwand in der Nutzung
Auch die Zugriffskontrolle ist unbeholfen. Es gibt keine Möglichkeit, einen Typ nur paketintern sichtbar zu machen; er ist entweder dateiintern oder im ganzen Modul öffentlich. Dadurch wird die API-Oberfläche ungewollt größer, oder man kann Komplexität nicht auf mehrere Dateien aufteilen
Kotlin/Native und Kotlin/JVM sind faktisch unterschiedliche Dinge. Kotlin/JVM ist ausgereift, läuft aber auf der JVM, wodurch ein Teil der Einsatzbereiche wegfällt, für die man Rust nehmen würde
Das Kotlin/Native-Ökosystem ist sehr schwach, und auch die Dokumentation ist dürftig. Im Issue-Tracker finden sich zudem beängstigende Bugs
Kotlin/Native kann nicht einmal Libraries nur als Quellcode verteilen, sodass man eine komplexe CI braucht, die Binaries für alle Betriebssysteme und Architekturen baut. Oder man veröffentlicht Libraries gar nicht erst, was dieses ohnehin schwache Ökosystem offenbar weiter verschlechtert
So wie Generics hinzugefügt wurden, bräuchte es genau drei Dinge: Result als generischen Container für Fehler, Optional für besseren Umgang mit nil und ein wenig syntaktischen Zucker wie den Elvis-Operator
Der große Vorteil, der den Erfolg von Go beinahe allein erklärt, ist, dass eine riesige Firma die Sprache entwickelt, selbst nutzt und die Ressourcen hatte, hervorragende Tools und Open-Source-Libraries zu bauen. Dass eine Sprache bereits genutzt wird und Libraries hat, scheint der wichtigste Erfolgsfaktor für eine Sprache zu sein
Die Grundsyntax ist sehr direkt, die Verbreitung bereits enorm, das Ökosystem groß, die Runtime wird ständig besser, und es lässt sich auch nach WASM kompilieren
Dann entfernt man die seltsamen Teile, etwa zu viel Flexibilität bei Redefinitionen, Missbrauch des globalen Scopes und numerische Merkwürdigkeiten, und fügt Typen, Summentypen/Produkttypen, Result, Maybe, decimal usw. hinzu
Wenn man dann noch einen Stil ergänzt, in dem alles ein Ausdruck ist, sowie Pattern Matching, funktionale Features wie Currying und eine umfassende Standardbibliothek, könnte eine Sprache entstehen, die dem am nächsten kommt, was sich viele wünschen
Ein weiterer großer Fortschritt, den ich bei Entwickler-Tools gern sehen würde, wäre, HTML zu überarbeiten, richtige fortgeschrittene Controls einzubauen und sie leicht per Inline-CSS stilisierbar zu machen. Dann bräuchte man clientseitig weniger JS
Ich glaube, diese beiden Dinge würden die Programmierproduktivität in der Webentwicklung stark erhöhen
Mit
dotnet publish -p:PublishAot=truekann man zu einem statischen nativen Binary kompilieren. In F# muss man stattprint*die MethodenConsole.*verwenden, dennprintenthält wegen der strukturellen Ausgabe des Format-Spezifizierers%Aintern unbeschränkte Reflection, was die Binary-Größe negativ beeinflusst und den Compiler meckern lässtBesonders F# kann man als „einfachere, stärker businessorientierte Rust-Alternative“ empfehlen. Es ist ausdrucksorientiert und passt mit Discriminated Unions, vollständiger HM-Typinferenz und gut funktionierender gradueller Typisierung sehr gut. Auch Datenanalyse und Domain Modeling sind sehr angenehm
Für Systemprogrammierung ist C# eine Option. Es bietet hervorragende Nebenläufigkeits-Primitives, schnelle und teils fast kostenlose native Interop, kleinere native Binaries als Go und viele Low-Level-APIs einschließlich portablem SIMD
Go passt für solche Aufgaben oft nicht gut oder kann sie ohne Umwege gar nicht erledigen. .NET gewinnt in diesem Bereich außerhalb der Gaming-Community an Beliebtheit, und es entstehen viele High-Performance-Libraries. Außerdem profitiert man, anders als bei den in einem Schwesterkommentar vorgeschlagenen Nischensprachen, von einem riesigen bestehenden Ökosystem und muss nicht alle Grundlagenarbeit selbst erledigen
Go: „Ich bin eine einfache Sprache!“
Der Nutzer verwendet Go eine Weile
Nutzer: „Ich hasse dich, du bist doch eine einfache Sprache!“
Vielleicht liegt es daran, dass ich über 50 bin, aber ich mag einfache Sprachen
Diese „Kritik“ wirkt nicht besonders ausgewogen, und in der Technik ist alles ein Kompromiss; unausgewogene Urteile halte ich für schwach
Meine Schlussfolgerung ist natürlich eine andere: https://www.inkmi.com/blog/why-we-chose-go-over-rust-for-our...
Der Artikel enthält viele Beispiele, die auf nervige Inkonsistenzen der Sprache hinweisen, und solche Dinge sind das Gegenteil von Einfachheit
Ich mag Rob Pikes Go-Vortrag, und Teile davon haben mir die Augen geöffnet. Ich wünschte nur, das Go, das man in der Praxis sieht, wäre viel näher an dem Go, das Go-Fans abstrakt beschreiben
Entgegen dem ersten Eindruck zeigt sich, sobald man anfängt, Go zu verwenden, recht schnell, dass es nicht einfach ist. Es gibt viel versteckte Komplexität und viele Fallstricke. Beispiel: https://archive.ph/WcyF4
Trotzdem ist es eine nützliche Sprache und wird ziemlich viel verwendet, aber „einfach“ ist sie nicht
Eine einfache Sprache sollte meiner Meinung nach eine hohe Konsistenz haben. Es sollte wenige Regeln geben, aber sie sollten überall allgemein gültig und konsistent sein. In Go gibt es jedoch überall Merkwürdigkeiten, Inkonsistenzen und Workarounds
Ein gutes Beispiel ist das Weglassen von Typen. Bei Deklarationen von Arrays, Slices und Maps ist es möglich, bei Structs aber nicht. Wenn man es auch bei Structs erlauben würde, hätte das mehrere Vorteile für die Lesbarkeit, und benannte Parameter über anonyme Struct-Argumente würden möglich; das wäre als Design deutlich besser als die hässlichen Workarounds, die man derzeit verwendet
Natürlich sind die meisten vernünftigen Alternativen zu Go aus dieser Sicht ebenfalls schlechter. Dinge wie Python, Ruby und JS
Ich frage mich, was jemanden dazu motiviert, eine Sprache so ausführlich herunterzumachen. Selbst einige der berechtigten Punkte im Text sind nicht in einem konstruktiven Stil geschrieben, also nenne ich es Anklage
Welche Sprache kann denn nicht kritisiert werden?
Ist das ein Text, der geschrieben wurde, um sich besser zu fühlen, indem man das Scheitern in diesem Projekt der Sprache zuschreibt? Oder versteht der Autor nicht, dass nicht alle auf dieselbe Weise denken und arbeiten, und dass etwas, das für jemanden unerträglich störend ist, anderen kaum auffällt?
Sprachen, die echte Bedürfnisse nicht erfüllen, verschwinden in der Regel von selbst und brauchen dabei kaum Hilfe
Go hat in den Projekten, die mir begegnet sind, hervorragend funktioniert, auch wenn es sich von „feineren“ Sprachen unterscheidet. Ich hoffe, der Autor wird nicht gezwungen, mit Go zu arbeiten. Der Rest der Community wird weiter Services bauen, dem Go-Team Feedback geben und zusehen, wie sich die Sprache langsam weiterentwickelt, ohne die Produktion kaputtzumachen
Essenzielle Komplexität verschwindet nicht, nur weil man die Augen schließt
Wenn man sich entscheidet, sich nicht um Komplexität zu kümmern, schiebt man sie nur auf andere Entwickler in der Organisation, auf Betriebsteams, Kunden oder irgendwen sonst ab. Dann müssen diese Leute um deine Annahmen herumarbeiten, damit alles reibungslos läuft
Heutzutage bin ich oft dieser „Jemand“, und ich bin es leid
Dass Go in den Projekten, denen du begegnet bist, hervorragend funktioniert hat, ist nicht überraschend. C, C++, PHP und JavaScript funktionieren auch. In unserer Welt „funktionieren“ sehr viele Dinge. Eine zusätzliche Fahrspur zu bauen funktioniert auch, und ein Polizeistaat funktioniert auch
Aber könnte nicht etwas anderes viel besser funktionieren?
Warum er es geschrieben hat? Vermutlich, weil der Autor des Originals das Gefühl hatte, dass seine Sicht „in der Welt“ nicht ausreichend vertreten ist. Ich bin auch kein Go-Fan, und die Popularität von Go im Jahr 2022 war für mich verwirrend, weil recht offensichtliche Nachteile ignoriert zu werden schienen
Lange Blogposts darüber, dass PHP eine schlechte Sprache ist, schreiben die Leute heute kaum noch, weil das schon zu oft behandelt wurde
Nur weil dieser Blogpost nicht so geschrieben ist, wie es dir gefällt, heißt das nicht, dass er für andere keinen Wert hat
Alles, was Go tat, wurde als notwendig, als das Beste und als die einzige Lösung für das Problem verkauft, egal ob es die simpelste Implementierung war oder etwas, das andere Sprachen bereits besser konnten
Alles, was Go nicht tat, wurde als unnötig behandelt, als Verschwörung von Big Complexity, um uns ahnungslose Nicht-Go-Nutzer süchtig nach syntactic sugar zu machen. Dazu gehörten Dinge wie ordentliche null-Behandlung, Fehlerbehandlung, weniger Boilerplate, Generics oder konsistente Lösungen, ohne für alles Spezialfälle zu schaffen
Auch in diesem Thread geht das Strohmann-Argumentieren weiter. Sobald Fehlerbehandlung zur Sprache kommt, wird statt der offensichtlichen Tatsache anzuerkennen, dass Gos Fehlerbehandlung viel besser sein könnte, irgendetwas wie JavaScript hervorgeholt. Als ob Leute, die Go kritisieren, JavaScript für den Gipfel der Fehlerbehandlung hielten
Jedes Mal, wenn ich mit einer anderen Sprache arbeite, will ich zu Go zurück, auch wenn Go keine perfekte Sprache ist.
Mir gefällt, dass es im wahrsten Sinne des Wortes einfach funktioniert. Man installiert Go, lädt den Code herunter, schreibt Code, und das war’s.
Man muss nicht herausfinden, welche Version, Runtime, Konfiguration, Build-Tools oder Paketmanager man verwenden soll. Einfach installieren und Go nutzen.
Die einzige andere Sprache, die vermutlich eine ähnliche Erfahrung bietet, ist Rust.
Vielleicht ist das eine Lüge, die ich mir selbst erzähle, aber jedes Mal, wenn ich Python, TypeScript oder Java verwende, will ich einfach nur Code schreiben, bekomme aber Angst vorm Programmieren, weil ich Konfiguration, Paketmanager, Build-Tools oder Versionsprobleme debuggen oder verstehen muss.
Jedes Mal, wenn ich Kritik an Go lese, denke ich dasselbe: Ich werde Go trotzdem weiter verwenden.
Ich verstehe, dass es viele Probleme gibt, die sich theoretisch leicht dokumentieren lassen und auf die Leute in der Praxis auch häufig stoßen, aber trotzdem fühlt es sich im Arbeitsalltag weiterhin wie eine der besseren Programmiersprachen an.
Bei manchen Dingen, die oft als Nachteile aufgelistet werden, stimme ich nicht zu; zum Beispiel mag ich die explizite Fehlerbehandlung überall. Bei anderen Punkten stimme ich zu, aber sie stören mich nicht annähernd so sehr wie die Nachteile anderer Programmiersprachen.
Es tut mir auch ein wenig leid für die Leute, die besonders empfindlich auf die Bereiche reagieren, in denen Go nicht erfolgreich war; sie werden sich wohl ihr Leben lang darüber beschweren.
Aber wenn ich auswähle, welche Sprache ich für ein Projekt verwende, ziehe ich nicht jede Menge rationale Argumente darüber heran, „welche Sprache die beste ist“. Ich wähle eine Sprache, die zu mir passt, bei der ich das Gefühl habe, dauerhaft gute Software schreiben zu können, und mit der sich gut arbeiten lässt.
Als jemand, dem Korrektheit wichtig ist, würde ich mir wünschen, dass diese Sprache näher an Rust wäre, aber derzeit ist sie das nicht. Ich mag Rust auch nicht nicht, aber es ist nicht die Sprache, zu der ich greife, wenn es eilig ist.
Inzwischen wurden genug Texte darüber geschrieben, wie schrecklich Go ist. Zumindest weiß ich jetzt, wie es sich vor ein paar Jahren angefühlt haben muss, PHP-Fan zu sein. Das ist übertrieben, aber meiner Meinung nach keine sehr große Übertreibung.
Man wird Go verwenden.
Und man wird es bereuen.
Aber dann ist es bereits zu spät, und man steckt fest.
Der Autor bringt ziemlich überzeugende Argumente vor, und sie sind sehr schwer zu widerlegen. In vielen der ausdrücklich aufgezählten Fälle sowie in vielen beruflichen Situationen, in denen technisch nicht besonders kompetente Unternehmen ungeeignete Technologie einsetzen, ist Go eine schlechte Wahl, und man wird bereuen, es in Produktion gebracht zu haben.
Unternehmen sollten bei langweiligen Werkzeugen bleiben. Bei privaten Projekten kann man natürlich machen, was man will.
Ansonsten sehe ich die Einfachheit als Stärke. Es fühlt sich gut an, ist leicht zu lernen und hat im Allgemeinen auch recht ordentliche Performance. Ich würde es nicht für jede Situation wählen, aber es gibt viele Situationen, in denen es ein gutes Werkzeug ist.
Ebenso mag ich Java und Python bis zu einem gewissen Grad und beherrsche beide ausreichend gut. Diese Sprachen sind jeweils gute Werkzeuge für das, wofür sie gedacht sind.
Ich verstehe nicht, warum Menschen sich so leidenschaftlich an Programmiersprachen klammern. Sprachen sind Werkzeuge. Man kann manche Werkzeuge lieber mögen, aber das macht die Werkzeuge, die man nicht mag, nicht ungültig.
if err != nil-Dämonen ins Gesicht schlagen.Es gab viele gute Vorschläge, dieses Problem zu beheben, aber ein extrem konservativer und lautstarker Teil der Community hat alle Vorschläge mit Feedback, das schon an Gehässigkeit grenzte, abgewürgt.
Inzwischen hat das Go-Team psychologisch Angst vor solchen Leuten und den Kampf um Verbesserungen der Fehlerbehandlung aufgegeben.
In jeder Go-Entwicklerumfrage stimmt die Community mit überwältigender Mehrheit für Verbesserungen der Fehlerbehandlung, aber eine Minderheit von Extremisten hat jeden Fortschritt entgleisen lassen.
Go nahm die arrogante Haltung ein, selbst entscheiden zu wollen, welche Features man braucht, und stellte für die Arbeit nur sehr wenig zur Verfügung.
Diskussionen von damals:
(130 Punkte, 148 Kommentare) https://news.ycombinator.com/item?id=34188528
(748 Punkte, 544 Kommentare) https://news.ycombinator.com/item?id=31205072
Es gibt eine Stelle: „Ich begann, mich viel mehr für Semantik als für Syntax zu interessieren, daher habe ich mir auch Zig, Nim, Odin usw. nicht angesehen. An einem ‚besseren C‘ bin ich nicht mehr interessiert.“
Ich finde den Text ziemlich weitschweifig. Der Teil, in dem Go als „zufällig“ bezeichnet wird, ist Unsinn, denn Rust ist hinsichtlich Ursprung und Design genauso „zufällig“.
Eine Sache, die auffiel: Nim ist keineswegs ein „besseres C“. Es hat Garbage Collection, und man kann auch Reference Counting verwenden. Wenn man möchte, kann man es wie ein besseres C verwenden.
Nims Typsystem vermeidet viele der Probleme, die Go hat, ist aber nicht so strikt wie Rust.
Am Ende wurde mit Go viel Software geschrieben und ausgeliefert; sie läuft schnell, hat wenig Downtime, ist im Allgemeinen effektiv, und es scheint weniger Sicherheitsprobleme zu geben. Ich denke, dass deutlich weniger Softwareprojekte mit Rust ausgeliefert wurden, und Firefox besteht immer noch zu über 95 % aus C++.
Ich habe in diesem Streit keine eigenen Interessen und nutze bei der Arbeit zufrieden Python und C++. Selbst wenn deutlich weniger Software in Rust geschrieben ist, kann man dennoch sagen, dass viel Software in Rust geschrieben und ausgeliefert wurde. Auch die Investitionen großer Unternehmen darf man nicht auslassen.
0: https://x.com/m_ou_se/status/1599173341117435905
Ich verstehe nicht, warum Go ständig mit Rust verglichen wird. Der passendste Vergleich ist Java.
Der Grund, warum der Vergleich zwischen Go und Rust häufiger, aber weniger nützlich ist, dürfte sein, dass beide Teil der neuen Sprachwelle sind und beide standardmäßig statisch gelinkte Binaries erzeugen.
In Situationen, in denen Java eine gute Wahl ist, ist Go eine wirklich schlechte Wahl. Nicht nur die Sprache ist eingeschränkt, auch das Ökosystem ist im Vergleich zu Java sehr begrenzt.
Ich nutze Go, C# und TypeScript als Hauptsprachen, und diese Kombination deckt einen hervorragenden Bereich ab. Wenn ich einmal sechs Monate habe, in denen ich einen Produktivitätsrückgang in Kauf nehmen kann, oder ein Projekt mit niedrigeren Qualitätsanforderungen, werde ich Rust hinzufügen.
Eine Sprache zu lernen dauert ein bis zwei Wochen, aber zu lernen, wie man in ihrem Ökosystem Engineering betreibt, braucht Zeit.
Die meisten Unternehmen, bei denen Performance-Sorgen nicht im Vordergrund stehen, verwenden C++ nicht für Netzwerkdienste. Wenn Rust damals schon ausgereift gewesen wäre, wäre es eine gute Wahl gewesen, aber eines der frühen Kernprobleme, die Compile-Zeiten, kann in Rust-Codebases sogar schlimmer sein.
Ich habe auch in der Praxis Fälle gesehen, in denen Go statt Rust verwendet wurde, und der wichtigste Grund war, dass Go deutlich schneller kompiliert.
Wenn die Frage ist, ob Rust und Go unterschiedlich genug sind, dass sie jeweils für andere Fälle gewählt werden sollten: ja. Genau deshalb betrachtet man beide zusammen und entscheidet sich dann für eine.
Eine Zeit lang suchte ich nach einem „besseren“ Python. Ich wollte eine Sprache, die einfacher und sicherer als C/C++ ist, aber schneller als Python, und vor allem ein einzelnes Binary erzeugen kann.
Deshalb habe ich mir alles angesehen, von Rust bis hin zu weniger bekannten Sprachen wie Hare.
Go hätte die naheliegende Wahl sein müssen, aber aus irgendeinem Grund mochte ich die Syntax nicht. Rust verstehe ich: Es verwendet viele Sonderzeichen, die man sich merken muss und die auf Nicht-QWERTY-Tastaturen schwer zu tippen sind, und es gibt weitere Schmerzpunkte. Bei diversen Lisps waren Klammern und umgekehrte polnische Notation das Problem. Aber bei Go kann ich nicht rational begründen, warum ich es nicht mag.
Am Ende habe ich mich entschieden, Python-Code mit Nuitka zu kompilieren. Ob es schneller ist, weiß ich nicht, aber jetzt kann ich ein Binary ausliefern.
C# schaue ich mir ebenfalls zunehmend an, weil die AOT-Kompilierung Fortschritte macht. Was ich aber nicht mag, sind die Ausführlichkeit im Standardmodus und dass es zu sehr an Windows gebunden ist.
Nim und Crystal gefielen mir sehr gut, aber die kleine Community war eine Hürde. Trotzdem bin ich wirklich beeindruckt davon, was Nim mit einer so kleinen Community schafft, und halte es für eine großartige Sprache.
Ich werde versuchen, mich selbst zu motivieren, eine der genannten Sprachen zu lernen.
Heutzutage ist es tatsächlich ziemlich angenehm zu nutzen.
Es gibt auch einen anderen Ansatz: die C-API aufgeben und eine Brücke zu Sprachen wie Go oder Rust bauen.
Ich habe einige Jahre an py2many gearbeitet. Zu diesem Ansatz würde ich gern Feedback bekommen.