Waarom Go slecht is voor onslimme programmeurs

Het artikel is geschreven als reactie op een eerder gepubliceerd artikel antipodisch artikel.

Waarom Go slecht is voor onslimme programmeurs

De afgelopen ruim twee jaar heb ik Go gebruikt om een ​​gespecialiseerde RADIUS-server met een ontwikkeld factureringssysteem te implementeren. Gaandeweg leer ik de fijne kneepjes van de taal zelf. De programma's zelf zijn heel eenvoudig en vormen niet het doel van dit artikel, maar de ervaring met het gebruik van Go zelf verdient een paar woorden ter verdediging. Go wordt steeds meer een mainstream-taal voor serieuze, schaalbare code. De taal is gemaakt door Google, waar deze actief wordt gebruikt. Kortom, ik denk eerlijk gezegd dat het ontwerp van de Go-taal slecht is voor niet-intelligente programmeurs.

Ontworpen voor zwakke programmeurs?

De zwakken spreken van problemen. Het sterke gesprek over ideeën en dromen...

Go is heel gemakkelijk te leren, zo eenvoudig dat u de code vrijwel zonder enige training kunt lezen. Deze functie van de taal wordt in veel internationale bedrijven gebruikt wanneer de code samen met niet-kernspecialisten (managers, klanten, enz.) wordt gelezen. Dit is erg handig voor methodieken als Design Driven Development.
Zelfs beginnende programmeurs beginnen na een week of twee behoorlijk behoorlijke code te produceren. Het boek waaruit ik heb gestudeerd is “Go Programming” (door Mark Summerfield). Het boek is erg goed, het raakt veel nuances van de taal. Na onnodig ingewikkelde talen als Java, PHP is het gebrek aan magie verfrissend. Maar vroeg of laat komen veel beperkte programmeurs op het idee om oude methoden op een nieuw gebied te gebruiken. Is dit echt nodig?

Rob Pike (de belangrijkste ideoloog van de taal) creëerde de Go-taal als een industriële taal die gemakkelijk te begrijpen en effectief te gebruiken is. De taal is ontworpen voor maximale productiviteit in grote teams en daar bestaat geen twijfel over. Veel beginnende programmeurs klagen dat er veel functies zijn die ze missen. Dit verlangen naar eenvoud was een bewuste beslissing van de ontwerpers van de taal, en om volledig te begrijpen waarom dit nodig was, moeten we de motivatie van de ontwikkelaars begrijpen en wat ze probeerden te bereiken met Go.

Dus waarom werd het zo eenvoudig gemaakt? Hier zijn een paar citaten van Rob Pike:

Het belangrijkste punt hier is dat onze programmeurs geen onderzoekers zijn. Ze zijn in de regel vrij jong en komen na hun studie naar ons toe, misschien hebben ze Java gestudeerd, of C/C++, of Python. Ze begrijpen geen geweldige taal, maar tegelijkertijd willen we dat ze goede software maken. Daarom moet de taal gemakkelijk te begrijpen en te leren zijn.

Hij zou bekend moeten zijn, grofweg vergelijkbaar met C. Programmeurs die bij Google werken, beginnen hun carrière vroeg en zijn meestal bekend met proceduretalen, in het bijzonder de C-familie. De eis voor snelle productiviteit in een nieuwe programmeertaal betekent dat de taal niet te radicaal mag zijn.

Wijze woorden, nietwaar?

Artefacten van eenvoud

Eenvoud is een noodzakelijke voorwaarde voor schoonheid. Lev Tolstoj.

Het simpel houden is een van de belangrijkste doelen bij elk ontwerp. Zoals u weet is een perfect project geen project waaraan niets kan worden toegevoegd, maar een project waaraan niets kan worden verwijderd. Veel mensen zijn van mening dat er een complex hulpmiddel nodig is om complexe problemen op te lossen (of zelfs uit te drukken). Dat is echter niet het geval. Laten we als voorbeeld de PERL-taal nemen. Taalideologen waren van mening dat een programmeur minstens drie verschillende manieren moet hebben om één probleem op te lossen. De ideologen van de Go-taal kozen een ander pad; ze besloten dat één manier, maar dan wel een hele goede, genoeg was om het doel te bereiken. Deze aanpak heeft een serieuze basis: de enige manier is gemakkelijker te leren en moeilijker te vergeten.

Veel migranten klagen dat de taal geen elegante abstracties bevat. Ja, dit is waar, maar dit is een van de belangrijkste voordelen van de taal. De taal bevat een minimum aan magie - er is dus geen diepgaande kennis vereist om het programma te lezen. Wat de breedsprakigheid van de code betreft, dit is helemaal geen probleem. Een goed geschreven Golang-programma leest verticaal, met weinig of geen structuur. Bovendien is de snelheid waarmee een programma wordt gelezen minstens een orde van grootte groter dan de snelheid waarmee het wordt geschreven. Als je bedenkt dat alle code een uniforme opmaak heeft (gedaan met de ingebouwde opdracht gofmt), dan is het lezen van een paar extra regels helemaal geen probleem.

Niet erg expressief

Kunst tolereert niet wanneer haar vrijheid wordt beperkt. Nauwkeurigheid is niet zijn verantwoordelijkheid.

Door het verlangen naar eenvoud mist Go constructies die in andere talen door mensen die eraan gewend zijn als iets natuurlijks worden ervaren. In eerste instantie kan het wat onhandig zijn, maar dan merk je dat het programma veel makkelijker en eenduidiger te lezen is.

Een consolehulpprogramma dat stdin of een bestand van opdrachtregelargumenten leest, zou er bijvoorbeeld als volgt uitzien:

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

De oplossing voor hetzelfde probleem in D lijkt weliswaar wat korter, maar is niet makkelijker te lezen

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

Een hel van kopiëren

De mens draagt ​​de hel in zich. Martin Luther.

Beginners klagen voortdurend over Go in termen van het gebrek aan generieke geneesmiddelen. Om dit probleem op te lossen, gebruiken de meeste van hen het direct kopiëren van code. Als het bijvoorbeeld gaat om een ​​functie voor het optellen van een lijst met gehele getallen, zijn zulke aspirant-professionals van mening dat de functionaliteit op geen enkele andere manier kan worden geïmplementeerd dan door simpelweg te kopiëren en plakken voor elk gegevenstype.

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

De taal beschikt over voldoende middelen om dergelijke constructies te implementeren. Generieke programmering zou bijvoorbeeld prima zijn.

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

En hoewel onze code iets langer bleek te zijn dan het vorige geval, is deze gegeneraliseerd geworden. Daarom zal het voor ons niet moeilijk zijn om alle rekenkundige bewerkingen te implementeren.

Velen zullen zeggen dat een programma in D er aanzienlijk korter uitziet, en ze zullen gelijk hebben.

import std.stdio;
import std.algorithm;

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

Het is echter alleen korter, maar niet correcter, aangezien de D-implementatie het probleem van foutafhandeling volledig negeert.

In het echte leven wordt de kloof snel kleiner naarmate de complexiteit van de logica toeneemt. De kloof wordt nog sneller gedicht als u een actie moet uitvoeren die niet met standaardtaaloperatoren kan worden uitgevoerd.

In termen van onderhoudbaarheid, uitbreidbaarheid en leesbaarheid wint de Go-taal naar mijn mening, hoewel deze verliest aan breedsprakigheid.

Gegeneraliseerde programmering levert ons in sommige gevallen onmiskenbare voordelen op. Dit wordt duidelijk geïllustreerd door het sorteerpakket. Om een ​​lijst te sorteren, hoeven we dus alleen maar de sort.Interface-interface te implementeren.

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

Als je een open source-project neemt en de opdracht grep “interface{}” -R uitvoert, zul je zien hoe vaak verwarrende interfaces worden gebruikt. Bekrompen kameraden zullen onmiddellijk zeggen dat dit allemaal te wijten is aan het gebrek aan generieke geneesmiddelen. Dit is echter niet altijd het geval. Laten we DELPHI als voorbeeld nemen. Ondanks de aanwezigheid van dezelfde generieke geneesmiddelen, bevat het een speciaal VARIANT-type voor bewerkingen met willekeurige gegevenstypen. De Go-taal doet hetzelfde.

Van een kanon tot mussen

En het keurslijf moet passen bij de omvang van de waanzin. Stanislav Lec.

Veel extreme fans beweren misschien dat Go een ander mechanisme heeft voor het creëren van generieke geneesmiddelen: reflectie. En ze zullen gelijk hebben... maar alleen in zeldzame gevallen.

Rob Pike waarschuwt ons:

Dit is een krachtig hulpmiddel dat met voorzichtigheid moet worden gebruikt. Het moet worden vermeden, tenzij strikt noodzakelijk.

Wikipedia vertelt ons het volgende:

Reflectie verwijst naar het proces waarbij een programma zijn eigen structuur en gedrag tijdens de uitvoering kan monitoren en wijzigen. Het programmeerparadigma dat ten grondslag ligt aan reflectie wordt reflectief programmeren genoemd. Dit is een vorm van metaprogrammering.

Maar zoals je weet, moet je voor alles betalen. In dit geval is het:

  • moeite met het schrijven van programma's
  • uitvoeringssnelheid van het programma

Daarom moet reflectie met voorzichtigheid worden gebruikt, als een wapen van groot kaliber. Ondoordacht gebruik van reflectie leidt tot onleesbare programma's, constante fouten en lage snelheid. Precies wat een snobprogrammeur nodig heeft om zijn code te kunnen laten zien aan andere, meer pragmatische en bescheiden collega's.

Culturele bagage van Xi? Nee, vanuit een aantal talen!

Naast het fortuin worden ook de schulden aan de erfgenamen nagelaten.

Ondanks dat velen geloven dat de taal volledig gebaseerd is op het C-erfgoed, is dit niet het geval. De taal bevat veel aspecten van de beste programmeertalen.

syntaxis

Allereerst is de syntaxis van grammaticale structuren gebaseerd op de syntaxis van de C-taal. De DELPHI-taal had echter ook een aanzienlijke invloed. We zien dus dat de overbodige haakjes, die de leesbaarheid van het programma aanzienlijk verminderen, volledig zijn verwijderd. De taal bevat ook de operator “:=” die inherent is aan de DELPHI-taal. Het concept van pakketten is ontleend aan talen als ADA. De aangifte van ongebruikte entiteiten is ontleend aan de PROLOG-taal.

Semantiek

De pakketten waren gebaseerd op de semantiek van de DELPHI-taal. Elk pakket omvat gegevens en code en bevat particuliere en publieke entiteiten. Hierdoor kunt u de pakketinterface tot een minimum beperken.

De implementatiebewerking via delegatiemethode is ontleend aan de DELPHI-taal.

Compilatie

Er is niet voor niets een grapje: Go is ontwikkeld terwijl er een C-programma werd gecompileerd. Een van de sterke punten van de taal is de ultrasnelle compilatie. Het idee is ontleend aan de DELPHI-taal. Elk Go-pakket komt overeen met een DELPHI-module. Deze pakketten worden alleen opnieuw gecompileerd als dat echt nodig is. Daarom hoeft u na de volgende bewerking niet het hele programma te compileren, maar hoeft u alleen de gewijzigde pakketten en pakketten die afhankelijk zijn van deze gewijzigde pakketten opnieuw te compileren (en zelfs dan alleen als de pakketinterfaces zijn gewijzigd).

Constructies op hoog niveau

De taal bevat veel verschillende constructies op hoog niveau die op geen enkele manier gerelateerd zijn aan talen op laag niveau zoals C.

  • lijnen
  • Hash-tabellen
  • Plakjes
  • Duck-typering is ontleend aan talen als RUBY (die helaas door velen niet volledig wordt begrepen of gebruikt).

Geheugen management

Geheugenbeheer verdient over het algemeen een apart artikel. Als in talen als C++ de controle volledig aan de ontwikkelaar wordt overgelaten, werd in latere talen zoals DELPHI een referentietelmodel gebruikt. Met deze aanpak waren cyclische verwijzingen niet toegestaan, omdat er weesclusters werden gevormd, en Go een ingebouwde detectie van dergelijke clusters heeft (zoals C#). Bovendien is de garbage collector efficiënter dan de meeste momenteel bekende implementaties en kan deze al voor veel realtime taken worden gebruikt. De taal herkent zelf situaties waarin een waarde voor het opslaan van een variabele op de stapel kan worden toegewezen. Dit vermindert de belasting van de geheugenbeheerder en verhoogt de snelheid van het programma.

Gelijktijdigheid en gelijktijdigheid

Het parallellisme en het concurrentievermogen van de taal zijn niet te prijzen. Geen enkele taal op laag niveau kan zelfs maar op afstand concurreren met Go. Om eerlijk te zijn is het vermeldenswaard dat het model niet is uitgevonden door de auteurs van de taal, maar eenvoudigweg is ontleend aan de goede oude ADA-taal. De taal kan miljoenen parallelle verbindingen verwerken met behulp van alle CPU's, terwijl er sprake is van een orde van grootte minder complexe problemen met impasses en race-omstandigheden die typisch zijn voor multi-threaded code.

Aanvullende voordelen

Als het winstgevend is, wordt iedereen onbaatzuchtig.

Taal biedt ons ook een aantal onbetwiste voordelen:

  • Eén enkel uitvoerbaar bestand na het bouwen van het project vereenvoudigt de implementatie van applicaties aanzienlijk.
  • Statisch typen en type-inferentie kunnen het aantal fouten in uw code aanzienlijk verminderen, zelfs zonder tests te schrijven. Ik ken een aantal programmeurs die helemaal geen tests schrijven en de kwaliteit van hun code daar niet significant onder lijdt.
  • Zeer eenvoudige cross-compilatie en uitstekende portabiliteit van de standaardbibliotheek, wat de ontwikkeling van platformonafhankelijke applicaties aanzienlijk vereenvoudigt.
  • RE2 reguliere expressies zijn thread-safe en hebben voorspelbare uitvoeringstijden.
  • Een krachtige standaardbibliotheek waarmee de meeste projecten zonder frameworks van derden kunnen werken.
  • De taal is krachtig genoeg om zich op het probleem te concentreren in plaats van op de oplossing ervan, maar toch laag genoeg om het probleem efficiënt op te lossen.
  • Het Go-ecosysteem bevat al kant-en-klare tools voor alle gelegenheden: tests, documentatie, pakketbeheer, krachtige linters, codegeneratie, detectie van raceomstandigheden, enz.
  • Go versie 1.11 introduceerde ingebouwd semantisch afhankelijkheidsbeheer, gebouwd bovenop de populaire VCS-hosting. Alle tools waaruit het Go-ecosysteem bestaat, gebruiken deze services om er in één klap code van te downloaden, bouwen en installeren. En dat is geweldig. Met de komst van versie 1.11 was ook het probleem met pakketversiebeheer volledig opgelost.
  • Omdat het kernidee van de taal het verminderen van magie is, stimuleert de taal ontwikkelaars om expliciet aan foutafhandeling te doen. En dat is juist, want anders vergeet het de foutafhandeling helemaal. Een ander ding is dat de meeste ontwikkelaars opzettelijk de foutafhandeling negeren en er de voorkeur aan geven de fout eenvoudigweg naar boven door te sturen in plaats van ze te verwerken.
  • De taal implementeert niet de klassieke OOP-methodologie, omdat er in zijn pure vorm geen virtualiteit is in Go. Bij het gebruik van interfaces is dit echter geen probleem. De afwezigheid van OOP vermindert de toetredingsdrempel voor beginners aanzienlijk.

Eenvoud ten behoeve van de gemeenschap

Het is gemakkelijk ingewikkeld te maken, moeilijk te vereenvoudigen.

Go is ontworpen om eenvoudig te zijn en slaagt daarin. Het is geschreven voor slimme programmeurs die de voordelen van teamwerk begrijpen en de eindeloze variabiliteit van talen op ondernemingsniveau beu zijn. Omdat het een relatief klein aantal syntactische structuren in zijn arsenaal heeft, is het praktisch niet onderhevig aan veranderingen in de loop van de tijd, dus ontwikkelaars hebben veel tijd vrijgemaakt voor ontwikkeling, en niet voor het eindeloos bestuderen van taalinnovaties.

Bedrijven profiteren ook van een aantal voordelen: dankzij een lage toetredingsdrempel kunnen ze snel een specialist vinden, en dankzij de onveranderlijkheid van de taal kunnen ze zelfs na tien jaar dezelfde code gebruiken.

Conclusie

Grote hersengroottes hebben nog nooit van een olifant een Nobelprijswinnaar gemaakt.

Voor programmeurs wier persoonlijke ego voorrang heeft boven teamgeest, maar ook voor theoretici die houden van academische uitdagingen en eindeloze ‘zelfverbetering’, is de taal echt slecht, omdat het een ambachtelijke taal voor algemeen gebruik is die je niet toestaat om esthetisch plezier van het resultaat van je werk en jezelf professioneel tonen tegenover collega's (op voorwaarde dat we intelligentie meten aan de hand van deze criteria, en niet aan de hand van IQ). Zoals alles in het leven is het een kwestie van persoonlijke prioriteiten. Zoals alle waardevolle innovaties heeft de taal al een lange weg afgelegd: van universele ontkenning naar massale acceptatie. De taal is ingenieus in zijn eenvoud, en zoals je weet is alles ingenieus eenvoudig!

Bron: www.habr.com

Voeg een reactie