Prečo je Go zlé pre neinteligentných programátorov

Článok bol napísaný ako reakcia na predtým publikovaný článok antipodovský článok.

Prečo je Go zlé pre neinteligentných programátorov

Počas posledných viac ako dvoch rokov používam Go na implementáciu špecializovaného servera RADIUS s vyvinutým fakturačným systémom. Popri tom sa učím zložitosti samotného jazyka. Samotné programy sú veľmi jednoduché a nie sú účelom tohto článku, no samotná skúsenosť s používaním Go si zaslúži pár slov na jeho obranu. Go sa stáva čoraz bežnejším jazykom pre seriózny, škálovateľný kód. Jazyk bol vytvorený spoločnosťou Google, kde sa aktívne používa. Zrátané a podčiarknuté, úprimne si myslím, že dizajn jazyka Go je zlý pre NEinteligentných programátorov.

Určené pre slabých programátorov?

Slabí hovoria o problémoch. Silné reči o nápadoch a snoch...

Go sa veľmi ľahko učí, je taký jednoduchý, že môžete čítať kód prakticky bez akéhokoľvek školenia. Táto vlastnosť jazyka sa používa v mnohých globálnych spoločnostiach, keď sa kód číta spolu s vedľajšími špecialistami (manažéri, zákazníci atď.). To je veľmi výhodné pre metodiky ako Design Driven Development.
Dokonca aj začínajúci programátori začínajú produkovať celkom slušný kód po týždni alebo dvoch. Kniha, z ktorej som študoval, je „Go Programming“ (od Marka Summerfielda). Kniha je veľmi dobrá, dotýka sa mnohých nuáns jazyka. Po zbytočne komplikovaných jazykoch ako Java, PHP je nedostatok mágie osviežujúci. Ale skôr či neskôr mnohí obmedzení programátori majú nápad použiť staré metódy v novej oblasti. Je to naozaj potrebné?

Rob Pike (hlavný ideológ jazyka) vytvoril jazyk Go ako priemyselný jazyk, ktorý je ľahko zrozumiteľný a efektívny na používanie. Jazyk je navrhnutý pre maximálnu produktivitu vo veľkých tímoch a o tom niet pochýb. Mnoho začínajúcich programátorov sa sťažuje, že existuje veľa funkcií, ktoré im chýbajú. Táto túžba po jednoduchosti bola vedomým rozhodnutím dizajnérov jazyka, a aby sme plne pochopili, prečo to bolo potrebné, musíme pochopiť motiváciu vývojárov a to, čo sa v Go snažili dosiahnuť.

Prečo to teda bolo také jednoduché? Tu je pár citátov od Roba Pikea:

Kľúčovým bodom je, že naši programátori nie sú výskumníci. Sú spravidla dosť mladí, prichádzajú k nám po štúdiu, možno študovali Javu, C/C++ alebo Python. Nerozumejú skvelému jazyku, no zároveň chceme, aby vytvorili dobrý softvér. To je dôvod, prečo by mal byť jazyk ľahko zrozumiteľný a osvojiteľný.

Mal by byť známy, zhruba povedané podobne ako C. Programátori pracujúci v Google začínajú svoju kariéru skoro a väčšinou poznajú procedurálne jazyky, najmä rodinu C. Požiadavka rýchlej produktivity v novom programovacom jazyku znamená, že jazyk by nemal byť príliš radikálny.

Múdre slová, však?

Artefakty jednoduchosti

Jednoduchosť je nevyhnutnou podmienkou krásy. Lev Tolstoj.

Udržiavanie jednoduchosti je jedným z najdôležitejších cieľov každého dizajnu. Ako viete, dokonalý projekt nie je projekt, kde nie je čo pridať, ale projekt, z ktorého nie je čo uberať. Mnoho ľudí verí, že na vyriešenie (alebo dokonca vyjadrenie) zložitých problémov je potrebný komplexný nástroj. Avšak nie je. Vezmime si napríklad jazyk PERL. Jazykoví ideológovia verili, že programátor by mal mať aspoň tri rôzne spôsoby, ako vyriešiť jeden problém. Ideológovia jazyka Go sa vydali inou cestou, rozhodli sa, že na dosiahnutie cieľa stačí jedna, ale naozaj dobrá. Tento prístup má vážny základ: jediný spôsob je ľahšie sa naučiť a ťažšie zabudnúť.

Mnohí migranti sa sťažujú, že jazyk neobsahuje elegantné abstrakcie. Áno, to je pravda, ale toto je jedna z hlavných výhod jazyka. Jazyk obsahuje minimum mágie – na čítanie programu teda nie sú potrebné žiadne hlboké znalosti. Čo sa týka výrečnosti kódu, nie je to vôbec problém. Dobre napísaný program Golang číta vertikálne, s malou alebo žiadnou štruktúrou. Navyše rýchlosť čítania programu je minimálne o rádovo väčšia ako rýchlosť jeho zápisu. Ak uvážite, že celý kód má jednotné formátovanie (vykonané pomocou vstavaného príkazu gofmt), potom prečítanie niekoľkých riadkov navyše nie je vôbec problém.

Nie veľmi expresívne

Umenie netoleruje, keď je jeho sloboda obmedzená. Presnosť nie je jeho zodpovednosťou.

Kvôli túžbe po jednoduchosti Go chýbajú konštrukcie, ktoré v iných jazykoch ľudia na ne zvyknutí vnímajú ako niečo prirodzené. Spočiatku to môže byť trochu nepohodlné, ale potom si všimnete, že program sa číta oveľa ľahšie a jednoznačnejšie.

Napríklad pomôcka konzoly, ktorá číta stdin alebo súbor z argumentov príkazového riadku, by vyzerala 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)
}

Riešenie toho istého problému v D, aj keď vyzerá o niečo kratšie, nie je o nič ľahšie čitateľné

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

Pekelné kopírovanie

Človek v sebe nosí peklo. Martina Luthera.

Začiatočníci sa neustále sťažujú na Go z hľadiska nedostatku generík. Na vyriešenie tohto problému väčšina z nich používa priame kopírovanie kódu. Napríklad funkcia na sčítanie zoznamu celých čísel, takíto potenciálni profesionáli sa domnievajú, že funkciu nemožno implementovať iným spôsobom ako jednoduchým kopírovaním a prilepením pre každý typ údajov.

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á dostatočné prostriedky na realizáciu takýchto konštrukcií. Napríklad generické programovanie by bolo v poriadku.

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 hoci sa náš kód ukázal byť o niečo dlhší ako predchádzajúci prípad, zovšeobecnil sa. Preto nebude pre nás ťažké implementovať všetky aritmetické operácie.

Mnohí si povedia, že program v D vyzerá výrazne kratší a budú mať 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 len kratšia, ale nie správnejšia, keďže implementácia D úplne ignoruje problém spracovania chýb.

V reálnom živote, keď sa zložitosť logiky zvyšuje, medzera sa rýchlo zmenšuje. Medzera sa uzatvára ešte rýchlejšie, keď potrebujete vykonať akciu, ktorú nemožno vykonať pomocou štandardných jazykových operátorov.

Z hľadiska udržiavateľnosti, rozšíriteľnosti a čitateľnosti podľa mňa vyhráva jazyk Go, aj keď stráca na výrečnosti.

Zovšeobecnené programovanie nám v niektorých prípadoch poskytuje nepopierateľné výhody. Jasne to ilustruje triediaci balík. Na zoradenie akéhokoľvek zoznamu teda stačí implementovať rozhranie 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)
}

Ak vezmete akýkoľvek open source projekt a spustíte príkaz grep „interface{}“ -R, uvidíte, ako často sa používajú mätúce rozhrania. Blízko zmýšľajúci súdruhovia okamžite povedia, že to všetko je spôsobené nedostatkom generík. Nie je to však vždy tak. Zoberme si DELPHI ako príklad. Napriek prítomnosti týchto rovnakých generík obsahuje špeciálny typ VARIANT pre operácie s ľubovoľnými typmi údajov. Jazyk Go robí to isté.

Od dela až po vrabce

A zvieracia kazajka musí zodpovedať veľkosti šialenstva. Stanislav Lec.

Mnohí extrémni fanúšikovia môžu tvrdiť, že Go má ďalší mechanizmus tvorby generík – reflexiu. A budú mať pravdu... ale len v ojedinelých prípadoch.

Rob Pike nás varuje:

Toto je výkonný nástroj, ktorý by sa mal používať opatrne. Je potrebné sa mu vyhnúť, pokiaľ to nie je nevyhnutne potrebné.

Wikipedia nám hovorí nasledovné:

Reflexia sa vzťahuje na proces, počas ktorého môže program monitorovať a upravovať svoju vlastnú štruktúru a správanie počas vykonávania. Programovacia paradigma, ktorá je základom reflexie, sa nazýva reflektívne programovanie. Toto je typ metaprogramovania.

Ako však viete, za všetko musíte platiť. V tomto prípade je to:

  • ťažkosti s písaním programov
  • rýchlosť vykonávania programu

Odraz sa preto musí používať opatrne, ako pri zbrani veľkého kalibru. Bezmyšlienkovité používanie odrazu vedie k nečitateľnosti programov, neustálym chybám a nízkej rýchlosti. Práve to, aby sa snobský programátor mohol pochváliť kódom pred inými, pragmatickejšími a skromnejšími kolegami.

Kultúrna batožina od Xi? Nie, z viacerých jazykov!

Spolu s majetkom zostali dedičom aj dlhy.

Napriek tomu, že mnohí veria, že jazyk je úplne založený na dedičstve C, nie je to tak. Jazyk zahŕňa mnoho aspektov najlepších programovacích jazykov.

syntax

V prvom rade syntax gramatických štruktúr vychádza zo syntaxe jazyka C. Nemalý vplyv však mal aj jazyk DELPHI. Vidíme teda, že boli úplne odstránené nadbytočné zátvorky, ktoré značne znižujú čitateľnosť programu. Jazyk tiež obsahuje operátor „:=“, ktorý je vlastný jazyku DELPHI. Koncept balíkov je vypožičaný z jazykov ako ADA. Deklarácia nepoužitých entít je prevzatá z jazyka PROLOG.

Sémantika

Balíky boli založené na sémantike jazyka DELPHI. Každý balík zapuzdruje dáta a kód a obsahuje súkromné ​​a verejné entity. To vám umožní znížiť rozhranie balíka na minimum.

Operácia implementácie metódou delegovania bola prevzatá z jazyka DELPHI.

zostavenie

Nie je to bez dôvodu, že existuje vtip: Go bol vyvinutý počas kompilácie programu C. Jednou zo silných stránok jazyka je jeho ultrarýchla kompilácia. Myšlienka bola vypožičaná z jazyka DELPHI. Každé balenie Go zodpovedá modulu DELPHI. Tieto balíky sa prekompilujú iba vtedy, keď je to skutočne potrebné. Preto po ďalšej úprave nemusíte skompilovať celý program, ale radšej prekompilovať len zmenené balíčky a balíčky, ktoré na týchto zmenených balíčkoch závisia (a aj to len vtedy, ak sa zmenili rozhrania balíčkov).

Konštrukcie na vysokej úrovni

Jazyk obsahuje mnoho rôznych konštruktov na vysokej úrovni, ktoré v žiadnom prípade nesúvisia s jazykmi nízkej úrovne, ako je C.

  • Struny
  • Hash tabuľky
  • Plátky
  • Písanie kačíc je vypožičané z jazykov ako RUBY (ktorým, žiaľ, mnohí nerozumejú alebo ich naplno nevyužívajú).

Správa pamäte

Správa pamäte si vo všeobecnosti zaslúži samostatný článok. Ak je v jazykoch ako C++ ovládanie úplne ponechané na vývojára, potom v neskorších jazykoch ako DELPHI bol použitý model počítania referencií. S týmto prístupom neboli povolené cyklické referencie, pretože sa vytvorili osirotené klastre, potom má Go zabudovanú detekciu takýchto klastrov (ako C#). Okrem toho je zberač odpadu efektívnejší ako väčšina v súčasnosti známych implementácií a už sa dá použiť na mnohé úlohy v reálnom čase. Samotný jazyk rozpoznáva situácie, kedy je možné v zásobníku prideliť hodnotu na uloženie premennej. Tým sa zníži zaťaženie správcu pamäte a zvýši sa rýchlosť programu.

Súbeh a súbeh

Paralelnosť a konkurencieschopnosť jazyka je mimo chvály. Žiadny jazyk nízkej úrovne nemôže ani zďaleka konkurovať Go. Aby sme boli spravodliví, stojí za zmienku, že model nevymysleli autori jazyka, ale jednoducho si ho požičali zo starého dobrého jazyka ADA. Jazyk je schopný spracovať milióny paralelných pripojení pomocou všetkých CPU, pričom má rádovo menej zložité problémy s uviaznutiami a závodmi, ktoré sú typické pre viacvláknový kód.

Ďalšie výhody

Ak je to ziskové, každý sa stane nesebeckým.

Jazyk nám tiež poskytuje množstvo nepochybných výhod:

  • Jediný spustiteľný súbor po zostavení projektu výrazne zjednodušuje nasadenie aplikácií.
  • Statické písanie a odvodzovanie typu môže výrazne znížiť počet chýb vo vašom kóde, a to aj bez písania testov. Poznám niektorých programátorov, ktorí sa zaobídu úplne bez písania testov a kvalita ich kódu tým výrazne neutrpí.
  • Veľmi jednoduchá krížová kompilácia a vynikajúca prenosnosť štandardnej knižnice, čo výrazne zjednodušuje vývoj aplikácií naprieč platformami.
  • Regulárne výrazy RE2 sú bezpečné pre vlákna a majú predvídateľné časy vykonania.
  • Výkonná štandardná knižnica, ktorá umožňuje väčšine projektov pracovať bez rámcov tretích strán.
  • Jazyk je dostatočne výkonný na to, aby sa sústredil na problém a nie na to, ako ho vyriešiť, no zároveň dostatočne nízky na to, aby sa problém dal vyriešiť efektívne.
  • Systém Go eco už obsahuje vyvinuté nástroje pre všetky príležitosti: testy, dokumentáciu, správu balíkov, výkonné linters, generovanie kódu, detektor závodných podmienok atď.
  • Go verzia 1.11 zaviedla vstavanú správu sémantických závislostí postavenú na populárnom VCS hostingu. Všetky nástroje, ktoré tvoria ekosystém Go, využívajú tieto služby na sťahovanie, vytváranie a inštaláciu kódu z nich jedným ťahom. A to je skvelé. S príchodom verzie 1.11 sa úplne vyriešil aj problém s verziou balíkov.
  • Pretože hlavnou myšlienkou jazyka je znížiť mágiu, jazyk motivuje vývojárov, aby explicitne riešili chyby. A to je správne, pretože inak jednoducho úplne zabudne na spracovanie chýb. Ďalšia vec je, že väčšina vývojárov zámerne ignoruje spracovanie chýb a namiesto ich spracovania uprednostňuje jednoducho preposlať chybu smerom nahor.
  • Jazyk neimplementuje klasickú metodológiu OOP, keďže v čistej forme v Go nie je žiadna virtualita. Pri používaní rozhraní to však nie je problém. Absencia OOP výrazne znižuje bariéru vstupu pre začiatočníkov.

Jednoduchosť v prospech komunity

Je ľahké to skomplikovať, ťažko zjednodušiť.

Go bol navrhnutý tak, aby bol jednoduchý a podarilo sa mu dosiahnuť tento cieľ. Bol napísaný pre šikovných programátorov, ktorí chápu výhody tímovej práce a sú unavení z nekonečnej variability jazykov na úrovni Enterprise. Keďže má vo svojom arzenáli relatívne malý súbor syntaktických štruktúr, v priebehu času prakticky nepodlieha zmenám, takže vývojári majú veľa času na vývoj, a nie na nekonečné štúdium jazykových inovácií.

Spoločnosti získavajú aj množstvo výhod: nízka vstupná bariéra im umožňuje rýchlo nájsť špecialistu a nemennosť jazyka im umožňuje používať rovnaký kód aj po 10 rokoch.

Záver

Veľká veľkosť mozgu nikdy neurobila zo žiadneho slona nositeľa Nobelovej ceny.

Pre tých programátorov, ktorých osobné ego má prednosť pred tímovým duchom, ako aj pre teoretikov, ktorí milujú akademické výzvy a nekonečné „sebazlepšovanie“, je tento jazyk naozaj zlý, keďže ide o univerzálny remeselný jazyk, ktorý vám neumožňuje dostať estetické potešenie z výsledku vašej práce a ukážte sa pred kolegami profesionálne (za predpokladu, že inteligenciu meriame týmito kritériami a nie IQ). Ako všetko v živote, aj tu ide o osobné priority. Rovnako ako všetky užitočné inovácie, jazyk už prešiel dlhou cestou od všeobecného popierania k masovému prijatiu. Jazyk je geniálny vo svojej jednoduchosti a ako viete, všetko dômyselné je jednoduché!

Zdroj: hab.com

Pridať komentár