Per què Go Design és dolent per als programadors intel·ligents

Durant els últims mesos he estat utilitzant Go per a implementacions. Prova de concepte (aprox.: codi per provar la funcionalitat d'una idea) en el seu temps lliure, en part per estudiar el propi llenguatge de programació. Els programes en si són molt senzills i no són l'objectiu d'aquest article, però la pròpia experiència d'utilitzar Go mereix unes quantes paraules al respecte. Go promet ser (aprox.: article escrit el 2015) un llenguatge popular per a codi escalable seriós. L'idioma va ser creat per Google, on s'utilitza activament. En resum, sincerament crec que el disseny del llenguatge Go és dolent per als programadors intel·ligents.

Dissenyat per a programadors febles?

Go és molt fàcil d'aprendre, tan fàcil que la presentació em va portar un vespre, després de la qual ja vaig poder codificar de manera productiva. El llibre que vaig aprendre abans es diu Go Una introducció a la programació a Go (traducció), està disponible en línia. El llibre, com el propi codi font de Go, és fàcil de llegir, té bons exemples de codi i conté unes 150 pàgines que es poden llegir d'una vegada. Aquesta senzillesa és refrescant al principi, sobretot en un món de programació ple de tecnologia massa complicada. Però al final, tard o d'hora, sorgeix el pensament: "És realment així?"

Google afirma que la senzillesa de Go és el seu argument de venda i que el llenguatge està dissenyat per a la màxima productivitat en equips grans, però ho dubto. Hi ha funcions que falten o estan massa detallades. I tot per falta de confiança en els desenvolupadors, amb el supòsit que no són capaços de fer res bé. Aquest desig de senzillesa va ser una decisió conscient dels dissenyadors de l'idioma, i per entendre completament per què era necessari, hem d'entendre la motivació dels desenvolupadors i què estaven intentant aconseguir a Go.

Aleshores, per què es va fer tan senzill? Aquí teniu un parell de cites Rob Pike (aprox.: un dels co-creadors del llenguatge Go):

El punt clau aquí és que els nostres programadors (aprox.: Googlers) no són investigadors. Són, per regla general, força joves, ens venen després d'estudiar, potser van estudiar Java, o C/C++ o Python. No poden entendre un gran llenguatge, però al mateix temps volem que creïn un bon programari. És per això que la seva llengua ha de ser fàcil d'entendre i aprendre.
 
Hauria de ser familiar, aproximadament semblant a C. Els programadors que treballen a Google comencen la seva carrera aviat i estan familiaritzats principalment amb els llenguatges procedimentals, en particular la família C. El requisit de productivitat ràpida en un nou llenguatge de programació significa que el llenguatge no hauria de ser massa radical.

Què? Així que Rob Pike bàsicament diu que els desenvolupadors de Google no són tan bons, per això van crear un llenguatge per a idiotes (aprox.: mut) perquè siguin capaços de fer alguna cosa. Quina mena de mirada arrogant als teus propis companys? Sempre he cregut que els desenvolupadors de Google són triats a mà entre els més brillants i millors de la Terra. Segur que poden fer front a alguna cosa més difícil?

Artefactes d'excessiva simplicitat

Ser senzill és un objectiu digne en qualsevol disseny, i intentar fer alguna cosa senzill és difícil. Tanmateix, quan s'intenta resoldre (o fins i tot expressar) problemes complexos, de vegades es necessita una eina complexa. La complexitat i la complexitat no són les millors característiques d'un llenguatge de programació, però hi ha un terme mitjà en què el llenguatge pot crear abstraccions elegants que siguin fàcils d'entendre i utilitzar.

Poc expressiu

A causa del seu compromís amb la senzillesa, Go no té construccions que es percebin com a naturals en altres idiomes. Això pot semblar una bona idea al principi, però a la pràctica resulta en codi detallat. La raó d'això hauria de ser òbvia: els desenvolupadors han de ser fàcils de llegir el codi d'altres persones, però de fet aquestes simplificacions només perjudiquen la llegibilitat. No hi ha abreviatures a Go: o molt o res.

Per exemple, una utilitat de consola que llegeix stdin o un fitxer des d'arguments de línia d'ordres tindria aquest aspecte:

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

Tot i que aquest codi també intenta ser el més general possible, la verbositat forçada de Go s'interposa en el camí i, com a resultat, resoldre un problema senzill dóna com a resultat una gran quantitat de codi.

Aquí, per exemple, hi ha una solució al mateix problema 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);
    }
}

I qui és més llegible ara? Donaré el meu vot a D. El seu codi és molt més llegible perquè descriu les accions amb més claredat. D utilitza conceptes molt més complexos (aprox.: trucada de funció alternativa и plantilles) que a l'exemple de Go, però realment no hi ha res complicat a l'hora d'entendre'ls.

L'infern de la còpia

Un suggeriment popular per millorar Go és la generalitat. Això, almenys, ajudarà a evitar la còpia innecessària del codi per admetre tots els tipus de dades. Per exemple, una funció per sumar una llista d'enters no es pot implementar d'una altra manera que copiant-enganxant la seva funció bàsica per a cada tipus d'enter; no hi ha cap altra manera:

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

I aquest exemple ni tan sols funciona per als tipus signats. Aquest enfocament viola completament el principi de no repetir-se (SEC), un dels principis més famosos i evidents, ignorant quina és la font de molts errors. Per què Go fa això? Aquest és un aspecte terrible del llenguatge.

El mateix exemple a D:

import std.stdio;
import std.algorithm;

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

Simple, elegant i directe al punt. La funció que s'utilitza aquí és reduce per al tipus de plantilla i predicat. Sí, això torna a ser més complicat que la versió Go, però no és tan difícil d'entendre per als programadors intel·ligents. Quin exemple és més fàcil de mantenir i de llegir?

Bypass del sistema de tipus simple

Imagino que els programadors de Go que llegeixen això faran escuma a la boca i cridaran: "Ho estàs fent malament!" Bé, hi ha una altra manera de fer una funció i tipus genèrics, però trenca completament el sistema de tipus!

Mireu aquest exemple d'una solució d'idioma estúpid per solucionar el problema:

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

Aquesta implementació Reduce va ser manllevat de l'article Genèrics idiomàtics a Go (aprox.: No he trobat la traducció, estaré encantada si m'ajudeu). Bé, si és idiomàtic, no m'agradaria veure un exemple no idiomàtic. Ús interface{} - una farsa, i en l'idioma només cal per evitar la mecanografia. Es tracta d'una interfície buida i tots els tipus la implementen, permetent una llibertat total per a tothom. Aquest estil de programació és terriblement lleig, i això no és tot. Les gestes acrobàtiques com aquestes requereixen l'ús de la reflexió en temps d'execució. Fins i tot a Rob Pike no li agraden les persones que abusen d'això, com va esmentar en un dels seus informes.

Aquesta és una eina potent que s'ha d'utilitzar amb precaució. S'ha d'evitar tret que sigui estrictament necessari.

Prendria plantilles D en comptes d'aquesta tonteria. Com ho pot dir algú interface{} més llegible o fins i tot tipus segur?

Els problemes de la gestió de la dependència

Go té un sistema de dependència integrat integrat a la part superior dels proveïdors d'allotjament populars VCS. Les eines que inclou Go coneixen aquests serveis i poden descarregar-ne, crear-ne i instal·lar codi d'un sol cop. Tot i que això és fantàstic, hi ha un defecte important amb la versió! Sí, és cert que podeu obtenir el codi font de serveis com github o bitbucket mitjançant les eines Go, però no podeu especificar la versió. I de nou la simplicitat a costa de la utilitat. No sóc capaç d'entendre la lògica d'aquesta decisió.

Després de fer preguntes sobre una solució a aquest problema, l'equip de desenvolupament de Go va crear fil del fòrum, que exposava com solucionarien aquest problema. La seva recomanació va ser simplement copiar tot el dipòsit al vostre projecte un dia i deixar-lo "tal qual". Què dimonis estan pensant? Tenim sistemes de control de versions increïbles amb un gran etiquetatge i suport de versions que els creadors de Go ignoren i només copien el codi font.

Bagatge cultural de Xi

Al meu entendre, Go va ser desenvolupat per persones que havien utilitzat C tota la vida i per aquells que no volien provar alguna cosa nova. El llenguatge es pot descriure com a C amb rodes addicionals (orig.: rodes d'entrenament). No hi ha idees noves, excepte el suport al paral·lelisme (que, per cert, és meravellós) i això és una llàstima. Tens un paral·lelisme excel·lent en un llenguatge poc utilitzable i coix.

Un altre problema que cruixent és que Go és un llenguatge procedimental (com l'horror silenciós de C). Acaba escrivint codi amb un estil procedimental que se sent arcaic i obsolet. Sé que la programació orientada a objectes no és una bala de plata, però seria fantàstic poder abstraure els detalls en tipus i proporcionar encapsulació.

Simplicitat per al vostre propi benefici

Go va ser dissenyat per ser senzill i aconsegueix aquest objectiu. Va ser escrit per a programadors febles, utilitzant un llenguatge antic com a plantilla. Ve complet amb eines senzilles per fer coses senzilles. És fàcil de llegir i fàcil d'utilitzar.

És extremadament detallat, poc impressionant i dolent per als programadors intel·ligents.

Gràcies mersinvald per a edicions

Font: www.habr.com

Afegeix comentari