Pse Go Design është i keq për programuesit inteligjentë

Gjatë muajve të fundit kam përdorur Go për implementime. Dëshmi e konceptit (përafërsisht.: kod për të testuar funksionalitetin e një ideje) në kohën e tij të lirë, pjesërisht për të studiuar vetë gjuhën e programimit. 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ë për të. Shko premton të jetë (përafërsisht.: artikull i shkruar në 2015) një gjuhë popullore për kode serioze 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 e zgjuar.

Dizenjuar për programues të dobët?

Go është shumë e lehtë për t'u mësuar, aq e lehtë sa që prezantimi më mori një mbrëmje, pas së cilës unë tashmë mund të kodoja në mënyrë produktive. Libri që mësova Go quhet Një hyrje në programimin në Go (përkthim), është i disponueshëm në internet. Libri, si vetë kodi burimor Go, është i lehtë për t'u lexuar, ka shembuj të mirë kodesh dhe përmban rreth 150 faqe që mund të lexohen në një seancë. Kjo thjeshtësi është freskuese në fillim, veçanërisht në një botë programimi të mbushur me teknologji tepër të ndërlikuar. Por në fund, herët a vonë lind mendimi: "A është vërtet kështu?"

Google pretendon se thjeshtësia e Go është pika e saj e shitjes dhe gjuha është krijuar për produktivitet maksimal në ekipe të mëdha, por unë dyshoj. Ka veçori që ose mungojnë ose janë tepër të detajuara. Dhe gjithçka për shkak të mungesës së besimit te zhvilluesit, me supozimin se ata nuk janë në gjendje të bëjnë asgjë siç duhet. 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 Rob Pike (përafërsisht.: një nga bashkë-krijuesit e gjuhës Go):

Pika kryesore këtu është se programuesit tanë (përafërsisht.: Punonjësit e Google) 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 e tyre duhet të jetë e lehtë për ta 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.

Çfarë? Pra, Rob Pike në thelb po thotë se zhvilluesit në Google nuk janë aq të mirë, kjo është arsyeja pse ata krijuan një gjuhë për idiotët (përafërsisht.: memec) që të jenë në gjendje të bëjnë diçka. Çfarë lloj shikimi arrogant ndaj kolegëve tuaj? Gjithmonë kam besuar se zhvilluesit e Google janë zgjedhur nga më të zgjuarit dhe më të mirët në Tokë. Me siguri ata mund të përballojnë diçka më të vështirë?

Artefakte të thjeshtësisë së tepruar

Të qenit i thjeshtë është një qëllim i denjë në çdo dizajn, dhe të përpiqesh të bësh diçka të thjeshtë është e vështirë. Sidoqoftë, kur përpiqeni të zgjidhni (ose edhe të shprehni) probleme komplekse, ndonjëherë nevojitet një mjet kompleks. Kompleksiteti dhe ndërlikimi nuk janë tiparet më të mira të një gjuhe programimi, por ekziston një rrugë e mesme në të cilën gjuha mund të krijojë abstraksione elegante që janë të lehta për t'u kuptuar dhe përdorur.

Jo shumë ekspresive

Për shkak të përkushtimit ndaj thjeshtësisë, Go i mungojnë konstruktet që perceptohen si të natyrshme në gjuhë të tjera. Kjo mund të duket si një ide e mirë në fillim, por në praktikë rezulton në kod të përmbledhur. Arsyeja për këtë duhet të jetë e qartë - duhet të jetë e lehtë për zhvilluesit të lexojnë kodin e njerëzve të tjerë, por në fakt këto thjeshtime dëmtojnë vetëm lexueshmërinë. Nuk ka shkurtesa në Go: ose shumë ose asgjë.

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

Edhe pse edhe ky kod përpiqet të jetë sa më i përgjithshëm që të jetë e mundur, shprehja e detyruar e Go-s pengon dhe si rezultat, zgjidhja e një problemi të thjeshtë rezulton në një sasi të madhe kodi.

Këtu, për shembull, është një zgjidhje për të njëjtin problem në 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);
    }
}

Dhe kush është më i lexueshëm tani? Unë do t'ia jap votën time D. Kodi i tij është shumë më i lexueshëm sepse ai i përshkruan veprimet më qartë. D përdor koncepte shumë më komplekse (përafërsisht.: thirrje funksioni alternativ и templates) sesa në shembullin Go, por në të vërtetë nuk ka asgjë të komplikuar për t'i kuptuar ato.

Ferri i kopjimit

Një sugjerim popullor për përmirësimin e Go është i përgjithshëm. Kjo të paktën do të ndihmojë në shmangien e kopjimit të panevojshëm të kodit për të mbështetur të gjitha llojet e të dhënave. Për shembull, një funksion për përmbledhjen e një liste numrash të plotë nuk mund të zbatohet në asnjë mënyrë tjetër veçse duke kopjuar funksionin e tij bazë për çdo lloj numëri të plotë; nuk ka asnjë mënyrë tjetër:

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

Dhe ky shembull nuk funksionon as për tipat e nënshkruar. Kjo qasje shkel plotësisht parimin e mospërsëritjes (E THAT), një nga parimet më të famshme dhe më të dukshme, injorimi i të cilit është burimi i shumë gabimeve. Pse Go e bën këtë? Ky është një aspekt i tmerrshëm i gjuhës.

I njëjti shembull në D:

import std.stdio;
import std.algorithm;

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

E thjeshtë, elegante dhe drejt e në thelb. Funksioni i përdorur këtu është reduce për llojin e shabllonit dhe kallëzuesin. Po, kjo është përsëri më e komplikuar se versioni Go, por jo aq i vështirë për t'u kuptuar nga programuesit e zgjuar. Cili shembull është më i lehtë për t'u mbajtur dhe më i lehtë për t'u lexuar?

Anashkalimi i sistemit të tipit të thjeshtë

Imagjinoj që programuesit e Go-së që e lexojnë këtë do të kenë shkumë nga goja dhe do të bërtasin, "Po e bëni gabim!" Epo, ka një mënyrë tjetër për të bërë një funksion dhe tipe gjenerike, por e thyen plotësisht sistemin e tipit!

Hidhini një sy këtij shembulli të një rregullimi të trashë të gjuhës për të zgjidhur problemin:

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

Ky zbatim Reduce është huazuar nga artikulli Gjenerika idiomatike në Go (përafërsisht.: Nuk e gjeta përkthimin, do të jem i lumtur nëse më ndihmoni me këtë). Epo, nëse është idiomatike, nuk do të doja të shikoja një shembull joidiomatik. Përdorimi interface{} - një farsë, dhe në gjuhë nevojitet vetëm për të anashkaluar shtypjen. Kjo është një ndërfaqe boshe dhe të gjitha llojet e zbatojnë atë, duke lejuar liri të plotë për të gjithë. Ky stil programimi është tmerrësisht i shëmtuar, dhe kjo nuk është e gjitha. Bërat akrobatike si këto kërkojnë përdorimin e reflektimit të kohës së funksionimit. Edhe Rob Pike nuk i pëlqejnë individët që abuzojnë me këtë, siç ka përmendur në një nga raportimet e tij.

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

Unë do të merrja modele D në vend të kësaj marrëzie. Si mund ta thotë dikush këtë interface{} më i lexueshëm apo edhe shkruani i sigurt?

Problemet e menaxhimit të varësisë

Go ka një sistem të integruar varësie të ndërtuar në krye të ofruesve të njohur të pritjes QV. Mjetet që vijnë me Go dinë për këto shërbime dhe mund të shkarkojnë, ndërtojnë dhe instalojnë kodin prej tyre me një goditje të vetme. Ndërsa kjo është e mrekullueshme, ka një të metë të madhe me versionimin! Po, është e vërtetë që mund ta merrni kodin burim nga shërbime si github ose bitbucket duke përdorur mjetet Go, por nuk mund të specifikoni versionin. Dhe përsëri thjeshtësia në kurriz të dobisë. Nuk jam në gjendje ta kuptoj logjikën e një vendimi të tillë.

Pasi bëri pyetje në lidhje me një zgjidhje për këtë problem, ekipi i zhvillimit Go u krijua tema e forumit, i cili përvijonte se si do ta kalonin këtë çështje. Rekomandimi i tyre ishte që thjesht të kopjoni të gjithë depon në projektin tuaj një ditë dhe ta lini atë "siç është". Çfarë dreqin po mendojnë? Ne kemi sisteme të mahnitshme të kontrollit të versioneve me etiketime dhe mbështetje të shkëlqyer të versioneve që krijuesit Go i injorojnë dhe thjesht kopjojnë kodin burimor.

Bagazh kulturor nga Xi

Sipas mendimit tim, Go u zhvillua nga njerëz që kishin përdorur C gjatë gjithë jetës së tyre dhe nga ata që nuk donin të provonin diçka të re. Gjuha mund të përshkruhet si C me rrota shtesë (origjinën.: rrota trajnimi). Nuk ka asnjë ide të re në të, përveç mbështetjes për paralelizmin (i cili, nga rruga, është i mrekullueshëm) dhe kjo është turp. Keni një paralelizëm të shkëlqyer në një gjuhë mezi të përdorshme, të çalë.

Një problem tjetër kërcitës është se Go është një gjuhë procedurale (si tmerri i heshtur i C). Ju përfundoni duke shkruar kodin në një stil procedural që ndihet arkaik dhe i vjetëruar. E di që programimi i orientuar drejt objektit nuk është një plumb i argjendtë, por do të ishte mirë të ishim në gjendje të abstraktojmë detajet në lloje dhe të siguronim kapsulim.

Thjeshtësia për përfitimin tuaj

Go është krijuar për të qenë e thjeshtë dhe ka sukses në këtë qëllim. Është shkruar për programues të dobët, duke përdorur një gjuhë të vjetër si shabllon. Ai vjen i kompletuar me mjete të thjeshta për të bërë gjëra të thjeshta. Është e lehtë për t'u lexuar dhe e lehtë për t'u përdorur.

Është jashtëzakonisht i folur, jo mbresëlënës dhe i keq për programuesit inteligjentë.

Falënderim mersinvald për redaktime

Burimi: www.habr.com

Shto një koment