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

L'article va ser escrit com a resposta a un article publicat anteriorment article antípoda.

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

Durant els últims dos anys, he estat utilitzant Go per implementar un servidor RADIUS especialitzat amb un sistema de facturació desenvolupat. Al llarg del camí, estic aprenent les complexitats del propi llenguatge. 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 paraules en la seva defensa. Go s'està convertint en un llenguatge cada cop més habitual per al codi seriós i escalable. 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 poc intel·ligents.

Dissenyat per a programadors febles?

Els febles parlen de problemes. La xerrada forta sobre idees i somnis...

Go és molt fàcil d'aprendre, tan fàcil que pots llegir el codi sense pràcticament cap formació. Aquesta característica de l'idioma s'utilitza en moltes empreses globals, quan el codi es llegeix juntament amb especialistes no centrals (gerents, clients, etc.). Això és molt convenient per a metodologies com el desenvolupament impulsat pel disseny.
Fins i tot els programadors novells comencen a produir un codi bastant decent després d'una o dues setmanes. El llibre del qual vaig estudiar és "Go Programming" (de Mark Summerfield). El llibre és molt bo, toca molts matisos de la llengua. Després de llenguatges innecessàriament complicats com Java, PHP, la manca de màgia és refrescant. Però tard o d'hora, molts programadors limitats tenen la idea d'utilitzar mètodes antics en un camp nou. Això és realment necessari?

Rob Pike (el principal ideòleg de la llengua) va crear el llenguatge Go com un llenguatge industrial fàcil d'entendre i eficaç d'utilitzar. El llenguatge està dissenyat per a la màxima productivitat en equips grans i no hi ha cap dubte. Molts programadors novells es queixen que hi ha moltes funcions que els falten. 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í hi ha un parell de cites de Rob Pike:

El punt clau aquí és que els nostres programadors 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 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.

Paraules sàvies, no?

Artefactes de la senzillesa

La senzillesa és una condició necessària per a la bellesa. Lev Tolstoi.

Mantenir-ho senzill és un dels objectius més importants de qualsevol disseny. Com sabeu, un projecte perfecte no és un projecte on no hi ha res a afegir, sinó un del qual no hi ha res a eliminar. Molta gent creu que per resoldre (o fins i tot expressar) problemes complexos es necessita una eina complexa. Tanmateix, no ho és. Prenguem per exemple el llenguatge PERL. Els ideòlegs del llenguatge creien que un programador hauria de tenir almenys tres maneres diferents de resoldre un problema. Els ideòlegs de la llengua Go van prendre un camí diferent, van decidir que n'hi havia prou amb un camí, però realment bo per aconseguir l'objectiu. Aquest enfocament té una base seriosa: l'única manera és més fàcil d'aprendre i més difícil d'oblidar.

Molts migrants es queixen que la llengua no conté abstraccions elegants. Sí, això és cert, però aquest és un dels principals avantatges de la llengua. El llenguatge conté un mínim de màgia, de manera que no es requereix cap coneixement profund per llegir el programa. Pel que fa a la verbositat del codi, això no és cap problema. Un programa Golang ben escrit llegeix verticalment, amb poca o cap estructura. A més, la velocitat de lectura d'un programa és almenys un ordre de magnitud superior a la velocitat d'escriptura. Si teniu en compte que tot el codi té un format uniforme (s'ha fet amb l'ordre incorporada gofmt), llegir unes quantes línies addicionals no és cap problema.

Poc expressiu

L'art no tolera quan la seva llibertat està limitada. La precisió no és la seva responsabilitat.

A causa del desig de senzillesa, Go manca de construccions que en altres llengües siguin percebudes com una cosa natural per persones acostumades a ells. Al principi pot ser una mica incòmode, però després observeu que el programa és molt més fàcil i sense ambigüitats de llegir.

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

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

La solució al mateix problema en D, tot i que sembla una mica més curta, no és més fàcil de llegir

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

L'infern de la còpia

L'home porta l'infern dins seu. Martí Luter.

Els principiants es queixen constantment de Go pel que fa a la manca de genèrics. Per resoldre aquest problema, la majoria d'ells utilitzen la còpia directa de codi. Per exemple, una funció per sumar una llista d'enters, aquests aspirants a professionals creuen que la funcionalitat no es pot implementar d'una altra manera que no sigui mitjançant un simple còpia i enganxa per a cada tipus de dades.

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

El llenguatge té mitjans suficients per implementar aquestes construccions. Per exemple, la programació genèrica estaria bé.

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

I, tot i que el nostre codi va resultar ser una mica més llarg que el cas anterior, s'ha generalitzat. Per tant, no ens serà difícil implementar totes les operacions aritmètiques.

Molts diran que un programa en D sembla molt més curt, i tindran raó.

import std.stdio;
import std.algorithm;

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

Tanmateix, només és més breu, però no més correcte, ja que la implementació D ignora completament el problema de la gestió d'errors.

A la vida real, a mesura que augmenta la complexitat de la lògica, la bretxa es redueix ràpidament. La bretxa es tanca encara més ràpidament quan cal dur a terme una acció que no es pot realitzar amb operadors de llenguatge estàndard.

Pel que fa al manteniment, extensibilitat i llegibilitat, al meu entendre, el llenguatge Go guanya, tot i que perd en verbositat.

La programació generalitzada en alguns casos ens aporta beneficis innegables. Això s'il·lustra clarament amb el paquet de classificació. Per tant, per ordenar qualsevol llista, només hem d'implementar la interfície sort.Interface.

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

Si agafeu qualsevol projecte de codi obert i executeu l'ordre grep "interface{}" -R, veureu amb quina freqüència s'utilitzen interfícies confuses. Els companys de ment propera de seguida diran que tot això es deu a la manca de genèrics. Tanmateix, no sempre és així. Prenguem DELPHI com a exemple. Malgrat la presència d'aquests mateixos genèrics, conté un tipus VARIANT especial per a operacions amb tipus de dades arbitraris. L'idioma Go fa el mateix.

D'un canó als pardals

I la camisa de força ha d'ajustar-se a la mida de la bogeria. Stanislav Lec.

Molts fans extrems poden afirmar que Go té un altre mecanisme per crear genèrics: la reflexió. I tindran raó... però només en casos excepcionals.

Rob Pike ens avisa:

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

La Viquipèdia ens diu el següent:

La reflexió fa referència al procés durant el qual un programa pot controlar i modificar la seva pròpia estructura i comportament durant l'execució. El paradigma de programació subjacent a la reflexió s'anomena programació reflexiva. Aquest és un tipus de metaprogramació.

Tanmateix, com sabeu, heu de pagar per tot. En aquest cas és:

  • dificultat per escriure programes
  • velocitat d'execució del programa

Per tant, la reflexió s'ha d'utilitzar amb precaució, com una arma de gran calibre. L'ús irreflexiu de la reflexió condueix a programes il·legibles, errors constants i baixa velocitat. Just el que un programador snob pugui mostrar el seu codi davant d'altres companys més pragmàtics i modestos.

Bagatge cultural de Xi? No, de diversos idiomes!

Juntament amb la fortuna, els deutes també es deixen als hereus.

Tot i que molts creuen que la llengua es basa totalment en l'herència C, no és així. El llenguatge incorpora molts aspectes dels millors llenguatges de programació.

sintaxi

En primer lloc, la sintaxi de les estructures gramaticals es basa en la sintaxi del llenguatge C. Tanmateix, la llengua DELPHI també va tenir una influència important. Així, veiem que els parèntesis redundants, que redueixen molt la llegibilitat del programa, s'han eliminat completament. El llenguatge també conté l'operador ":="" inherent al llenguatge DELPHI. El concepte de paquets es pren en préstec d'idiomes com ADA. La declaració d'entitats no utilitzades es pren en préstec del llenguatge PROLOG.

Semàntica

Els paquets es basaven en la semàntica del llenguatge DELPHI. Cada paquet encapsula dades i codi i conté entitats privades i públiques. Això us permet reduir la interfície del paquet al mínim.

L'operació d'implementació per mètode de delegació es va prendre en préstec del llenguatge DELPHI.

Recopilació

No és sense raó que hi ha una broma: Go es va desenvolupar mentre es compilava un programa C. Un dels punts forts del llenguatge és la seva compilació ultraràpida. La idea va ser manllevada de la llengua DELPHI. Cada paquet Go correspon a un mòdul DELPHI. Aquests paquets només es recompilen quan realment sigui necessari. Per tant, després de la propera edició, no cal que compileu tot el programa, sinó que recompileu només els paquets modificats i els paquets que depenen d'aquests paquets modificats (i fins i tot llavors, només si les interfícies dels paquets han canviat).

Construccions d'alt nivell

El llenguatge conté moltes construccions diferents d'alt nivell que no estan de cap manera relacionades amb llenguatges de baix nivell com C.

  • Cordes
  • Taules hash
  • Llesques
  • L'escriptura d'ànec es pren en préstec d'idiomes com RUBY (que, malauradament, molts no entenen ni utilitzen al màxim).

Gestió de la memòria

La gestió de la memòria generalment mereix un article a part. Si en llenguatges com C++, el control es deixa completament al desenvolupador, llavors en llenguatges posteriors com DELPHI es va utilitzar un model de recompte de referència. Amb aquest enfocament, no es permetien les referències cícliques, ja que es van formar clústers orfes, aleshores Go té la detecció integrada d'aquests clústers (com C#). A més, el col·lector d'escombraries és més eficient que la majoria de les implementacions conegudes actualment i ja es pot utilitzar per a moltes tasques en temps real. El propi llenguatge reconeix situacions en què es pot assignar un valor per emmagatzemar una variable a la pila. Això redueix la càrrega del gestor de memòria i augmenta la velocitat del programa.

Concurrència i concurrència

El paral·lelisme i la competitivitat de la llengua està més enllà dels elogis. Cap llenguatge de baix nivell pot competir de manera remota amb Go. Per ser justos, val la pena assenyalar que el model no va ser inventat pels autors de l'idioma, sinó que simplement es va prendre en préstec del bon vell llenguatge ADA. El llenguatge és capaç de processar milions de connexions paral·leles utilitzant totes les CPU, alhora que té un ordre de magnitud de problemes menys complexos amb bloquejos i condicions de carrera que són típiques per al codi multiprocés.

Beneficis addicionals

Si és rendible, tothom es tornarà desinteressat.

La llengua també ens proporciona una sèrie d'avantatges indubtables:

  • Un únic fitxer executable després de crear el projecte simplifica molt el desplegament d'aplicacions.
  • L'escriptura estàtica i la inferència de tipus poden reduir significativament el nombre d'errors del codi, fins i tot sense escriure proves. Conec alguns programadors que es fan sense escriure proves i la qualitat del seu codi no es ressent significativament.
  • Compilació creuada molt senzilla i excel·lent portabilitat de la biblioteca estàndard, que simplifica molt el desenvolupament d'aplicacions multiplataforma.
  • Les expressions regulars RE2 són segures per a fils i tenen temps d'execució previsibles.
  • Una biblioteca estàndard potent que permet a la majoria de projectes prescindir de marcs de tercers.
  • El llenguatge és prou potent com per centrar-se en el problema en lloc de com resoldre'l, però de nivell prou baix perquè el problema es pugui resoldre de manera eficient.
  • El sistema Go eco ja conté eines desenvolupades per a totes les ocasions: proves, documentació, gestió de paquets, potents linters, generació de codi, detector de condicions de carrera, etc.
  • La versió 1.11 de Go va introduir la gestió de dependències semàntiques integrada, construïda sobre l'allotjament VCS popular. Totes les eines que formen l'ecosistema Go utilitzen aquests serveis per descarregar-los, crear-los i instal·lar-los codi d'un sol cop. I això és genial. Amb l'arribada de la versió 1.11, el problema amb la versió de paquets també es va resoldre completament.
  • Com que la idea bàsica del llenguatge és reduir la màgia, el llenguatge incentiva els desenvolupadors a gestionar els errors de manera explícita. I això és correcte, perquè en cas contrari, simplement s'oblidarà de la gestió d'errors per complet. Una altra cosa és que la majoria dels desenvolupadors ignoren deliberadament la gestió d'errors, preferint en comptes de processar-los simplement reenviar l'error cap amunt.
  • El llenguatge no implementa la metodologia POO clàssica, ja que en la seva forma pura no hi ha virtualitat a Go. Tanmateix, això no és un problema quan s'utilitzen interfícies. L'absència de POO redueix significativament la barrera d'entrada per als principiants.

Simplicitat per al benefici de la comunitat

És fàcil de complicar, difícil de simplificar.

Go va ser dissenyat per ser senzill i aconsegueix aquest objectiu. Va ser escrit per a programadors intel·ligents que entenen els beneficis del treball en equip i estan cansats de la variabilitat infinita dels llenguatges de nivell empresarial. Tenint un conjunt relativament petit d'estructures sintàctiques al seu arsenal, pràcticament no està subjecte a canvis al llarg del temps, de manera que els desenvolupadors tenen molt de temps alliberat per al desenvolupament, i no per estudiar sense parar les innovacions lingüístiques.

Les empreses també reben una sèrie d'avantatges: una baixa barrera d'entrada els permet trobar ràpidament un especialista, i la immutabilitat de l'idioma els permet utilitzar el mateix codi fins i tot després de 10 anys.

Conclusió

La gran mida del cervell mai ha fet que cap elefant guanyés un premi Nobel.

Per a aquells programadors l'ego personal dels quals prima sobre l'esperit d'equip, així com per als teòrics que estimen els reptes acadèmics i la "superació personal" sense fi, el llenguatge és realment dolent, ja que és un llenguatge artesanal de propòsit general que no permet obtenir plaer estètic pel resultat del teu treball i mostrar-te professional davant dels companys (sempre que mesurem la intel·ligència amb aquests criteris, i no pel coeficient intel·lectual). Com tot a la vida, és una qüestió de prioritats personals. Com totes les innovacions que valen la pena, el llenguatge ja ha recorregut un llarg camí des de la negació universal fins a l'acceptació massiva. El llenguatge és enginyós en la seva senzillesa i, com sabeu, tot enginyós és senzill!

Font: www.habr.com

Afegeix comentari