Kodėl „Go Design“ blogas išmaniesiems programuotojams

Pastaruosius mėnesius naudoju „Go“ diegimui. Įrodymas koncepcija (apytiksliai: kodas idėjos funkcionalumui išbandyti) laisvalaikiu, iš dalies studijuoti pačią programavimo kalbą. Pačios programos yra labai paprastos ir nėra šio straipsnio tikslas, tačiau pati „Go“ naudojimo patirtis nusipelno kelių žodžių apie tai. Eiti žada būti (apytiksliai: straipsnis, parašytas 2015 m.) populiari rimto keičiamo kodo kalba. Kalbą sukūrė Google, kur ji aktyviai naudojama. Apatinė eilutė, aš nuoširdžiai manau, kad „Go“ kalbos dizainas yra blogas išmaniesiems programuotojams.

Skirta silpniems programuotojams?

Go labai lengva išmokti, taip lengva, kad įžanga užtruko vieną vakarą, po kurio jau galėjau produktyviai koduoti. Knyga, kurią mokiausi „Eiti“, vadinasi „Go“ programavimo įvadas (vertimas), jį galima rasti internete. Knyga, kaip ir pats „Go“ šaltinio kodas, yra lengvai skaitomas, turi gerų kodų pavyzdžių, joje yra apie 150 puslapių, kuriuos galima perskaityti vienu prisėdimu. Šis paprastumas iš pradžių yra gaivus, ypač programavimo pasaulyje, kuriame gausu pernelyg sudėtingų technologijų. Bet galų gale anksčiau ar vėliau kyla mintis: „Ar tai tikrai taip?

„Google“ teigia, kad „Go“ paprastumas yra jo pardavimo taškas, o kalba sukurta maksimaliam produktyvumui didelėse komandose, bet aš tuo abejoju. Yra funkcijų, kurių trūksta arba jos yra pernelyg išsamios. Ir viskas dėl nepasitikėjimo kūrėjais, darant prielaidą, kad jie nesugeba nieko padaryti teisingai. Šis paprastumo troškimas buvo sąmoningas kalbos kūrėjų sprendimas, o norėdami visiškai suprasti, kam to reikėjo, turime suprasti kūrėjų motyvaciją ir tai, ką jie bandė pasiekti „Go“.

Taigi kodėl tai buvo padaryta taip paprasta? Štai keletas citatų Robas Pike'as (apytiksliai: vienas iš Go kalbos kūrėjų):

Svarbiausia yra tai, kad mūsų programuotojai (apytiksliai: „Google“ darbuotojai) nėra tyrinėtojai. Jie, kaip taisyklė, gana jauni, pas mus ateina po studijų, galbūt studijavo Java, C/C++, Python. Jie nesupranta puikios kalbos, bet tuo pat metu norime, kad jie sukurtų gerą programinę įrangą. Štai kodėl jų kalba turėtų būti lengvai suprantama ir išmokta.
 
Jis turėtų būti pažįstamas, grubiai tariant, panašus į C. „Google“ dirbantys programuotojai savo karjerą pradeda anksti ir dažniausiai yra susipažinę su procedūrinėmis kalbomis, ypač C šeima. Greito produktyvumo reikalavimas naudojant naują programavimo kalbą reiškia, kad kalba neturėtų būti per daug radikali.

Ką? Taigi Robas Pike'as iš esmės sako, kad „Google“ kūrėjai nėra tokie geri, todėl jie sukūrė kalbą idiotams (apytiksliai: nutildyti), kad jie galėtų ką nors padaryti. Koks arogantiškas žvilgsnis į savo kolegas? Visada tikėjau, kad „Google“ kūrėjai yra atrinkti iš pačių ryškiausių ir geriausių Žemėje. Neabejotinai jie gali susidoroti su kažkuo sunkesniu?

Perdėto paprastumo artefaktai

Būti paprastam yra vertas bet kokio dizaino tikslas, o bandyti padaryti ką nors paprasto yra sunku. Tačiau bandant išspręsti (ar net išreikšti) sudėtingas problemas kartais prireikia kompleksinio įrankio. Sudėtingumas ir sudėtingumas nėra geriausios programavimo kalbos ypatybės, tačiau yra vidurys, kai kalba gali sukurti elegantiškas abstrakcijas, kurias lengva suprasti ir naudoti.

Nelabai išraiškingas

Dėl savo įsipareigojimo siekti paprastumo, Go trūksta konstrukcijų, kurios kitomis kalbomis būtų suvokiamos kaip natūralios. Iš pradžių tai gali atrodyti gera idėja, tačiau praktiškai tai sukelia žodinį kodą. To priežastis turėtų būti akivaizdi – kūrėjams turi būti lengva perskaityti kitų žmonių kodą, tačiau iš tikrųjų šie supaprastinimai tik kenkia skaitomumui. „Go“ santrumpų nėra: arba daug, arba nieko.

Pavyzdžiui, konsolės įrankis, nuskaitantis stdin arba failą iš komandinės eilutės argumentų, atrodytų taip:

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

Nors šis kodas taip pat stengiasi būti kiek įmanoma bendresnis, Go priverstinis žodiškumas trukdo, todėl sprendžiant paprastą problemą gaunama daug kodo.

Pavyzdžiui, čia yra tos pačios problemos sprendimas 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);
    }
}

O kas dabar skaitomesnis? Savo balsą atiduosiu D. Jo kodas daug lengviau įskaitomas, nes jis aiškiau aprašo veiksmus. D vartoja daug sudėtingesnes sąvokas (apytiksliai: alternatyvios funkcijos skambutis и šablonai) nei pavyzdyje „Go“, tačiau juos suprasti tikrai nėra nieko sudėtingo.

Pragaras kopijavimas

Populiarus „Go“ tobulinimo pasiūlymas yra bendrumas. Tai bent jau padės išvengti nereikalingo kodo kopijavimo, kad būtų palaikomi visų tipų duomenys. Pavyzdžiui, sveikųjų skaičių sąrašo sumavimo funkcija gali būti įgyvendinta tik nukopijuojant-įklijuojant jos pagrindinę funkciją kiekvienam sveikųjų skaičių tipui; kito būdo nėra:

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

Ir šis pavyzdys net netinka pasirašytiems tipams. Toks požiūris visiškai pažeidžia principą nesikartoti (SAUSAS), vienas žinomiausių ir akivaizdžiausių principų, kurio ignoravimas yra daugelio klaidų šaltinis. Kodėl Go tai daro? Tai baisus kalbos aspektas.

Tas pats pavyzdys D:

import std.stdio;
import std.algorithm;

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

Paprasta, elegantiška ir tiesiai į reikalą. Čia naudojama funkcija reduce šablono tipui ir predikatui. Taip, tai vėlgi sudėtingesnė nei „Go“ versija, bet ne taip sunku suprasti išmaniesiems programuotojams. Kurį pavyzdį lengviau prižiūrėti ir lengviau skaityti?

Paprasto tipo sistemos apėjimas

Įsivaizduoju, kad „Go“ programuotojai, skaitydami tai, putos iš burnos ir rėks: „Tu darai neteisingai! Na, yra dar vienas būdas sukurti bendrąją funkciją ir tipus, tačiau tai visiškai suardo tipų sistemą!

Pažvelkite į šį kvailo kalbos taisymo pavyzdį, kad išspręstumėte 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)
}

Šis įgyvendinimas Reduce buvo pasiskolintas iš straipsnio Idiominiai generiniai vaistai Go (apytiksliai: Neradau vertimo, džiaugsiuosi, jei padėsite). Na, jei tai idiomatiška, man nepatiktų matyti ne idiomatinį pavyzdį. Naudojimas interface{} - farsas, o kalba jo reikia tik norint apeiti spausdinimą. Tai tuščia sąsaja ir visi tipai ją įgyvendina, suteikdami visišką laisvę kiekvienam. Toks programavimo stilius siaubingai bjaurus, ir tai dar ne viskas. Tokiems akrobatiniams žygdarbiams reikia naudoti vykdymo laiko apmąstymus. Net Robas Pike'as nemėgsta asmenų, kurie tuo piktnaudžiauja, kaip jis minėjo viename iš savo pranešimų.

Tai galingas įrankis, kurį reikia naudoti atsargiai. To reikėtų vengti, nebent tai griežtai būtina.

Vietoj šitų nesąmonių imčiau D šablonus. Kaip kas nors gali taip pasakyti interface{} skaitomesnis ar net saugus?

Priklausomybės valdymo bėdos

„Go“ turi integruotą priklausomybės sistemą, sukurtą populiarių prieglobos paslaugų teikėjų pagrindu VCS. Su „Go“ pateikiami įrankiai žino apie šias paslaugas ir gali vienu ypu atsisiųsti, sukurti ir įdiegti kodą iš jų. Nors tai puiku, versijų kūrime yra didelis trūkumas! Taip, tiesa, kad šaltinio kodą galite gauti iš tokių paslaugų kaip „github“ ar „bitbucket“, naudodami „Go“ įrankius, tačiau negalite nurodyti versijos. Ir vėl paprastumas naudingumo sąskaita. Negaliu suprasti tokio sprendimo logikos.

Uždavusi klausimus apie šios problemos sprendimą, Go kūrimo komanda sukūrė forumo tema, kuriame nurodyta, kaip jie ketina išspręsti šią problemą. Jų rekomendacija buvo vieną dieną tiesiog nukopijuoti visą saugyklą į savo projektą ir palikti ją „tokia, kokia yra“. Ką po velnių jie galvoja? Turime nuostabias versijų valdymo sistemas su puikiu žymėjimu ir versijų palaikymu, kurių Go kūrėjai nepaiso ir tiesiog nukopijuoja šaltinio kodą.

Kultūrinis bagažas iš Xi

Mano nuomone, „Go“ sukūrė žmonės, kurie visą gyvenimą naudojo C, ir tie, kurie nenorėjo išbandyti kažko naujo. Kalbą galima apibūdinti kaip C su papildomais ratukais (orig.: treniruokliai). Jame nėra jokių naujų idėjų, išskyrus paralelizmo palaikymą (kuris, beje, yra nuostabus) ir tai yra gėda. Turite puikų lygiagretumą vos vartojama, šlapia kalba.

Kita girgždanti problema yra ta, kad Go yra procedūrinė kalba (kaip tylus C siaubas). Jūs rašote kodą procedūriniu stiliumi, kuris atrodo archajiškas ir pasenęs. Žinau, kad į objektą orientuotas programavimas nėra sidabrinė kulka, bet būtų puiku, jei pavyktų detales suskirstyti į tipus ir pateikti inkapsuliaciją.

Paprastumas jūsų pačių naudai

„Go“ buvo sukurta taip, kad būtų paprasta, ir jam pavyksta pasiekti šį tikslą. Jis buvo parašytas silpniems programuotojams, naudojant seną kalbą kaip šabloną. Jis komplektuojamas su paprastais įrankiais, leidžiančiais atlikti paprastus dalykus. Tai lengva skaityti ir paprasta naudoti.

Tai labai žodinis, neįspūdingas ir blogas išmaniesiems programuotojams.

Ačiū mersinvaldas redagavimui

Šaltinis: www.habr.com

Добавить комментарий