Warum Go Design für intelligente Programmierer schlecht ist

In den letzten Monaten habe ich Go für Implementierungen verwendet. Konzeptioneller Beweiß (ca.: Code zum Testen der Funktionalität einer Idee) in seiner Freizeit, teilweise um die Programmiersprache selbst zu studieren. Die Programme selbst sind sehr einfach und nicht Gegenstand dieses Artikels, aber die Erfahrung mit Go selbst verdient ein paar Worte darüber. Go verspricht zu sein (ca.: Artikel aus dem Jahr 2015) ist eine beliebte Sprache für seriösen skalierbaren Code. Die Sprache wurde von Google erstellt und wird dort aktiv verwendet. Unterm Strich bin ich ehrlich gesagt der Meinung, dass das Design der Go-Sprache schlecht für kluge Programmierer ist.

Für schwache Programmierer konzipiert?

Go ist sehr einfach zu erlernen, so einfach, dass die Einführung einen Abend gedauert hat und ich danach bereits produktiv programmieren konnte. Das Buch, mit dem ich Go gelernt habe, heißt Eine Einführung in die Programmierung in Go (Übersetzung), ist online verfügbar. Das Buch ist, wie der Go-Quellcode selbst, leicht zu lesen, enthält gute Codebeispiele und enthält etwa 150 Seiten, die am Stück gelesen werden können. Diese Einfachheit ist zunächst erfrischend, insbesondere in einer Programmierwelt voller überkomplizierter Technologie. Doch am Ende stellt sich früher oder später der Gedanke: „Ist das wirklich so?“

Google behauptet, die Einfachheit von Go sei sein Verkaufsargument und die Sprache sei auf maximale Produktivität in großen Teams ausgelegt, aber ich bezweifle das. Es gibt Funktionen, die entweder fehlen oder zu detailliert sind. Und das alles aus mangelndem Vertrauen in die Entwickler und der Annahme, dass sie nichts richtig machen können. Dieser Wunsch nach Einfachheit war eine bewusste Entscheidung der Designer der Sprache, und um vollständig zu verstehen, warum er notwendig war, müssen wir die Motivation der Entwickler verstehen und wissen, was sie mit Go erreichen wollten.

Warum wurde es so einfach gemacht? Hier ein paar Zitate Rob Pike (ca.: einer der Mitschöpfer der Go-Sprache):

Der entscheidende Punkt hierbei ist, dass unsere Programmierer (ca.: Googler) sind keine Forscher. Sie sind in der Regel recht jung, kommen nach dem Studium zu uns, vielleicht haben sie Java, C/C++ oder Python studiert. Sie können keine großartige Sprache verstehen, aber gleichzeitig möchten wir, dass sie gute Software erstellen. Deshalb sollte ihre Sprache für sie leicht zu verstehen und zu erlernen sein.
 
Er sollte bekannt sein, grob gesagt ähnlich wie C. Programmierer, die bei Google arbeiten, beginnen ihre Karriere früh und sind meist mit prozeduralen Sprachen, insbesondere der C-Familie, vertraut. Die Anforderung an eine schnelle Produktivität in einer neuen Programmiersprache bedeutet, dass die Sprache nicht zu radikal sein sollte.

Was? Rob Pike sagt also im Grunde, dass die Entwickler bei Google nicht so gut sind, deshalb haben sie eine Sprache für Idioten geschaffen (ca.: verdummt), damit sie etwas tun können. Was für ein arroganter Blick auf die eigenen Kollegen? Ich habe immer geglaubt, dass die Entwickler von Google aus den klügsten und besten der Welt ausgewählt werden. Sicherlich können sie mit etwas Schwierigerem umgehen?

Artefakte von übermäßiger Einfachheit

Einfachheit ist bei jedem Design ein erstrebenswertes Ziel, und der Versuch, etwas Einfaches zu schaffen, ist schwierig. Wenn man jedoch versucht, komplexe Probleme zu lösen (oder sogar auszudrücken), ist manchmal ein komplexes Werkzeug erforderlich. Komplexität und Komplexität sind nicht die besten Eigenschaften einer Programmiersprache, aber es gibt einen Mittelweg, auf dem die Sprache elegante Abstraktionen erzeugen kann, die leicht zu verstehen und zu verwenden sind.

Nicht sehr ausdrucksstark

Aufgrund seines Strebens nach Einfachheit fehlen in Go Konstrukte, die in anderen Sprachen als natürlich wahrgenommen werden. Das mag auf den ersten Blick wie eine gute Idee erscheinen, in der Praxis führt es jedoch zu ausführlichem Code. Der Grund dafür sollte offensichtlich sein – es muss für Entwickler einfach sein, den Code anderer Leute zu lesen, aber tatsächlich beeinträchtigen diese Vereinfachungen nur die Lesbarkeit. In Go gibt es keine Abkürzungen: entweder viel oder nichts.

Ein Konsolendienstprogramm, das stdin oder eine Datei aus Befehlszeilenargumenten liest, würde beispielsweise so aussehen:

package main

import (
    "bufio"
    "flag"
    "fmt"
    "log"
    "os"
)

func main() {

    flag.Parse()
    flags := flag.Args()

    var text string
    var scanner *bufio.Scanner
    var err error

    if len(flags) > 0 {

        file, err := os.Open(flags[0])

        if err != nil {
            log.Fatal(err)
        }

        scanner = bufio.NewScanner(file)

    } else {
        scanner = bufio.NewScanner(os.Stdin)
    }

    for scanner.Scan() {
        text += scanner.Text()
    }

    err = scanner.Err()
    if err != nil {
        log.Fatal(err)
    }

    fmt.Println(text)
}

Obwohl dieser Code auch versucht, so allgemein wie möglich zu sein, stört die erzwungene Ausführlichkeit von Go, und als Ergebnis führt die Lösung eines einfachen Problems zu einer großen Menge an Code.

Hier ist zum Beispiel eine Lösung für das gleiche Problem D:

import std.stdio, std.array, std.conv;

void main(string[] args)
{
    try
    {
        auto source = args.length > 1 ? File(args[1], "r") : stdin;
        auto text   = source.byLine.join.to!(string);

        writeln(text);
    }
    catch (Exception ex)
    {
        writeln(ex.msg);
    }
}

Und wer ist jetzt besser lesbar? Ich gebe D meine Stimme. Sein Code ist viel besser lesbar, weil er die Aktionen klarer beschreibt. D verwendet viel komplexere Konzepte (ca.: Alternativer Funktionsaufruf и Vorlagen) als im Go-Beispiel, aber es ist wirklich nicht kompliziert, sie zu verstehen.

Hölle des Kopierens

Ein beliebter Vorschlag zur Verbesserung von Go ist die Allgemeingültigkeit. Dies wird zumindest dazu beitragen, unnötiges Kopieren von Code zur Unterstützung aller Datentypen zu vermeiden. Beispielsweise kann eine Funktion zum Summieren einer Liste von Ganzzahlen auf keine andere Weise implementiert werden, als durch Kopieren und Einfügen ihrer Grundfunktion für jeden Ganzzahltyp; es gibt keine andere Möglichkeit:

package main

import "fmt"

func int64Sum(list []int64) (uint64) {
    var result int64 = 0
    for x := 0; x < len(list); x++ {
        result += list[x]
    }
    return uint64(result)
}

func int32Sum(list []int32) (uint64) {
    var result int32 = 0
    for x := 0; x < len(list); x++ {
        result += list[x]
    }
    return uint64(result)
}

func int16Sum(list []int16) (uint64) {
    var result int16 = 0
    for x := 0; x < len(list); x++ {
        result += list[x]
    }
    return uint64(result)
}

func int8Sum(list []int8) (uint64) {
    var result int8 = 0
    for x := 0; x < len(list); x++ {
        result += list[x]
    }
    return uint64(result)
}

func main() {

    list8  := []int8 {1, 2, 3, 4, 5}
    list16 := []int16{1, 2, 3, 4, 5}
    list32 := []int32{1, 2, 3, 4, 5}
    list64 := []int64{1, 2, 3, 4, 5}

    fmt.Println(int8Sum(list8))
    fmt.Println(int16Sum(list16))
    fmt.Println(int32Sum(list32))
    fmt.Println(int64Sum(list64))
}

Und dieses Beispiel funktioniert nicht einmal für signierte Typen. Dieser Ansatz verstößt völlig gegen den Grundsatz, sich nicht zu wiederholen (TROCKEN), eines der bekanntesten und offensichtlichsten Prinzipien, dessen Missachtung die Quelle vieler Fehler ist. Warum macht Go das? Das ist ein schrecklicher Aspekt der Sprache.

Gleiches Beispiel auf D:

import std.stdio;
import std.algorithm;

void main(string[] args)
{
    [1, 2, 3, 4, 5].reduce!((a, b) => a + b).writeln;
}

Schlicht, elegant und direkt auf den Punkt. Die hier verwendete Funktion ist reduce für Vorlagentyp und Prädikat. Ja, das ist wieder komplizierter als die Go-Version, aber für kluge Programmierer nicht so schwer zu verstehen. Welches Beispiel ist einfacher zu pflegen und leichter zu lesen?

Einfacher Systembypass

Ich kann mir vorstellen, dass Go-Programmierer, die das lesen, Schaum vor dem Mund haben und schreien werden: „Du machst es falsch!“ Nun, es gibt eine andere Möglichkeit, eine generische Funktion und Typen zu erstellen, aber diese macht das Typsystem völlig kaputt!

Schauen Sie sich dieses Beispiel einer dummen Sprachkorrektur an, um das Problem zu umgehen:

package main

import "fmt"
import "reflect"

func Reduce(in interface{}, memo interface{}, fn func(interface{}, interface{}) interface{}) interface{} {
    val := reflect.ValueOf(in)

    for i := 0; i < val.Len(); i++ {
        memo = fn(val.Index(i).Interface(), memo)
    }

    return memo
}

func main() {

    list := []int{1, 2, 3, 4, 5}

    result := Reduce(list, 0, func(val interface{}, memo interface{}) interface{} {
        return memo.(int) + val.(int)
    })

    fmt.Println(result)
}

Diese Implementierung Reduce wurde dem Artikel entlehnt Idiomatische Generika in Go (ca.: Ich konnte die Übersetzung nicht finden, ich würde mich freuen, wenn Sie dabei helfen). Nun, wenn es idiomatisch ist, würde ich es hassen, ein nicht-idiomatisches Beispiel zu sehen. Verwendung interface{} - eine Farce, und in der Sprache wird sie nur benötigt, um das Tippen zu umgehen. Dies ist eine leere Schnittstelle, die von allen Typen implementiert wird, sodass jeder völlige Freiheit hat. Dieser Programmierstil ist furchtbar hässlich, und das ist noch nicht alles. Akrobatische Kunststücke wie diese erfordern den Einsatz von Laufzeitreflexion. Sogar Rob Pike mag keine Personen, die dies missbrauchen, wie er in einem seiner Berichte erwähnte.

Dies ist ein leistungsstarkes Tool, das mit Vorsicht verwendet werden sollte. Es sollte vermieden werden, es sei denn, es ist unbedingt erforderlich.

Ich würde D-Vorlagen anstelle dieses Unsinns nehmen. Wie kann das jemand sagen? interface{} besser lesbar oder sogar typsicher?

Die Probleme des Abhängigkeitsmanagements

Go verfügt über ein integriertes Abhängigkeitssystem, das auf beliebten Hosting-Anbietern aufbaut VCS. Die mit Go gelieferten Tools kennen diese Dienste und können auf einen Schlag Code von ihnen herunterladen, erstellen und installieren. Das ist zwar großartig, aber es gibt einen großen Fehler bei der Versionierung! Ja, es stimmt, dass Sie den Quellcode mit Go-Tools von Diensten wie Github oder Bitbucket erhalten können, aber Sie können die Version nicht angeben. Und wieder Einfachheit auf Kosten der Nützlichkeit. Ich bin nicht in der Lage, die Logik einer solchen Entscheidung zu verstehen.

Nachdem das Go-Entwicklungsteam Fragen zu einer Lösung für dieses Problem gestellt hatte, erstellte es Forenthread, in dem dargelegt wurde, wie sie dieses Problem umgehen würden. Ihre Empfehlung war, eines Tages einfach das gesamte Repository in Ihr Projekt zu kopieren und es so zu belassen, wie es ist. Was zum Teufel denken sie? Wir verfügen über erstaunliche Versionskontrollsysteme mit hervorragender Tag-Kennzeichnung und Versionsunterstützung, die die Go-Ersteller ignorieren und einfach den Quellcode kopieren.

Kulturelles Gepäck aus Xi

Meiner Meinung nach wurde Go von Leuten entwickelt, die C ihr ganzes Leben lang verwendet hatten, und von denen, die nichts Neues ausprobieren wollten. Die Sprache kann als C mit zusätzlichen Rädern beschrieben werden(orig.: Stützräder). Es enthält keine neuen Ideen, außer der Unterstützung für Parallelität (was übrigens wunderbar ist), und das ist eine Schande. Sie haben eine hervorragende Parallelität in einer kaum verwendbaren, lahmen Sprache.

Ein weiteres knarrendes Problem ist, dass Go eine prozedurale Sprache ist (wie der stille Horror von C). Am Ende schreiben Sie Code in einem prozeduralen Stil, der sich archaisch und veraltet anfühlt. Ich weiß, dass objektorientierte Programmierung kein Allheilmittel ist, aber es wäre großartig, die Details in Typen zu abstrahieren und eine Kapselung bereitzustellen.

Einfachheit zum eigenen Vorteil

Go wurde so konzipiert, dass es einfach ist, und dieses Ziel wird erreicht. Es wurde für schwache Programmierer geschrieben und nutzte eine alte Sprache als Vorlage. Im Lieferumfang sind einfache Werkzeuge enthalten, mit denen Sie einfache Dinge erledigen können. Es ist leicht zu lesen und einfach zu bedienen.

Es ist äußerst ausführlich, unscheinbar und schlecht für kluge Programmierer.

Danke mersinvald für Bearbeitungen

Source: habr.com

Kommentar hinzufügen