Zakaj je Go Design slab za pametne programerje

V zadnjih mesecih sem uporabljal Go za implementacije. Dokaz koncepta (pribl.: koda za testiranje funkcionalnosti ideje) v prostem času, deloma za študij samega programskega jezika. Sami programi so zelo preprosti in niso namen tega članka, vendar si sama izkušnja uporabe Go zasluži nekaj besed o tem. Go obljublja, da bo (pribl.: članek napisan leta 2015) priljubljen jezik za resno razširljivo kodo. Jezik je ustvaril Google, kjer se aktivno uporablja. Pod črto, iskreno mislim, da je zasnova jezika Go slaba za pametne programerje.

Zasnovan za šibke programerje?

Go se je zelo enostavno naučiti, tako enostavno, da mi je uvajanje vzelo en večer, po katerem sem že lahko produktivno kodiral. Knjiga, s katero sem se učil Go, se imenuje Uvod v programiranje v Go (prevod), je na voljo na spletu. Knjiga, tako kot sama izvorna koda Go, je enostavna za branje, ima dobre primere kode in vsebuje približno 150 strani, ki jih je mogoče prebrati naenkrat. Ta preprostost je na začetku osvežujoča, zlasti v svetu programiranja, polnem preveč zapletene tehnologije. Toda na koncu se prej ali slej pojavi misel: "Je res tako?"

Google trdi, da je preprostost Go njegova prodajna točka in da je jezik zasnovan za največjo produktivnost v velikih skupinah, vendar dvomim v to. Obstajajo funkcije, ki manjkajo ali so preveč podrobne. In vse zaradi pomanjkanja zaupanja v razvijalce, s predpostavko, da ne morejo narediti ničesar prav. Ta želja po preprostosti je bila zavestna odločitev oblikovalcev jezika in da bi popolnoma razumeli, zakaj je bil potreben, moramo razumeti motivacijo razvijalcev in kaj so poskušali doseči v Go.

Zakaj je bilo torej narejeno tako preprosto? Tukaj je nekaj citatov Rob Pike (pribl.: eden od soustvarjalcev jezika Go):

Ključno pri tem je, da naši programerji (pribl.: Googlovci) niso raziskovalci. Praviloma so precej mladi, pridejo k nam po študiju, morda so študirali Javo, ali C/C++ ali Python. Ne morejo razumeti odličnega jezika, a hkrati želimo, da ustvarijo dobro programsko opremo. Zato mora biti njihov jezik enostaven za razumevanje in učenje.
 
Moral bi biti znan, grobo rečeno podoben C. Programerji, ki delajo pri Googlu, svojo kariero začnejo zgodaj in večinoma poznajo proceduralne jezike, zlasti družino C. Zahteva po hitri produktivnosti v novem programskem jeziku pomeni, da jezik ne sme biti preveč radikalen.

Kaj? Torej Rob Pike v bistvu pravi, da razvijalci pri Googlu niso tako dobri, zato so ustvarili jezik za idiote (pribl.: poneumljeni), da lahko nekaj naredijo. Kakšen aroganten pogled na lastne kolege? Vedno sem verjel, da so Googlovi razvijalci ročno izbrani med najpametnejšimi in najboljšimi na Zemlji. Zagotovo lahko prenesejo kaj težjega?

Artefakti pretirane preprostosti

Biti preprost je vreden cilj pri vsakem dizajnu in težko je poskušati narediti nekaj preprostega. Ko poskušate rešiti (ali celo izraziti) zapletene probleme, je včasih potrebno zapleteno orodje. Kompleksnost in zapletenost nista najboljši lastnosti programskega jezika, vendar obstaja srednja pot, v kateri lahko jezik ustvari elegantne abstrakcije, ki jih je enostavno razumeti in uporabljati.

Ni zelo ekspresivno

Zaradi svoje zavezanosti k preprostosti Go nima konstruktov, ki se v drugih jezikih dojemajo kot naravni. To se na prvi pogled morda zdi dobra ideja, vendar v praksi povzroči besedno kodo. Razlog za to bi moral biti očiten - razvijalcem mora biti enostavno branje kode drugih ljudi, a te poenostavitve dejansko samo škodijo berljivosti. V Go ni okrajšav: ali veliko ali nič.

Na primer, pripomoček konzole, ki bere stdin ali datoteko iz argumentov ukazne vrstice, bi bil videti takole:

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

Čeprav tudi ta koda skuša biti čim bolj splošna, jo moti vsiljena dobesednost Goja, posledično pa reševanje preproste težave povzroči veliko količino kode.

Tukaj je na primer rešitev istega problema v 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);
    }
}

In kdo je zdaj bolj berljiv? Jaz bom dal svoj glas D. Njegova koda je veliko bolj berljiva, ker bolj jasno opisuje dejanja. D uporablja veliko bolj zapletene koncepte (pribl.: alternativni klic funkcije и predloge) kot v primeru Go, vendar v resnici ni nič zapletenega pri njihovem razumevanju.

Hudičevo kopiranje

Priljubljen predlog za izboljšanje Go je splošnost. To bo vsaj pomagalo preprečiti nepotrebno kopiranje kode za podporo vseh vrst podatkov. Na primer, funkcije za seštevanje seznama celih števil ni mogoče implementirati na noben drug način kot s kopiranjem in lepljenjem njene osnovne funkcije za vsak tip celega števila; ni drugega načina:

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

In ta primer ne deluje niti za podpisane tipe. Ta pristop popolnoma krši načelo neponavljanja (DRY), eno najbolj znanih in očitnih načel, ignoriranje katerega je vir številnih napak. Zakaj Go to počne? To je grozen vidik jezika.

Enak primer na D:

import std.stdio;
import std.algorithm;

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

Enostavno, elegantno in naravnost do bistva. Tukaj uporabljena funkcija je reduce za tip predloge in predikat. Da, to je spet bolj zapleteno kot različica Go, vendar ni tako težko razumljivo za pametne programerje. Kateri primer je lažje vzdrževati in lažje brati?

Preprost tip sistema bypass

Predstavljam si, da se bodo programerji Go, ki bodo to brali, penili na usta in kričali: "Delaš narobe!" No, obstaja še en način za ustvarjanje generične funkcije in tipov, vendar popolnoma poruši tipski sistem!

Oglejte si ta primer neumnega jezikovnega popravka za rešitev težave:

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

Ta izvedba Reduce je bila izposojena iz članka Idiomatični generiki v Go (pribl.: Nisem našel prevoda, vesel bom, če mi boste pri tem pomagali). No, če je idiomatsko, ne bi želel videti neidiomatskega primera. Uporaba interface{} - farsa, v jeziku pa je potreben le za izogibanje tipkanju. To je prazen vmesnik in vse vrste ga uporabljajo, kar omogoča popolno svobodo za vsakogar. Ta slog programiranja je strašno grd in to še ni vse. Akrobatski podvigi, kot so ti, zahtevajo uporabo refleksije med izvajanjem. Tudi Rob Pike ne mara posameznikov, ki to zlorabljajo, kot je omenil v enem od svojih poročil.

To je močno orodje, ki ga je treba uporabljati previdno. Izogibati se je treba, razen če je to nujno potrebno.

Namesto te neumnosti bi vzel D šablone. Kako lahko kdo to reče interface{} bolj berljiv ali celo varen za tip?

Teže obvladovanja odvisnosti

Go ima vgrajen sistem odvisnosti, zgrajen poleg priljubljenih ponudnikov gostovanja VCS. Orodja, ki so priložena Go, poznajo te storitve in lahko z enim zamahom prenesejo, zgradijo in namestijo kodo iz njih. Čeprav je to super, obstaja velika napaka pri različicah! Da, res je, da lahko z orodji Go dobite izvorno kodo iz storitev, kot sta github ali bitbucket, vendar ne morete določiti različice. In spet preprostost na račun uporabnosti. Ne morem razumeti logike takšne odločitve.

Po vprašanjih o rešitvi tega problema je razvojna ekipa Go ustvarila forumska nit, ki je orisal, kako bodo rešili to težavo. Njihovo priporočilo je bilo, da nekega dne preprosto kopirate celotno skladišče v svoj projekt in ga pustite »kot je«. Kaj za vraga razmišljajo? Imamo neverjetne sisteme za nadzor različic z odličnim označevanjem in podporo za različice, ki jih ustvarjalci Go ignorirajo in preprosto kopirajo izvorno kodo.

Kulturna prtljaga iz Xi

Po mojem mnenju so Go razvili ljudje, ki so vse življenje uporabljali C, in tisti, ki niso želeli poskusiti nečesa novega. Jezik lahko opišemo kot C z dodatnimi kolesi (orig.: vadbena kolesa). V njem ni novih idej, razen podpore za vzporednost (ki je, mimogrede, čudovita) in to je škoda. Imaš odličen paralelizem v komaj uporabnem, hromem jeziku.

Druga škripajoča težava je, da je Go proceduralni jezik (kot tiha groza C). Na koncu pišete kodo v proceduralnem slogu, ki se zdi arhaičen in zastarel. Vem, da objektno usmerjeno programiranje ni srebrna paleta, vendar bi bilo super, če bi lahko abstrahirali podrobnosti v vrste in zagotovili enkapsulacijo.

Enostavnost za lastno korist

Go je zasnovan tako, da je preprost in ta cilj mu tudi uspe. Napisan je bil za šibke programerje z uporabo starega jezika kot predloge. Na voljo je skupaj s preprostimi orodji za preproste stvari. Je enostaven za branje in enostaven za uporabo.

Je izredno podrobno, nevpadljivo in slabo za pametne programerje.

Hvala mersinvald za urejanja

Vir: www.habr.com

Dodaj komentar