Miért rossz a Go Design az intelligens programozóknak?

Az elmúlt hónapokban a Go-t használtam implementációkhoz. Proof of Concept (kb.: kód egy ötlet működőképességének tesztelésére) szabadidejében, részben magának a programozási nyelvnek a tanulmányozására. Maguk a programok nagyon egyszerűek, és nem ez a cikk célja, de maga a Go használatának tapasztalata megérdemel néhány szót róla. A go azt ígéri, hogy (kb.: 2015-ben írt cikk) egy népszerű nyelv komoly méretezhető kódokhoz. A nyelvet a Google hozta létre, ahol aktívan használják. A lényeg, őszintén úgy gondolom, hogy a Go nyelv kialakítása rossz az okos programozóknak.

Gyenge programozóknak tervezték?

A Go nagyon könnyen megtanulható, olyan könnyű, hogy egy estébe telt a bemutatkozás, ami után már tudtam eredményesen kódolni. A könyv, amit régebben tanultam Go, az úgynevezett Bevezetés a Go programozásba (fordítás), elérhető az interneten. A könyv, akárcsak maga a Go forráskód, könnyen olvasható, jó kódpéldákkal rendelkezik, és körülbelül 150 oldalt tartalmaz, amelyeket egyhuzamban el lehet olvasni. Ez az egyszerűség elsőre üdítő, különösen a túlbonyolított technológiával teli programozási világban. De a végén előbb-utóbb felvetődik a gondolat: "Ez tényleg így van?"

A Google azt állítja, hogy a Go egyszerűsége az eladási pontja, és a nyelvet a nagy csapatok maximális termelékenységére tervezték, de kétlem. Vannak olyan funkciók, amelyek hiányoznak vagy túlságosan részletesek. És mindez a fejlesztőkbe vetett bizalom hiánya miatt, azzal a feltételezéssel, hogy semmit sem tudnak jól csinálni. Ez az egyszerűség iránti vágy tudatos döntés volt a nyelv tervezőitől, és ahhoz, hogy teljes mértékben megértsük, miért volt erre szükség, meg kell értenünk a fejlesztők motivációját és azt, hogy mit próbáltak elérni a Go-val.

Akkor miért lett ilyen egyszerű? Íme néhány idézet Rob Pike (kb.: a Go nyelv egyik társalkotója):

A lényeg itt az, hogy programozóink (kb.: Google-alkalmazottak) nem kutatók. Ők általában elég fiatalok, tanulás után jönnek hozzánk, esetleg Java-t, C/C++-t vagy Python-t tanultak. Nem értenek egy nagyszerű nyelvet, de ugyanakkor azt akarjuk, hogy jó szoftvereket készítsenek. Éppen ezért nyelvüknek könnyen érthetőnek és megtanulhatónak kell lennie.
 
Ismerősnek kell lennie, nagyjából C-hez hasonlóan. A Google-nál dolgozó programozók korán kezdik pályafutásukat, és többnyire ismerik az eljárási nyelveket, különösen a C családot. Az új programozási nyelv gyors termelékenységének követelménye azt jelenti, hogy a nyelv nem lehet túl radikális.

Mit? Tehát Rob Pike alapvetően azt mondja, hogy a Google fejlesztői nem olyan jók, ezért hoztak létre egy nyelvet az idióták számára (kb.: lebutították), hogy képesek legyenek valamit tenni. Milyen arrogáns pillantás a saját kollégáira? Mindig is azt hittem, hogy a Google fejlesztőit a Föld legfényesebb és legjobbjai közül választották ki. Biztosan tudnak valami nehezebbet kezelni?

Túlzott egyszerűség műtermékei

Az egyszerűség minden tervezésnél méltó cél, és nehéz valami egyszerűt csinálni. Ha azonban összetett problémákat próbálunk megoldani (vagy akár kifejezni), néha összetett eszközre van szükség. A komplexitás és a bonyolultság nem a legjobb tulajdonsága a programozási nyelvnek, de van egy boldog közeg, amelyben egy nyelv elegáns absztrakciókat hozhat létre, amelyek könnyen érthetők és használhatók.

Nem túl kifejező

Az egyszerűség iránti elkötelezettsége miatt a Go-ból hiányoznak a más nyelvekben természetesnek tekintett konstrukciók. Ez elsőre jó ötletnek tűnhet, de a gyakorlatban bőbeszédű kódot eredményez. Ennek okának nyilvánvalónak kell lennie – a fejlesztők számára könnyen el kell tudni olvasni mások kódját, de valójában ezek az egyszerűsítések csak rontják az olvashatóságot. A Go-ban nincsenek rövidítések: vagy sok, vagy semmi.

Például egy konzol-segédprogram, amely beolvassa az stdin-t vagy egy fájlt a parancssori argumentumokból, így néz ki:

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

Bár ez a kód is igyekszik a lehető legáltalánosabb lenni, a Go erőltetett bőbeszédűsége akadályozza, és ennek eredményeként egy egyszerű probléma megoldása nagy mennyiségű kódot eredményez.

Itt van például egy megoldás ugyanarra a problémára 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);
    }
}

És most ki az olvasottabb? A voksomat D-re adom. A kódja sokkal olvashatóbb, mert világosabban írja le a műveleteket. A D sokkal összetettebb fogalmakat használ (kb.: alternatív függvényhívás и Minták), mint a Go példában, de valójában nincs semmi bonyolult ezek megértésében.

Pokoli másolás

A Go fejlesztésének népszerű javaslata az általánosság. Ez legalább segít elkerülni a kód szükségtelen másolását az összes adattípus támogatásához. Például egy egész számok listájának összegzésére szolgáló függvényt nem lehet másképpen megvalósítani, mint az alapfunkciók bemásolásával minden egész számtípushoz, nincs más mód:

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

És ez a példa nem is működik az aláírt típusoknál. Ez a megközelítés teljesen sérti azt az elvet, hogy ne ismételd magad (DRY), az egyik leghíresebb és legnyilvánvalóbb elv, amelynek figyelmen kívül hagyása sok hiba forrása. Miért csinálja ezt a Go? Ez a nyelv borzalmas aspektusa.

Ugyanez a példa a D-n:

import std.stdio;
import std.algorithm;

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

Egyszerű, elegáns és egyenesen a lényegre. Az itt használt függvény a reduce sablontípushoz és állítmányhoz. Igen, ez megint bonyolultabb, mint a Go verzió, de az okos programozók számára nem olyan nehéz megérteni. Melyik példa könnyebben karbantartható és könnyebben olvasható?

Egyszerű típusú rendszer bypass

Elképzelem, hogy a Go programozói, akik ezt olvassák, habzik a szájukból, és azt kiabálják: „Rosszul csinálod!” Nos, van más mód is egy általános függvény és típus elkészítésére, de az teljesen megbontja a típusrendszert!

Tekintse meg ezt a példát egy hülye nyelvi javításra a probléma megkerüléséhez:

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

Ez a megvalósítás Reduce a cikkből kölcsönözték Idiomatikus általánosságok a Go-ban (kb.: Nem találtam a fordítást, örülök, ha segítesz ebben). Nos, ha idiomatikus, nem szívesen látok nem idiomatikus példát. Használat interface{} - bohózat, és a nyelven csak a gépelés megkerüléséhez szükséges. Ez egy üres felület, és minden típus megvalósítja, így mindenki számára teljes szabadságot biztosít. Ez a programozási stílus pokolian csúnya, és ez még nem minden. Az ehhez hasonló akrobatikus bravúrokhoz futásidejű tükrözés szükséges. Még Rob Pike sem szereti azokat az egyéneket, akik visszaélnek ezzel, ahogy azt az egyik jelentésében említette.

Ez egy hatékony eszköz, amelyet óvatosan kell használni. El kell kerülni, hacsak nem feltétlenül szükséges.

Én D sablonokat vennék e hülyeség helyett. Hogy mondhat ilyet bárki interface{} olvashatóbb vagy akár gépbiztosabb?

A függőségkezelés bajai

A Go beépített függőségi rendszerrel rendelkezik, amely a népszerű tárhelyszolgáltatókra épül VCS. A Go-hoz tartozó eszközök ismerik ezeket a szolgáltatásokat, és egy csapásra kódot tölthetnek le, készíthetnek és telepíthetnek belőlük. Bár ez nagyszerű, van egy nagy hibája a verziószámításnak! Igen, valóban beszerezheti a forráskódot olyan szolgáltatásokból, mint a github vagy a bitbucket a Go eszközök segítségével, de nem adhatja meg a verziót. És ismét az egyszerűség a hasznosság rovására. Nem tudom megérteni egy ilyen döntés logikáját.

Miután kérdéseket tett fel a probléma megoldásával kapcsolatban, a Go fejlesztőcsapata létrehozta fórum téma, amely felvázolta, hogyan fogják megkerülni ezt a kérdést. Azt javasolták, hogy egy nap egyszerűen másold be a teljes adattárat a projektedbe, és hagyd „úgy ahogy van”. Mi a fenére gondolnak? Csodálatos verziókezelő rendszereink vannak nagyszerű címkézéssel és verziótámogatással, amelyeket a Go készítői figyelmen kívül hagynak, és csak a forráskódot másolják.

Kulturális poggyász Xi-től

Véleményem szerint a Go-t olyan emberek fejlesztették ki, akik egész életükben használták a C-t, és olyanok, akik nem akartak valami újat kipróbálni. A nyelv C-vel írható le extra kerekekkel(orig.: edzőkerekek). Nincsenek benne új ötletek, kivéve a párhuzamosság támogatását (ami egyébként csodálatos) és ez kár. Kiváló párhuzamosságod van egy alig használható, béna nyelven.

Egy másik csikorgó probléma, hogy a Go egy procedurális nyelv (mint a C néma horrorja). A végén archaikusnak és elavultnak tűnő eljárási stílusban ír kódot. Tudom, hogy az objektumorientált programozás nem egy ezüst golyó, de jó lenne, ha a részleteket típusokba tudnánk vonni, és beágyazott lenne.

Egyszerűség a saját érdekedben

A Go-t egyszerűnek tervezték, és ezt a célt eléri. Gyenge programozóknak íródott, egy régi nyelvet használva sablonként. Egyszerű eszközökkel rendelkezik az egyszerű dolgok elvégzéséhez. Könnyen olvasható és könnyen használható.

Rendkívül bőbeszédű, nem lenyűgöző, és rossz az okos programozóknak.

Köszönöm mersinvald szerkesztésekhez

Forrás: will.com

Hozzászólás