Übersetzung: Ein Jahr mit Go

Ursprünglicher Autor: Andrew Thompson
  • Übersetzung
Under the cut - eine Übersetzung eines Artikels eines erfahrenen Entwicklers über seine Erfahrungen mit Go. Wichtig - Die Meinung des Übersetzers stimmt möglicherweise nicht mit der Meinung des Autors des Artikels überein.




Es ist also ein Jahr her, seit ich angefangen habe, Go zu verwenden. Vor einer Woche habe ich es aus der Produktion gelöscht.

Ich schreibe diesen Beitrag, weil mich im letzten Jahr viele nach meinen Eindrücken von der Arbeit mit Go gefragt haben, und ich möchte Ihnen mehr darüber erzählen, als auf Twitter und IRC möglich ist - bis meine Erinnerungen nachließen.

Lassen Sie uns darüber sprechen, warum ich Go nicht als nützliches Werkzeug betrachte:

Toolkit


Die mit Go gelieferten Tools sind seltsam. Auf den ersten Blick sind viele von ihnen nicht schlecht, aber bei längerer Arbeit zeigen die meisten von ihnen ihre Grenzen. Im Vergleich zu C- oder Erlang- Toolkits sehen sie wie ein nicht so guter Witz aus.

Abdeckungsanalyse


Genau genommen ist das Dienstprogramm zur Analyse der Codeabdeckung in Go ein Hack. Es funktioniert jeweils nur mit einer Datei und fügt dazu Folgendes ein:

GoCover.Count[n] = 1

Wobei n die Kennung der Verzweigungsposition in der Datei ist. Nun, sie fügt am Ende der Datei auch eine so gigantische Struktur ein:

wirklich riesig
var GoCover = struct {
        Count     [7]uint32
        Pos       [3 * 7]uint32
        NumStmt   [7]uint16
} {
        Pos: [3 * 7]uint32{
                3, 4, 0xc0019, // [0]
                16, 16, 0x160005, // [1]
                5, 6, 0x1a0005, // [2]
                7, 8, 0x160005, // [3]
                9, 10, 0x170005, // [4]
                11, 12, 0x150005, // [5]
                13, 14, 0x160005, // [6]
        },
        NumStmt: [7]uint16{
                1, // 0
                1, // 1
                1, // 2
                1, // 3
                1, // 4
                1, // 5
                1, // 6
        },
}


Dieser Ansatz funktioniert für einfache Komponententests in einer einzelnen Datei angemessen. Wenn Sie jedoch eine Abdeckungsanalyse für einen großen Integrationstest erhalten möchten, kann ich Ihnen nur viel Glück wünschen. Bezeichner im globalen Bereich stehen in Konflikt zwischen Dateien, und bei Verwendung eindeutiger Namen gibt es keine einfache Möglichkeit, einen allgemeinen Abdeckungsbericht zu erhalten. Für andere Programmiersprachen funktionieren Lösungen zur Analyse der Codeabdeckung mit dem gesamten Programm, jedoch nicht für einzelne Dateien.

Leistungsanalyse


Das gleiche gilt für das Dienstprogramm zur Leistungsanalyse - es sieht gut aus, bis Sie sehen, wie es funktioniert. Und es funktioniert, indem der Code in eine Schleife mit einer variablen Anzahl von Iterationen eingeschlossen wird. Danach wird die Schleife iteriert, bis der Code "lang genug" ausgeführt wird (standardmäßig 1 Sekunde). Danach wird die Gesamtausführungszeit durch die Anzahl der Iterationen geteilt. Dieser Ansatz bezieht nicht nur den Zyklus selbst in die Leistungsmessung ein, sondern verbirgt auch Schwankungen. Implementierungscode von Benchmark.go :

func (b *B) nsPerOp() int64 {
    if b.N <= 0 {
        return 0
    }
    return b.duration.Nanoseconds() / int64(b.N)
}

Diese Implementierung maskiert die Pausen des Garbage Collector, die mit Ressourcenzuweisungsrennen verbundene Verlangsamung und andere interessante Dinge, wenn sie nicht zu oft auftreten.

Compiler und Tierarzt gehen


Eine der besprochenen Stärken von Go ist die schnelle Kompilierung. Soweit ich das beurteilen kann, wird dies teilweise dadurch erreicht, dass viele der Überprüfungen, die der Compiler normalerweise durchführt, einfach übersprungen werden - sie werden in go vet implementiert . Der Compiler prüft keine Probleme mit denselben Variablennamen in verschiedenen Bereichen oder mit dem falschen printf- Format . Alle diese Prüfungen werden in go vet implementiert . Darüber hinaus verschlechtert sich die Qualität der Überprüfungen in neuen Versionen: In Version 1.3 werden die Probleme, die in Version 1.2 aufgetreten sind, nicht angezeigt .

geh holen


Der Benutzer-Chorus sagt, dass er get nicht verwenden soll, tut jedoch nichts, um es als erfolglose Implementierung zu markieren und einen offiziellen Ersatz zu erstellen.

$ GOPATH


Eine andere Idee, über die ich nicht glücklich bin. Mit viel größerem Vergnügen würde ich das Projekt in das Home-Verzeichnis klonen und das Build-System verwenden, um alle erforderlichen Abhängigkeiten zu installieren. Nicht, dass die Implementierung von $ GOPATH viel Ärger bereitet hätte, aber es ist eine unangenehme Kleinigkeit , die man sich merken sollte.

Gehen Sie Renndetektor


Das ist eine gute Sache. Es ist traurig, dass es allgemein benötigt wird. Nun, die Tatsache, dass es nicht auf allen unterstützten Plattformen funktioniert (FreeBSD, irgendjemand?) Und die maximale Anzahl von Goroutinen beträgt nur 8192. Außerdem müssen Sie es schaffen, auf eine Rennbedingung zu stoßen - was ziemlich schwierig ist, wenn man bedenkt, wie viel Renndetektor alles verlangsamt.

Rantime


Kanäle / Mutexe


Kanäle und Mutexe LANGSAM. Durch Hinzufügen der Synchronisation über Mutexe zur Produktion wurde die Arbeitsgeschwindigkeit so verringert, dass die beste Lösung darin bestand, den Prozess unter Daemontools zu starten und ihn im Falle eines Absturzes neu zu starten.

Protokolle fallen


Wenn go ausnahmslos abstürzt, sendet Goroutine seinen Aufrufstapel an stdout. Die Menge dieser Informationen wächst mit dem Wachstum Ihres Programms. Darüber hinaus sind viele Fehlermeldungen falsch formuliert, z. B. "Evakuierung nicht rechtzeitig" oder "Freelist leer". Es scheint, dass die Autoren dieser Beiträge versucht haben, den Verkehr zur Google-Suchmaschine zu maximieren, da dies in den meisten Fällen der einzige Weg ist, um zu verstehen, was passiert.

Introspection-Laufzeit



Es funktioniert nicht, in der Praxis unterstützt Go das Konzept des "Print-Debugging". Sie können gdb verwenden , aber ich glaube nicht, dass Sie dies tun möchten.

Sprache


Ich schreibe keinen Code gerne auf Go. Ich kämpfe entweder mit einem begrenzten Typsystem mit Kasten von allem in der Schnittstelle {} oder kopiere und füge Code ein, der für verschiedene Typen fast dasselbe tut. Jedes Mal, wenn ich neue Funktionen hinzufüge, führt dies zur Definition von noch mehr Typen und zur Vervollständigung des Codes, um mit ihnen zu arbeiten. Was ist besser als die Verwendung von C mit geeigneten Zeigern oder die Verwendung von Funktionscode mit komplexen Typen?

Anscheinend habe ich auch Probleme, Zeiger in Go zu verstehen (mit C.es gibt keine solchen Probleme). In vielen Fällen funktionierte das Hinzufügen eines Sternchens zum Code auf magische Weise, obwohl der Compiler beide Optionen fehlerfrei kompilierte. Warum sollte ich mit Zeigern arbeiten, die eine Garbage Collector-Sprache verwenden?

Das Konvertieren von Byte [] in einen String und das Arbeiten mit Arrays / Slices verursacht Probleme. Ja, ich verstehe, warum dies alles getan wurde, aber meiner Meinung nach ist es im Verhältnis zum Rest der Sprache zu niedrig.

Und es gibt [:], ... mit Anhang . Schauen Sie sich das an:

iv = append(iv, truncatedIv[:]...)

Dieser Code erfordert eine manuelle Steuerung, da das Anhängen je nach Größe des Arrays entweder Werte hinzufügt oder Speicher neu zuweist und einen neuen Zeiger zurückgibt. Hallo, guter alter Realloc .

Standardbibliothek


Ein Teil der Standardbibliothek ist nicht schlecht, insbesondere die Kryptografie, die sich zum Besseren von einem einfachen Wrapper über OpenSSL unterscheidet, den die meisten Sprachen Ihnen anbieten. Aber die Dokumentation und alles, was mit den Schnittstellen zu tun hat ... Ich muss oft den Implementierungscode anstelle der Dokumentation lesen, da letztere oft auf nutzlose "Implementierungsmethode X" beschränkt ist.

Die großen Probleme sind die Netzbibliothek. Im Gegensatz zu normalen Netzwerkbibliotheken können in dieser Bibliothek die Parameter der erstellten Sockets nicht geändert werden. Möchten Sie das Flag IP_RECVPKTINFO setzen ? Verwenden Sie die Syscall-Bibliothek, den schlechtesten POSIX-Wrapper, den ich je gesehen habe. Sie können nicht einmal den Dateideskriptor der erstellten Verbindung erhalten, Sie müssen alles über 'syscall' erledigen:

Versteckter Text
fd, err := syscall.Socket(syscall.AF_INET6, syscall.SOCK_DGRAM, 0)
if err != nil {
    rlog.Fatal("failed to create socket", err.Error())
}
rlog.Debug("socket fd is %d\n", fd)
err = syscall.SetsockoptInt(fd, syscall.IPPROTO_IPV6, syscall.IPV6_RECVPKTINFO, 1)
if err != nil {
    rlog.Fatal("unable to set IPV6_RECVPKTINFO", err.Error())
}
err = syscall.SetsockoptInt(fd, syscall.IPPROTO_IPV6, syscall.IPV6_V6ONLY, 1)
if err != nil {
    rlog.Fatal("unable to set IPV6_V6ONLY", err.Error())
}
addr := new(syscall.SockaddrInet6)
addr.Port = UDPPort
rlog.Notice("UDP listen port is %d", addr.Port)
err = syscall.Bind(fd, addr)
if err != nil {
    rlog.Fatal("bind error ", err.Error())
}


Sie werden auch viel Freude daran haben, Byte [] zu empfangen und zu übergeben, wenn Sie 'syscall'-Funktionen aufrufen. Das Erstellen und Entfernen von C- Strukturen aus Go ist nur eine Art positive Ladung.

Vielleicht wird dies nur für die Verwendung von Sockets in Abfrageskripten durchgeführt? Ich weiß es nicht, aber jeder Versuch einer komplexen Netzwerkprogrammierung macht es notwendig, schrecklichen und nicht portablen Code zu schreiben.

Schlussfolgerungen


Ich kann die Bedeutung von Go nicht verstehen. Wenn ich eine Systemsprache benötige, verwende ich C / D / Rust. Wenn ich eine Sprache mit guter Parallelitätsunterstützung benötige, verwende ich Erlang oder Haskell. Die einzige Go-Anwendung, die ich sehe, sind Befehlszeilendienstprogramme, die portabel sein und keine Abhängigkeiten hinzufügen sollten. Ich denke nicht, dass die Sprache für "langlebige" Serveraufgaben gut geeignet ist. Vielleicht sieht es für Ruby / Python / Java-Entwickler attraktiv aus, von denen, wie ich es verstehe, die meisten Entwickler auf Go kamen. Ich schließe auch nicht aus, dass Go angesichts der einfachen Bereitstellung und des guten Rufs der Sprache zum „neuen Java“ wird. Wenn Sie nach einer besseren Version von Ruby / Python / Java suchen, passt Go vielleicht zu Ihnen - aber ich würde nicht empfehlen, Ihre Suche in dieser Sprache zu beenden. Gute Programmiersprachen ermöglichen es Ihnen, als Programmierer zu wachsen. LISP demonstriert die Idee von „Code als Daten“, „C“ zeigt, wie man mit einem Computer auf niedriger Ebene arbeitet, Ruby zeigt, wie man mit Nachrichten und anonymen Funktionen arbeitet, Erlang spricht über Parallelität und Fehlertoleranz, Haskell zeigt ein reales Typsystem und arbeitet ohne Nebenwirkungen, Rust lässt Sie verstehen wie man Speicher für parallelen Code teilt. Aber ich kann nicht sagen, dass ich mit Go etwas gelernt habe.

Jetzt auch beliebt: