1 Punkte von GN⁺ 2 시간 전 | 1 Kommentare | Auf WhatsApp teilen
  • Go ist eine Option, um die übermäßige Komplexität in der Backend-Entwicklung zu reduzieren; die wichtigsten Vorteile sind schnelle Kompilierung, Deployment als einzelne Binärdatei und ein stabiles Abhängigkeitsmanagement
  • Statt komplexer Abstraktionen wie Decorators, Metaclasses, Macros, Traits oder Monaden setzt Go auf ein bewusst einfaches Sprachdesign rund um struct, Funktionen, Interfaces, Goroutinen und Channels
  • Mit Standardbibliothek und Bordmitteln wie embed, html/template, net/http, database/sql, encoding/json, go test und pprof lassen sich Web-Apps, Datenbanken, Tests, Benchmarks und Profiling abdecken
  • Eine Goroutine ist eine stackful Ausführungseinheit mit Kosten von etwa 2 KB; über Channels, sync.Mutex, den Race Detector und context.Context lassen sich Nebenläufigkeit und Cancellation Propagation einfach handhaben
  • Der Ablauf go mod init, go build, scp, systemctl restart steht für ein simples Deployment mit einer Go-Binärdatei und Postgres statt node_modules, komplexer Docker-/Kubernetes-Setups oder überzogener Microservices

Warum man Go wählen sollte

  • Go ist eine Option, um die übermäßige Komplexität in der Backend-Entwicklung zu reduzieren; die wichtigsten Vorteile sind schnelle Kompilierung, Deployment als einzelne Binärdatei und ein stabiles Abhängigkeitsmanagement
  • So wie im Frontend HTML als Alternative zur Überkomplizierung übrig blieb, existiert Go seit mehr als zehn Jahren als Option zur Vereinfachung des Backends
  • Für einfache Formulare oder CRUD-Apps mit etwa 40 Requests pro Sekunde gleich eine Vielzahl von Node-Paketen, TypeScript-Build-Tools, Kubernetes, ein Rails-Plattform-Team oder gar ein Rewrite in Rust aufzufahren, ist übertrieben
  • Go zielt eher auf gut lesbaren Code, deploybare Artefakte und einen geringen Betriebsaufwand als auf „clevere Abstraktionen“

Ein absichtlich langweiliges Sprachdesign

  • Dass Go langweilig wirkt, ist Absicht; die Sprache bietet keine komplexen Abstraktionen wie Decorators, Metaclasses, Macros, Traits oder Monaden
  • Die Kernelemente beschränken sich im Wesentlichen auf struct, Funktionen, Interfaces, Goroutinen und Channels
  • Ziel ist eine Einfachheit, bei der man die Spezifikation in kurzer Zeit lesen und noch am selben Tag produktiv Code schreiben kann
  • Gerade diese Langeweile ist in einem Team-Codebestand ein Vorteil
    • Selbst ein Junior, der erst letzten Monat angefangen hat, kann Code lesen, den vor zwei Jahren ein Principal geschrieben hat
    • gofmt erzwingt ein einheitliches Format und reduziert dadurch Stil-Debatten
    • Die Sprache selbst macht es schwer, übermäßig komplexe Abstraktionen in die Codebasis hineinzuschieben

Die Standardbibliothek übernimmt die Rolle des Frameworks

  • Mit Go lässt sich auch ohne separates Web-Framework allein mit der Standardbibliothek eine Web-App bauen
  • Mit embed, html/template und net/http kann man HTML-Templates in die Binärdatei einbetten und über HTTP-Handler rendern
package main

import (
    "embed"
    "html/template"
    "net/http"
)

//go:embed templates/*.html
var files embed.FS

var tmpl = template.Must(template.ParseFS(files, "templates/*.html"))

func main() {
    http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
        tmpl.ExecuteTemplate(w, "index.html", map[string]string{
            "Name": "asshole",
        })
    })

    http.ListenAndServe(":8080", nil)
}
  • Dieses Beispiel ist eine funktionierende Web-App; die HTML-Templates werden in die Binärdatei einkompiliert
  • Ohne webpack, Vite, Dev-Server oder ein riesiges node_modules kann man nach go build einfach eine einzige Datei deployen
  • Die wichtigsten Backend-Aufgaben lassen sich allein mit Standardbibliothek und Bordmitteln erledigen
    • Datenbank: database/sql
    • JSON: encoding/json
    • Aufrufe anderer Services: net/http-Client
    • Nebenläufige Ausführung: Schlüsselwort go
    • Tests: go test
    • Benchmarks: go test -bench
    • Profiling: pprof

Tiefgehende Bausteine in der Standardbibliothek

  • io.Reader und io.Writer

    • io.Reader und io.Writer sind Interfaces mit jeweils nur einer Methode, bilden aber eine wichtige Grundlage im gesamten Go-Ökosystem
    • Man kann etwa den Body einer HTTP-Antwort an einen gzip-Writer anschließen und das Ergebnis anschließend mit wenig Code in eine Datei auf Disk schreiben
    • Weil viele zentrale Pakete diese beiden Interfaces teilen, lassen sich dieselben Muster an vielen Stellen wiederverwenden
  • context.Context

    • context.Context ist der Standardmechanismus zur Weitergabe von Abbrüchen
    • Wenn der Nutzer den Browser-Tab schließt, wird der Request-Context abgebrochen, woraufhin auch die Datenbankabfrage und nachgelagerte HTTP-Aufrufe beendet werden können
    • Um Goroutine-Leaks oder Zombie-Queries zu vermeiden, die den Connection Pool aufbrauchen, sollte man den Context als erstes Argument weiterreichen und respektieren
  • Encoding-Pakete

    • encoding/json, encoding/xml, encoding/csv und encoding/binary sind alle Teil der Standardbibliothek
    • Durch ähnliche Nutzungsmuster mit Struct Tags und Decoding in Pointer fühlt sich die Arbeit mit ihnen konsistent an; wer eins kennt, kann die anderen leicht verwenden

Ein Nebenläufigkeitsmodell, das Schmerzen reduziert

  • Eine Goroutine ist kein OS-Thread, sondern eine stackful Ausführungseinheit, die die Runtime auf OS-Threads multiplexed
  • Die Startkosten einer Goroutine liegen bei etwa 2 KB, sodass man selbst auf einem Laptop 100.000 davon erzeugen kann
  • Channels sind typisierte Pipes zwischen Goroutinen; wenn eine Seite sendet und die andere empfängt, übernimmt die Runtime die Synchronisation
  • Wenn gemeinsamer Zustand nötig ist, kann man sync.Mutex verwenden, und der Race Detector findet Data Races
  • Selbst ein paralleler HTTP-Fetcher lässt sich ohne zusätzliche Bibliotheken, Frameworks oder async/await-Rituale schreiben
results := make(chan string, len(urls))
for _, url := range urls {
    go func(u string) {
        resp, _ := http.Get(u)
        results <- resp.Status
    }(url)
}
for range urls {
    fmt.Println(<-results)
}

Beispiel für eine echte CRUD-Route

  • Auch eine CRUD-artige Route, die Posts aus Postgres liest und HTML rendert, bleibt so einfach, dass sie auf einen Bildschirm passt
//go:embed templates/*.html
var tmplFS embed.FS

var tmpl = template.Must(template.ParseFS(tmplFS, "templates/*.html"))

type Post struct {
    ID    int
    Title string
    Body  string
}

func postsHandler(db *sql.DB) http.HandlerFunc {
    return func(w http.ResponseWriter, r *http.Request) {
        rows, err := db.QueryContext(r.Context(),
            "SELECT id, title, body FROM posts ORDER BY id DESC LIMIT 50")
        if err != nil {
            http.Error(w, err.Error(), http.StatusInternalServerError)
            return
        }
        defer rows.Close()

        var posts []Post
        for rows.Next() {
            var p Post
            if err := rows.Scan(&p.ID, &p.Title, &p.Body); err != nil {
                http.Error(w, err.Error(), http.StatusInternalServerError)
                return
            }
            posts = append(posts, p)
        }

        tmpl.ExecuteTemplate(w, "posts.html", posts)
    }
}
  • Dieses Beispiel zeigt Datenbank, Template und HTTP-Handler an einer Stelle
  • Weil r.Context() an die SQL-Abfrage weitergereicht wird, kann die Query ebenfalls abgebrochen werden, wenn die Verbindung geschlossen wird
  • Ohne ORM, DI-Container, Service-Schicht oder ein controllers/-Verzeichnis voller abstrakter Basisklassen kann man den Ablauf einfach von oben nach unten lesen

Abhängigkeitsmanagement, das nicht das Wochenende ruiniert

  • Mit go mod init startet man ein Modul; die Abhängigkeiten werden in go.mod und go.sum festgehalten
  • go.sum ist praktisch ein kryptografisches Protokoll der tatsächlich geladenen Inhalte und hilft zu erkennen, wenn andere Abhängigkeiten als erwartet hereinkommen
  • Es gibt keine Komplexität durch ein node_modules-Verzeichnis, Lockfile-Drift zwischen Dev-Umgebung und CI, Peer Dependencies, Optional Dependencies, DevDependencies oder peerDependenciesMeta
  • Wenn Offline-Builds nötig sind, lädt go mod vendor die Abhängigkeiten in das Verzeichnis vendor/, das vom Tooling automatisch verwendet wird
  • Das gesamte Projekt samt Abhängigkeiten passt in ein einziges Tarball, was für Betrieb und Sicherheitsprüfung vorteilhaft ist

Werkzeuge, die mit dem Compiler kommen

  • Die Standardtools von Go werden ohne Third-Party-Plugins oder separate Konfigurationsdateien mitgeliefert
  • gofmt standardisiert die Formatierung und reduziert Debatten sowie unnötige Diffs durch Leerzeichen
  • go vet dient dazu, offensichtliche Fehler zu finden
  • go test führt Tests aus
  • go test -race führt Tests mit dem Race Detector aus, um Data Races zu finden
  • go test -bench führt Benchmarks aus
  • go test -cover prüft die Testabdeckung
  • go tool pprof kann über den HTTP-Endpunkt eines laufenden Produktionsdienstes Flame Graphs für CPU- und Speichernutzung liefern

Deployment endet mit einem Kopierbefehl

  • Der Kern des Go-Deployments ist: Binärdatei bauen, auf den Server kopieren und ausführen
GOOS=linux GOARCH=amd64 go build -o myapp ./cmd/myapp
scp myapp user@server:/usr/local/bin/
ssh user@server 'systemctl restart myapp'
  • So kann man ohne Dockerfile, Multi-Stage-Build, CVE-Warnungen für Base Images, Kubernetes-Manifeste, Helm-Charts, ArgoCD, Service Mesh oder Sidecars deployen
  • Schon mit einer statisch gelinkten Binärdatei von etwa 12 MB und einer 20 Zeilen langen systemd-Unit-Datei ist ein Produktions-Deployment möglich
  • Wenn Docker unbedingt nötig ist, reicht es aus, die Go-Binärdatei in ein FROM scratch-Image zu legen

Gegenüberstellung zu Frameworks

  • Frameworks wie Rails, Django, Express oder Next.js bringen jeweils eigene Lasten mit sich: Deployment-Prozeduren, ORM, Admin, Middleware, npm-Warnungen oder wechselnde Routing-Konventionen
  • Eine Go-Binärdatei wird kompiliert und ausgeführt und hat den Vorteil, auch in fünf Jahren noch stabil laufen zu können
  • Vor dem Hintergrund, dass Frameworks schneller ausgemustert werden oder Maintainer ausbrennen können, sticht das einfache Ausführungsmodell von Go hervor

Eine einzelne Go-Binärdatei statt Microservices

  • Microservices sollten nicht die Voreinstellung sein; besser ist es, zuerst einen Monolithen zu schreiben
  • Die empfohlene Konfiguration ist eine Go-Binärdatei, ein Postgres und nur bei echtem Bedarf ein Redis
  • HTML und JSON-API können über denselben Port ausgeliefert und auf einem einzelnen VPS betrieben werden
  • Weil Goroutinen wenig kosten und Go stark in Nebenläufigkeit ist, lässt sich auch auf etwa 10.000 Requests pro Sekunde ohne große Mühe skalieren
  • Wenn eine Aufteilung wirklich nötig wird, kann man Pakete aus dem Go-Monolithen in separate Repositories verschieben
  • Interfaces existieren ohnehin schon, sodass die Sprache auf natürliche Weise eine Struktur begünstigt, die Trennung mitdenkt

Generics und Fehlerbehandlung

  • if err != nil ist kein Bug, sondern ein Feature
  • Es zwingt dazu, an jeder Fehlerstelle selbst zu entscheiden, was zu tun ist, statt Fehler zu verstecken
  • Verschachteltes try/catch beseitigt Fehler nicht, sondern kann sie nur bis zum Produktionsausfall verbergen
  • Generics wurden mit Go 1.18 eingeführt und können verwendet werden, wenn sie wirklich gebraucht werden

Fazit

  • Frameworks, Microservices, Rewrites in Rust oder neue JavaScript-Metaframeworks sind nicht zwangsläufig nötig
  • Empfohlen wird ein einfacher Ablauf: go mod init ausführen, main.go schreiben, Templates per Embed einbinden, kompilieren und deployen
  • Die langweilige Wahl ist die richtige Wahl, und Go ist diese Wahl

1 Kommentare

 
GN⁺ 2 시간 전
Lobste.rs-Meinungen
  • Ich will nicht dem Überbringer der Botschaft die Schuld geben, aber dieser Blogstil ist ermüdend und kindisch. Am Anfang mag das noch witzig gewesen sein, aber je öfter man es sieht, desto exponentiell nerviger wird es
    Trotzdem ist Go gut. Ich bin kürzlich von einem TypeScript-Projekt zu einem Go-Projekt gewechselt, und meine psychische Gesundheit sowie die Arbeitsmoral verbessern sich schnell
    Ich akzeptiere die Aussage, dass if err != nil kein Bug, sondern ein Feature ist, halte es aber immer noch für den größten Makel von Go. Mit Summentypen (sum types) hätte man das viel ergonomischer machen können, ohne auf Laufzeit-Typassertions angewiesen zu sein

    • Das ist mir lieber als diese AI-Wischiwaschi-Texte, die zu allem und jedem ein bisschen Stellung beziehen und am Ende gar keine echte Position haben
    • Ich fand es unterhaltsam, habe aber noch nicht viele Texte dieser Art gesehen. Trotzdem finde ich es lustiger, jemanden „walnut“ statt „dipshit“ zu nennen
      Wenn man schon so schreibt, dann sollten die Beleidigungen wenigstens etwas geistreicher sein
    • Stimme zu. Gibt es eine Möglichkeit, das zu melden? Es passt in keine Meldekategorie
  • Wenn ich die anderen Kommentare lese, scheint das eine unpopuläre Meinung zu sein, und ich möchte nicht harsch klingen, aber ich hasse Go wirklich
    Go ist eine Sprache mit halbwegs brauchbarer Syntax auf einer für Nebenläufigkeit effizienten Laufzeitumgebung, deren Ökosystem mit der Macht von Google vorangetrieben wurde. Abgesehen davon finde ich sie schrecklich
    Das größte Problem ist, dass sie so wirkt, als sei sie absichtlich so entworfen worden, jahrzehntelange Forschung zum Design von Programmiersprachen oder sogar etablierte Praxis zu ignorieren. Zwar kamen nach Jahrzehnten endlich Generics hinzu
    Ich sage nicht, dass man immer abhängige Typen verwenden muss, aber es gibt Grenzen. Go hat fast keine Möglichkeiten zur Datenmodellierung, zur Modellierung von Invarianten oder zur Strukturierung von Code, die man von einer modernen Sprache erwarten würde. Rust hat eine steilere Lernkurve, ist in dieser Hinsicht aber deutlich besser, und es muss nicht einmal ein so ausgefeiltes Typsystem wie in Rust sein, um ausreichend gut zu sein. Wenn Kompilierzeiten die Sorge sind, kann man auch mit einfachen, aber nützlichen Features ein schnelles und ausdrucksstarkes solides Typsystem bauen
    Und if err != nil ist meiner Meinung nach die schlimmste Art, Code mit Fehlerbehandlungsrauschen zu tapezieren. Ich verstehe nicht, warum die Go-Seite so eine Abneigung gegen Summentypen hat. In diesem Punkt sind sogar Java-Exceptions besser. Die Realität ist: Weil die Sprache keine besseren Mittel zur Fehlerbehandlung hat, halten Leute den denkbar schlechtesten Notbehelf für ein Feature
    Wenn der Originaltext nicht so selbstgefällig gewesen wäre, hätte ich diesen Kommentar gar nicht geschrieben. „Benutz einfach X“ ist eine dumme Aussage. Man sollte das Werkzeug nehmen, das zum Anwendungsfall passt, angenehm ist und produktiv macht. Wenn das Go ist, dann nimm Go, andernfalls eben etwas anderes

    • Die Position von Go im Designraum ist für mich eine Wahl, die Einfachheit für Junior-Entwickler in großen Codebasen und großen Organisationen über fast alles andere stellt. Dadurch ist es für weniger erfahrene Entwickler relativ leicht, Code zu lesen und lokal zu ändern, ohne viel Kontext aufzubauen
      Das hilft besonders in Organisationen wie Google, wo es Tausende Entwickler gibt und die Verweildauer in einem bestimmten Team oder Unternehmen kurz sein kann
      In diesem Kontext ist das Fehlen eines fortgeschrittenen Typsystems bis zu einem gewissen Grad sogar ein Vorteil, besonders für noch unerfahrene Entwickler. Man muss über Typen kaum nachdenken, über Basistypen oder Structs hinaus. Es gibt einem fast keine Werkzeuge zur Modellierung von Daten, aber dafür kann man ohne viel Nachdenken viel Code schreiben
      Für Korrektheit auf Sprachebene ist das meiner Meinung nach nicht besonders gut. Aber in großen Organisationen stützt man sich dann stärker auf umgebende Infrastruktur wie Monorepo-Analyse, CI/CD, Canary-Tests und Observability-Tools. Diese Infrastruktur trägt dort viel mehr Last als in kleinen Organisationen
      Ich mag Go aus einem ähnlichen Grund mit seiner geringen kognitiven Last bis zu einem gewissen Grad auch. Ich schreibe nur gelegentlich Code für bestimmte Projekte und bin derzeit nicht täglich tief in ein langfristiges Projekt eingebunden. Es ist ein großer Vorteil, in eine Codebasis zu gehen, die ich einen Monat nicht gesehen habe, und in weniger als einer Stunde etwas daran erledigen zu können. Als Vollzeitentwickler an einem komplexen Projekt würde ich es aber vermutlich weniger mögen
    • Dart ist auch eine Google-Sprache, die nicht so wirkt, als würde sie jahrzehntelange Forschung ignorieren, aber außerhalb von Flutter nutzt sie niemand. Go ist okay
    • Dieser Text kopiert ein aggressives, selbstgefälliges Meme-Format. Deshalb war klar, dass er Leute provoziert, und ich finde das schade, weil der Kern des Textes meiner Meinung nach eine vernünftige Diskussion statt eines Flamewars wert wäre
      Die Go-Entwickler haben sich meiner Ansicht nach darauf konzentriert, die Grundlagen richtig zu machen. Denn bisherige Sprachen und die Forschungsgemeinschaft zur Programmiersprachentheorie haben die Grundlagen vernachlässigt. Leute fixieren sich auf das möglichst umfassende Typsystem, aber je komplexer und ausdrucksstärker ein Typsystem wird, desto geringer wird der Ertrag, und egal wie viel Energie man in das Typsystem steckt: Es kompensiert kein schreckliches Paketmanagement, keine Build-Tools, bei denen das Team erst eine neue DSL lernen muss, keine Dokumentationssysteme, die keine Typinformationen oder Links zur Doku von Drittanbieter-Paketen automatisch erzeugen, keine schwache Standardbibliothek, keine gravierenden Performance-Probleme, keine fehlende Strategie für statisches Kompilieren, keine quälenden Build-Zeiten, keine steile Lernkurve, kein strafendes Typsystem, keine schwer lesbare Syntax und keine miserable Editor-Integration
      Zu sagen, Go habe überhaupt keine Möglichkeiten zur Datenmodellierung, ist offensichtlich falsch. In jeder Sprache kann man Daten und Invarianten modellieren, und Go bietet durchaus ein Typsystem, das dieses Modell durchsetzen kann
      Rust ist großartig und eine gute Wahl, wenn Iterationsgeschwindigkeit nicht wichtig ist oder man auf Bare Metal deployt oder sehr hohe Anforderungen an Korrektheit und Performance hat. Aber als Standard für allgemeine Anwendungsentwicklung, besonders im Team, ist es nicht gut geeignet. Man tippt zwar oft if err != nil, aber niemand hat Eingaben pro Sekunde als Flaschenhals
    • Abgesehen von Rust gibt es kaum moderne Sprachen mit solchen Features. Es sei denn, man will in Gleam oder Swift programmieren, aber wenn es schon so nischig wird, kann man auch gleich Haskell nehmen
  • Die Aussage, if err != nil sei kein Bug, sondern ein Feature, und sorge dafür, dass man jede potenzielle Fehlerstelle sieht, ist falsch
    In Wirklichkeit wird es nicht erzwungen. Es ist sogar leichter, Fehler zu ignorieren, wenn man sie nicht explizit prüft
    Für die Art, wie Fehler behandelt oder propagiert werden, bleibt Rust weiterhin das leuchtende Beispiel

    • Genau. Ohne etwas wie errcheck werden Fehler viel zu leicht ignoriert, und das ist einfach dumm. Man sollte zumindest dazu gezwungen werden, Fehler explizit zu verwerfen
      Glücklicherweise haben alle Go-Projekte, an denen ich in den letzten Jahren gearbeitet habe, golangci-lint zusätzlich zu Gos schwacher eingebauter statischer Analyse verwendet. Ehrlich gesagt sollte das für jedes Go-Projekt Pflicht sein
    • Hier ist Swift besser. Funktional ist es dasselbe Modell, aber auf Swift-Seite ist die Fehlerpropagierung zwischen verschiedenen Bibliotheken leichter. Das sind aber einfach andere Vor- und Nachteile und andere Designentscheidungen, nicht unbedingt besser oder schlechter
  • Ich hasse diesen Schreibtrend wirklich, aber dem Kern dessen, was der Text sagen will, stimme ich zu
    Die Aussage „kein node_modules in Volkswagen-Größe“ stimmt zwar, aber statt projektlokalen node_modules ist es einfach nur ein globaler Paket-Cache in ~/go

    • Außerdem verschmutzt es das Home-Verzeichnis. Nicht einmal mit einem Punkt davor. Ich verstehe nicht, wie das akzeptiert wird
    • Ich möchte immer sagen: Bevor ihr über die Größe von Abhängigkeitsbäumen in anderen Sprachökosystemen herzieht, führt erst mal wc -l go.sum aus
  • Ich habe die Seite geöffnet, „Hey, dipshit.“ gesehen und sie sofort wieder geschlossen

  • Hat dasselbe Problem wie die meisten Texte, die Programmiersprachen hochjubeln. Sie konzentrieren sich weniger darauf, wie großartig die aktuelle Sprache ist, sondern mehr darauf, wie schrecklich die vorherige Sprache war
    Der Autor scheint mit Ruby und TypeScript, vielleicht auch Python, stark gelitten zu haben, und Go hat das für ihn gelöst. Aber ich benutze weder Ruby noch TypeScript, deshalb hat mich der Text kaum angesprochen
    Es fühlt sich an, als hätte ich Dutzende Varianten davon über Jahre hinweg gelesen. Benutz Haskell, weil es im Gegensatz zu Python und JavaScript statische Typen hat. Benutz Rust, weil man es im Gegensatz zu Perl und Erlang als einzelnes Binary ausliefern kann. Benutz Elixir, weil es im Gegensatz zu Ruby und Tcl vernünftige Nebenläufigkeit und Channels hat
    Ich freue mich, dass der Autor eine Sprache gefunden hat, die zu ihm passt, aber ich werde seinem Rat nicht folgen

    • Hier scheinen einige zu glauben, sie müssten Go den Lobsters-Lesern verkaufen. Auf manche Leute kann das sogar kontraproduktiv wirken
  • Der Zero Value von Go hat sich für mich immer wie ein Makel angefühlt. Ich finde, es wäre besser, wenn Benutzer gezwungen würden, Default-Werte explizit anzugeben. Davon abgesehen ist es, gemessen daran, dass es nicht OCaml ist, eine ziemlich gute Sprache

    • Ich mag den Zero Value und halte ihn für ziemlich clever. Aber eine Möglichkeit zum Setzen von Default-Werten fehlt wirklich. Zum Beispiel ist es sehr schwer, ein JSON-Objekt zu marshallen, bei dem ein fehlendes bool den Wert true haben soll
  • Das Deployment- und Kompiliererlebnis ist großartig, aber ich hasse es wirklich, die Sprache selbst zu schreiben. Jedes Mal ist es eine schlechte Erfahrung. Gibt es andere Sprachen mit einem guten Deployment-Erlebnis, die nicht so einschränkend sind wie Go?
    Übersehe ich bei Go vielleicht irgendetwas?
    Ich habe kürzlich eine kleine Rails-Anwendung deployt und musste so viel konfigurieren, dass ich Gos Vorteile definitiv mehr zu schätzen wusste

    • Ich habe vor Kurzem angefangen, Rust-Projekte für x86_64-unknown-linux-musl zu kompilieren. Dadurch bekommt man ein statisches Binary, das einfach auf jeder 64-Bit-Linux-Maschine läuft. Danach kopiere ich es per scp rüber und starte es
      Das Problem, noch einen Port zuzuweisen und es manuell zu starten, bleibt zwar, aber ich plane, das mit ein bisschen systemd-Magie zu lösen
    • Beim Deployment-Erlebnis hatten wir im Unternehmen überraschend großen Erfolg mit einem nix bundler. Zur Einordnung: Wir entwickeln eine Qt6-GUI-Anwendung
      Mit dem Bundler kann man eine einzelne ausführbare Datei erzeugen und sie auf Linux-Maschinen mit anderen Distributionen abwerfen; selbst wenn Qt nicht installiert ist, kann der Nutzer einfach die Datei starten und die komplette GUI läuft
      Es gibt allerdings einen Haken bei OpenGL-Treibern. Es geht weiterhin, aber es wird komplizierter als „kopieren und ausführen“
  • Das größte Problem ist für mich, dass Go angeblich für Nebenläufigkeit entworfen wurde, aber eingebaute rohe Zeiger hat, die man versehentlich leicht teilen kann

  • Gegen Langeweile an sich ist nichts einzuwenden, aber ich finde, Go scheitert auffällig daran, wirklich eine langweilige Sprache zu sein
    Es heißt zwar, „es gibt keine Decorators“, aber es gibt struct tags und Reflection. Wie diese Dinge zusammenspielen, ist oft schwer zu verstehen, bevor man es ausführt
    Strukturelle Interfaces und Reflection sind beängstigende Quellen für Verhaltensänderungen an weit entfernten Stellen. Man fügt einem Struct nur eine falsche Methode hinzu, und schon kann sich das Verhalten einer Bibliothek komplett ändern
    Auch aus Dokumentationssicht ist das seltsam. Warum sollte man nicht klar sichtbar machen wollen, dass ein Typ ein bestimmtes Interface erfüllen soll?
    Warum nennt man Goroutines nicht einfach Threads?
    Warum müssen Channels ein Sprachfeature sein? Meiner Meinung nach, weil Go zehn Jahre zu spät anerkannt hat, dass Generics auch für mehr als drei Typen nützlich sind

    • Goroutines sind keine Threads, sondern eine leichtere Abstraktion, die auf einem Thread-Pool läuft. Deshalb kann man problemlos Tausende Goroutines erzeugen
      Dass Channels Teil der Laufzeit sind, liegt meiner Meinung nach daran, dass der Goroutine-Scheduler Channels kennen muss, damit er empfangende Goroutines leichter aufwecken kann, wenn ein Channel nicht mehr leer ist. Wahrscheinlich war dieser Ansatz einfacher
    • Goroutines sind eben Green Threads mit ein paar zusätzlichen Hilfsmitteln