Miks Go Design on nutikatele programmeerijatele halb?

Viimastel kuudel olen rakenduste jaoks Go't kasutanud. Kontseptsiooni tõendamine (u.: kood idee funktsionaalsuse testimiseks) vabal ajal, osaliselt programmeerimiskeele enda õppimiseks. Programmid ise on väga lihtsad ega ole selle artikli eesmärk, kuid Go kasutamise kogemus väärib selle kohta paar sõna. Mine lubab olla (u.: 2015. aastal kirjutatud artikkel) populaarne keel tõsise skaleeritava koodi jaoks. Keele lõi Google, kus seda aktiivselt kasutatakse. Kokkuvõtteks arvan ausalt, et Go keele disain on nutikatele programmeerijatele halb.

Mõeldud nõrkadele programmeerijatele?

Go on väga lihtne õppida, nii lihtne, et sissejuhatus võttis mul ühe õhtu, pärast mida sain juba produktiivselt kodeerida. Raamat, mida ma varem õppisin minema, kannab nime Sissejuhatus programmis Go (tõlkimine), on see veebis saadaval. Raamat, nagu ka Go lähtekood ise, on kergesti loetav, sellel on head koodinäited ja see sisaldab umbes 150 lehekülge, mida saab lugeda ühe istumisega. See lihtsus mõjub esmapilgul värskendavalt, eriti programmeerimismaailmas, mis on täis liiga keerulist tehnoloogiat. Kuid lõpuks tekib varem või hiljem mõte: "Kas see on tõesti nii?"

Google väidab, et Go lihtsus on selle müügiargument ja keel on loodud suurtes meeskondades maksimaalse tootlikkuse tagamiseks, kuid ma kahtlen selles. On funktsioone, mis puuduvad või on liiga üksikasjalikud. Ja seda kõike seetõttu, et arendajate vastu puudub usaldus, eeldades, et nad ei suuda midagi õigesti teha. See lihtsuse soov oli keele disainerite teadlik otsus ja selleks, et täielikult mõista, miks seda vaja oli, peame mõistma arendajate motivatsiooni ja seda, mida nad Go-ga püüdsid saavutada.

Miks see siis nii lihtsaks tehti? Siin on paar tsitaati Rob Pike (u.: üks Go keele kaasloojatest):

Võtmepunkt on siin see, et meie programmeerijad (u.: Google'i töötajad) ei ole teadlased. Nad on reeglina üsna noored, tulevad meile peale õppimist, võib-olla õppisid Java, C/C++ või Python. Nad ei saa suurepärasest keelest aru, kuid samal ajal tahame, et nad looks head tarkvara. Seetõttu peaks nende keelest olema lihtne aru saada ja õppida.
 
Ta peaks olema tuttav, jämedalt öeldes sarnane C-ga. Google'is töötavad programmeerijad alustavad oma karjääri varakult ja tunnevad enamasti protseduurikeeli, eriti C-perekonda. Uue programmeerimiskeele kiire tootlikkuse nõue tähendab, et keel ei tohiks olla liiga radikaalne.

Mida? Nii et Rob Pike ütleb põhimõtteliselt, et Google'i arendajad pole nii head, sellepärast nad lõid keele idiootidele (u.: tummaks), et nad saaksid midagi teha. Milline üleolev pilk teie enda kolleegidele? Olen alati uskunud, et Google'i arendajad on käsitsi valitud Maa eredaimate ja parimate seast. Kindlasti saavad nad hakkama millegi raskemaga?

Ülemäärase lihtsuse artefaktid

Lihtsus on iga kujunduse väärt eesmärk ja millegi lihtsaks tegemine on keeruline. Keerulisi probleeme lahendada (või isegi väljendada) püüdes on aga mõnikord vaja keerukat tööriista. Keerukus ja keerukus pole programmeerimiskeele parimad omadused, kuid on olemas kuldne kesktee, mille puhul keel saab luua elegantseid abstraktsioone, mida on lihtne mõista ja kasutada.

Mitte väga väljendusrikas

Lihtsusele pühendumise tõttu puuduvad Go-l konstruktsioonid, mida teistes keeltes loomulikuna peetakse. See võib alguses tunduda hea ideena, kuid praktikas annab see paljusõnalise koodi. Selle põhjus peaks olema ilmne – arendajatel peab olema lihtne teiste inimeste koodi lugeda, kuid tegelikult kahjustavad need lihtsustused ainult loetavust. Go-s pole lühendeid: kas palju või mitte midagi.

Näiteks konsooliutiliit, mis loeb stdini või faili käsurea argumentidest, näeb välja selline:

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

Kuigi ka see kood püüab olla võimalikult üldine, segab Go pealesunnitud paljusõnalisus ning selle tulemusena tekib lihtsa ülesande lahendamisel suur hulk koodi.

Siin on näiteks lahendus samale probleemile 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);
    }
}

Ja kes on nüüd loetavam? Annan oma hääle D-le. Tema kood on palju loetavam, kuna ta kirjeldab toiminguid selgemalt. D kasutab palju keerulisemaid mõisteid (u.: alternatiivse funktsiooni kutse и malle) kui näites Go, kuid nende mõistmises pole tegelikult midagi keerulist.

Kuradi kopeerimine

Populaarne soovitus Go täiustamiseks on üldistus. See aitab vähemalt vältida koodi asjatut kopeerimist, et toetada kõiki andmetüüpe. Näiteks funktsiooni täisarvude loendi summeerimiseks ei saa rakendada muul viisil kui selle põhifunktsiooni iga täisarvutüübi jaoks kopeerides ja kleepides; muud võimalust pole:

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

Ja see näide ei tööta isegi allkirjastatud tüüpide puhul. Selline lähenemine rikub täielikult põhimõtet mitte korrata ennast (DRY), üks kuulsamaid ja ilmsemaid põhimõtteid, mille eiramine on paljude vigade allikas. Miks Go seda teeb? See on keele kohutav aspekt.

Sama näide D kohta:

import std.stdio;
import std.algorithm;

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

Lihtne, elegantne ja otse asja juurde. Siin kasutatav funktsioon on reduce malli tüübi ja predikaadi jaoks. Jah, see on jällegi keerulisem kui Go versioon, kuid nutikatel programmeerijatel pole seda raske mõista. Millist näidet on lihtsam säilitada ja kergem lugeda?

Lihtsat tüüpi süsteemi ümbersõit

Ma kujutan ette, et Go programmeerijad hakkavad seda lugedes vahutama ja karjuma: "Te teete seda valesti!" Noh, on veel üks võimalus üldise funktsiooni ja tüüpide tegemiseks, kuid see rikub tüübisüsteemi täielikult!

Probleemi lahendamiseks vaadake seda rumala keeleparanduse näidet:

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

See rakendamine Reduce oli artiklist laenatud Idiomaatilised geneerilised ravimid Go-s (u.: Ma ei leidnud tõlget, mul on hea meel, kui aitate sellega). Noh, kui see on idiomaatiline, siis ma ei sooviks näha mitteidiomaatilist näidet. Kasutamine interface{} - farss ja keeles on seda vaja ainult tippimisest mööda hiilimiseks. See on tühi liides ja kõik tüübid rakendavad seda, võimaldades kõigile täielikku vabadust. Selline programmeerimisstiil on kohutavalt kole ja see pole veel kõik. Sellised akrobaatilised saavutused nõuavad käitusaegse peegelduse kasutamist. Isegi Rob Pike'ile ei meeldi inimesed, kes seda kuritarvitavad, nagu ta ühes oma raportis mainis.

See on võimas tööriist, mida tuleks kasutada ettevaatusega. Seda tuleks vältida, kui see pole tingimata vajalik.

Ma võtaks selle jama asemel D mallid. Kuidas saab keegi nii öelda interface{} loetavam või isegi tüübikindel?

Sõltuvusjuhtimise hädad

Go'l on sisseehitatud sõltuvussüsteem, mis on üles ehitatud populaarsetele hostiteenuse pakkujatele VCS. Go-ga kaasas olevad tööriistad teavad neid teenuseid ning saavad neist koodi ühe hoobiga alla laadida, luua ja installida. Kuigi see on suurepärane, on versioonide loomisel suur viga! Jah, on tõsi, et Go tööriistade abil saate lähtekoodi hankida sellistest teenustest nagu github või bitbucket, kuid te ei saa versiooni määrata. Ja jälle lihtsus kasulikkuse arvelt. Ma ei suuda sellise otsuse loogikast aru saada.

Pärast selle probleemi lahenduse kohta küsimuste esitamist lõi Go arendusmeeskond foorumi teema, milles kirjeldati, kuidas nad kavatsevad sellest probleemist mööda hiilida. Nende soovitus oli ühel päeval lihtsalt kogu hoidla oma projekti kopeerida ja jätta see "nagu on". Mida kuradit nad mõtlevad? Meil on suurepärased versioonihaldussüsteemid, millel on suurepärane sildistamine ja versiooni tugi, mida Go loojad ignoreerivad ja lihtsalt kopeerivad lähtekoodi.

Xi kultuuripagas

Minu arvates on Go arendanud inimesed, kes olid C-d terve elu kasutanud, ja need, kes ei tahtnud midagi uut proovida. Keelt võib kirjeldada kui C koos lisaratastega (orig.: treeningrattad). Selles pole uusi ideid, välja arvatud paralleelsuse toetamine (mis, muide, on imeline) ja sellest on kahju. Teil on suurepärane paralleelsus vaevu kasutatavas ja labases keeles.

Teine krigisev probleem on see, et Go on protseduuriline keel (nagu C vaikne õudus). Lõppkokkuvõttes kirjutate koodi protseduurilises stiilis, mis tundub arhailine ja aegunud. Ma tean, et objektorienteeritud programmeerimine ei ole tühine täpp, kuid oleks tore, kui osataks üksikasju tüüpidesse abstraktselt ja kapseldada.

Lihtsus teie enda kasuks

Go loodi lihtsaks ja see saavutab selle eesmärgi. See oli kirjutatud nõrkadele programmeerijatele, kasutades mallina vana keelt. See on varustatud lihtsate tööriistadega lihtsate asjade tegemiseks. Seda on lihtne lugeda ja seda on lihtne kasutada.

See on äärmiselt sõnasõnaline, muljetavaldav ja nutikatele programmeerijatele halb.

Tänan mersinvald toimetuste jaoks

Allikas: www.habr.com

Lisa kommentaar