Hvorfor Go er dårligt for ukloge programmører

Artiklen er skrevet som et svar på en tidligere offentliggjort antipodisk artikel.

Hvorfor Go er dårligt for ukloge programmører

I løbet af de sidste to år har jeg brugt Go til at implementere en specialiseret RADIUS-server med et udviklet faktureringssystem. Undervejs lærer jeg selve sprogets forviklinger. Programmerne i sig selv er meget enkle og er ikke formålet med denne artikel, men selve oplevelsen af ​​at bruge Go fortjener et par ord til sit forsvar. Go bliver et stadig mere almindeligt sprog for seriøs, skalerbar kode. Sproget er skabt af Google, hvor det bruges aktivt. Bundlinjen tror jeg ærligt talt, at designet af Go-sproget er dårligt for Uintelligente programmører.

Designet til svage programmører?

De svage taler om problemer. Den stærke snak om ideer og drømme...

Go er meget let at lære, så nemt, at du kan læse koden stort set uden træning overhovedet. Denne funktion ved sproget bruges i mange globale virksomheder, når koden læses sammen med ikke-kernespecialister (ledere, kunder osv.). Dette er meget praktisk for metoder som Design Driven Development.
Selv uerfarne programmører begynder at producere ganske anstændig kode efter en uge eller to. Bogen jeg studerede fra er "Go Programming" (af Mark Summerfield). Bogen er meget god, den berører mange nuancer af sproget. Efter unødvendigt komplicerede sprog som Java, PHP er manglen på magi forfriskende. Men før eller siden har mange begrænsede programmører ideen om at bruge gamle metoder på et nyt felt. Er dette virkelig nødvendigt?

Rob Pike (sprogets hovedideolog) skabte Go-sproget som et industrisprog, der er let at forstå og effektivt at bruge. Sproget er designet til maksimal produktivitet i store teams, og der er ingen tvivl om det. Mange uerfarne programmører klager over, at der er mange funktioner, de mangler. Dette ønske om enkelhed var en bevidst beslutning fra sprogets designere, og for fuldt ud at forstå, hvorfor det var nødvendigt, må vi forstå udviklernes motivation og hvad de forsøgte at opnå i Go.

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

Det centrale her er, at vores programmører ikke er forskere. De er som regel ret unge, kom til os efter at have studeret, måske studerede de Java eller C/C++ eller Python. De kan ikke forstå et fantastisk sprog, men samtidig vil vi gerne have, at de skaber god software. Derfor skal sproget være let at forstå og lære.

Han burde være bekendt, groft sagt ligner C. Programmører, der arbejder hos Google, starter deres karriere tidligt og er for det meste fortrolige med proceduresprog, især C-familien. Kravet om hurtig produktivitet i et nyt programmeringssprog betyder, at sproget ikke skal være for radikalt.

Kloge ord, ikke?

Artefakter af enkelhed

Enkelhed er en nødvendig betingelse for skønhed. Lev Tolstoj.

At holde det enkelt er et af de vigtigste mål i ethvert design. Som du ved, er et perfekt projekt ikke et projekt, hvor der ikke er noget at tilføje, men et, hvorfra der ikke er noget at fjerne. Mange mennesker tror, ​​at for at løse (eller endda udtrykke) komplekse problemer, er der brug for et komplekst værktøj. Det er det dog ikke. Lad os tage PERL-sproget for eksempel. Sprogideologer mente, at en programmør burde have mindst tre forskellige måder at løse et problem på. Go-sprogets ideologer tog en anden vej; de besluttede, at én vej, men en rigtig god, var nok til at nå målet. Denne tilgang har et seriøst grundlag: den eneste måde er lettere at lære og sværere at glemme.

Mange migranter klager over, at sproget ikke indeholder elegante abstraktioner. Ja, det er sandt, men dette er en af ​​de vigtigste fordele ved sproget. Sproget indeholder et minimum af magi - så der kræves ingen dyb viden for at læse programmet. Hvad angår kodens omfang, er dette overhovedet ikke et problem. Et velskrevet Golang-program læser lodret, med lidt eller ingen struktur. Derudover er hastigheden af ​​at læse et program mindst en størrelsesorden større end hastigheden for at skrive det. Hvis du mener, at al koden har ensartet formatering (udført ved hjælp af den indbyggede gofmt-kommando), så er det slet ikke noget problem at læse et par ekstra linjer.

Ikke særlig udtryksfuld

Kunsten tolererer ikke, når dens frihed er begrænset. Nøjagtighed er ikke hans ansvar.

På grund af ønsket om enkelhed mangler Go konstruktioner, der på andre sprog opfattes som noget naturligt af folk, der er vant til dem. I starten kan det være noget besværligt, men så bemærker man, at programmet er meget nemmere og mere entydigt at læse.

For eksempel ville et konsolværktøj, der læser stdin eller en fil fra kommandolinjeargumenter, se sådan ud:

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 er, selvom den ser noget kortere ud, ikke nemmere at læse

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

Helvede med kopiering

Mennesket bærer helvede i sig selv. Martin Luther.

Begyndere klager konstant over Go med hensyn til manglen på generiske lægemidler. For at løse dette problem bruger de fleste af dem direkte kodekopiering. For eksempel en funktion til at summere en liste over heltal, mener sådanne potentielle fagfolk, at funktionaliteten ikke kan implementeres på anden måde end ved simpel copy-paste 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))
}

Sproget har tilstrækkelige midler til at implementere sådanne konstruktioner. For eksempel ville generisk programmering være fint.

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 selvom vores kode viste sig at være noget længere end det tidligere tilfælde, er den blevet generaliseret. Derfor vil det ikke være svært for os at implementere alle regneoperationer.

Mange vil sige, at et program i D ser væsentligt kortere ud, og de vil have ret.

import std.stdio;
import std.algorithm;

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

Den er dog kun kortere, men ikke mere korrekt, da D-implementeringen fuldstændig ignorerer problemet med fejlhåndtering.

I det virkelige liv, efterhånden som logikkens kompleksitet øges, indsnævres kløften hurtigt. Mellemrummet lukkes endnu hurtigere, når du skal udføre en handling, der ikke kan udføres med standardsprogoperatorer.

Med hensyn til vedligeholdelse, udvidelsesmuligheder og læsbarhed vinder Go-sproget efter min mening, selvom det taber i ordlyd.

Generaliseret programmering giver os i nogle tilfælde ubestridelige fordele. Dette er tydeligt illustreret af sorteringspakken. Så for at sortere enhver liste skal vi bare implementere sort.Interface-grænsefladen.

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 tager et open source-projekt og kører kommandoen grep "interface{}" -R, vil du se, hvor ofte forvirrende grænseflader bruges. Nærsindede kammerater vil straks sige, at alt dette skyldes manglen på generiske lægemidler. Dette er dog ikke altid tilfældet. Lad os tage DELPHI som et eksempel. På trods af tilstedeværelsen af ​​disse samme generiske stoffer, indeholder den en speciel VARIANT-type til operationer med vilkårlige datatyper. Go-sproget gør det samme.

Fra en kanon til spurve

Og spændetrøjen skal passe til galskabens størrelse. Stanislav Lec.

Mange ekstreme fans kan hævde, at Go har en anden mekanisme til at skabe generiske stoffer - refleksion. Og de vil have ret... men kun i sjældne tilfælde.

Rob Pike advarer os:

Dette er et kraftfuldt værktøj, der bør bruges med forsigtighed. Det bør undgås, medmindre det er strengt nødvendigt.

Wikipedia fortæller os følgende:

Refleksion refererer til den proces, hvorunder et program kan overvåge og ændre sin egen struktur og adfærd under udførelsen. Det programmeringsparadigme, der ligger til grund for refleksion, kaldes refleksiv programmering. Dette er en form for metaprogrammering.

Du skal dog som bekendt betale for alt. I dette tilfælde er det:

  • svært ved at skrive programmer
  • programudførelseshastighed

Derfor skal refleksion bruges med forsigtighed, ligesom et stort kaliber våben. Tankeløs brug af refleksion fører til ulæselige programmer, konstante fejl og lav hastighed. Lige noget for en snob-programmør at kunne vise sin kode frem foran andre, mere pragmatiske og beskedne kolleger.

Kulturbagage fra Xi? Nej, fra en række sprog!

Sammen med formuen efterlades også gæld til arvingerne.

På trods af, at mange mener, at sproget udelukkende er baseret på C-arven, er det ikke tilfældet. Sproget inkorporerer mange aspekter af de bedste programmeringssprog.

syntaks

Først og fremmest er syntaksen af ​​grammatiske strukturer baseret på syntaksen for C-sproget. DELPHI-sproget havde dog også en betydelig indflydelse. Dermed ser vi, at de overflødige parenteser, som i høj grad reducerer programmets læsbarhed, er blevet helt fjernet. Sproget indeholder også operatoren ":=", der er iboende til DELPHI-sproget. Konceptet med pakker er lånt fra sprog som ADA. Deklarationen af ​​ubrugte enheder er lånt fra PROLOG-sproget.

Semantik

Pakkerne var baseret på DELPHI-sprogets semantik. Hver pakke indkapsler data og kode og indeholder private og offentlige enheder. Dette giver dig mulighed for at reducere pakkegrænsefladen til et minimum.

Implementeringsoperationen ved delegation blev lånt fra DELPHI-sproget.

Samling

Det er ikke uden grund, at der er en joke: Go blev udviklet, mens et C-program blev kompileret. En af styrkerne ved sproget er dets ultrahurtige kompilering. Idéen er lånt fra DELPHI-sproget. Hver Go-pakke svarer til et DELPHI-modul. Disse pakker genkompileres kun, når det virkelig er nødvendigt. Derfor behøver du ikke efter næste redigering at kompilere hele programmet, men derimod kun omkompilere de ændrede pakker og pakker, der er afhængige af disse ændrede pakker (og selv da kun hvis pakkegrænsefladerne er ændret).

Konstruktioner på højt niveau

Sproget indeholder mange forskellige højniveaukonstruktioner, der på ingen måde er relateret til lavniveausprog som C.

  • linjer
  • Hash tabeller
  • Skiver
  • Andeskrivning er lånt fra sprog som RUBY (som desværre mange ikke forstår eller bruger til sit fulde potentiale).

Hukommelseshåndtering

Hukommelseshåndtering fortjener generelt en separat artikel. Hvis i sprog som C++, kontrollen er fuldstændig overladt til udvikleren, så blev der i senere sprog som DELPHI brugt en referencetællingsmodel. Med denne tilgang var cykliske referencer ikke tilladt, da forældreløse klynger blev dannet, så har Go indbygget detektion af sådanne klynger (som C#). Derudover er skraldeopsamleren mere effektiv end de fleste kendte implementeringer og kan allerede bruges til mange realtidsopgaver. Sproget selv genkender situationer, hvor en værdi til at lagre en variabel kan allokeres på stakken. Dette reducerer belastningen på hukommelseshåndteringen og øger programmets hastighed.

Samtidighed og samtidighed

Sprogets parallelitet og konkurrenceevne er hævet over ros. Intet sprog på lavt niveau kan endda eksternt konkurrere med Go. For at være retfærdig er det værd at bemærke, at modellen ikke er opfundet af sprogets forfattere, men blot er lånt fra det gode gamle ADA-sprog. Sproget er i stand til at behandle millioner af parallelle forbindelser ved hjælp af alle CPU'er, mens det har en størrelsesorden mindre komplekse problemer med deadlocks og raceforhold, der er typiske for multi-threaded kode.

Yderligere fordele

Hvis det er rentabelt, vil alle blive uselviske.

Sproget giver os også en række utvivlsomme fordele:

  • En enkelt eksekverbar fil efter opbygning af projektet forenkler i høj grad implementeringen af ​​applikationer.
  • Statisk skrivning og typeslutning kan reducere antallet af fejl i din kode markant, selv uden at skrive test. Jeg kender nogle programmører, der overhovedet undlader at skrive test, og kvaliteten af ​​deres kode lider ikke væsentligt.
  • Meget enkel krydskompilering og fremragende portabilitet af standardbiblioteket, hvilket i høj grad forenkler udviklingen af ​​applikationer på tværs af platforme.
  • RE2 regulære udtryk er trådsikre og har forudsigelige eksekveringstider.
  • Et kraftfuldt standardbibliotek, der tillader de fleste projekter at klare sig uden tredjepartsrammer.
  • Sproget er kraftfuldt nok til at fokusere på problemet snarere end hvordan man løser det, men alligevel lavt niveau nok til at problemet kan løses effektivt.
  • Go eco-systemet indeholder allerede udviklede værktøjer ud af æsken til alle lejligheder: test, dokumentation, pakkehåndtering, kraftfulde linters, kodegenerering, racerforholdsdetektor osv.
  • Go version 1.11 introducerede indbygget semantisk afhængighedsstyring, bygget oven på populær VCS-hosting. Alle de værktøjer, der udgør Go-økosystemet, bruger disse tjenester til at downloade, bygge og installere kode fra dem i ét hug. Og det er fantastisk. Med ankomsten af ​​version 1.11 blev problemet med pakkeversionering også fuldstændig løst.
  • Fordi kerneideen med sproget er at reducere magi, tilskynder sproget udviklere til eksplicit at udføre fejlhåndtering. Og det er korrekt, for ellers vil den simpelthen helt glemme fejlhåndteringen. En anden ting er, at de fleste udviklere bevidst ignorerer fejlhåndtering og foretrækker i stedet for at behandle dem blot at videresende fejlen opad.
  • Sproget implementerer ikke den klassiske OOP-metodologi, da der i sin rene form ikke er virtualitet i Go. Dette er dog ikke et problem ved brug af grænseflader. Fraværet af OOP reducerer markant adgangsbarrieren for begyndere.

Enkelhed til gavn for samfundet

Det er let at komplicere, svært at forenkle.

Go er designet til at være enkelt, og det lykkes med det mål. Den er skrevet til smarte programmører, der forstår fordelene ved teamwork og er trætte af den uendelige variation af sprog på virksomhedsniveau. Med et relativt lille sæt syntaktiske strukturer i sit arsenal er det praktisk talt ikke genstand for ændringer over tid, så udviklere har frigjort en masse tid til udvikling og ikke til uendeligt at studere sproginnovationer.

Virksomheder får også en række fordele: en lav adgangsbarriere giver dem mulighed for hurtigt at finde en specialist, og sprogets uforanderlighed giver dem mulighed for at bruge den samme kode selv efter 10 år.

Konklusion

Stor hjernestørrelse har aldrig gjort nogen elefant til en nobelprisvinder.

For de programmører, hvis personlige ego har forrang over holdånd, såvel som teoretikere, der elsker akademiske udfordringer og endeløs "selv-forbedring", er sproget virkelig dårligt, da det er et almindeligt håndværkssprog, der ikke tillader dig at få æstetisk fornøjelse af resultatet af dit arbejde og vis dig selv professionel foran kolleger (forudsat at vi måler intelligens efter disse kriterier og ikke efter IQ). Som alt andet her i livet er det et spørgsmål om personlige prioriteringer. Som alle værdifulde innovationer er sproget allerede nået langt fra universel benægtelse til masseaccept. Sproget er genialt i sin enkelthed, og som bekendt er alt genialt enkelt!

Kilde: www.habr.com

Tilføj en kommentar