Kial Go Design Estas Malbona por Saĝaj Programistoj

Dum la pasintaj monatoj mi uzis Go por efektivigoj. Pruvo de Koncepto (ĉ.: kodo por testi la funkciecon de ideo) en sia libera tempo, parte por studi la programlingvon mem. La programoj mem estas tre simplaj kaj ne estas la celo de ĉi tiu artikolo, sed la sperto uzi Go mem meritas kelkajn vortojn pri ĝi. Iru promesas esti (ĉ.: artikolo verkita en 2015) populara lingvo por serioza skalebla kodo. La lingvo estis kreita de Guglo, kie ĝi estas aktive uzata. En la fundo, mi honeste pensas, ke la dezajno de la lingvo Go estas malbona por inteligentaj programistoj.

Desegnita por malfortaj programistoj?

Go estas tre facile lernebla, tiel facila, ke la enkonduko prenis min iun vesperon, post kio mi jam povis produktive kodi. La libro, kiun mi kutimis lerni Go, nomiĝas Enkonduko al Programado en Go (traduko), ĝi haveblas interrete. La libro, kiel la fontkodo Go mem, estas facile legebla, havas bonajn kodekzemplojn, kaj enhavas ĉirkaŭ 150 paĝojn, kiujn oni povas legi unufoje. Ĉi tiu simpleco estas refreŝiga komence, precipe en programada mondo plena de tro komplika teknologio. Sed finfine, pli aŭ malpli frue ekestas la penso: "Ĉu ĉi tio vere estas?"

Google asertas, ke la simpleco de Go estas ĝia vendpunkto kaj la lingvo estas desegnita por maksimuma produktiveco en grandaj teamoj, sed mi dubas pri tio. Estas funkcioj, kiuj aŭ mankas aŭ tro detalaj. Kaj ĉio pro manko de fido al programistoj, kun la supozo, ke ili ne kapablas fari ion ĝuste. Ĉi tiu deziro al simpleco estis konscia decido de la projektistoj de la lingvo, kaj por plene kompreni kial ĝi estis bezonata, ni devas kompreni la instigon de la programistoj kaj kion ili klopodis atingi en Go.

Do kial ĝi fariĝis tiel simpla? Jen kelkaj citaĵoj Rob Pike (ĉ.: unu el la kunkreintoj de la lingvo Go):

La ŝlosila punkto ĉi tie estas, ke niaj programistoj (ĉ.: Guglantoj) ne estas esploristoj. Ili estas, kiel regulo, sufiĉe junaj, venas al ni post studado, eble ili studis Java, aŭ C/C++, aŭ Python. Ili ne povas kompreni bonegan lingvon, sed samtempe ni volas, ke ili kreu bonan programaron. Tial ilia lingvo estu facile komprenebla kaj lerni por ili.
 
Li devus esti konata, proksimume simila al C. Programistoj laborantaj ĉe Guglo komencas siajn karierojn frue kaj plejparte konas procedurajn lingvojn, precipe la C-familion. La postulo por rapida produktiveco en nova programlingvo signifas, ke la lingvo ne estu tro radikala.

Kio? Do Rob Pike esence diras, ke la programistoj ĉe Guglo ne estas tiom bonaj, tial ili kreis lingvon por idiotoj (ĉ.: dumbed down) por ke ili povu ion fari. Kia aroganta rigardo al viaj propraj kolegoj? Mi ĉiam kredis, ke la programistoj de Guglo estas elektataj el la plej brilaj kaj plej bonaj sur la Tero. Certe ili povas trakti ion pli malfacilan?

Artefaktoj de troa simpleco

Esti simpla estas inda celo en iu ajn dezajno, kaj provi fari ion simplan estas malfacila. Tamen, kiam oni provas solvi (aŭ eĉ esprimi) kompleksajn problemojn, foje necesas kompleksa ilo. Komplekseco kaj komplikeco ne estas la plej bonaj trajtoj de programlingvo, sed ekzistas meza vojo en kiu la lingvo povas krei elegantajn abstraktaĵojn kiuj estas facile kompreneblaj kaj uzeblaj.

Ne tre esprimplena

Pro ĝia engaĝiĝo al simpleco, Go mankas konstrukcioj kiuj estas perceptitaj kiel naturaj en aliaj lingvoj. Ĉi tio eble ŝajnas bona ideo komence, sed praktike ĝi rezultigas multvortan kodon. La kialo de tio devus esti evidenta - devas esti facile por programistoj legi aliulan kodon, sed fakte tiuj simpligoj nur damaĝas legeblecon. Ne estas mallongigoj en Go: aŭ multe aŭ nenio.

Ekzemple, konzola ilo, kiu legas stdin aŭ dosieron de komandliniaj argumentoj, aspektus jene:

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

Kvankam ĉi tiu kodo ankaŭ provas esti kiel eble plej ĝenerala, la trudvorteco de Go malhelpas, kaj kiel rezulto, solvi simplan problemon rezultigas grandan kvanton da kodo.

Jen, ekzemple, solvo al la sama problemo en 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);
    }
}

Kaj kiu nun estas pli legebla? Mi donos mian voĉon al D. Lia kodo estas multe pli legebla ĉar li priskribas la agojn pli klare. D uzas multe pli kompleksajn konceptojn (ĉ.: alternativa funkciovoko и padronoj) ol en la Go-ekzemplo, sed vere estas nenio komplika por kompreni ilin.

Infero de kopiado

Populara sugesto por plibonigi Go estas ĝeneraleco. Ĉi tio almenaŭ helpos eviti nenecesan kopiadon de kodo por subteni ĉiujn datumtipojn. Ekzemple, funkcio por sumado de listo de entjeroj povas esti efektivigita en neniu alia maniero ol kopiante-algluante sian bazan funkcion por ĉiu entjera tipo; ekzistas neniu alia maniero:

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

Kaj ĉi tiu ekzemplo eĉ ne funkcias por subskribitaj tipoj. Ĉi tiu aliro tute malobservas la principon de ne ripeti vin (SEKA), unu el la plej famaj kaj evidentaj principoj, ignorante kiu estas la fonto de multaj eraroj. Kial Go faras ĉi tion? Ĉi tio estas terura aspekto de lingvo.

Sama ekzemplo sur D:

import std.stdio;
import std.algorithm;

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

Simpla, eleganta kaj rekta al la punkto. La funkcio ĉi tie uzata estas reduce por ŝablona tipo kaj predikativo. Jes, ĉi tio estas denove pli komplika ol la Go-versio, sed ne tiom malfacila por inteligentaj programistoj kompreni. Kiu ekzemplo estas pli facile konservebla kaj pli facile legebla?

Simpla tipa sistemo pretervojo

Mi imagas, ke Go-programistoj, kiuj legas ĉi tion, ŝaŭmos ĉe la buŝo kaj krios, "Vi faras ĝin malbone!" Nu, ekzistas alia maniero fari ĝeneralajn funkciojn kaj tipojn, sed ĝi tute rompas la tipsistemon!

Rigardu ĉi tiun ekzemplon de stulta lingva solvo por trakti la problemon:

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

Ĉi tiu efektivigo Reduce estis pruntita el la artikolo Idiomaj generikoj en Go (ĉ.: Mi ne povis trovi la tradukon, mi ĝojos se vi helpos pri tio). Nu, se ĝi estas idioma, mi malamus vidi ne-idioman ekzemplon. Uzado interface{} - farso, kaj en la lingvo necesas nur por preteriri tajpadon. Ĉi tio estas malplena interfaco kaj ĉiuj tipoj efektivigas ĝin, permesante kompletan liberecon por ĉiuj. Ĉi tiu stilo de programado estas terure malbela, kaj tio ne estas ĉio. Akrobataj heroaĵoj kiel ĉi tiuj postulas la uzon de rultempa reflektado. Eĉ Rob Pike ne ŝatas individuojn, kiuj misuzas ĉi tion, kiel li menciis en unu el siaj raportoj.

Ĉi tio estas potenca ilo, kiu devas esti uzata singarde. Ĝi devus esti evitita krom se strikte necesa.

Mi prenus D-ŝablonojn anstataŭ ĉi tiun sensencaĵon. Kiel iu povas diri tion interface{} pli legebla aŭ eĉ tajpi sekuran?

La Malfeliĉoj de Dependeca Administrado

Go havas enkonstruitan dependecan sistemon konstruitan supre de popularaj gastigaj provizantoj VCS. La iloj kiuj venas kun Go scias pri ĉi tiuj servoj kaj povas elŝuti, konstrui kaj instali kodon de ili unufoje. Kvankam ĉi tio estas bonega, estas grava difekto kun versionado! Jes, efektive, vi povas akiri la fontkodon de servoj kiel github aŭ bitbucket per Go-iloj, sed vi ne povas specifi la version. Kaj denove simpleco koste de utileco. Mi ne kapablas kompreni la logikon de tia decido.

Post demandado pri solvo al ĉi tiu problemo, la Go-disvolva teamo kreis forumfadeno, kiu skizis kiel ili solvi ĉi tiun aferon. Ilia rekomendo estis simple kopii la tutan deponejon en vian projekton unu tagon kaj lasi ĝin "kiel estas". Kion diable ili pensas? Ni havas mirindajn versikontrolajn sistemojn kun bonega etikedado kaj versio-subteno, kiujn la kreintoj de Go ignoras kaj nur kopias la fontkodon.

Kultura bagaĝo de Xi

Laŭ mi, Go estis evoluigita de homoj, kiuj uzis C dum sia tuta vivo kaj de tiuj, kiuj ne volis provi ion novan. La lingvo povas esti priskribita kiel C kun kromaj radoj (orig.: trejnaj radoj). Ne estas novaj ideoj en ĝi, krom subteno por paraleleco (kiu, cetere, estas mirinda) kaj ĉi tio estas domaĝe. Vi havas bonegan paralelecon en apenaŭ uzebla, lama lingvo.

Alia knaranta problemo estas, ke Go estas procedura lingvo (kiel la silenta hororo de C). Vi finas skribi kodon en procedura stilo, kiu sentiĝas arkaika kaj malmoderna. Mi scias, ke objekt-orientita programado ne estas arĝenta kuglo, sed estus bonege povi abstrakti la detalojn en tipojn kaj disponigi enkapsuligon.

Simpleco por via propra profito

Go estis desegnita por esti simpla kaj ĝi sukcesas ĉe tiu celo. Ĝi estis verkita por malfortaj programistoj, uzante malnovan lingvon kiel ŝablonon. Ĝi venas kompleta kun simplaj iloj por fari simplajn aferojn. Ĝi estas facile legebla kaj facile uzebla.

Ĝi estas ege multvorta, neimpresa kaj malbona por inteligentaj programistoj.

Спасибо mersinvald por redaktoj

fonto: www.habr.com

Aldoni komenton