Perché Go è dannoso per i programmatori poco intelligenti

L'articolo è stato scritto come risposta ad un articolo precedentemente pubblicato articolo agli antipodi.

Perché Go è dannoso per i programmatori poco intelligenti

Negli ultimi due e più anni ho utilizzato Go per implementare un server RADIUS specializzato con un sistema di fatturazione sviluppato. Lungo il percorso, sto imparando le complessità della lingua stessa. I programmi stessi sono molto semplici e non costituiscono lo scopo di questo articolo, ma l'esperienza di utilizzo di Go merita qualche parola in sua difesa. Go sta diventando un linguaggio sempre più diffuso per un codice serio e scalabile. La lingua è stata creata da Google, dove viene utilizzata attivamente. In conclusione, onestamente penso che il design del linguaggio Go sia dannoso per i programmatori UNintelligent.

Progettato per programmatori deboli?

I deboli parlano di problemi. Il forte parlare di idee e sogni...

Go è molto facile da imparare, così facile che puoi leggere il codice praticamente senza alcuna formazione. Questa caratteristica del linguaggio viene utilizzata in molte aziende globali quando il codice viene letto insieme a specialisti non core (manager, clienti, ecc.). Ciò è molto conveniente per metodologie come Design Driven Development.
Anche i programmatori alle prime armi iniziano a produrre codice abbastanza decente dopo una settimana o due. Il libro da cui ho studiato è "Go Programming" (di Mark Summerfield). Il libro è molto bello, tocca molte sfumature della lingua. Dopo linguaggi inutilmente complicati come Java, PHP, la mancanza di magia è rinfrescante. Ma prima o poi, molti programmatori limitati hanno l’idea di utilizzare i vecchi metodi in un nuovo campo. È davvero necessario?

Rob Pike (il principale ideologo della lingua) ha creato la lingua Go come linguaggio industriale facile da capire ed efficace da usare. Il linguaggio è progettato per la massima produttività in team numerosi e su questo non ci sono dubbi. Molti programmatori alle prime armi si lamentano del fatto che mancano molte funzionalità. Questo desiderio di semplicità è stata una decisione consapevole da parte dei progettisti del linguaggio e, per comprendere appieno il motivo per cui era necessario, dobbiamo comprendere la motivazione degli sviluppatori e ciò che stavano cercando di ottenere in Go.

Allora perché è stato reso così semplice? Ecco un paio di citazioni di Rob Pike:

Il punto chiave qui è che i nostri programmatori non sono ricercatori. Di solito sono piuttosto giovani, vengono da noi dopo aver studiato, forse hanno studiato Java, o C/C++, o Python. Non riescono a capire una grande lingua, ma allo stesso tempo vogliamo che creino un buon software. Ecco perché la lingua dovrebbe essere facile da capire e da imparare.

Dovrebbe essere familiare, grosso modo simile a C. I programmatori che lavorano in Google iniziano presto la loro carriera e hanno per lo più familiarità con i linguaggi procedurali, in particolare la famiglia C. L'esigenza di una rapida produttività in un nuovo linguaggio di programmazione significa che il linguaggio non dovrebbe essere troppo radicale.

Parole sagge, non è vero?

Artefatti della semplicità

La semplicità è una condizione necessaria per la bellezza. Lev Tolstoj.

Mantenere le cose semplici è uno degli obiettivi più importanti in qualsiasi progetto. Come sai, un progetto perfetto non è un progetto in cui non c'è nulla da aggiungere, ma uno a cui non c'è nulla da togliere. Molte persone credono che per risolvere (o addirittura esprimere) problemi complessi sia necessario uno strumento complesso. Tuttavia non lo è. Prendiamo ad esempio il linguaggio PERL. Gli ideologi del linguaggio credevano che un programmatore dovesse avere almeno tre modi diversi per risolvere un problema. Gli ideologi della lingua Go hanno preso una strada diversa: hanno deciso che bastava un modo, ma davvero buono, per raggiungere l'obiettivo. Questo approccio ha basi serie: l’unico modo è più facile da imparare e più difficile da dimenticare.

Molti migranti lamentano che il linguaggio non contenga astrazioni eleganti. Sì, è vero, ma questo è uno dei principali vantaggi della lingua. Il linguaggio contiene un minimo di magia, quindi non è richiesta alcuna conoscenza approfondita per leggere il programma. Per quanto riguarda la verbosità del codice, questo non è affatto un problema. Un programma Golang ben scritto si legge verticalmente, con poca o nessuna struttura. Inoltre, la velocità di lettura del programma è almeno un ordine di grandezza maggiore della velocità di scrittura. Se consideri che tutto il codice ha una formattazione uniforme (eseguita utilizzando il comando gofmt integrato), leggere qualche riga in più non è affatto un problema.

Non molto espressivo

L'arte non tollera che la sua libertà sia limitata. La precisione non è sua responsabilità.

A causa del desiderio di semplicità, Go è privo di costrutti che in altre lingue vengono percepiti come qualcosa di naturale dalle persone abituate ad essi. All'inizio potrebbe risultare un po' scomodo, ma poi noterete che il programma è molto più semplice e chiaro da leggere.

Ad esempio, un'utilità della console che legge stdin o un file dagli argomenti della riga di comando sarebbe simile a questa:

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 soluzione allo stesso problema in D, sebbene sembri un po’ più breve, non è di facile lettura

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'inferno della copia

L'uomo porta dentro di sé l'inferno. Martin Lutero.

I principianti si lamentano costantemente di Go per la mancanza di farmaci generici. Per risolvere questo problema, la maggior parte di essi utilizza la copia diretta del codice. Ad esempio, una funzione per sommare un elenco di numeri interi, tali aspiranti professionisti ritengono che la funzionalità non possa essere implementata in altro modo se non semplicemente copiando e incollando per ciascun tipo di dati.

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

La lingua ha mezzi sufficienti per implementare tali costruzioni. Ad esempio, la programmazione generica andrebbe bene.

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

E, sebbene il nostro codice si sia rivelato leggermente più lungo del caso precedente, è diventato generalizzato. Pertanto, non sarà difficile per noi implementare tutte le operazioni aritmetiche.

Molti diranno che un programma in D sembra decisamente più breve e avranno ragione.

import std.stdio;
import std.algorithm;

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

Tuttavia è solo più breve, ma non più corretta, poiché l'implementazione D ignora completamente il problema della gestione degli errori.

Nella vita reale, man mano che la complessità della logica aumenta, il divario si riduce rapidamente. Il divario si colma ancora più rapidamente quando è necessario eseguire un'azione che non può essere eseguita utilizzando gli operatori del linguaggio standard.

In termini di manutenibilità, estensibilità e leggibilità, secondo me, il linguaggio Go vince, anche se perde in verbosità.

La programmazione generalizzata in alcuni casi ci offre innegabili vantaggi. Ciò è chiaramente illustrato dal pacchetto sort. Quindi, per ordinare qualsiasi elenco, dobbiamo solo implementare l'interfaccia 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)
}

Se prendi un progetto open source ed esegui il comando grep “interface{}” -R, vedrai quanto spesso vengono utilizzate interfacce confuse. I compagni più ottusi diranno subito che tutto ciò è dovuto alla mancanza di farmaci generici. Tuttavia, non è sempre così. Prendiamo DELPHI come esempio. Nonostante la presenza di questi stessi generici, contiene un tipo VARIANT speciale per operazioni con tipi di dati arbitrari. La lingua Go fa lo stesso.

Da una pistola sui passeri

E la camicia di forza deve adattarsi alle dimensioni della follia. Stanislav Lec.

Molti fan estremi potrebbero affermare che Go ha un altro meccanismo per creare farmaci generici: la riflessione. E avranno ragione... ma solo in rari casi.

Rob Pike ci avverte:

Questo è uno strumento potente che dovrebbe essere usato con cautela. Dovrebbe essere evitato se non strettamente necessario.

Wikipedia ci dice quanto segue:

La riflessione si riferisce al processo durante il quale un programma può monitorare e modificare la propria struttura e il proprio comportamento durante l'esecuzione. Il paradigma di programmazione alla base della riflessione è chiamato programmazione riflessiva. Questo è un tipo di metaprogrammazione.

Tuttavia, come sai, devi pagare tutto. In questo caso è:

  • difficoltà nello scrivere programmi
  • velocità di esecuzione del programma

Pertanto, la riflessione deve essere usata con cautela, come un'arma di grosso calibro. L'uso sconsiderato della riflessione porta a programmi illeggibili, errori costanti e bassa velocità. Proprio quello che ci vuole per un programmatore snob per poter sfoggiare il suo codice davanti ad altri colleghi più pragmatici e modesti.

Bagaglio culturale di Xi? No, da diverse lingue!

Insieme al patrimonio vengono lasciati agli eredi anche i debiti.

Nonostante molti credano che la lingua sia interamente basata sull'eredità C, non è così. Il linguaggio incorpora molti aspetti dei migliori linguaggi di programmazione.

sintassi

Innanzitutto la sintassi delle strutture grammaticali si basa sulla sintassi del linguaggio C. Tuttavia, anche la lingua DELPHI ha avuto un'influenza significativa. Vediamo quindi che le parentesi ridondanti, che riducono notevolmente la leggibilità del programma, sono state completamente rimosse. La lingua contiene anche l'operatore “:=" inerente al linguaggio DELPHI. Il concetto di pacchetto è preso in prestito da linguaggi come ADA. La dichiarazione delle entità inutilizzate è presa in prestito dal linguaggio PROLOG.

Semantica

I pacchetti erano basati sulla semantica del linguaggio DELPHI. Ogni pacchetto incapsula dati e codice e contiene entità private e pubbliche. Ciò consente di ridurre al minimo l'interfaccia del pacchetto.

L'operazione di implementazione tramite metodo di delega è stata mutuata dal linguaggio DELPHI.

compilazione

Non per niente si scherza: Go è stato sviluppato mentre veniva compilato un programma in C. Uno dei punti di forza del linguaggio è la sua compilazione ultraveloce. L'idea è stata presa in prestito dalla lingua DELPHI. Ogni pacchetto Go corrisponde a un modulo DELPHI. Questi pacchetti vengono ricompilati solo quando realmente necessari. Pertanto, dopo la successiva modifica, non è necessario compilare l'intero programma, ma piuttosto ricompilare solo i pacchetti modificati e i pacchetti che dipendono da questi pacchetti modificati (e anche in questo caso, solo se le interfacce del pacchetto sono cambiate).

Costrutti di alto livello

Il linguaggio contiene molti costrutti diversi di alto livello che non sono in alcun modo correlati a linguaggi di basso livello come C.

  • stringhe
  • Tabelle hash
  • Fette
  • La digitazione della papera è presa in prestito da lingue come RUBY (che, sfortunatamente, molti non capiscono o non sfruttano al massimo delle sue potenzialità).

Gestione della memoria

La gestione della memoria merita generalmente un articolo a parte. Se in linguaggi come C++ il controllo è completamente lasciato allo sviluppatore, in linguaggi successivi come DELPHI è stato utilizzato un modello di conteggio dei riferimenti. Con questo approccio, i riferimenti ciclici non erano consentiti, poiché si formavano cluster orfani, quindi Go ha il rilevamento integrato di tali cluster (come C#). Inoltre, il Garbage Collector è più efficiente della maggior parte delle implementazioni attualmente conosciute e può già essere utilizzato per molte attività in tempo reale. Il linguaggio stesso riconosce le situazioni in cui un valore per memorizzare una variabile può essere allocato nello stack. Ciò riduce il carico sul gestore della memoria e aumenta la velocità del programma.

Concorrenza e concorrenza

Il parallelismo e la competitività della lingua sono oltre ogni lode. Nessun linguaggio di basso livello può competere nemmeno lontanamente con Go. Per essere onesti, vale la pena notare che il modello non è stato inventato dagli autori del linguaggio, ma è stato semplicemente preso in prestito dal buon vecchio linguaggio ADA. Il linguaggio è in grado di elaborare milioni di connessioni parallele utilizzando tutte le CPU, pur presentando problemi di un ordine di grandezza meno complessi con deadlock e condizioni di competizione tipiche del codice multi-thread.

Ulteriori vantaggi

Se è redditizio, tutti diventeranno altruisti.

La lingua ci offre anche una serie di indubbi vantaggi:

  • Un singolo file eseguibile dopo la creazione del progetto semplifica notevolmente la distribuzione delle applicazioni.
  • La tipizzazione statica e l'inferenza del tipo possono ridurre significativamente il numero di errori nel codice, anche senza scrivere test. Conosco alcuni programmatori che rinunciano del tutto a scrivere test e la qualità del loro codice non ne risente in modo significativo.
  • Compilazione incrociata molto semplice ed eccellente portabilità della libreria standard, che semplifica notevolmente lo sviluppo di applicazioni multipiattaforma.
  • Le espressioni regolari RE2 sono thread-safe e hanno tempi di esecuzione prevedibili.
  • Una potente libreria standard che consente alla maggior parte dei progetti di fare a meno di framework di terze parti.
  • Il linguaggio è abbastanza potente da concentrarsi sul problema piuttosto che su come risolverlo, ma sufficientemente basso da consentire di risolvere il problema in modo efficiente.
  • Il sistema Go eco contiene già strumenti sviluppati e pronti all'uso per tutte le occasioni: test, documentazione, gestione dei pacchetti, linter potenti, generazione di codici, rilevatore di condizioni di gara, ecc.
  • La versione Go 1.11 ha introdotto la gestione integrata delle dipendenze semantiche, basata sul popolare hosting VCS. Tutti gli strumenti che compongono l'ecosistema Go utilizzano questi servizi per scaricare, creare e installare il codice da essi in un colpo solo. E questo è fantastico. Con l'arrivo della versione 1.11 è stato completamente risolto anche il problema relativo al controllo delle versioni dei pacchetti.
  • Poiché l'idea centrale del linguaggio è ridurre la magia, il linguaggio incentiva gli sviluppatori a gestire esplicitamente gli errori. E questo è corretto, perché altrimenti si dimenticherà semplicemente del tutto della gestione degli errori. Un'altra cosa è che la maggior parte degli sviluppatori ignora deliberatamente la gestione degli errori, preferendo invece di elaborarli semplicemente inoltrare l'errore verso l'alto.
  • Il linguaggio non implementa la classica metodologia OOP, poiché nella sua forma pura non c'è virtualità in Go. Tuttavia, questo non è un problema quando si utilizzano le interfacce. L'assenza di OOP riduce significativamente la barriera all'ingresso per i principianti.

Semplicità a vantaggio della comunità

È facile complicare, difficile semplificare.

Go è stato progettato per essere semplice e riesce a raggiungere questo obiettivo. È stato scritto per programmatori intelligenti che comprendono i vantaggi del lavoro di squadra e sono stanchi dell'infinita variabilità dei linguaggi di livello aziendale. Avendo un insieme relativamente piccolo di strutture sintattiche nel suo arsenale, non è praticamente soggetto a cambiamenti nel tempo, quindi gli sviluppatori hanno molto tempo libero per lo sviluppo e non per studiare all'infinito le innovazioni linguistiche.

Le aziende ricevono anche una serie di vantaggi: una bassa barriera all'ingresso consente loro di trovare rapidamente uno specialista e l'immutabilità della lingua consente loro di utilizzare lo stesso codice anche dopo 10 anni.

conclusione

Le grandi dimensioni del cervello non hanno mai reso nessun elefante vincitore del Premio Nobel.

Per quei programmatori il cui ego personale ha la precedenza sullo spirito di squadra, così come per i teorici che amano le sfide accademiche e l'infinito "auto-miglioramento", il linguaggio è davvero pessimo, poiché è un linguaggio artigianale generico che non consente di ottenere piacere estetico dal risultato del tuo lavoro e mostrarti professionale di fronte ai colleghi (a condizione che misuriamo l'intelligenza con questi criteri e non con il QI). Come ogni cosa nella vita, è una questione di priorità personali. Come tutte le innovazioni meritevoli, il linguaggio ha già fatto molta strada dalla negazione universale all’accettazione di massa. Il linguaggio è ingegnoso nella sua semplicità e, come sai, tutto ciò che è ingegnoso è semplice!

Fonte: habr.com

Aggiungi un commento