Proč je Go Design špatný pro chytré programátory

V posledních měsících jsem pro implementace používal Go. Ověření konceptu (Cca.: kód k testování funkčnosti nápadu) ve volném čase, částečně ke studiu samotného programovacího jazyka. Samotné programy jsou velmi jednoduché a nejsou účelem tohoto článku, ale samotná zkušenost s používáním Go si zaslouží pár slov. Go slibuje, že bude (Cca.: článek napsaný v roce 2015) populární jazyk pro seriózní škálovatelný kód. Jazyk byl vytvořen společností Google, kde se aktivně používá. Sečteno a podtrženo, upřímně si myslím, že design jazyka Go je pro chytré programátory špatný.

Určeno pro slabé programátory?

Go je velmi snadné se naučit, tak snadné, že mi úvod zabral jeden večer, po kterém jsem již mohl produktivně kódovat. Kniha, kterou jsem se naučil Go, se jmenuje Úvod do programování v Go (překlad), je k dispozici online. Kniha se stejně jako samotný zdrojový kód Go snadno čte, má dobré příklady kódu a obsahuje asi 150 stránek, které lze přečíst na jedno posezení. Tato jednoduchost je zpočátku osvěžující, zvláště ve světě programování plném příliš komplikovaných technologií. Ale nakonec se dříve nebo později objeví myšlenka: "Je to opravdu tak?"

Google tvrdí, že jednoduchost Go je jeho prodejním argumentem a jazyk je navržen pro maximální produktivitu ve velkých týmech, ale pochybuji o tom. Existují funkce, které buď chybí, nebo jsou příliš podrobné. A to vše kvůli nedostatku důvěry ve vývojáře, s předpokladem, že nejsou schopni udělat nic správně. Tato touha po jednoduchosti byla vědomým rozhodnutím návrhářů jazyka, a abychom plně pochopili, proč to bylo potřeba, musíme pochopit motivaci vývojářů a to, čeho se v Go snažili dosáhnout.

Proč to tedy bylo tak jednoduché? Zde je pár citátů Rob Pike (Cca.: jeden ze spolutvůrců jazyka Go):

Klíčovým bodem je, že naši programátoři (Cca.: zaměstnanci společnosti Google) nejsou výzkumníci. Jsou zpravidla docela mladí, přicházejí k nám po studiu, možná studovali Javu, C/C++ nebo Python. Nedokážou rozumět skvělému jazyku, ale zároveň po nich chceme, aby vytvořili dobrý software. Proto by pro ně měl být jejich jazyk snadno srozumitelný a naučit se jej.
 
Měl by být známý, zhruba řečeno podobně jako C. Programátoři pracující ve společnosti Google začínají svou kariéru brzy a většinou znají procedurální jazyky, zejména rodinu C. Požadavek na rychlou produktivitu v novém programovacím jazyce znamená, že jazyk by neměl být příliš radikální.

Co? Takže Rob Pike v podstatě říká, že vývojáři v Googlu nejsou tak dobří, proto vytvořili jazyk pro idioty (Cca.: otupělý), aby byli schopni něco udělat. Jaký arogantní pohled na vlastní kolegy? Vždy jsem věřil, že vývojáři Google jsou ručně vybíráni z těch nejchytřejších a nejlepších na Zemi. Určitě zvládnou něco těžšího?

Artefakty přílišné jednoduchosti

Být jednoduchý je hodný cíl v každém designu a snažit se udělat něco jednoduchého je obtížné. Při snaze vyřešit (nebo dokonce vyjádřit) složité problémy je však někdy potřeba komplexní nástroj. Složitost a složitost nejsou nejlepší vlastnosti programovacího jazyka, ale existuje střední cesta, ve které jazyk může vytvářet elegantní abstrakce, které jsou snadno pochopitelné a použitelné.

Ne moc expresivní

Kvůli své oddanosti jednoduchosti postrádá Go konstrukce, které jsou v jiných jazycích vnímány jako přirozené. Na první pohled se to může zdát jako dobrý nápad, ale v praxi to vede k podrobnému kódu. Důvod by měl být zřejmý – pro vývojáře musí být snadné číst kód jiných lidí, ale ve skutečnosti tato zjednodušení pouze poškozují čitelnost. V Go nejsou žádné zkratky: buď hodně, nebo nic.

Například obslužný program konzoly, který čte stdin nebo soubor z argumentů příkazového řádku, by vypadal takto:

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

I když se tento kód také snaží být co nejobecnější, vynucená upovídanost Go překáží a výsledkem řešení jednoduchého problému je velké množství kódu.

Zde je například řešení stejného problému 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);
    }
}

A kdo je teď čitelnější? Dám svůj hlas D. Jeho kód je mnohem čitelnější, protože jasněji popisuje akce. D používá mnohem složitější koncepty (Cca.: volání alternativní funkce и Šablony) než v příkladu Go, ale na jejich pochopení není opravdu nic složitého.

Peklo kopírování

Oblíbeným návrhem na vylepšení Go je obecnost. To alespoň pomůže vyhnout se zbytečnému kopírování kódu pro podporu všech datových typů. Například funkci pro sčítání seznamu celých čísel nelze implementovat jinak než zkopírováním a vložením její základní funkce pro každý typ celého čísla; jiný způsob neexistuje:

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 tento příklad nefunguje ani pro podepsané typy. Tento přístup zcela porušuje zásadu neopakovat se (DRY), jeden z nejznámějších a nejzjevnějších principů, jehož ignorování je zdrojem mnoha chyb. Proč to Go dělá? To je hrozný aspekt jazyka.

Stejný příklad na D:

import std.stdio;
import std.algorithm;

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

Jednoduché, elegantní a přímo k věci. Zde použitá funkce je reduce pro typ šablony a predikát. Ano, toto je opět složitější než verze Go, ale pro chytré programátory to není tak složité na pochopení. Který příklad se snadněji udržuje a čte?

Jednoduchý typ systémového bypassu

Představuji si, že programátoři Go, kteří to čtou, budou mít pěnu u úst a křičet: "Děláš to špatně!" No, existuje jiný způsob, jak vytvořit generickou funkci a typy, ale úplně to rozbije typový systém!

Podívejte se na tento příklad hloupé jazykové opravy, abyste problém vyřešili:

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

Tato implementace Reduce byl vypůjčen z článku Idiomatická generika v Go (Cca.: Překlad jsem nenašel, budu rád, když mi s tím pomůžete). No, pokud je to idiomatické, nerad bych viděl neidiomatický příklad. Používání interface{} - fraška a v jazyce je potřeba pouze obejít psaní. Toto je prázdné rozhraní a všechny typy jej implementují, což umožňuje úplnou svobodu pro každého. Tento styl programování je strašně ošklivý a to není všechno. Akrobatické činy, jako jsou tyto, vyžadují použití odrazu za běhu. Ani Rob Pike nemá rád jedince, kteří toto zneužívají, jak zmínil v jedné ze svých reportáží.

Jedná se o mocný nástroj, který by měl být používán s opatrností. Je třeba se mu vyhnout, pokud to není nezbytně nutné.

Místo těchto nesmyslů bych vzal D šablony. Jak to někdo může říct interface{} čitelnější nebo dokonce typově bezpečné?

Běda řízení závislostí

Go má vestavěný závislý systém postavený na populárních poskytovatelích hostingu VCS. Nástroje dodávané s Go o těchto službách vědí a dokážou z nich stáhnout, sestavit a nainstalovat kód jedním tahem. I když je to skvělé, existuje velká chyba ve verzi! Ano, je pravda, že můžete získat zdrojový kód ze služeb jako github nebo bitbucket pomocí nástrojů Go, ale nemůžete specifikovat verzi. A opět jednoduchost na úkor účelnosti. Nejsem schopen pochopit logiku takového rozhodnutí.

Po dotazech na řešení tohoto problému vytvořil vývojový tým Go vlákno na fóru, který nastínil, jak chtějí tento problém obejít. Jejich doporučení bylo jednoho dne jednoduše zkopírovat celé úložiště do vašeho projektu a nechat jej „tak jak je“. Co si sakra myslí? Máme úžasné systémy pro správu verzí se skvělým značkováním a podporou verzí, které tvůrci Go ignorují a pouze zkopírují zdrojový kód.

Kulturní zavazadlo od Xi

Podle mého názoru byl Go vyvinut lidmi, kteří používali C celý život, a těmi, kteří nechtěli zkoušet něco nového. Jazyk lze popsat jako C s extra kolečky (pův.: tréninková kola). Nejsou v něm žádné nové nápady, kromě podpory paralelismu (který je mimochodem úžasný) a to je škoda. Máte vynikající paralelismus v sotva použitelném, chromém jazyce.

Dalším vrzajícím problémem je, že Go je procedurální jazyk (jako tichý horor C). Nakonec napíšete kód procedurálním stylem, který vám připadá archaický a zastaralý. Vím, že objektově orientované programování není stříbrná kulka, ale bylo by skvělé být schopen abstrahovat detaily do typů a zajistit zapouzdření.

Jednoduchost ve vlastní prospěch

Go byl navržen tak, aby byl jednoduchý a tento cíl splnil. Byl napsán pro slabé programátory, jako šablona byl použit starý jazyk. Dodává se s jednoduchými nástroji pro jednoduché věci. Snadno se čte a snadno se používá.

Je extrémně podrobný, nevýrazný a špatný pro chytré programátory.

Díky mersinvald pro úpravy

Zdroj: www.habr.com

Přidat komentář