Waarom Go Design sleg is vir slim programmeerders

Oor die afgelope maande het ek Go vir implementering gebruik. Bewys van die konsep (ongeveer.: kode om die funksionaliteit van 'n idee te toets) in sy vrye tyd, deels om die programmeertaal self te bestudeer. Die programme self is baie eenvoudig en is nie die doel van hierdie artikel nie, maar die ervaring van die gebruik van Go self verdien 'n paar woorde daaroor. Gaan beloof om te wees (ongeveer.: artikel geskryf in 2015) 'n gewilde taal vir ernstige skaalbare kode. Die taal is deur Google geskep, waar dit aktief gebruik word. Kortom, ek dink eerlikwaar dat die ontwerp van die Go-taal sleg is vir slim programmeerders.

Ontwerp vir swak programmeerders?

Go is baie maklik om te leer, so maklik dat die inleiding my een aand geneem het, waarna ek reeds produktief kon kodeer. Die boek wat ek gebruik het om te leer Gaan heet 'n Inleiding tot programmering in Go (vertaling), is dit aanlyn beskikbaar. Die boek, soos die Go-bronkode self, is maklik om te lees, het goeie kodevoorbeelde en bevat sowat 150 bladsye wat in een slag gelees kan word. Hierdie eenvoud is aanvanklik verfrissend, veral in 'n programmeringswêreld gevul met oorgekompliseerde tegnologie. Maar op die ou end kom die gedagte vroeër of later op: "Is dit regtig so?"

Google beweer Go se eenvoud is sy verkoopspunt en die taal is ontwerp vir maksimum produktiwiteit in groot spanne, maar ek twyfel daaraan. Daar is kenmerke wat óf ontbreek óf te gedetailleerd is. En dit alles weens 'n gebrek aan vertroue in ontwikkelaars, met die veronderstelling dat hulle niks reg kan doen nie. Hierdie begeerte na eenvoud was 'n bewuste besluit deur die taal se ontwerpers, en om ten volle te verstaan ​​waarom dit nodig was, moet ons die motivering van die ontwikkelaars verstaan ​​en wat hulle in Go probeer bereik het.

So hoekom is dit so eenvoudig gemaak? Hier is 'n paar aanhalings Rob Pike (ongeveer.: een van die mede-skeppers van die Go-taal):

Die sleutelpunt hier is dat ons programmeerders (ongeveer.: Googlers) is nie navorsers nie. Hulle is, as 'n reël, redelik jonk, kom na ons toe nadat hulle studeer het, miskien het hulle Java, of C/C++, of Python bestudeer. Hulle kan nie 'n wonderlike taal verstaan ​​nie, maar terselfdertyd wil ons hê hulle moet goeie sagteware skep. Daarom moet hul taal vir hulle maklik wees om te verstaan ​​en te leer.
 
Hy behoort bekend te wees, min of meer soortgelyk aan C. Programmeerders wat by Google werk, begin hul loopbane vroeg en is meestal vertroud met proseduretale, veral die C-familie. Die vereiste vir vinnige produktiwiteit in 'n nuwe programmeertaal beteken dat die taal nie te radikaal moet wees nie.

Wat? So Rob Pike sê basies dat die ontwikkelaars by Google nie so goed is nie, dis hoekom hulle 'n taal vir idiote geskep het (ongeveer.: afgestomp) sodat hulle in staat is om iets te doen. Watter soort arrogante kyk na jou eie kollegas? Ek het nog altyd geglo dat Google se ontwikkelaars uit die slimste en beste op aarde gekies word. Hulle kan tog iets moeiliker hanteer?

Artefakte van oormatige eenvoud

Om eenvoudig te wees is 'n waardige doel in enige ontwerp, en om iets eenvoudig te probeer maak, is moeilik. Wanneer daar egter probeer word om komplekse probleme op te los (of selfs uiting te gee), is soms 'n komplekse hulpmiddel nodig. Kompleksiteit en ingewikkeldheid is nie die beste kenmerke van 'n programmeertaal nie, maar daar is 'n middeweg waarin die taal elegante abstraksies kan skep wat maklik is om te verstaan ​​en te gebruik.

Nie baie ekspressief nie

Vanweë sy verbintenis tot eenvoud, het Go nie konstrukte wat as natuurlik in ander tale beskou word nie. Dit mag aanvanklik na 'n goeie idee lyk, maar in die praktyk lei dit tot 'n uitgebreide kode. Die rede hiervoor behoort voor die hand liggend te wees – dit moet maklik wees vir ontwikkelaars om ander mense se kode te lees, maar in werklikheid benadeel hierdie vereenvoudigings net die leesbaarheid. Daar is geen afkortings in Go nie: óf baie óf niks.

Byvoorbeeld, 'n konsolehulpmiddel wat stdin lees of 'n lêer vanaf opdragreëlargumente sal soos volg lyk:

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

Alhoewel hierdie kode ook so algemeen as moontlik probeer wees, staan ​​Go se gedwonge breedsprakigheid in die pad, en gevolglik lei die oplossing van 'n eenvoudige probleem tot 'n groot hoeveelheid kode.

Hier is byvoorbeeld 'n oplossing vir dieselfde probleem in 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);
    }
}

En wie is nou meer leesbaar? Ek sal my stem aan D gee. Sy kode is baie meer leesbaar omdat hy die optrede duideliker beskryf. D gebruik baie meer komplekse konsepte (ongeveer.: alternatiewe funksie oproep и templates) as in die Go-voorbeeld, maar daar is regtig niks ingewikkeld daaraan om hulle te verstaan ​​nie.

Hel van kopiëring

'n Gewilde voorstel om Go te verbeter, is algemeenheid. Dit sal ten minste help om onnodige kopiëring van kode te vermy om alle datatipes te ondersteun. Byvoorbeeld, 'n funksie om 'n lys heelgetalle op te som kan op geen ander manier geïmplementeer word as deur die basiese funksie daarvan vir elke heelgetaltipe te kopieer-plak nie; daar is geen ander manier nie:

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

En hierdie voorbeeld werk nie eers vir getekende tipes nie. Hierdie benadering oortree heeltemal die beginsel om nie jouself te herhaal nie (DRY), een van die bekendste en voor die hand liggende beginsels, en ignoreer wat die bron van baie foute is. Hoekom doen Go dit? Dit is 'n verskriklike aspek van taal.

Dieselfde voorbeeld op D:

import std.stdio;
import std.algorithm;

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

Eenvoudig, elegant en reguit tot die punt. Die funksie wat hier gebruik word is reduce vir sjabloontipe en predikaat. Ja, dit is weer meer ingewikkeld as die Go-weergawe, maar nie so moeilik vir slim programmeerders om te verstaan ​​nie. Watter voorbeeld is makliker om te onderhou en makliker om te lees?

Eenvoudige tipe stelsel omleiding

Ek stel my voor dat Go-programmeerders wat hierdie lees, sal skuim om die mond en skree, "Jy doen dit verkeerd!" Wel, daar is 'n ander manier om 'n generiese funksie en tipes te maak, maar dit breek die tipe stelsel heeltemal!

Kyk na hierdie voorbeeld van 'n dom taaloplossing om die probleem te omseil:

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

Hierdie implementering Reduce is uit die artikel geleen Idiomatiese generieke in Go (ongeveer.: Ek kon nie die vertaling vind nie, ek sal bly wees as jy hiermee help). Wel, as dit idiomaties is, sal ek dit nie wil hê om 'n nie-idiomatiese voorbeeld te sien. Gebruik interface{} - 'n klug, en in die taal is dit net nodig om tik te omseil. Dit is 'n leë koppelvlak en alle tipes implementeer dit, wat volledige vryheid vir almal toelaat. Hierdie styl van programmering is verskriklik lelik, en dit is nie al nie. Akrobatiese prestasies soos hierdie vereis die gebruik van runtime refleksie. Selfs Rob Pike hou nie van individue wat dit misbruik nie, soos hy in een van sy verslae genoem het.

Dit is 'n kragtige instrument wat met omsigtigheid gebruik moet word. Dit moet vermy word tensy dit streng nodig is.

Ek sal D-sjablone neem in plaas van hierdie nonsens. Hoe kan iemand dit sê interface{} meer leesbaar of selfs tikveilig?

Die ellende van afhanklikheidsbestuur

Go het 'n ingeboude afhanklikheidstelsel wat bo-op gewilde gasheerverskaffers gebou is VCS. Die nutsgoed wat saam met Go kom, weet van hierdie dienste en kan in een klap kode daaruit aflaai, bou en installeer. Alhoewel dit wonderlik is, is daar 'n groot fout met weergawe! Ja, dit is waar dat jy die bronkode van dienste soos github of bitbucket kan kry met behulp van Go-gereedskap, maar jy kan nie die weergawe spesifiseer nie. En weer eenvoud ten koste van bruikbaarheid. Ek is nie in staat om die logika van so 'n besluit te verstaan ​​nie.

Nadat hy vrae gevra het oor 'n oplossing vir hierdie probleem, het die Go-ontwikkelingspan geskep forum draad, wat uiteengesit het hoe hulle hierdie kwessie gaan omseil. Hul aanbeveling was om eenvoudig die hele bewaarplek eendag na jou projek te kopieer en dit "soos dit is" te laat. Wat de hel dink hulle? Ons het ongelooflike weergawebeheerstelsels met uitstekende etikettering en weergawe-ondersteuning wat die Go-skeppers ignoreer en net die bronkode kopieer.

Kulturele bagasie van Xi

Myns insiens is Go ontwikkel deur mense wat C hul hele lewe lank gebruik het en deur diegene wat nie iets nuuts wou probeer nie. Die taal kan beskryf word as C met ekstra wiele(oorspronklike.: oefenwiele). Daar is geen nuwe idees daarin nie, behalwe vir ondersteuning vir parallelisme (wat terloops wonderlik is) en dit is jammer. Jy het uitstekende parallelisme in 'n skaars bruikbare, lam taal.

Nog 'n knarsende probleem is dat Go 'n proseduretaal is (soos die stille gruwel van C). Jy skryf uiteindelik kode in 'n prosedurele styl wat argaïes en verouderd voel. Ek weet objekgeoriënteerde programmering is nie 'n silwer koeël nie, maar dit sal wonderlik wees om die besonderhede in tipes te kan abstraheer en inkapseling te verskaf.

Eenvoud tot u eie voordeel

Go is ontwerp om eenvoudig te wees en dit slaag met daardie doelwit. Dit is geskryf vir swak programmeerders, met 'n ou taal as 'n sjabloon. Dit kom kompleet met eenvoudige gereedskap om eenvoudige dinge te doen. Dit is maklik om te lees en maklik om te gebruik.

Dit is uiters breedvoerig, onindrukwekkend en sleg vir slim programmeerders.

Dankie mersinvald vir wysigings

Bron: will.com

Voeg 'n opmerking