Hvorfor Go er dårlig for usmarte programmerere

Artikkelen er skrevet som et svar på en tidligere publisert antipodeisk artikkel.

Hvorfor Go er dårlig for usmarte programmerere

I løpet av de siste to pluss årene har jeg brukt Go til å implementere en spesialisert RADIUS-server med et utviklet faktureringssystem. Underveis lærer jeg det vanskelige med selve språket. Programmene i seg selv er veldig enkle og er ikke hensikten med denne artikkelen, men selve opplevelsen av å bruke Go fortjener noen ord til sitt forsvar. Go blir et stadig mer vanlig språk for seriøs, skalerbar kode. Språket er laget av Google, der det brukes aktivt. Bunnlinjen, jeg tror ærlig talt at utformingen av Go-språket er dårlig for Uintelligente programmerere.

Designet for svake programmerere?

De svake snakker om problemer. Den sterke samtalen om ideer og drømmer...

Go er veldig enkelt å lære, så enkelt at du kan lese koden nesten uten trening i det hele tatt. Denne funksjonen i språket brukes i mange globale selskaper når koden leses sammen med ikke-kjernespesialister (ledere, kunder, etc.). Dette er veldig praktisk for metoder som Design Driven Development.
Selv nybegynnere programmerere begynner å produsere ganske anstendig kode etter en uke eller to. Boken jeg studerte fra er "Go Programming" (av Mark Summerfield). Boken er veldig bra, den berører mange nyanser av språket. Etter unødvendig kompliserte språk som Java, PHP, er mangelen på magi forfriskende. Men før eller siden har mange begrensede programmerere ideen om å bruke gamle metoder på et nytt felt. Er dette virkelig nødvendig?

Rob Pike (språkets hovedideolog) skapte Go-språket som et industrispråk som er lett å forstå og effektivt å bruke. Språket er designet for maksimal produktivitet i store team og det er ingen tvil om det. Mange nybegynnere programmerere klager over at det er mange funksjoner de mangler. Dette ønsket om enkelhet var en bevisst avgjørelse fra språkets designere, og for å fullt ut forstå hvorfor det var nødvendig, må vi forstå motivasjonen til utviklerne og hva de prøvde å oppnå i Go.

Så hvorfor ble det gjort så enkelt? Her er et par sitater fra Rob Pike:

Hovedpoenget her er at våre programmerere ikke er forskere. De er som regel ganske unge, kom til oss etter å ha studert, kanskje de studerte Java, eller C/C++, eller Python. De kan ikke forstå et godt språk, men samtidig vil vi at de skal lage god programvare. Derfor skal språket være lett å forstå og lære.

Han burde være kjent, omtrent lik C. Programmerere som jobber hos Google starter karrieren tidlig og er for det meste kjent med prosedyrespråk, spesielt C-familien. Kravet om rask produktivitet i et nytt programmeringsspråk gjør at språket ikke skal være for radikalt.

Kloke ord, ikke sant?

Artefakter av enkelhet

Enkelhet er en nødvendig betingelse for skjønnhet. Lev Tolstoj.

Å holde det enkelt er et av de viktigste målene i ethvert design. Som du vet, er et perfekt prosjekt ikke et prosjekt der det ikke er noe å legge til, men et som det ikke er noe å fjerne fra. Mange tror at for å løse (eller til og med uttrykke) komplekse problemer, trengs et komplekst verktøy. Det er det imidlertid ikke. La oss ta PERL-språket for eksempel. Språkideologer mente at en programmerer burde ha minst tre forskjellige måter å løse ett problem på. Go-språkets ideologer tok en annen vei; de bestemte at én vei, men en virkelig god, var nok til å nå målet. Denne tilnærmingen har et seriøst grunnlag: den eneste måten er lettere å lære og vanskeligere å glemme.

Mange migranter klager over at språket ikke inneholder elegante abstraksjoner. Ja, dette er sant, men dette er en av hovedfordelene med språket. Språket inneholder et minimum av magi - så det kreves ingen dyp kunnskap for å lese programmet. Når det gjelder omfanget av koden, er dette ikke et problem i det hele tatt. Et velskrevet Golang-program leser vertikalt, med liten eller ingen struktur. I tillegg er hastigheten på å lese et program minst en størrelsesorden større enn hastigheten på å skrive det. Hvis du mener at all koden har ensartet formatering (gjort ved hjelp av den innebygde gofmt-kommandoen), så er det ikke noe problem å lese noen ekstra linjer.

Ikke veldig uttrykksfull

Kunsten tolererer ikke når dens frihet er begrenset. Nøyaktighet er ikke hans ansvar.

På grunn av ønsket om enkelhet, mangler Go konstruksjoner som på andre språk oppfattes som noe naturlig av folk som er vant til dem. Først kan det være noe upraktisk, men så merker du at programmet er mye enklere og mer entydig å lese.

For eksempel vil et konsollverktøy som leser stdin eller en fil fra kommandolinjeargumenter se slik ut:

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

Løsningen på samme problem i D, selv om den ser noe kortere ut, er ikke lettere å lese

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

Helvete med kopiering

Mennesket bærer helvete i seg selv. Martin Luther.

Nybegynnere klager stadig på Go når det gjelder mangelen på generiske legemidler. For å løse dette problemet bruker de fleste av dem direkte kodekopiering. For eksempel, en funksjon for å summere en liste over heltall, mener slike potensielle fagfolk at funksjonaliteten ikke kan implementeres på noen annen måte enn ved enkel kopiering og liming for hver datatype.

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

Språket har tilstrekkelige midler til å implementere slike konstruksjoner. For eksempel ville generisk programmering være greit.

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

Og selv om koden vår viste seg å være noe lengre enn den forrige saken, har den blitt generalisert. Derfor vil det ikke være vanskelig for oss å implementere alle aritmetiske operasjoner.

Mange vil si at et program i D ser betydelig kortere ut, og de vil ha rett.

import std.stdio;
import std.algorithm;

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

Den er imidlertid bare kortere, men ikke mer korrekt, siden D-implementeringen fullstendig ignorerer problemet med feilhåndtering.

I det virkelige liv, når kompleksiteten til logikken øker, reduseres gapet raskt. Gapet lukkes enda raskere når du trenger å utføre en handling som ikke kan utføres med standard språkoperatorer.

Når det gjelder vedlikehold, utvidbarhet og lesbarhet, vinner etter min mening Go-språket, selv om det taper i detalj.

Generalisert programmering gir oss i noen tilfeller ubestridelige fordeler. Dette er tydelig illustrert av sorteringspakken. Så for å sortere en liste, trenger vi bare å implementere sort.Interface-grensesnittet.

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

Hvis du tar et åpen kildekodeprosjekt og kjører kommandoen grep "grensesnitt{}" -R, vil du se hvor ofte forvirrende grensesnitt brukes. Nære kamerater vil umiddelbart si at alt dette skyldes mangelen på generika. Dette er imidlertid ikke alltid tilfelle. La oss ta DELPHI som et eksempel. Til tross for tilstedeværelsen av de samme generiske stoffene, inneholder den en spesiell VARIANT-type for operasjoner med vilkårlige datatyper. Go-språket gjør det samme.

Fra en kanon til spurver

Og tvangstrøya må passe størrelsen på galskapen. Stanislav Lec.

Mange ekstreme fans kan hevde at Go har en annen mekanisme for å lage generiske stoffer – refleksjon. Og de vil ha rett... men bare i sjeldne tilfeller.

Rob Pike advarer oss:

Dette er et kraftig verktøy som bør brukes med forsiktighet. Det bør unngås med mindre det er strengt nødvendig.

Wikipedia forteller oss følgende:

Refleksjon refererer til prosessen der et program kan overvåke og endre sin egen struktur og oppførsel under utførelse. Programmeringsparadigmet som ligger til grunn for refleksjon kalles reflekterende programmering. Dette er en type metaprogrammering.

Men, som du vet, må du betale for alt. I dette tilfellet er det:

  • vanskeligheter med å skrive programmer
  • programutførelseshastighet

Derfor må refleksjon brukes med forsiktighet, som et stort kaliber våpen. Tankeløs bruk av refleksjon fører til uleselige programmer, konstante feil og lav hastighet. Akkurat tingen for at en snobbete programmerer skal kunne vise frem koden sin foran andre, mer pragmatiske og beskjedne kolleger.

Kulturbagasje fra Xi? Nei, fra en rekke språk!

Sammen med formuen etterlates også gjeld til arvingene.

Til tross for at mange mener at språket helt og holdent er basert på C-arven, er ikke dette tilfellet. Språket inneholder mange aspekter av de beste programmeringsspråkene.

syntaks

Først av alt er syntaksen til grammatiske strukturer basert på syntaksen til C-språket. Imidlertid hadde DELPHI-språket også en betydelig innflytelse. Dermed ser vi at de overflødige parentesene, som i stor grad reduserer lesbarheten til programmet, er fullstendig fjernet. Språket inneholder også ":="-operatoren som er iboende for DELPHI-språket. Konseptet med pakker er lånt fra språk som ADA. Erklæringen om ubrukte enheter er lånt fra PROLOG-språket.

Semantikk

Pakkene var basert på semantikken til DELPHI-språket. Hver pakke innkapsler data og kode og inneholder private og offentlige enheter. Dette lar deg redusere pakkegrensesnittet til et minimum.

Implementeringsoperasjonen ved delegering ble lånt fra DELPHI-språket.

Samling

Det er ikke uten grunn at det er en vits: Go ble utviklet mens et C-program ble kompilert. En av styrkene til språket er dens ultraraske kompilering. Ideen ble lånt fra DELPHI-språket. Hver Go-pakke tilsvarer en DELPHI-modul. Disse pakkene rekompileres bare når det virkelig er nødvendig. Derfor, etter neste redigering, trenger du ikke å kompilere hele programmet, men heller rekompilere bare de endrede pakkene og pakkene som er avhengige av disse endrede pakkene (og selv da, bare hvis pakkegrensesnittene har endret seg).

Konstruksjoner på høyt nivå

Språket inneholder mange forskjellige høynivåkonstruksjoner som på ingen måte er relatert til lavnivåspråk som C.

  • Strenger
  • Hash-tabeller
  • Skiver
  • Andeskriving er lånt fra språk som RUBY (som dessverre mange ikke forstår eller bruker til sitt fulle potensial).

Minnehåndtering

Minnehåndtering fortjener generelt en egen artikkel. Hvis kontrollen i språk som C++ er fullstendig overlatt til utvikleren, ble det brukt en referansetellingsmodell på senere språk som DELPHI. Med denne tilnærmingen var sykliske referanser ikke tillatt, siden foreldreløse klynger ble dannet, så har Go innebygd deteksjon av slike klynger (som C#). I tillegg er søppelsamleren mer effektiv enn de fleste kjente implementeringer og kan allerede brukes til mange sanntidsoppgaver. Språket selv gjenkjenner situasjoner når en verdi for å lagre en variabel kan tildeles på stabelen. Dette reduserer belastningen på minnebehandlingen og øker hastigheten på programmet.

Samtidighet og samtidighet

Språkets parallellitet og konkurranseevne er hinsides ros. Ingen språk på lavt nivå kan engang eksternt konkurrere med Go. For å være rettferdig er det verdt å merke seg at modellen ikke ble oppfunnet av forfatterne av språket, men ganske enkelt ble lånt fra det gode gamle ADA-språket. Språket er i stand til å behandle millioner av parallelle tilkoblinger ved å bruke alle CPUer, samtidig som det har en størrelsesorden mindre komplekse problemer med vranglåser og raseforhold som er typiske for flertrådskode.

Ytterligere fordeler

Hvis det er lønnsomt, vil alle bli uselviske.

Språket gir oss også en rekke utvilsomme fordeler:

  • En enkelt kjørbar fil etter å ha bygget prosjektet forenkler distribusjonen av applikasjoner.
  • Statisk skriving og typeslutning kan redusere antallet feil i koden din betraktelig, selv uten å skrive tester. Jeg kjenner noen programmerere som klarer seg uten å skrive tester i det hele tatt, og kvaliteten på koden deres lider ikke nevneverdig.
  • Veldig enkel krysskompilering og utmerket portabilitet av standardbiblioteket, noe som i stor grad forenkler utviklingen av applikasjoner på tvers av plattformer.
  • RE2 regulære uttrykk er trådsikre og har forutsigbare utførelsestider.
  • Et kraftig standardbibliotek som lar de fleste prosjekter klare seg uten tredjeparts rammer.
  • Språket er kraftig nok til å fokusere på problemet i stedet for hvordan det skal løses, men likevel lavt nok til at problemet kan løses effektivt.
  • Go øko-systemet inneholder allerede utviklet verktøy for alle anledninger: tester, dokumentasjon, pakkehåndtering, kraftige linters, kodegenerering, løpsforholdsdetektor, etc.
  • Go versjon 1.11 introduserte innebygd semantisk avhengighetsadministrasjon, bygget på toppen av populær VCS-hosting. Alle verktøyene som utgjør Go-økosystemet bruker disse tjenestene til å laste ned, bygge og installere kode fra dem i ett grep. Og det er flott. Med ankomsten av versjon 1.11 ble problemet med pakkeversjon også fullstendig løst.
  • Fordi kjerneideen med språket er å redusere magi, oppmuntrer språket utviklere til å gjøre feilhåndtering eksplisitt. Og dette er riktig, for ellers vil den ganske enkelt glemme feilhåndteringen helt. En annen ting er at de fleste utviklere bevisst ignorerer feilhåndtering, og foretrekker i stedet for å behandle dem å bare videresende feilen oppover.
  • Språket implementerer ikke den klassiske OOP-metodikken, siden det i sin rene form ikke er virtualitet i Go. Dette er imidlertid ikke et problem når du bruker grensesnitt. Fraværet av OOP reduserer adgangsbarrieren betydelig for nybegynnere.

Enkelhet til samfunnsnytte

Det er lett å komplisere, vanskelig å forenkle.

Go ble designet for å være enkelt, og det lykkes med det målet. Den ble skrevet for smarte programmerere som forstår fordelene med teamarbeid og som er lei av den endeløse variasjonen av språk på bedriftsnivå. Med et relativt lite sett med syntaktiske strukturer i arsenalet, er det praktisk talt ikke gjenstand for endringer over tid, så utviklere har frigjort mye tid for utvikling, og ikke for uendelig å studere språkinnovasjoner.

Bedrifter får også en rekke fordeler: en lav inngangsbarriere lar dem raskt finne en spesialist, og språkets uforanderlighet lar dem bruke samme kode selv etter 10 år.

Konklusjon

Stor hjernestørrelse har aldri gjort noen elefant til nobelprisvinner.

For de programmererne hvis personlige ego har forrang over lagånden, så vel som teoretikere som elsker akademiske utfordringer og endeløs "selvforbedring", er språket virkelig dårlig, siden det er et håndverksspråk for generell bruk som ikke lar deg få estetisk nytelse av resultatet av arbeidet ditt og vis deg selv profesjonell foran kolleger (forutsatt at vi måler intelligens etter disse kriteriene, og ikke etter IQ). Som alt annet i livet er det et spørsmål om personlige prioriteringer. Som alle verdifulle innovasjoner, har språket allerede kommet langt fra universell fornektelse til masseaksept. Språket er genialt i sin enkelhet, og som du vet er alt genialt enkelt!

Kilde: www.habr.com

Legg til en kommentar