Hvorfor Go Design er dårligt for smarte programmører

I løbet af de sidste måneder har jeg brugt Go til implementeringer. Proof of Concept (ca.: kode til at teste funktionaliteten af ​​en idé) i sin fritid, dels for at studere selve programmeringssproget. Selve programmerne er meget enkle og er ikke formålet med denne artikel, men selve oplevelsen af ​​at bruge Go fortjener et par ord om det. Go lover at blive (ca.: artikel skrevet i 2015) et populært sprog for seriøs skalerbar kode. Sproget er skabt af Google, hvor det bruges aktivt. Bundlinjen tror jeg ærligt talt, at designet af Go-sproget er dårligt for smarte programmører.

Designet til svage programmører?

Go er meget let at lære, så let, at introduktionen tog mig en aften, hvorefter jeg allerede kunne kode produktivt. Bogen jeg plejede at lære Go hedder En introduktion til programmering in Go (oversættelse), den er tilgængelig online. Bogen er ligesom selve Go-kildekoden let at læse, har gode kodeeksempler og indeholder omkring 150 sider, der kan læses på én gang. Denne enkelhed er forfriskende i starten, især i en programmeringsverden fyldt med alt for kompliceret teknologi. Men i sidste ende opstår tanken før eller siden: "Er det virkelig sådan?"

Google hævder, at Go's enkelhed er dets salgsargument, og sproget er designet til maksimal produktivitet i store teams, men jeg tvivler på det. Der er funktioner, der enten mangler eller er for detaljerede. Og alt sammen på grund af manglende tillid til udviklere, med den antagelse, at de ikke er i stand til at gøre noget rigtigt. Dette ønske om enkelhed var en bevidst beslutning fra sprogets designere, og for fuldt ud at forstå, hvorfor det var nødvendigt, må vi forstå udviklernes motivation og hvad de forsøgte at opnå i Go.

Så hvorfor blev det gjort så enkelt? Her er et par citater Rob Pike (ca.: en af ​​medskaberne af Go-sproget):

Nøglepunktet her er, at vores programmører (ca.: Googlere) er ikke forskere. De er som regel ret unge, kom til os efter at have studeret, måske studerede de Java eller C/C++ eller Python. De kan ikke forstå et fantastisk sprog, men samtidig vil vi gerne have, at de skaber god software. Derfor skal deres sprog være nemt for dem at forstå og lære.
 
Han burde være bekendt, groft sagt ligner C. Programmører, der arbejder hos Google, starter deres karriere tidligt og er for det meste fortrolige med proceduresprog, især C-familien. Kravet om hurtig produktivitet i et nyt programmeringssprog betyder, at sproget ikke skal være for radikalt.

Hvad? Så Rob Pike siger dybest set, at udviklerne hos Google ikke er så gode, det er derfor, de har skabt et sprog for idioter (ca.: dumbed down), så de er i stand til at gøre noget. Hvad er det for et arrogant blik på dine egne kolleger? Jeg har altid troet, at Googles udviklere er håndplukket blandt de smarteste og bedste på jorden. De kan vel klare noget sværere?

Artefakter af overdreven enkelhed

At være enkel er et værdigt mål i ethvert design, og det er svært at prøve at lave noget enkelt. Men når man forsøger at løse (eller endda udtrykke) komplekse problemer, er det nogle gange nødvendigt med et komplekst værktøj. Kompleksitet og forviklinger er ikke de bedste egenskaber ved et programmeringssprog, men der er en mellemvej, hvor sproget kan skabe elegante abstraktioner, der er lette at forstå og bruge.

Ikke særlig udtryksfuld

På grund af ønsket om enkelhed mangler Go konstruktioner, der opfattes som noget naturligt i andre sprog. Dette kan virke som en god idé i starten, men i praksis resulterer det i en udførlig kode. Årsagen til dette burde være indlysende – det skal være nemt for udviklere at læse andres kode, men faktisk skader disse forenklinger kun læsbarheden. Der er ingen forkortelser i Go: enten meget eller intet.

For eksempel ville et konsolværktøj, der læser stdin eller en fil fra kommandolinjeargumenter, se sådan ud:

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)
}

Selvom denne kode også forsøger at være så generel som muligt, kommer Gos tvungne ordlyd i vejen, og som følge heraf resulterer løsning af et simpelt problem i en stor mængde kode.

Her er for eksempel en løsning på samme problem i 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);
    }
}

Og hvem er mere læsbar nu? Jeg giver min stemme til D. Hans kode er meget mere læsbar, fordi han beskriver handlingerne mere klart. D bruger meget mere komplekse begreber (ca.: alternativt funktionskald и skabeloner) end i Go-eksemplet, men der er virkelig ikke noget kompliceret ved at forstå dem.

Helvede med kopiering

Et populært forslag til forbedring af Go er generalitet. Dette vil i det mindste hjælpe med at undgå unødvendig kopiering af kode for at understøtte alle datatyper. For eksempel kan en funktion til at summere en liste over heltal implementeres på ingen anden måde end ved at kopiere dens grundlæggende funktion for hver heltaltype; der er ingen anden måde:

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))
}

Og dette eksempel virker ikke engang for signerede typer. Denne tilgang er fuldstændig i strid med princippet om ikke at gentage dig selv (TØR), et af de mest berømte og indlysende principper, der ignorerer hvilket er kilden til mange fejl. Hvorfor gør Go dette? Dette er et forfærdeligt aspekt af sproget.

Samme eksempel på D:

import std.stdio;
import std.algorithm;

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

Enkel, elegant og lige til sagen. Funktionen brugt her er reduce for skabelontype og prædikat. Ja, dette er igen mere kompliceret end Go-versionen, men ikke så svært for smarte programmører at forstå. Hvilket eksempel er lettere at vedligeholde og lettere at læse?

Enkel type system bypass

Jeg forestiller mig, at Go-programmører, der læser dette, vil fråde om munden og skrige: "Du gør det forkert!" Nå, der er en anden måde at lave en generisk funktion og typer på, men den bryder fuldstændig typesystemet!

Tag et kig på dette eksempel på en dum sprogløsning for at omgå problemet:

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)
}

Denne implementering Reduce er lånt fra artiklen Idiomatiske generika i Go (ca.: Jeg kunne ikke finde oversættelsen, jeg vil blive glad, hvis du hjælper med dette). Tja, hvis det er idiomatisk, ville jeg nødig se et ikke-idiomatisk eksempel. Brug interface{} - en farce, og på sproget er det kun nødvendigt at omgå skrivning. Dette er en tom grænseflade, og alle typer implementerer den, hvilket giver fuldstændig frihed for alle. Denne programmeringsstil er frygtelig grim, og det er ikke alt. Akrobatiske bedrifter som disse kræver brug af runtime-refleksion. Selv Rob Pike kan ikke lide personer, der misbruger dette, som han nævnte i en af ​​sine rapporter.

Dette er et kraftfuldt værktøj, der bør bruges med forsigtighed. Det bør undgås, medmindre det er strengt nødvendigt.

Jeg ville tage D skabeloner i stedet for dette sludder. Hvordan kan nogen sige det interface{} mere læsbar eller endda skrivesikker?

Afhængighedsforvaltningens elendighed

Go har et indbygget afhængighedssystem bygget oven på populære hostingudbydere VCS. Værktøjerne, der følger med Go, kender til disse tjenester og kan downloade, bygge og installere kode fra dem i ét hug. Selvom dette er fantastisk, er der en stor fejl ved versionering! Ja, du kan faktisk få kildekoden fra tjenester som github eller bitbucket ved hjælp af Go-værktøjer, men du kan ikke angive versionen. Og igen enkelhed på bekostning af anvendelighed. Jeg er ikke i stand til at forstå logikken i en sådan beslutning.

Efter at have stillet spørgsmål om en løsning på dette problem, oprettede Go-udviklingsteamet forum tråd, som skitserede, hvordan de skulle komme rundt om dette problem. Deres anbefaling var blot at kopiere hele depotet ind i dit projekt en dag og lade det være "som det er." Hvad fanden tænker de på? Vi har fantastiske versionskontrolsystemer med fantastisk tagging og versionssupport, som Go-skaberne ignorerer og bare kopierer kildekoden.

Kulturbagage fra Xi

Efter min mening er Go udviklet af folk, der havde brugt C hele deres liv, og af dem, der ikke havde lyst til at prøve noget nyt. Sproget kan beskrives som C med ekstra hjul(orig.: træningshjul). Der er ingen nye ideer i det, bortset fra støtte til parallelisme (som i øvrigt er vidunderligt), og det er en skam. Du har fremragende parallelitet i et knap så brugbart, haltende sprog.

Et andet knirkende problem er, at Go er et proceduresprog (som den tavse rædsel i C). Du ender med at skrive kode i en proceduremæssig stil, der føles arkaisk og forældet. Jeg ved, at objektorienteret programmering ikke er en sølvkugle, men det ville være fantastisk at kunne abstrahere detaljerne i typer og give indkapsling.

Enkelhed til din egen fordel

Go er designet til at være enkelt, og det lykkes med det mål. Det blev skrevet til svage programmører, ved at bruge et gammelt sprog som skabelon. Den leveres komplet med enkle værktøjer til at gøre simple ting. Den er nem at læse og nem at bruge.

Det er ekstremt omfattende, uimponerende og dårligt for smarte programmører.

Tak mersinvald for redigeringer

Kilde: www.habr.com

Tilføj en kommentar