Zašto je Go Design loš za pametne programere

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

Dizajniran za slabe programere?

Go je vrlo jednostavan za naučiti, toliko jednostavan da mi je upoznavanje trebalo jednu večer, nakon čega sam već mogao produktivno kodirati. Knjiga koju sam koristio da naučim Go zove se Uvod u programiranje u Gou (prijevod), dostupan je online. Knjiga je, kao i sam Go izvorni kod, laka za čitanje, ima dobre primjere koda i sadrži oko 150 stranica koje se mogu pročitati odjednom. Ova jednostavnost isprva je osvježavajuća, posebno u svijetu programiranja punom prekomplicirane tehnologije. Ali na kraju, prije ili kasnije javlja se misao: "Je li to stvarno tako?"

Google tvrdi da je jednostavnost Goa njegova prodajna prednost i da je jezik dizajniran za maksimalnu produktivnost u velikim timovima, ali sumnjam u to. Postoje značajke koje nedostaju ili su previše detaljne. A sve zbog nepovjerenja u programere, uz pretpostavku da nisu u stanju napraviti ništa kako treba. Ova želja za jednostavnošću bila je svjesna odluka dizajnera jezika, a kako bismo u potpunosti razumjeli zašto je to bilo potrebno, moramo razumjeti motivaciju programera i ono što su pokušavali postići u Gou.

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

Ključna stvar ovdje je da naši programeri (cca.: Zaposleni u Googleu) nisu istraživači. Oni su u pravilu prilično mladi, dolaze kod nas nakon studija, možda su učili Javu, ili C/C++, ili Python. Oni ne razumiju dobar jezik, ali u isto vrijeme želimo da kreiraju dobar softver. Zato im njihov jezik treba biti lak za razumijevanje i učenje.
 
Trebao bi biti poznat, otprilike sličan C-u. Programeri koji rade u Googleu rano započinju svoje karijere i uglavnom su upoznati s proceduralnim jezicima, posebice obitelji C. Zahtjev za brzom produktivnošću u novom programskom jeziku znači da jezik ne bi trebao biti previše radikalan.

Što? Dakle, Rob Pike zapravo govori da programeri u Googleu nisu toliko dobri, da su zato stvorili jezik za idiote (cca.: zaglupljen) kako bi mogli nešto učiniti. Kakav bahat pogled na vlastite kolege? Uvijek sam vjerovao da su Googleovi programeri ručno odabrani među najpametnijima i najboljima na Zemlji. Oni sigurno mogu podnijeti nešto teže?

Artefakti pretjerane jednostavnosti

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

Ne baš izražajno

Zbog svoje predanosti jednostavnosti, Go nema konstrukcije koje se percipiraju kao prirodne u drugim jezicima. To se u početku može činiti kao dobra ideja, ali u praksi rezultira opširnim kodom. Razlog za to trebao bi biti očit - programerima mora biti lako čitati kod drugih ljudi, ali zapravo ta pojednostavljenja samo štete čitljivosti. U Go-u nema kratica: ili puno ili ništa.

Na primjer, uslužni program konzole koji čita stdin ili datoteku iz argumenata naredbenog retka izgledao bi 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đer nastoji biti što općenitiji, Go-ova prisilna opširnost stoji na putu, a kao rezultat toga, rješ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 tko je sada čitaniji? Ja ću dati svoj glas D. Njegov kod je puno čitljiviji jer jasnije opisuje akcije. D koristi mnogo složenije koncepte (cca.: alternativni poziv funkcije и Uzorci) nego u primjeru Go, ali zapravo nema ništa komplicirano u njihovom razumijevanju.

Vraga kopiranja

Popularan prijedlog za poboljšanje Goa je općenitost. To će barem pomoći u izbjegavanju nepotrebnog kopiranja koda za podršku svih vrsta podataka. Na primjer, funkcija za zbrajanje popisa cijelih brojeva ne može se implementirati ni na koji drugi način osim kopiranjem i lijepljenjem svoje osnovne funkcije za svaki tip cijelog broja; 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 ne radi ni za označene tipove. Ovakav pristup u potpunosti krši princip neponavljanja (SUHI), jedno od najpoznatijih i očitih načela čije je ignoriranje izvor mnogih pogreš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 izravno u stvar. Funkcija koja se ovdje koristi je reduce za tip predloška i predikat. Da, ovo je opet kompliciranije od Go verzije, ali pametnim programerima nije tako teško razumjeti. Koji primjer je lakše održavati i lakše čitati?

Premosnica jednostavnog tipa sustava

Pretpostavljam da će Go programeri koji ovo čitaju imati pjenu na ustima i vrištati: "Radiš to pogrešno!" Pa, postoji još jedan način za stvaranje generičke funkcije i tipova, ali on potpuno razbija sustav tipova!

Pogledajte ovaj primjer glupog popravka jezika 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 posuđeno iz članka Idiomatski generički pojmovi u Gou (cca.: Nisam mogao pronaći prijevod, bit će mi drago ako mi pomognete s ovim). Pa, ako je idiomatski, ne bih volio vidjeti neidiomatski primjer. Korištenje interface{} - farsa, au jeziku je potrebno samo da se zaobiđe tipkanje. Ovo je prazno sučelje i svi ga tipovi implementiraju, dopuštajući potpunu slobodu svima. Ovaj stil programiranja je užasno ružan, i to nije sve. Akrobatski podvizi poput ovih zahtijevaju korištenje refleksije tijekom izvođenja. Čak ni Rob Pike ne voli pojedince koji to zlorabe, kako je naveo u jednom od svojih izvještaja.

Ovo je moćan alat koji treba koristiti s oprezom. Treba ga izbjegavati osim ako nije nužno.

Uzeo bih D šablone umjesto ovih gluposti. Kako to netko može reći interface{} čitljiviji ili čak sigurniji za tipkanje?

Problemi upravljanja ovisnošću

Go ima ugrađeni sustav ovisnosti izgrađen na vrhu popularnih pružatelja usluga hostinga VCS. Alati koji dolaze s Goom znaju za te usluge i mogu preuzeti, izgraditi i instalirati kod s njih jednim potezom. Iako je ovo sjajno, postoji veliki nedostatak s verzijama! Da, istina je da možete dobiti izvorni kod od usluga kao što su github ili bitbucket pomoću Go alata, ali ne možete odrediti verziju. I opet jednostavnost na uštrb korisnosti. Ne mogu shvatiti logiku takve odluke.

Nakon postavljanja pitanja o rješenju ovog problema, Go razvojni tim je stvorio nit foruma, koji je opisao kako će zaobići ovaj problem. Njihova je preporuka bila da jednog dana jednostavno kopirate cijeli repozitorij u svoj projekt i ostavite ga "kakav jest". O čemu dovraga razmišljaju? Imamo nevjerojatne sustave kontrole verzija sa sjajnim označavanjem i podrškom za verzije koje Go kreatori ignoriraju i samo kopiraju izvorni kod.

Kulturna prtljaga iz Xija

Po mom mišljenju, Go su razvili ljudi koji su cijeli život koristili C i oni koji nisu htjeli isprobati nešto novo. Jezik se može opisati kao C s dodatnim kotačima (orig.: kotačići za vježbanje). U njemu nema novih ideja, osim podrške za paralelizam (koji je, usput rečeno, divan) i to je šteta. Imate odličan paralelizam u jedva upotrebljivom, jadnom jeziku.

Još jedan škripavi problem je taj što je Go proceduralni jezik (poput tihog užasa C-a). Na kraju pišete kod u proceduralnom stilu koji se čini arhaičnim i zastarjelim. Znam da objektno orijentirano programiranje nije srebrni metak, ali bilo bi sjajno moći apstrahirati detalje u tipove i omogućiti enkapsulaciju.

Jednostavnost za vlastitu korist

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

Izuzetno je opširno, nedojmljivo i loše za pametne programere.

Hvala mersinvald za izmjene

Izvor: www.habr.com

Dodajte komentar