Pse Go është e keqe për programuesit jo të zgjuar

Artikulli është shkruar si përgjigje ndaj një të publikuar më parë artikull antipod.

Pse Go është e keqe për programuesit jo të zgjuar

Gjatë dy viteve të fundit kam përdorur Go për të implementuar një server të specializuar RADIUS me një sistem faturimi të zhvilluar. Gjatë rrugës, po mësoj ndërlikimet e vetë gjuhës. Vetë programet janë shumë të thjeshta dhe nuk janë qëllimi i këtij artikulli, por vetë përvoja e përdorimit të Go meriton disa fjalë në mbrojtje të saj. Go po bëhet një gjuhë gjithnjë e më e zakonshme për kode serioze dhe të shkallëzueshme. Gjuha është krijuar nga Google, ku përdoret në mënyrë aktive. Në fund të fundit, sinqerisht mendoj se dizajni i gjuhës Go është i keq për programuesit jointeligjentë.

Dizenjuar për programues të dobët?

Të dobëtit flasin për problemet. Të fortët flasin për idetë dhe ëndrrat...

Go është shumë e lehtë për t'u mësuar, aq e lehtë sa mund ta lexoni kodin praktikisht pa asnjë trajnim fare. Kjo veçori e gjuhës përdoret në shumë kompani globale kur kodi lexohet së bashku me specialistë jo-thelbësorë (menaxherë, klientë, etj.). Kjo është shumë e përshtatshme për metodologji si Zhvillimi i Drejtuar nga Dizajni.
Edhe programuesit fillestarë fillojnë të prodhojnë kod mjaft të mirë pas një ose dy javësh. Libri nga i cili kam studiuar është "Go Programming" (nga Mark Summerfield). Libri është shumë i mirë, prek shumë nuanca të gjuhës. Pas gjuhëve të komplikuara të panevojshme si Java, PHP, mungesa e magjisë është freskuese. Por herët a vonë, shumë programues të kufizuar kanë idenë e përdorimit të metodave të vjetra në një fushë të re. A është vërtet e nevojshme kjo?

Rob Pike (ideologu kryesor i gjuhës) krijoi gjuhën Go si një gjuhë industriale që është e lehtë për t'u kuptuar dhe efektive për t'u përdorur. Gjuha është krijuar për produktivitet maksimal në ekipe të mëdha dhe nuk ka asnjë dyshim për këtë. Shumë programues fillestar ankohen se ka shumë veçori që u mungojnë. Kjo dëshirë për thjeshtësi ishte një vendim i vetëdijshëm nga dizajnerët e gjuhës, dhe për të kuptuar plotësisht pse ishte e nevojshme, ne duhet të kuptojmë motivimin e zhvilluesve dhe atë që ata po përpiqeshin të arrinin në Go.

Pra, pse u bë kaq e thjeshtë? Këtu janë disa citate nga Rob Pike:

Pika kryesore këtu është se programuesit tanë nuk janë studiues. Ata janë, si rregull, mjaft të rinj, vijnë tek ne pasi kanë studiuar, ndoshta kanë studiuar Java, ose C/C++, ose Python. Ata nuk mund të kuptojnë një gjuhë të mirë, por në të njëjtën kohë ne duam që ata të krijojnë softuer të mirë. Kjo është arsyeja pse gjuha duhet të jetë e lehtë për t'u kuptuar dhe mësuar.

Ai duhet të jetë i njohur, përafërsisht i ngjashëm me C. Programuesit që punojnë në Google e fillojnë karrierën e tyre herët dhe janë kryesisht të njohur me gjuhët procedurale, në veçanti me familjen C. Kërkesa për produktivitet të shpejtë në një gjuhë të re programimi do të thotë që gjuha nuk duhet të jetë shumë radikale.

Fjalë të mençura, apo jo?

Artefakte të thjeshtësisë

Thjeshtësia është një kusht i domosdoshëm për bukurinë. Lev Tolstoi.

Mbajtja e thjeshtë është një nga qëllimet më të rëndësishme në çdo dizajn. Siç e dini, një projekt i përsosur nuk është një projekt ku nuk ka asgjë për të shtuar, por ai nga i cili nuk ka asgjë për të hequr. Shumë njerëz besojnë se për të zgjidhur (ose edhe për të shprehur) probleme komplekse, nevojitet një mjet kompleks. Megjithatë, nuk është. Le të marrim për shembull gjuhën PERL. Ideologët e gjuhës besonin se një programues duhet të ketë të paktën tre mënyra të ndryshme për të zgjidhur një problem. Ideologët e gjuhës Go morën një rrugë tjetër; ata vendosën se një mënyrë, por vërtet e mirë, mjaftonte për të arritur qëllimin. Kjo qasje ka një bazë serioze: e vetmja mënyrë është më e lehtë për të mësuar dhe më e vështirë për t'u harruar.

Shumë migrantë ankohen se gjuha nuk përmban abstraksione elegante. Po, kjo është e vërtetë, por kjo është një nga avantazhet kryesore të gjuhës. Gjuha përmban një minimum magjie - kështu që nuk kërkohet njohuri e thellë për të lexuar programin. Për sa i përket verbozitetit të kodit, ky nuk është aspak problem. Një program Golang i shkruar mirë lexon vertikalisht, me pak ose aspak strukturë. Për më tepër, shpejtësia e leximit të një programi është të paktën një renditje madhësie më e madhe se shpejtësia e shkrimit të tij. Nëse mendoni se i gjithë kodi ka një formatim uniform (e bërë duke përdorur komandën e integruar gofmt), atëherë leximi i disa rreshtave shtesë nuk është aspak problem.

Jo shumë ekspresive

Arti nuk toleron kur liria e tij është e kufizuar. Saktësia nuk është përgjegjësi e tij.

Për shkak të dëshirës për thjeshtësi, Go-s i mungojnë konstruktet që në gjuhë të tjera perceptohen si diçka e natyrshme nga njerëzit e mësuar me to. Në fillim mund të jetë disi e papërshtatshme, por më pas vëreni se programi është shumë më i lehtë dhe më i paqartë për t'u lexuar.

Për shembull, një mjet i konsolës që lexon stdin ose një skedar nga argumentet e linjës së komandës do të duket kështu:

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

Zgjidhja e të njëjtit problem në D, megjithëse duket disi më e shkurtër, nuk është më e lehtë për t'u lexuar

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

Ferri i kopjimit

Njeriu mbart ferrin brenda vetes. Martin Luter.

Fillestarët ankohen vazhdimisht për Go për sa i përket mungesës së gjenerikëve. Për të zgjidhur këtë problem, shumica e tyre përdorin kopjim të drejtpërdrejtë të kodit. Për shembull, një funksion për përmbledhjen e një liste numrash të plotë, profesionistë të tillë të mundshëm besojnë se funksionaliteti nuk mund të zbatohet në asnjë mënyrë tjetër përveçse me një kopje të thjeshtë për çdo lloj të dhënash.

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

Gjuha ka mjete të mjaftueshme për të zbatuar ndërtime të tilla. Për shembull, programimi i përgjithshëm do të ishte mirë.

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

Dhe, megjithëse kodi ynë doli të ishte disi më i gjatë se rasti i mëparshëm, ai është bërë i përgjithësuar. Prandaj, nuk do të jetë e vështirë për ne të zbatojmë të gjitha veprimet aritmetike.

Shumë do të thonë se një program në D duket dukshëm më i shkurtër dhe do të kenë të drejtë.

import std.stdio;
import std.algorithm;

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

Sidoqoftë, është vetëm më e shkurtër, por jo më e saktë, pasi zbatimi D e injoron plotësisht problemin e trajtimit të gabimeve.

Në jetën reale, ndërsa kompleksiteti i logjikës rritet, hendeku ngushtohet me shpejtësi. Hendeku mbyllet edhe më shpejt kur duhet të kryeni një veprim që nuk mund të kryhet duke përdorur operatorët e gjuhës standarde.

Për sa i përket mirëmbajtjes, shtrirjes dhe lexueshmërisë, për mendimin tim, gjuha Go fiton, megjithëse humbet në fjalë.

Programimi i përgjithësuar në disa raste na jep përfitime të pamohueshme. Kjo ilustrohet qartë nga paketa e renditjes. Pra, për të renditur çdo listë, ne vetëm duhet të implementojmë ndërfaqen 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)
}

Nëse merrni ndonjë projekt me burim të hapur dhe ekzekutoni komandën grep “interface{}” -R, do të shihni se sa shpesh përdoren ndërfaqet konfuze. Shokët me mendje të ngushtë do të thonë menjëherë se e gjithë kjo është për shkak të mungesës së gjenerikëve. Megjithatë, kjo nuk është gjithmonë rasti. Le të marrim DELPHI si shembull. Pavarësisht pranisë së të njëjtave gjenerike, ai përmban një lloj të veçantë VARIANT për operacionet me lloje arbitrare të të dhënave. Gjuha Go bën të njëjtën gjë.

Nga një top te harabela

Dhe xhaketa duhet t'i përshtatet madhësisë së çmendurisë. Stanislav Lec.

Shumë fansa ekstremë mund të pretendojnë se Go ka një mekanizëm tjetër për krijimin e gjenerikëve - reflektimin. Dhe do të kenë të drejtë... por vetëm në raste të rralla.

Rob Pike na paralajmëron:

Ky është një mjet i fuqishëm që duhet përdorur me kujdes. Duhet të shmanget nëse nuk është rreptësisht e nevojshme.

Wikipedia na tregon si më poshtë:

Reflektimi i referohet procesit gjatë të cilit një program mund të monitorojë dhe modifikojë strukturën dhe sjelljen e tij gjatë ekzekutimit. Paradigma e programimit që qëndron në themel të reflektimit quhet programim reflektues. Ky është një lloj metaprogramimi.

Sidoqoftë, siç e dini, ju duhet të paguani për gjithçka. Në këtë rast është:

  • vështirësi në shkrimin e programeve
  • shpejtësia e ekzekutimit të programit

Prandaj, reflektimi duhet të përdoret me kujdes, si një armë e kalibrit të madh. Përdorimi i pamenduar i reflektimit çon në programe të palexueshme, gabime të vazhdueshme dhe shpejtësi të ulët. Vetëm gjëja që një programues snob mund të tregojë kodin e tij përpara kolegëve të tjerë, më pragmatikë dhe modestë.

Bagazh kulturor nga Xi? Jo, nga një sërë gjuhësh!

Bashkë me pasurinë trashëgimtarëve u lihen edhe borxhe.

Përkundër faktit se shumë besojnë se gjuha bazohet tërësisht në trashëgiminë C, nuk është kështu. Gjuha përfshin shumë aspekte të gjuhëve më të mira të programimit.

sintaksë

Para së gjithash, sintaksa e strukturave gramatikore bazohet në sintaksën e gjuhës C. Megjithatë, edhe gjuha DELPHI pati një ndikim të rëndësishëm. Kështu, shohim se kllapat e tepërta, të cilat pakësojnë shumë lexueshmërinë e programit, janë hequr plotësisht. Gjuha gjithashtu përmban operatorin “:=” të qenësishëm për gjuhën DELPHI. Koncepti i paketave është huazuar nga gjuhë si ADA. Deklarata e subjekteve të papërdorura është huazuar nga gjuha PROLOG.

Semantikë

Paketat bazoheshin në semantikën e gjuhës DELPHI. Çdo paketë përfshin të dhëna dhe kode dhe përmban entitete private dhe publike. Kjo ju lejon të reduktoni ndërfaqen e paketës në minimum.

Operacioni i zbatimit me metodën e delegimit është huazuar nga gjuha DELPHI.

Përpilimi

Nuk është pa arsye që ka një shaka: Go u zhvillua gjatë përpilimit të një programi C. Një nga pikat e forta të gjuhës është përpilimi i saj ultra i shpejtë. Ideja u huazua nga gjuha DELPHI. Çdo paketë Go korrespondon me një modul DELPHI. Këto paketa ripërpilohen vetëm kur është vërtet e nevojshme. Prandaj, pas modifikimit të radhës, nuk keni nevojë të përpiloni të gjithë programin, por të rikompiloni vetëm paketat dhe paketat e ndryshuara që varen nga këto paketa të ndryshuara (dhe edhe atëherë, vetëm nëse ndërfaqet e paketave kanë ndryshuar).

Konstruksione të nivelit të lartë

Gjuha përmban shumë konstruksione të ndryshme të nivelit të lartë që nuk lidhen në asnjë mënyrë me gjuhë të nivelit të ulët si C.

  • Vargjet
  • Tabelat hash
  • Feta
  • Shtypja e rosës është huazuar nga gjuhë si RUBY (të cilat, për fat të keq, shumë nuk e kuptojnë ose e përdorin në potencialin e tij të plotë).

Menaxhimi i memories

Menaxhimi i kujtesës në përgjithësi meriton një artikull të veçantë. Nëse në gjuhë si C++, kontrolli i lihet plotësisht zhvilluesit, atëherë në gjuhët e mëvonshme si DELPHI, u përdor një model numërimi referencash. Me këtë qasje, referencat ciklike nuk lejoheshin, pasi u formuan grupe jetimë, atëherë Go ka zbulimin e integruar të grupimeve të tilla (si C#). Përveç kësaj, mbledhësi i mbeturinave është më efikas se shumica e zbatimeve të njohura aktualisht dhe mund të përdoret tashmë për shumë detyra në kohë reale. Gjuha në vetvete njeh situatat kur një vlerë për të ruajtur një variabël mund të ndahet në rafte. Kjo zvogëlon ngarkesën në menaxherin e kujtesës dhe rrit shpejtësinë e programit.

Konkurrenca dhe Konkurrenca

Paralelizmi dhe konkurrueshmëria e gjuhës është përtej lavdërimit. Asnjë gjuhë e nivelit të ulët nuk mund të konkurrojë as në distancë me Go. Për të qenë të drejtë, vlen të theksohet se modeli nuk është shpikur nga autorët e gjuhës, por thjesht është huazuar nga gjuha e mirë e vjetër ADA. Gjuha është e aftë të përpunojë miliona lidhje paralele duke përdorur të gjitha CPU-të, ndërkohë që ka një renditje të përmasave më pak probleme komplekse me bllokime dhe kushte gare që janë tipike për kodin me shumë fije.

Përfitime shtesë

Nëse është fitimprurëse, të gjithë do të bëhen vetëmohues.

Gjuha gjithashtu na ofron një sërë përfitimesh të padyshimta:

  • Një skedar i vetëm i ekzekutueshëm pas ndërtimit të projektit thjeshton shumë vendosjen e aplikacioneve.
  • Shtypja statike dhe konkluzionet e tipit mund të zvogëlojnë ndjeshëm numrin e gabimeve në kodin tuaj, edhe pa shkruar teste. Unë njoh disa programues që bëjnë pa shkruar fare teste dhe cilësia e kodit të tyre nuk vuan ndjeshëm.
  • Kompilim shumë i thjeshtë i ndërthurur dhe transportueshmëri e shkëlqyer e bibliotekës standarde, e cila thjeshton shumë zhvillimin e aplikacioneve ndër-platformë.
  • Shprehjet e rregullta RE2 janë të sigurta në fije dhe kanë kohë të parashikueshme ekzekutimi.
  • Një bibliotekë e fuqishme standarde që lejon shumicën e projekteve të bëjnë pa korniza të palëve të treta.
  • Gjuha është mjaft e fuqishme për t'u përqëndruar në problemin sesa në mënyrën se si ta zgjidhë atë, por e një niveli mjaft të ulët që problemi mund të zgjidhet në mënyrë efikase.
  • Sistemi Go eco tashmë përmban mjete të zhvilluara jashtë kutisë për të gjitha rastet: teste, dokumentacion, menaxhim të paketave, linja të fuqishme, gjenerim kodesh, detektor të kushteve të garës, etj.
  • Versioni Go 1.11 prezantoi menaxhimin e integruar semantik të varësisë, i ndërtuar në krye të hostimit të njohur VCS. Të gjitha mjetet që përbëjnë ekosistemin Go përdorin këto shërbime për të shkarkuar, ndërtuar dhe instaluar kodin prej tyre me një goditje të vetme. Dhe kjo është e mrekullueshme. Me ardhjen e versionit 1.11, u zgjidh plotësisht edhe problemi me versionimin e paketës.
  • Për shkak se ideja thelbësore e gjuhës është të zvogëlojë magjinë, gjuha i nxit zhvilluesit të trajtojnë gabimet në mënyrë eksplicite. Dhe kjo është e saktë, sepse përndryshe, thjesht do të harrojë fare trajtimin e gabimeve. Një tjetër gjë është se shumica e zhvilluesve injorojnë qëllimisht trajtimin e gabimeve, duke preferuar në vend që t'i përpunojnë ato thjesht ta përcjellin gabimin lart.
  • Gjuha nuk zbaton metodologjinë klasike OOP, pasi në formën e saj të pastër nuk ka virtualitet në Go. Sidoqoftë, ky nuk është problem kur përdorni ndërfaqe. Mungesa e OOP zvogëlon ndjeshëm pengesën për hyrjen për fillestarët.

Thjeshtësia për përfitimin e komunitetit

Është e lehtë të komplikohet, e vështirë të thjeshtohet.

Go është krijuar për të qenë e thjeshtë dhe ka sukses në këtë qëllim. Është shkruar për programues të zgjuar që kuptojnë përfitimet e punës në grup dhe janë të lodhur nga ndryshueshmëria e pafundme e gjuhëve të nivelit të Ndërmarrjeve. Duke pasur një grup relativisht të vogël strukturash sintaksore në arsenalin e tij, praktikisht nuk i nënshtrohet ndryshimeve me kalimin e kohës, kështu që zhvilluesit kanë shumë kohë të liruar për zhvillim, dhe jo për studimin e pafund të risive gjuhësore.

Kompanitë gjithashtu marrin një sërë avantazhesh: një pengesë e ulët e hyrjes u lejon atyre të gjejnë shpejt një specialist dhe pandryshueshmëria e gjuhës u lejon atyre të përdorin të njëjtin kod edhe pas 10 vjetësh.

Përfundim

Madhësia e madhe e trurit nuk ka bërë kurrë asnjë elefant fitues të çmimit Nobel.

Për ata programues, egoja personale e të cilëve ka përparësi mbi shpirtin e ekipit, si dhe teoricienët që duan sfidat akademike dhe "vetë-përmirësimin" e pafund, gjuha është vërtet e keqe, pasi është një gjuhë artizanale me qëllime të përgjithshme që nuk të lejon të marrësh. kenaqesi estetike nga rezultati i punes tende dhe tregohu profesionist para kolegeve (me kusht qe ta masim inteligjencen me keto kritere dhe jo me IQ). Si çdo gjë në jetë, është çështje prioritetesh personale. Si të gjitha risitë e vlefshme, gjuha tashmë ka bërë një rrugë të gjatë nga mohimi universal në pranimin masiv. Gjuha është e zgjuar në thjeshtësinë e saj dhe, siç e dini, çdo gjë e zgjuar është e thjeshtë!

Burimi: www.habr.com

Shto një koment