Proč je Go špatný pro neinteligentní programátory

Článek byl napsán jako reakce na dříve publikovaný antipodský článek.

Proč je Go špatný pro neinteligentní programátory

Během posledních více než dvou let jsem pomocí Go implementoval specializovaný server RADIUS s vyvinutým fakturačním systémem. Po cestě se učím složitosti jazyka samotného. Samotné programy jsou velmi jednoduché a nejsou účelem tohoto článku, ale zkušenost s používáním samotného Go si zaslouží pár slov na jeho obranu. Go se stává stále běžnějším jazykem 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 NEinteligentní programátory špatný.

Určeno pro slabé programátory?

Slabí mluví o problémech. Silné řeči o nápadech a snech...

Go je velmi snadné se naučit, tak snadné, že můžete číst kód prakticky bez jakéhokoli školení. Tato vlastnost jazyka se používá v mnoha globálních společnostech, když je kód čten společně s vedlejšími specialisty (manažeři, zákazníci atd.). To je velmi výhodné pro metodiky jako Design Driven Development.
Dokonce i začínající programátoři začnou po týdnu nebo dvou produkovat docela slušný kód. Kniha, ze které jsem studoval, je „Go Programming“ (od Marka Summerfielda). Kniha je velmi dobrá, dotýká se mnoha nuancí jazyka. Po zbytečně komplikovaných jazycích jako Java, PHP je nedostatek magie osvěžující. Ale dříve nebo později, mnoho omezených programátorů má nápad použít staré metody v novém oboru. Je to opravdu nutné?

Rob Pike (hlavní ideolog jazyka) vytvořil jazyk Go jako průmyslový jazyk, který je snadno srozumitelný a efektivní při používání. Jazyk je navržen pro maximální produktivitu ve velkých týmech a o tom není pochyb. Mnoho začínajících programátorů si stěžuje, že existuje mnoho funkcí, které jim chybí. 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 několik citátů od Roba Pikea:

Klíčovým bodem je, že naši programátoři 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 měl být jazyk snadno srozumitelný a snadno se naučit.

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í.

Moudrá slova, že?

Artefakty jednoduchosti

Jednoduchost je nezbytnou podmínkou krásy. Lev Tolstoj.

Zachovat jednoduchost je jedním z nejdůležitějších cílů každého designu. Jak víte, dokonalý projekt není projekt, kde není co přidat, ale projekt, ze kterého není co ubírat. Mnoho lidí věří, že k řešení (nebo dokonce vyjádření) složitých problémů je zapotřebí komplexní nástroj. Nicméně není. Vezměme si například jazyk PERL. Jazykoví ideologové věřili, že programátor by měl mít alespoň tři různé způsoby, jak vyřešit jeden problém. Ideologové jazyka Go se vydali jinou cestou, rozhodli se, že k dosažení cíle stačí jedna cesta, ale opravdu dobrá. Tento přístup má vážný základ: jediný způsob je snazší se naučit a těžší zapomenout.

Mnoho migrantů si stěžuje, že jazyk neobsahuje elegantní abstrakce. Ano, to je pravda, ale to je jedna z hlavních výhod jazyka. Jazyk obsahuje minimum magie – ke čtení programu tedy nejsou potřeba žádné hluboké znalosti. Co se týče upovídanosti kódu, není to vůbec problém. Dobře napsaný program Golang se čte vertikálně, s malou nebo žádnou strukturou. Navíc rychlost čtení programu je minimálně o řád vyšší než rychlost jeho zápisu. Pokud uvážíte, že veškerý kód má jednotné formátování (provádí se pomocí vestavěného příkazu gofmt), pak přečtení pár řádků navíc není vůbec problém.

Ne moc expresivní

Umění netoleruje, když je jeho svoboda omezována. Přesnost není jeho odpovědností.

Kvůli touze po jednoduchosti Go postrádá konstrukty, které v jiných jazycích lidé na ně zvyklí vnímají jako něco přirozeného. Zpočátku to může být poněkud nepohodlné, ale pak si všimnete, že program se čte mnohem snadněji a jednoznačněji.

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

    scanner := newScanner(flag.Args())

    var text string
    for scanner.Scan() {
        text += scanner.Text()
    }

    if err := scanner.Err(); err != nil {
        log.Fatal(err)
    }

    fmt.Println(text)
}

func newScanner(flags []string) *bufio.Scanner {
    if len(flags) == 0 {
        return bufio.NewScanner(os.Stdin)
    }

    file, err := os.Open(flags[0])

    if err != nil {
        log.Fatal(err)
    }

    return bufio.NewScanner(file)
}

Řešení stejného problému v D, i když vypadá poněkud kratší, není o nic jednodušší na čtení

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

Peklo kopírování

Člověk v sobě nosí peklo. Martin Luther.

Začátečníci si neustále stěžují na Go, pokud jde o nedostatek generik. K vyřešení tohoto problému většina z nich používá přímé kopírování kódu. Například funkce pro sčítání seznamu celých čísel, tito případní profesionálové se domnívají, že funkci nelze implementovat jiným způsobem než prostým kopírováním a vkládáním pro každý datový typ.

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 main() {

    list32 := []int32{1, 2, 3, 4, 5}
    list64 := []int64{1, 2, 3, 4, 5}

    fmt.Println(int32Sum(list32))
    fmt.Println(int64Sum(list64))
}

Jazyk má dostatečné prostředky k realizaci takových konstrukcí. Například generické programování by bylo v pořádku.

package main

import "fmt"

func Eval32(list []int32, fn func(a, b int32)int32) int32 {
    var res int32
    for _, val := range list {
        res = fn(res, val)
    }
    return res
}

func int32Add(a, b int32) int32 {
    return a + b
}

func int32Sub(a, b int32) int32 {
    return a + b
}

func Eval64(list []int64, fn func(a, b int64)int64) int64 {
    var res int64
    for _, val := range list {
        res = fn(res, val)
    }
    return res
}

func int64Add(a, b int64) int64 {
    return a + b
}

func int64Sub(a, b int64) int64 {
    return a - b
}

func main() {

    list32 := []int32{1, 2, 3, 4, 5}
    list64 := []int64{1, 2, 3, 4, 5}

    fmt.Println(Eval32(list32, int32Add))
    fmt.Println(Eval64(list64, int64Add))
    fmt.Println(Eval64(list64, int64Sub))
}

A přestože se náš kód ukázal být poněkud delší než v předchozím případě, stal se zobecněným. Proto pro nás nebude těžké implementovat všechny aritmetické operace.

Mnozí řeknou, že program v D vypadá výrazně kratší, a budou mít pravdu.

import std.stdio;
import std.algorithm;

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

Je však pouze kratší, ale ne správnější, protože implementace D zcela ignoruje problém zpracování chyb.

V reálném životě, jak se zvyšuje složitost logiky, se mezera rychle zmenšuje. Mezera se uzavírá ještě rychleji, když potřebujete provést akci, kterou nelze provést pomocí standardních jazykových operátorů.

Co se týče udržovatelnosti, rozšiřitelnosti a čitelnosti, podle mě vítězí jazyk Go, i když ztrácí na upovídanosti.

Zobecněné programování nám v některých případech poskytuje nepopiratelné výhody. Jasně to ilustruje třídicí balíček. Abychom mohli seřadit jakýkoli seznam, stačí implementovat rozhraní sort.Interface.

import "sort"

type Names []string

func (ns Names) Len() int {
    return len(ns)
}

func (ns Names) Less(i, j int) bool {
    return ns[i] < ns[j]
}

func (ns Names) Swap(i, j int) {
    ns[i], ns[j] = ns[j], ns[i]
}

func main() {
    names := Names{"London", "Berlin", "Rim"}
    sort.Sort(names)
}

Pokud vezmete jakýkoli open source projekt a spustíte příkaz grep „interface{}“ -R, uvidíte, jak často se používají matoucí rozhraní. Blízko smýšlející soudruzi hned řeknou, že za tím vším je nedostatek generik. Není tomu však vždy tak. Vezměme si jako příklad DELPHI. Navzdory přítomnosti těchto stejných generik obsahuje speciální typ VARIANT pro operace s libovolnými datovými typy. Jazyk Go dělá totéž.

Od děla po vrabce

A svěrací kazajka musí odpovídat velikosti toho šílenství. Stanislav Lec.

Mnoho extrémních fanoušků může tvrdit, že Go má další mechanismus pro tvorbu generik – reflexi. A budou mít pravdu... ale jen ve vzácných případech.

Rob Pike nás varuje:

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é.

Wikipedie nám říká následující:

Reflexe se týká procesu, během kterého může program sledovat a upravovat svou vlastní strukturu a chování během provádění. Programovací paradigma, které je základem reflexe, se nazývá reflexivní programování. Jedná se o typ metaprogramování.

Jak však víte, za všechno se musí platit. V tomto případě je to:

  • potíže s psaním programů
  • rychlost provádění programu

Odraz musí být proto používán opatrně, jako u zbraně velkého kalibru. Bezmyšlenkovité používání odrazu vede k nečitelným programům, neustálým chybám a nízké rychlosti. Prostě věc pro snobského programátora, aby se mohl pochlubit svým kódem před ostatními, pragmatičtějšími a skromnějšími kolegy.

Kulturní zavazadlo od Xi? Ne, z několika jazyků!

Spolu s majetkem zůstávají dědicům i dluhy.

Navzdory skutečnosti, že mnozí věří, že jazyk je zcela založen na dědictví C, není tomu tak. Jazyk zahrnuje mnoho aspektů nejlepších programovacích jazyků.

syntax

Za prvé, syntaxe gramatických struktur vychází ze syntaxe jazyka C. Výrazný vliv však měl i jazyk DELPHI. Vidíme tedy, že byly zcela odstraněny nadbytečné závorky, které značně snižují čitelnost programu. Jazyk také obsahuje operátor „:=“, který je vlastní jazyku DELPHI. Koncept balíčků je vypůjčen z jazyků jako ADA. Deklarace nepoužitých entit je vypůjčena z jazyka PROLOG.

Sémantika

Balíčky byly založeny na sémantice jazyka DELPHI. Každý balíček zapouzdřuje data a kód a obsahuje soukromé a veřejné entity. To vám umožní snížit rozhraní balíčku na minimum.

Implementační operace metodou delegování byla vypůjčena z jazyka DELPHI.

Kompilace

Ne nadarmo existuje vtip: Go byl vyvinut, když byl kompilován program C. Jednou ze silných stránek jazyka je jeho ultrarychlá kompilace. Myšlenka byla vypůjčena z jazyka DELPHI. Každé balení Go odpovídá modulu DELPHI. Tyto balíčky jsou překompilovány pouze v případě, že je to skutečně nutné. Proto po další úpravě nemusíte kompilovat celý program, ale raději překompilovat pouze změněné balíčky a balíčky, které na těchto změněných balíčcích závisí (a to i tehdy, pouze pokud se změnila rozhraní balíčků).

Konstrukce na vysoké úrovni

Jazyk obsahuje mnoho různých konstrukcí na vysoké úrovni, které v žádném případě nesouvisí s jazyky nižší úrovně, jako je C.

  • Struny
  • Hash tabulky
  • plátky
  • Kachní psaní je vypůjčeno z jazyků, jako je RUBY (kterému bohužel mnozí nerozumí nebo jej plně nevyužívají).

Správa paměti

Správa paměti si obecně zaslouží samostatný článek. Pokud je v jazycích jako C++ ovládání zcela ponecháno na vývojáři, pak v pozdějších jazycích, jako je DELPHI, byl použit model počítání referencí. S tímto přístupem nebyly povoleny cyklické odkazy, protože byly vytvořeny osiřelé shluky, pak má Go vestavěnou detekci takových shluků (jako C#). Kromě toho je garbage collector efektivnější než většina aktuálně známých implementací a lze jej již použít pro mnoho úloh v reálném čase. Jazyk sám rozpoznává situace, kdy lze na zásobníku alokovat hodnotu pro uložení proměnné. To snižuje zatížení správce paměti a zvyšuje rychlost programu.

Souběh a souběh

Paralelnost a konkurenceschopnost jazyka nelze chválit. Žádný nízkoúrovňový jazyk nemůže ani vzdáleně konkurovat Go. Abychom byli spravedliví, stojí za zmínku, že model nebyl vynalezen autory jazyka, ale byl jednoduše vypůjčen ze starého dobrého jazyka ADA. Jazyk je schopen zpracovat miliony paralelních připojení pomocí všech CPU, přičemž má řádově méně složité problémy s uváznutím a závody, které jsou typické pro vícevláknový kód.

Další výhody

Pokud to bude ziskové, všichni se stanou nesobeckými.

Jazyk nám také poskytuje řadu nepochybných výhod:

  • Jediný spustitelný soubor po sestavení projektu výrazně zjednodušuje nasazení aplikací.
  • Statické psaní a odvození typu mohou výrazně snížit počet chyb ve vašem kódu, a to i bez psaní testů. Znám některé programátory, kteří se bez psaní testů obejdou úplně a kvalita jejich kódu tím nijak výrazně neutrpí.
  • Velmi jednoduchá křížová kompilace a vynikající přenositelnost standardní knihovny, což výrazně zjednodušuje vývoj aplikací pro různé platformy.
  • Regulární výrazy RE2 jsou bezpečné pro vlákna a mají předvídatelné doby provádění.
  • Výkonná standardní knihovna, která většině projektů umožňuje obejít se bez rámců třetích stran.
  • Jazyk je dostatečně silný na to, aby se soustředil na problém, spíše než na to, jak jej vyřešit, a přesto je dostatečně nízkoúrovňový, aby mohl být problém vyřešen efektivně.
  • Systém Go eco již obsahuje vyvinuté nástroje pro všechny příležitosti: testy, dokumentaci, správu balíčků, výkonné lintry, generování kódu, detektor závodních podmínek atd.
  • Go verze 1.11 zavedla vestavěnou správu sémantických závislostí, postavenou na populárním hostování VCS. Všechny nástroje, které tvoří ekosystém Go, využívají tyto služby ke stažení, sestavování a instalaci kódu z nich jedním tahem. A to je skvělé. S příchodem verze 1.11 byl také zcela vyřešen problém s verzováním balíčků.
  • Protože hlavní myšlenkou jazyka je omezit magii, jazyk motivuje vývojáře k explicitnímu zpracování chyb. A to je správné, protože jinak jednoduše zapomene na zpracování chyb úplně. Další věcí je, že většina vývojářů záměrně ignoruje zpracování chyb a místo jejich zpracování raději chybu jednoduše přepošle nahoru.
  • Jazyk neimplementuje klasickou metodologii OOP, protože v čisté podobě v Go není žádná virtualita. To však není problém při použití rozhraní. Absence OOP výrazně snižuje bariéru vstupu pro začátečníky.

Jednoduchost ve prospěch komunity

Je snadné to zkomplikovat, těžko zjednodušit.

Go byl navržen tak, aby byl jednoduchý a tento cíl splnil. Byl napsán pro chytré programátory, kteří chápou výhody týmové práce a jsou unaveni z nekonečné variability jazyků na úrovni Enterprise. Vzhledem k tomu, že má ve svém arzenálu relativně malou sadu syntaktických struktur, prakticky nepodléhá změnám v čase, takže vývojáři mají spoustu času uvolněného na vývoj, a ne na nekonečné studium jazykových novinek.

Společnosti také získávají řadu výhod: nízká vstupní bariéra jim umožňuje rychle najít specialistu a neměnnost jazyka jim umožňuje používat stejný kód i po 10 letech.

Závěr

Velká velikost mozku nikdy neudělala z žádného slona nositele Nobelovy ceny.

Pro programátory, jejichž osobní ego má přednost před týmovým duchem, stejně jako pro teoretiky, kteří milují akademické výzvy a nekonečné „sebezdokonalování“, je tento jazyk opravdu špatný, protože jde o univerzální řemeslný jazyk, který vám neumožňuje získat estetické potěšení z výsledku vaší práce a ukažte se před kolegy profesionálně (za předpokladu, že inteligenci měříme těmito kritérii, nikoli IQ). Jako všechno v životě je to otázka osobních priorit. Jako všechny inovace, které stojí za to, i jazyk ušel dlouhou cestu od všeobecného popírání k masovému přijetí. Jazyk je geniální ve své jednoduchosti, a jak víte, všechno důmyslné je jednoduché!

Zdroj: www.habr.com

Přidat komentář