Ինչու է Go Design-ը վատ խելացի ծրագրավորողների համար

Վերջին ամիսների ընթացքում ես օգտագործել եմ Go-ն իրագործումների համար: Proof հայեցակարգի (մոտ.Կոդ՝ գաղափարի ֆունկցիոնալությունը ստուգելու համար) ազատ ժամանակ, մասամբ հենց ծրագրավորման լեզուն ուսումնասիրելու համար։ Ծրագրերն իրենք շատ պարզ են և այս հոդվածի նպատակը չեն, սակայն Go-ի օգտագործման փորձն ինքնին արժանի է մի քանի խոսքի դրա մասին։ Գնալը խոստանում է լինել (մոտ.2015 թվականին գրված հոդված) հայտնի լեզու լուրջ մասշտաբավոր կոդի համար։ Լեզուն ստեղծվել է Google-ի կողմից, որտեղ այն ակտիվորեն օգտագործվում է։ Ներքեւի տող, ես անկեղծորեն կարծում եմ, որ Go լեզվի դիզայնը վատ է խելացի ծրագրավորողների համար:

Նախատեսված է թույլ ծրագրավորողների համար?

Go-ը շատ հեշտ է սովորել, այնքան հեշտ, որ ներածությունն ինձ տարավ մի երեկո, որից հետո ես արդեն կարող էի արդյունավետ կոդավորել: Գիրքը, որը ես սովորում էի Go, կոչվում է Ծրագրավորման ներածություն in Go-ում (թարգմանություն), այն հասանելի է առցանց։ Գիրքը, ինչպես Go-ի սկզբնաղբյուրը, հեշտ է կարդալ, ունի լավ կոդերի օրինակներ և պարունակում է մոտ 150 էջ, որոնք կարելի է կարդալ մեկ անգամ: Այս պարզությունը սկզբում թարմացնում է, հատկապես ծրագրավորման աշխարհում, որը լցված է չափազանց բարդ տեխնոլոգիայով: Բայց ի վերջո, վաղ թե ուշ միտք է առաջանում. «Իսկապե՞ս այդպես է»։

Google-ը պնդում է, որ Go-ի պարզությունը դրա վաճառքի կետն է, և լեզուն նախատեսված է մեծ թիմերում առավելագույն արտադրողականության համար, բայց ես կասկածում եմ դրան: Կան առանձնահատկություններ, որոնք կա՛մ բացակայում են, կա՛մ չափազանց մանրամասն: Եվ այս ամենը ծրագրավորողների նկատմամբ վստահության բացակայության պատճառով, այն ենթադրությամբ, որ նրանք ի վիճակի չեն որևէ բան ճիշտ անել: Պարզության այս ցանկությունը լեզվի դիզայներների գիտակցված որոշումն էր, և որպեսզի լիովին հասկանանք, թե ինչու էր դա անհրաժեշտ, մենք պետք է հասկանանք մշակողների մոտիվացիան և ինչի էին նրանք փորձում հասնել Go-ում:

Ուրեմն ինչու՞ էր այն այդքան պարզ: Ահա մի քանի մեջբերում Ռոբ Պայք (մոտ.Go լեզվի համահեղինակներից մեկը):

Հիմնական կետն այստեղ այն է, որ մեր ծրագրավորողները (մոտ.: Google-ի աշխատակիցներ) հետազոտողներ չեն: Նրանք, որպես կանոն, բավականին երիտասարդ են, սովորելուց հետո գալիս են մեզ մոտ, երևի Java են սովորել, կամ C/C++ կամ Python։ Նրանք չեն կարողանում հասկանալ հիանալի լեզու, բայց միևնույն ժամանակ մենք ցանկանում ենք, որ նրանք ստեղծեն լավ ծրագրեր: Այդ իսկ պատճառով նրանց լեզուն պետք է հեշտ հասկանալի և սովորել:
 
Նա պետք է ծանոթ լինի, կոպիտ ասած նման լինի Ք. Google-ում աշխատող ծրագրավորողները իրենց կարիերան սկսում են վաղ և հիմնականում ծանոթ են ընթացակարգային լեզուներին, մասնավորապես C ընտանիքին: Նոր ծրագրավորման լեզվի արագ արտադրողականության պահանջը նշանակում է, որ լեզուն չպետք է չափազանց արմատական ​​լինի:

Ինչ? Այսպիսով, Ռոբ Փայքը հիմնականում ասում է, որ Google-ի ծրագրավորողները այնքան էլ լավը չեն, այդ իսկ պատճառով նրանք ստեղծել են լեզու ապուշների համար (մոտ.: dumbed down), որպեսզի նրանք կարողանան ինչ-որ բան անել: Ինչպիսի՞ ամբարտավան հայացք ձեր սեփական գործընկերներին: Ես միշտ հավատացել եմ, որ Google-ի մշակողները ընտրված են Երկրի ամենապայծառ և լավագույններից: Անկասկած, նրանք կարող են ավելի դժվար բանի հետ գլուխ հանել:

Չափազանց պարզության արտեֆակտներ

Պարզ լինելը ցանկացած դիզայնի արժանի նպատակ է, իսկ պարզ բան անել փորձելը՝ դժվար: Այնուամենայնիվ, երբ փորձում են լուծել (կամ նույնիսկ արտահայտել) բարդ խնդիրներ, երբեմն բարդ գործիք է անհրաժեշտ: Բարդությունն ու խճճվածությունը ծրագրավորման լեզվի լավագույն հատկանիշները չեն, սակայն կա միջին ճանապարհ, որտեղ լեզուն կարող է ստեղծել էլեգանտ աբստրակցիաներ, որոնք հեշտ են հասկանալի և օգտագործել:

Ոչ շատ արտահայտիչ

Պարզության հանդեպ իր նվիրվածության պատճառով Go-ին բացակայում են այլ լեզուներում բնական ընկալվող կառուցվածքները: Սա կարող է սկզբում թվալ լավ գաղափար, բայց գործնականում դա հանգեցնում է բովանդակալից կոդի: Դրա պատճառը պետք է ակնհայտ լինի. մշակողների համար պետք է հեշտ լինի կարդալ այլ մարդկանց կոդը, բայց իրականում այս պարզեցումները միայն վնասում են ընթերցանությանը: Go-ում հապավումներ չկան՝ կա՛մ շատ, կա՛մ ոչինչ:

Օրինակ, վահանակի օգտակար ծրագիրը, որը կարդում է stdin կամ ֆայլ հրամանի տողի փաստարկներից, նման կլինի.

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

Չնայած այս կոդը նույնպես փորձում է հնարավորինս ընդհանուր լինել, բայց Go-ի պարտադրված խոսակցությունը խանգարում է, և արդյունքում պարզ խնդրի լուծումը հանգեցնում է կոդի մեծ քանակի:

Ահա, օրինակ, նույն խնդրի լուծումը 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);
    }
}

Իսկ հիմա ո՞վ է ավելի ընթեռնելի։ Ես իմ ձայնը կտամ Դ-ին: Նրա ծածկագիրը շատ ավելի ընթեռնելի է, քանի որ նա ավելի պարզ է նկարագրում գործողությունները: D-ն օգտագործում է շատ ավելի բարդ հասկացություններ (մոտ.: այլընտրանքային ֆունկցիայի կանչ и կաղապարներ), քան Go-ի օրինակում, բայց իրականում բարդ բան չկա դրանց հասկանալու մեջ:

Պատճենահանման դժոխք

Go-ի բարելավման հանրաճանաչ առաջարկը ընդհանուր է: Սա առնվազն կօգնի խուսափել կոդի անհարկի պատճենումից՝ տվյալների բոլոր տեսակներին աջակցելու համար: Օրինակ, ամբողջ թվերի ցուցակն ամփոփելու գործառույթը չի կարող իրականացվել ոչ այլ կերպ, քան դրա հիմնական ֆունկցիան յուրաքանչյուր ամբողջ տիպի համար copy-paste անելով, այլ կերպ չկա.

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

Եվ այս օրինակը նույնիսկ ստորագրված տեսակների համար չի աշխատում: Այս մոտեցումը լիովին խախտում է չկրկնվելու սկզբունքը (DRY), ամենահայտնի և ակնհայտ սկզբունքներից մեկը, որի անտեսումը բազմաթիվ սխալների աղբյուր է։ Ինչու՞ է Գոն դա անում: Սա լեզվի սարսափելի կողմն է։

Նույն օրինակը D:

import std.stdio;
import std.algorithm;

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

Պարզ, էլեգանտ և ուղիղ դեպի կետը: Այստեղ օգտագործվող ֆունկցիան է reduce կաղապարի տեսակի և պրեդիկատի համար։ Այո, սա կրկին ավելի բարդ է, քան Go տարբերակը, բայց այնքան էլ դժվար չէ հասկանալ խելացի ծրագրավորողների համար: Ո՞ր օրինակն է ավելի հեշտ պահպանելը և ավելի հեշտ կարդալը:

Պարզ տիպի համակարգի շրջանցում

Ես պատկերացնում եմ, որ Go-ի ծրագրավորողները, որոնք կարդում են սա, բերանից փրփուր կգան և կբղավեն. «Դու դա սխալ ես անում»: Դե, կա ընդհանուր գործառույթ և տեսակներ ստեղծելու ևս մեկ տարբերակ, բայց դա ամբողջովին խախտում է տիպային համակարգը:

Նայեք այս հիմար լեզվի շտկման օրինակին՝ խնդիրը լուծելու համար.

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

Այս իրականացումը Reduce վերցված է հոդվածից Իդիոմատիկ գեներիկա Go-ում (մոտ.Ես չկարողացա գտնել թարգմանությունը, ուրախ կլինեմ, եթե օգնեք այս հարցում): Դե, եթե դա իդիոմատիկ է, ես ատում եմ տեսնել ոչ իդիոմատիկ օրինակ: Օգտագործումը interface{} - ֆարս, իսկ լեզվով ասած՝ պետք է միայն մուտքագրումը շրջանցելու համար։ Սա դատարկ ինտերֆեյս է, և բոլոր տեսակներն իրականացնում են այն՝ թույլ տալով լիակատար ազատություն բոլորին: Ծրագրավորման այս ոճը սարսափելի տգեղ է, և սա դեռ ամենը չէ։ Նման ակրոբատիկ սխրանքները պահանջում են գործարկման ժամանակի արտացոլման օգտագործում: Նույնիսկ Ռոբ Փայքը չի սիրում այն ​​անհատներին, ովքեր չարաշահում են դա, ինչպես նա նշել է իր զեկույցներից մեկում։

Սա հզոր գործիք է, որը պետք է օգտագործվի զգուշությամբ: Այն պետք է խուսափել, եթե խիստ անհրաժեշտ չէ:

Ես կվերցնեի D կաղապարներ այս անհեթեթության փոխարեն: Ինչպես կարող է ինչ-որ մեկը դա ասել interface{} ավելի ընթեռնելի, թե՞ նույնիսկ անվտանգ մուտքագրեք:

Կախվածության կառավարման դժվարությունները

Go-ն ունի ներկառուցված կախվածության համակարգ, որը կառուցված է հանրաճանաչ հոստինգ պրովայդերների վրա VCS. Գործիքները, որոնք գալիս են Go-ի հետ, գիտեն այս ծառայությունների մասին և կարող են դրանցից մեկ հարվածով ներբեռնել, ստեղծել և տեղադրել կոդ: Թեև սա հիանալի է, տարբերակման հետ կապված մի մեծ թերություն կա: Այո, ճիշտ է, որ դուք կարող եք սկզբնական կոդը ստանալ այնպիսի ծառայություններից, ինչպիսիք են github-ը կամ bitbucket-ը, օգտագործելով Go գործիքները, բայց դուք չեք կարող նշել տարբերակը: Եվ կրկին պարզություն՝ օգտակարության հաշվին։ Ես չեմ կարողանում հասկանալ նման որոշման տրամաբանությունը։

Այս խնդրի լուծման վերաբերյալ հարցեր տալուց հետո Go մշակող թիմը ստեղծեց ֆորումի թեմա, որը նախանշում էր, թե ինչպես են նրանք պատրաստվում շրջանցել այս հարցը։ Նրանց առաջարկությունն էր պարզապես մի օր պատճենել ամբողջ պահեստը ձեր նախագծի մեջ և թողնել այն «ինչպես կա»: Ի՞նչ են նրանք մտածում: Մենք ունենք տարբերակների կառավարման հիասքանչ համակարգեր՝ մեծ հատկորոշմամբ և տարբերակների աջակցությամբ, որոնք Go ստեղծողները անտեսում են և պարզապես պատճենում են աղբյուրի կոդը:

Մշակութային ուղեբեռ Սի

Իմ կարծիքով, Go-ն մշակվել է այն մարդկանց կողմից, ովքեր օգտագործել են C-ն իրենց ողջ կյանքում և նրանց կողմից, ովքեր չէին ցանկանում նոր բան փորձել: Լեզուն կարելի է նկարագրել որպես C՝ լրացուցիչ անիվներով (ծագում.: ուսումնական անիվներ) Դրանում ոչ մի նոր գաղափար չկա, բացի զուգահեռության աջակցությունից (որն, ի դեպ, հրաշալի է) ու սա ամոթ է։ Դուք հիանալի զուգահեռություն ունեք հազիվ օգտագործելի, կաղ լեզվով։

Մեկ այլ ճռռացող խնդիր այն է, որ Go-ն ընթացակարգային լեզու է (ինչպես C-ի լուռ սարսափը): Դուք ի վերջո կոդ եք գրում ընթացակարգային ոճով, որը հնացած և հնացած է թվում: Ես գիտեմ, որ օբյեկտ կողմնորոշված ​​ծրագրավորումը արծաթափայլ չէ, բայց հիանալի կլինի, եթե կարողանայի մանրամասները վերացականացնել տիպերի և ապահովել encapsulation:

Պարզությունը ձեր շահի համար

Go-ն ստեղծվել է պարզ լինելու համար, և այն հաջողվում է հասնել այդ նպատակին: Այն գրվել է թույլ ծրագրավորողների համար՝ որպես կաղապար օգտագործելով հին լեզու։ Այն հագեցած է պարզ գործիքներով՝ պարզ բաներ անելու համար: Այն հեշտ է կարդալ և հեշտ օգտագործել:

Այն չափազանց խոսուն է, տպավորիչ և վատ խելացի ծրագրավորողների համար:

Շնորհակալություն Մերսինվալդ խմբագրումների համար

Source: www.habr.com

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