Zašto je Go Design loš za pametne programere

Proteklih mjeseci koristio sam Go za implementacije. Dokaz koncepta (cca.: kod za testiranje funkcionalnosti ideje) u slobodno vrijeme, dijelom i za učenje samog programskog jezika. Sami programi su vrlo jednostavni i nisu svrha ovog članka, ali samo iskustvo korištenja Go-a zaslužuje nekoliko riječi o tome. Go obećava da će biti (cca.: članak napisan 2015.) popularan jezik za ozbiljan skalabilan kod. Jezik je kreirao Google, gdje se aktivno koristi. U krajnjoj liniji, iskreno mislim da je dizajn Go jezika loš za pametne programere.

Dizajniran za slabe programere?

Go je vrlo lak za naučiti, toliko lak da mi je uvod oduzeo jedno veče, nakon čega sam već mogao produktivno kodirati. Knjiga koju sam naučio Go zove se Uvod u programiranje u Go (prevod), dostupan je na mreži. Knjiga je, kao i sam izvorni kod Go, laka za čitanje, ima dobre primjere koda i sadrži oko 150 stranica koje se mogu pročitati u jednom dahu. Ova jednostavnost je na početku osvježavajuća, posebno u svijetu programiranja prepunom prekomplicirane tehnologije. Ali na kraju, prije ili kasnije se javlja misao: "Je li to zaista tako?"

Google tvrdi da je Go-ova jednostavnost njegova prodajna tačka i da je jezik dizajniran za maksimalnu produktivnost u velikim timovima, ali sumnjam u to. Postoje karakteristike koje nedostaju ili su previše detaljne. A sve zbog nepovjerenja u programere, uz pretpostavku da ne mogu ništa učiniti kako treba. Ova želja za jednostavnošću bila je svjesna odluka dizajnera jezika, a da bismo u potpunosti razumjeli zašto je to bilo potrebno, moramo razumjeti motivaciju programera i šta su oni pokušavali postići u Go-u.

Pa zašto je to tako jednostavno? Evo nekoliko citata Rob Pike (cca.: jedan od kokreatora Go jezika):

Ključna stvar je da naši programeri (cca.: Google radnici) nisu istraživači. Oni su, po pravilu, prilično mladi, dolaze nam nakon studija, možda su učili Javu, ili C/C++, ili Python. Ne razumiju sjajan jezik, ali u isto vrijeme želimo da kreiraju dobar softver. Zato njihov jezik treba da im bude lak za razumevanje i učenje.
 
On bi trebao biti upoznat, grubo rečeno sličan C. Programeri koji rade u Google-u rano započinju svoju karijeru i uglavnom su upoznati sa proceduralnim jezicima, posebno sa porodicom C. Zahtjev za brzom produktivnošću u novom programskom jeziku znači da jezik ne bi trebao biti previše radikalan.

Šta? Dakle, Rob Pike u osnovi kaže da programeri u Googleu nisu toliko dobri, zato su stvorili jezik za idiote (cca.: zaglupljeni) da bi mogli nešto da urade. Kakav arogantan pogled na sopstvene kolege? Oduvijek sam vjerovao da su Googleovi programeri odabrani od najsjajnijih i najboljih na Zemlji. Sigurno mogu podnijeti nešto teže?

Artefakti pretjerane jednostavnosti

Biti jednostavan je dostojan cilj u svakom dizajnu, a teško je pokušati napraviti nešto jednostavno. Međutim, kada pokušavate riješiti (ili čak izraziti) složene probleme, ponekad je potreban kompleksan alat. Složenost i zamršenost nisu najbolje karakteristike programskog jezika, ali postoji sredina u kojoj jezik može stvoriti elegantne apstrakcije koje je lako razumjeti i koristiti.

Nije baš ekspresivno

Zbog svoje posvećenosti jednostavnosti, Gou nedostaju konstrukcije koje se u drugim jezicima doživljavaju kao prirodne. Ovo u početku može izgledati kao dobra ideja, ali u praksi rezultira opširnim kodom. Razlog za to bi trebao biti očigledan - programerima mora biti lako da čitaju tuđi kod, ali u stvari ova pojednostavljenja samo štete čitljivosti. U Go nema skraćenica: ili puno ili ništa.

Na primjer, uslužni program konzole koji čita stdin ili datoteku iz argumenata komandne linije bi izgledao ovako:

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

Iako ovaj kod takođe pokušava da bude što je moguće opštiji, Go-ova prisilna opširnost stoji na putu, i kao rezultat, rešavanje jednostavnog problema rezultira velikom količinom koda.

Evo, na primjer, rješenja za isti problem u 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);
    }
}

I ko je sada čitljiviji? Daću svoj glas D. Njegov kod je mnogo čitljiviji jer jasnije opisuje radnje. D koristi mnogo složenije koncepte (cca.: alternativni poziv funkcije и templates) nego u Go primjeru, ali zapravo nema ništa komplikovano u njihovom razumijevanju.

Pakao od kopiranja

Popularan prijedlog za poboljšanje Go je općenitost. Ovo će barem pomoći da se izbjegne nepotrebno kopiranje koda za podršku svih tipova podataka. Na primjer, funkcija za zbrajanje liste cijelih brojeva ne može se implementirati ni na koji drugi način osim kopiranjem svoje osnovne funkcije za svaki cjelobrojni tip; ne postoji drugi način:

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

A ovaj primjer čak ne radi ni za potpisane tipove. Ovaj pristup u potpunosti krši princip da se ne ponavljate (DRY), jedan od najpoznatijih i očiglednih principa, zanemarivanje kojeg je izvor mnogih grešaka. Zašto Go to radi? Ovo je užasan aspekt jezika.

Isti primjer na D:

import std.stdio;
import std.algorithm;

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

Jednostavno, elegantno i direktno u stvar. Funkcija koja se ovdje koristi je reduce za tip šablona i predikat. Da, ovo je opet komplikovanije od Go verzije, ali pametnim programerima nije tako teško razumjeti. Koji je primjer lakši za održavanje i lakši za čitanje?

Sistemski premosnik jednostavnog tipa

Zamišljam da će Go programeri koji ovo čitaju imati pjenu na ustima i vrištati: "Radiš to pogrešno!" Pa, postoji još jedan način da se napravi generička funkcija i tipovi, ali potpuno razbija sistem tipova!

Pogledajte ovaj primjer glupog jezičnog popravka kako biste zaobišli problem:

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

Ova implementacija Reduce je pozajmljeno iz članka Idiomatska generika u Go (cca.: Nisam mogao pronaći prijevod, bit će mi drago ako mi pomognete oko ovoga). Pa, ako je idiomatski, ne bih volio vidjeti neidiomatski primjer. Upotreba interface{} - farsa, a u jeziku je potrebno samo da se zaobiđe kucanje. Ovo je prazan interfejs i svi ga tipovi implementiraju, omogućavajući potpunu slobodu za svakoga. Ovaj stil programiranja je užasno ružan, i to nije sve. Akrobatski podvizi poput ovih zahtijevaju korištenje refleksije vremena izvođenja. Čak i Rob Pike ne voli pojedince koji to zloupotrebljavaju, kao što je spomenuo u jednom od svojih izvještaja.

Ovo je moćan alat koji treba koristiti s oprezom. Treba ga izbjegavati osim ako je to strogo neophodno.

Uzeo bih D šablone umjesto ovih gluposti. Kako to neko može reći interface{} čitljivije ili čak bezbednije za pisanje?

Jadi upravljanja zavisnošću

Go ima ugrađeni sistem zavisnosti izgrađen na vrhu popularnih hosting provajdera VCS. Alati koji dolaze uz Go znaju o ovim uslugama i mogu jednim potezom preuzeti, izgraditi i instalirati kod s njih. Iako je ovo sjajno, postoji velika mana s verzijama! Da, istina je da izvorni kod možete dobiti sa servisa kao što su github ili bitbucket koristeći Go alate, ali ne možete odrediti verziju. I opet jednostavnost na račun korisnosti. Nisam u stanju da shvatim logiku takve odluke.

Nakon postavljanja pitanja o rješenju ovog problema, kreirao je Go razvojni tim nit foruma, koji je naznačio kako će zaobići ovaj problem. Njihova preporuka je bila da jednog dana jednostavno kopirate čitavo spremište u svoj projekat i ostavite ga „kakvo jeste“. Šta dođavola oni misle? Imamo fantastične sisteme kontrole verzija sa odličnim označavanjem i podrškom za verzije koje kreatori Go ignorišu i samo kopiraju izvorni kod.

Kulturni prtljag iz Xi

Po mom mišljenju, Go su razvili ljudi koji su koristili C cijeli život i oni koji nisu htjeli isprobati nešto novo. Jezik se može opisati kao C sa dodatnim točkovima (orig.: trenažni točkovi). U njemu nema novih ideja, osim podrške za paralelizam (što je, inače, divno) i ovo je šteta. Imate odličan paralelizam na jedva upotrebljivom, jadnom jeziku.

Još jedan problem koji škripi je što je Go proceduralni jezik (poput tihog horora C-a). Na kraju pišete kod u proceduralnom stilu koji izgleda arhaično i zastarjelo. Znam da objektno orijentisano programiranje nije srebrni metak, ali bi bilo sjajno da se detalji mogu apstrahovati u tipove i obezbediti enkapsulaciju.

Jednostavnost za svoju korist

Go je dizajniran da bude jednostavan i uspijeva u tom cilju. Napisan je za slabe programere, koristeći stari jezik kao šablon. Dolazi u kompletu s jednostavnim alatima za obavljanje jednostavnih stvari. Lako se čita i koristi.

Izuzetno je opsežan, neimpresivan i loš za pametne programere.

Spasibo mersinvald za uređivanje

izvor: www.habr.com

Dodajte komentar