Kial Go estas Malbona por Malsaĝaj Programistoj

La artikolo estis skribita kiel respondo al antaŭe publikigita antipoda artikolo.

Kial Go estas Malbona por Malsaĝaj Programistoj

Dum la lastaj du jaroj mi uzis Go por efektivigi specialan RADIUS-servilon kun evoluinta faktura sistemo. Survoje, mi lernas la komplikaĵojn de la lingvo 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 en sia defendo. Go fariĝas ĉiam pli ĉefa lingvo por serioza, skalebla kodo. La lingvo estis kreita de Guglo, kie ĝi estas aktive uzata. Fundo, mi honeste pensas, ke la dezajno de la Go-lingvo estas malbona por UNinteligentaj programistoj.

Desegnita por malfortaj programistoj?

La malfortuloj parolas pri problemoj. La forta parolado pri ideoj kaj revoj...

Go estas tre facile lernebla, tiel facile ke vi povas legi la kodon preskaŭ sen trejnado. Tiu ĉi trajto de la lingvo estas uzata en multaj tutmondaj kompanioj kiam la kodo estas legita kune kun ne-kernaj specialistoj (manaĝeroj, klientoj, ktp.). Ĉi tio estas tre oportuna por metodaroj kiel Dezajna Stirita Disvolviĝo.
Eĉ komencaj programistoj komencas produkti sufiĉe decan kodon post unu aŭ du semajnoj. La libro, el kiu mi studis, estas "Go Programming" (de Mark Summerfield). La libro estas tre bona, ĝi tuŝas multajn nuancojn de la lingvo. Post nenecese komplikaj lingvoj kiel Java, PHP, la manko de magio estas refreŝiga. Sed frue aŭ malfrue, multaj limigitaj programistoj havas la ideon uzi malnovajn metodojn en nova kampo. Ĉu ĉi tio vere necesas?

Rob Pike (la ĉefa ideologo de la lingvo) kreis la Go-lingvon kiel industrian lingvon, kiu estas facile komprenebla kaj efika por uzi. La lingvo estas desegnita por maksimuma produktiveco en grandaj teamoj kaj ne estas dubo pri tio. Multaj komencaj programistoj plendas, ke ekzistas multaj funkcioj, kiujn ili mankas. Ĉ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 de Rob Pike:

La ŝlosila punkto ĉi tie estas, ke niaj programistoj 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 la lingvo estu facile komprenebla kaj lernebla.

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.

Saĝaj vortoj, ĉu ne?

Artefaktoj de Simpleco

Simpleco estas necesa kondiĉo por beleco. Lev Tolstoj.

Reteni ĝin simpla estas unu el la plej gravaj celoj en iu ajn dezajno. Kiel vi scias, perfekta projekto ne estas projekto kie estas nenio por aldoni, sed unu el kiu estas nenio por forigi. Multaj homoj kredas, ke por solvi (aŭ eĉ esprimi) kompleksajn problemojn, necesas kompleksa ilo. Tamen, ĝi ne estas. Ni prenu la PERL-lingvon ekzemple. Lingvaj ideologoj kredis ke programisto devus havi almenaŭ tri malsamajn manierojn solvi unu problemon. La ideologoj de la lingvo Go prenis alian vojon; ili decidis, ke unu vojo, sed vere bona, sufiĉas por atingi la celon. Ĉi tiu aliro havas seriozan fundamenton: la sola maniero estas pli facile lernebla kaj pli malfacile forgesebla.

Multaj migrantoj plendas, ke la lingvo ne enhavas elegantajn abstraktaĵojn. Jes, tio estas vera, sed tio estas unu el la ĉefaj avantaĝoj de la lingvo. La lingvo enhavas minimumon da magio - do ne necesas profunda scio por legi la programon. Koncerne la vortecon de la kodo, ĉi tio tute ne estas problemo. Bone verkita Golang-programo legas vertikale, kun malgranda aŭ neniu strukturo. Krome, la rapideco de legado de programo estas almenaŭ grandordo pli granda ol la rapideco de skribo de ĝi. Se vi konsideras, ke la tuta kodo havas uniforman formatadon (farita per la enkonstruita gofmt komando), tiam legi kelkajn kromliniojn tute ne estas problemo.

Ne tre esprimplena

Arto ne toleras kiam ĝia libereco estas limigita. Precizeco ne estas lia respondeco.

Pro la deziro al simpleco, al Go mankas konstruaĵoj, kiuj en aliaj lingvoj estas perceptataj kiel io natura de homoj alkutimiĝintaj al ili. Komence ĝi povas esti iom maloportuna, sed poste vi rimarkas, ke la programo estas multe pli facila kaj pli malambigua legebla.

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

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

La solvo de la sama problemo en D, kvankam ĝi aspektas iom pli mallonga, ne estas pli facile legebla

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

Infero de kopiado

Homo portas inferon en si. Marteno Lutero.

Komencantoj konstante plendas pri Go rilate la mankon de generikoj. Por solvi ĉi tiun problemon, plej multaj el ili uzas rektan kodan kopiadon. Ekzemple, funkcio por sumado de listo de entjeroj, tiaj estemaj profesiuloj kredas ke la funkcieco ne povas esti efektivigita alimaniere ol per simpla kopi-algluado por ĉiu datumtipo.

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

La lingvo havas sufiĉajn rimedojn por efektivigi tiajn konstruojn. Ekzemple, senmarka programado estus bona.

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

Kaj, kvankam nia kodo montriĝis iom pli longa ol la antaŭa kazo, ĝi ĝeneraliĝis. Tial, ne estos malfacile por ni efektivigi ĉiujn aritmetikajn operaciojn.

Multaj diros, ke programo en D aspektas signife pli mallonga, kaj ili pravos.

import std.stdio;
import std.algorithm;

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

Tamen, ĝi estas nur pli mallonga, sed ne pli ĝusta, ĉar la D-efektivigo tute ignoras la problemon de erartraktado.

En la reala vivo, ĉar la komplekseco de logiko pliiĝas, la interspaco mallarĝiĝas rapide. La interspaco fermiĝas eĉ pli rapide kiam vi bezonas fari agon kiu ne povas esti farita per normlingvaj funkciigistoj.

Laŭ konservebleco, etendebleco kaj legebleco, laŭ mi, la Go-lingvo gajnas, kvankam ĝi perdas je verboseco.

Ĝeneraligita programado en iuj kazoj donas al ni nekontesteblajn avantaĝojn. Ĉi tio estas klare ilustrita per la ordiga pako. Do, por ordigi ajnan liston, ni nur bezonas efektivigi la sort.Interface interfacon.

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

Se vi prenas iun malfermfontan projekton kaj rulas la grep "interfaco{}" -R-komandon, vi vidos kiom ofte konfuzaj interfacoj estas uzataj. Proksimaj kamaradoj tuj diros, ke ĉio ĉi estas pro la manko de generiloj. Tamen, ĉi tio ne ĉiam estas la kazo. Ni prenu DELPHI kiel ekzemplon. Malgraŭ la ĉeesto de ĉi tiuj samaj generikoj, ĝi enhavas specialan VARIANT-tipo por operacioj kun arbitraj datumtipoj. La lingvo Go faras same.

De kanono al paseroj

Kaj la fortega vesto devas konveni al la grandeco de la frenezo. Stanislav Lec.

Multaj ekstremaj ŝatantoj povas aserti, ke Go havas alian mekanismon por krei generikojn - pripensado. Kaj ili pravos... sed nur en maloftaj okazoj.

Rob Pike avertas nin:

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

Vikipedio diras al ni la jenon:

Reflektado rilatas al la procezo dum kiu programo povas monitori kaj modifi sian propran strukturon kaj konduton dum ekzekuto. La programa paradigmo subesta reflektado estas nomita reflekta programado. Ĉi tio estas speco de metaprogramado.

Tamen, kiel vi scias, vi devas pagi por ĉio. En ĉi tiu kazo ĝi estas:

  • malfacileco por verki programojn
  • rapido de ekzekuto de programo

Tial reflektado devas esti uzata singarde, kiel granda kalibro armilo. Senpensa uzo de reflektado kondukas al nelegeblaj programoj, konstantaj eraroj kaj malalta rapideco. Nur la afero por snoba programisto povi montri sian kodon antaŭ aliaj pli pragmataj kaj modestaj kolegoj.

Kultura bagaĝo de Xi? Ne, el kelkaj lingvoj!

Kune kun la riĉaĵo, ŝuldoj ankaŭ estas lasitaj al la heredantoj.

Malgraŭ tio, ke multaj kredas, ke la lingvo estas tute bazita sur la C-heredaĵo, tio ne estas la kazo. La lingvo enhavas multajn aspektojn de la plej bonaj programlingvoj.

sintakso

Antaŭ ĉio, la sintakso de gramatikaj strukturoj baziĝas sur la sintakso de la C-lingvo. Tamen la DELPHI-lingvo ankaŭ havis gravan influon. Tiel, ni vidas, ke la redundaj krampoj, kiuj multe reduktas la legeblecon de la programo, estis tute forigitaj. La lingvo ankaŭ enhavas la ":=" operatoron enecan al la DELPHI-lingvo. La koncepto de pakoj estas pruntita de lingvoj kiel ADA. La deklaro de neuzataj estaĵoj estas pruntita el la lingvo PROLOG.

Semantiko

La pakaĵoj baziĝis sur la semantiko de la DELPHI-lingvo. Ĉiu pako enhavas datumojn kaj kodon kaj enhavas privatajn kaj publikajn entojn. Ĉi tio permesas vin redukti la pakaĵinterfacon al minimumo.

La efektiviga operacio per delega metodo estis pruntita el la DELPHI-lingvo.

Kompilo

Ne senkaŭze estas ŝerco: Go estis evoluigita dum C-programo estis kompilita. Unu el la fortoj de la lingvo estas ĝia ultrarapida kompilo. La ideo estis pruntita el la DELPHI-lingvo. Ĉiu Go-pakaĵo respondas al DELPHI-modulo. Ĉi tiuj pakoj estas rekompilitaj nur kiam vere necese. Tial, post la sekva redakto, vi ne bezonas kompili la tutan programon, sed prefere rekompili nur la ŝanĝitajn pakaĵojn kaj pakaĵojn kiuj dependas de tiuj ŝanĝitaj pakaĵoj (kaj eĉ tiam, nur se la pakaĵinterfacoj ŝanĝiĝis).

Altnivelaj konstruoj

La lingvo enhavas multajn malsamajn altnivelajn konstrukciojn, kiuj neniel rilatas al malaltnivelaj lingvoj kiel C.

  • Kordoj
  • Hash-tabeloj
  • Tranĉaĵoj
  • Anasa tajpado estas pruntita el lingvoj kiel RUBY (kiun bedaŭrinde multaj ne komprenas aŭ uzas ĝis sia plena potencialo).

Memoradministrado

Memoradministrado ĝenerale meritas apartan artikolon. Se en lingvoj kiel C++, kontrolo estas tute lasita al la programisto, tiam en postaj lingvoj kiel DELPHI, oni uzis referencan kalkulmodelon. Kun ĉi tiu aliro, ciklaj referencoj ne estis permesitaj, ĉar orfaj aretoj estis formitaj, tiam Go havas enkonstruitan detekton de tiaj aretoj (kiel C#). Krome, la rubkolektanto estas pli efika ol la plej multaj nuntempe konataj efektivigoj kaj jam povas esti uzata por multaj realtempaj taskoj. La lingvo mem rekonas situaciojn kiam valoro por stoki variablon povas esti asignita sur la stako. Ĉi tio reduktas la ŝarĝon sur la memoradministranto kaj pliigas la rapidecon de la programo.

Samtempeco kaj Samtempeco

La paraleleco kaj konkurencivo de la lingvo estas preter laŭdoj. Neniu malaltnivela lingvo povas eĉ malproksime konkuri kun Go. Por esti juste, indas rimarki, ke la modelon ne inventis la aŭtoroj de la lingvo, sed simple pruntis el la bona malnova lingvo ADA. La lingvo kapablas prilabori milionojn da paralelaj konektoj uzante ĉiujn CPUojn, dum havante grandordon malpli kompleksajn problemojn kun blokiĝo kaj raskondiĉoj, kiuj estas tipaj por multfadena kodo.

Pliaj avantaĝoj

Se ĝi estas profita, ĉiuj fariĝos sindonemaj.

Lingvo ankaŭ provizas al ni kelkajn nedubajn avantaĝojn:

  • Ununura rulebla dosiero post konstruado de la projekto multe simpligas la disfaldiĝon de aplikaĵoj.
  • Senmova tajpado kaj tajpa inferenco povas signife redukti la nombron da eraroj en via kodo, eĉ sen verkado de testoj. Mi konas iujn programistojn, kiuj tute sen verkado de testoj kaj la kvalito de ilia kodo ne suferas grave.
  • Tre simpla transkompilo kaj bonega porteblo de la norma biblioteko, kiu multe simpligas la evoluon de transplatformaj aplikoj.
  • RE2-regulaj esprimoj estas faden-sekuraj kaj havas antaŭvideblajn ekzekuttempojn.
  • Potenca norma biblioteko, kiu ebligas al la plej multaj projektoj fari sen triaj kadroj.
  • La lingvo estas sufiĉe potenca por temigi la problemon prefere ol kiel solvi ĝin, tamen sufiĉe malaltnivela ke la problemo povas esti solvita efike.
  • La ekosistemo Go jam enhavas evoluintajn ilojn el la skatolo por ĉiuj okazoj: testoj, dokumentado, pakaĵadministrado, potencaj linters, kodgenerado, vetkuraj kondiĉoj detektilo, ktp.
  • Go-versio 1.11 enkondukis enkonstruitan semantikan dependecan administradon, konstruitan sur populara VCS-gastigado. Ĉiuj iloj, kiuj konsistigas la Go-ekosistemon, uzas ĉi tiujn servojn por elŝuti, konstrui kaj instali kodon de ili unufoje. Kaj tio estas bonega. Kun la alveno de versio 1.11, la problemo kun pakaĵversiado ankaŭ estis tute solvita.
  • Ĉar la kerna ideo de la lingvo estas redukti magion, la lingvo instigas programistojn fari erartraktadon eksplicite. Kaj ĉi tio estas ĝusta, ĉar alie, ĝi simple forgesos pri erartraktado entute. Alia afero estas, ke la plej multaj programistoj intence ignoras erartraktadon, preferante anstataŭ prilabori ilin simple plusendi la eraron supren.
  • La lingvo ne efektivigas la klasikan OOP-metodaron, ĉar en sia pura formo ekzistas neniu virtualeco en Go. Tamen, ĉi tio ne estas problemo kiam vi uzas interfacojn. La foresto de OOP signife reduktas la baron al eniro por komencantoj.

Simpleco por komunuma profito

Estas facile kompliki, malfacile simpligi.

Go estis desegnita por esti simpla kaj ĝi sukcesas ĉe tiu celo. Ĝi estis verkita por inteligentaj programistoj, kiuj komprenas la avantaĝojn de teamlaboro kaj estas lacaj de la senfina ŝanĝebleco de Enterprise-nivelaj lingvoj. Havante relative malgrandan aron da sintaksaj strukturoj en sia arsenalo, ĝi praktike ne estas submetata al ŝanĝoj laŭlonge de la tempo, do programistoj havas multe da tempo liberigita por evoluo, kaj ne por senfine studi lingvajn novigojn.

Firmaoj ankaŭ ricevas kelkajn avantaĝojn: malalta enirbaro permesas al ili rapide trovi specialiston, kaj la neŝanĝebleco de la lingvo permesas al ili uzi la saman kodon eĉ post 10 jaroj.

konkludo

Granda cerba grandeco neniam igis iun elefanton nobelpremiito.

Por tiuj programistoj, kies persona egoo superas teaman spiriton, kaj ankaŭ por teoriuloj, kiuj amas akademiajn defiojn kaj senfinan "mem-plibonigon", la lingvo estas vere malbona, ĉar ĝi estas ĝeneraluzebla metiista lingvo, kiu ne permesas vin akiri. estetika plezuro el la rezulto de via laboro kaj montru vin profesia antaŭ kolegoj (kondiĉe ke ni mezuru inteligentecon laŭ ĉi tiuj kriterioj, kaj ne laŭ IQ). Kiel ĉio en la vivo, temas pri personaj prioritatoj. Kiel ĉiuj valorindaj novigoj, la lingvo jam venis longan vojon de universala neado ĝis amasa akcepto. La lingvo estas sprita en sia simpleco, kaj, kiel vi scias, ĉio sprita estas simpla!

fonto: www.habr.com

Aldoni komenton