3 cose che vanno diversamente

Go è una lingua speciale. È molto rinfrescante nel suo approccio alla programmazione e ai principi che promuove. È d'aiuto che alcuni degli inventori della lingua fossero i primi pionieri del C. La sensazione generale di Go è il 21 ° secolo C. 

In questo tutorial imparerai a conoscere tre delle caratteristiche che rendono Go unico: la sua semplicità, il modello di concorrenza tramite goroutine e il meccanismo di gestione degli errori.

1. Semplicità

Molti dei linguaggi moderni di successo come Scala e Rust sono molto ricchi e forniscono sistemi di tipi avanzati e sistemi di gestione della memoria. Quelle lingue presero le lingue tradizionali del loro tempo come C ++, Java e C # e funzionalità aggiunte o migliorate. Go ha preso una strada diversa ed ha eliminato molte caratteristiche e funzionalità. 

No Generics

Generici o modelli sono un pilastro di molti linguaggi di programmazione. Spesso aggiungono complessità e i messaggi di errore associati ai generici a volte possono essere oscuri. Go designer ha deciso di saltarlo. 

Questa è probabilmente la decisione di design più controverso di Go. Personalmente, trovo molto valore nei generici e credo che possa essere fatto correttamente (vedi C # per un grande esempio di generici fatti bene). Si spera che i generici verranno incorporati in Go in futuro. 

Nessuna eccezione

La gestione degli errori di Golang si basa su codici di stato espliciti. Per separare lo stato dal risultato effettivo di una funzione, Go supporta più valori di ritorno da una funzione. Questo è abbastanza insolito. Lo esaminerò in modo molto più dettagliato in seguito, ma ecco un rapido esempio:

pacchetto main import ("fmt" "errors") func div (a, b float64) (float64, errore) if b == 0 return 0, errors.New (fmt.Sprintf ("Impossibile dividere% f di zero ", a)) restituisce a / b, nil func main () result, err: = div (8, 4) se err! = nil fmt.Println (" Oh-oh, qualcosa è andato storto. " + err.Error ()) else fmt.Println (risultato) risultato, err = div (5, 0) se err! = nil fmt.Println ("Oh-oh, qualcosa è sbagliato." + err.Error ()) else fmt.Println (risultato) 2 Oh-oh, qualcosa non va. Impossibile dividere 5.000000 per zero 

Eseguibile singolo

Go non ha una libreria di runtime separata. Genera un singolo file eseguibile, che è possibile implementare semplicemente copiando (a.k.a. implementazione di XCOPY). Questo è semplice come si arriva. Non c'è bisogno di preoccuparsi delle dipendenze o delle discrepanze di versione. È anche un grande vantaggio per le distribuzioni basate su container (Docker, Kubernetes e amici). Il singolo eseguibile standalone rende molto semplici i Dockerfiles.   

Nessuna libreria dinamica

OK. Questo è appena cambiato recentemente in Go 1.8. Ora puoi effettivamente caricare le librerie dinamiche attraverso il collegare pacchetto. Ma, dal momento che questa capacità non è stata introdotta fin dall'inizio, la considero ancora un'estensione per situazioni speciali. Lo spirito di Go è ancora un eseguibile staticamente compilato. È anche disponibile solo su Linux.

2. Goroutine

Le goroutine sono probabilmente l'aspetto più attraente di Go dal punto di vista pratico. Le goroutine consentono di sfruttare la potenza delle macchine multicore in un modo molto intuitivo. È basato su solide basi teoriche e la sintassi per supportarla è molto piacevole.

CSP

Il fondamento del modello di concorrenza di Go è rappresentato dai processi sequenziali comunicanti di C.A. R. Hoare. L'idea è di evitare la sincronizzazione sulla memoria condivisa tra più thread di esecuzione, che è soggetta a errori e laboriosa. Invece, comunicare attraverso canali che evitano la contesa.

Invoca una funzione come una Goroutine

Qualsiasi funzione può essere invocata come una goroutine chiamandola tramite la parola chiave go. Considerare prima il seguente programma lineare. Il foo ()la funzione dorme per diversi secondi e stampa quanti secondi ha dormito. In questa versione, ogni chiamata a foo () blocchi prima della prossima chiamata.

pacchetto main import ("fmt" "time") func foo (d time.Duration) d * = 1000000000 time.Sleep (d) fmt.Println (d) func main () foo (3) foo (2) foo (1) foo (4) 

L'output segue l'ordine delle chiamate nel codice:

3s 2s 1s 4s

Ora, apporterò una leggera modifica e aggiungerò la parola chiave "vai" prima delle prime tre invocazioni:

pacchetto main import ("fmt" // "errori" "time") func foo (d time.Duration) d * = 1000000000 time.Sleep (d) fmt.Println (d) func main () go foo ( 3) go foo (2) go foo (1) foo (4)

L'output è diverso ora. La 1 seconda chiamata è terminata per prima e stampata "1s", seguita da "2s" e "3s". 

1s 2s 3s 4s

Si noti che la chiamata di 4 secondi non è una goroutine. Questo è in base alla progettazione, quindi il programma attende e lascia terminare le goroutine. Senza di esso, il programma verrà completato immediatamente dopo il lancio delle goroutine. Ci sono vari modi oltre a dormire per aspettare che finisca una goroutine.

Sincronizza le goroutine

Un altro modo per attendere il completamento delle goroutine è utilizzare i gruppi di sincronizzazione. Dichiarate un oggetto del gruppo di attesa e lo passate a ciascuna goroutine, che è responsabile della sua chiamata Fatto() metodo quando è fatto. Quindi, si attende il gruppo di sincronizzazione. Ecco il codice che adatta l'esempio precedente per utilizzare un gruppo di attesa.

pacchetto main import ("fmt" "sync" "time") func foo (d time.Duration, wg * sync.WaitGroup) d * = 1000000000 time.Sleep (d) fmt.Println (d) wg.Done ()  func main () var wg sync.WaitGroup wg.Add (3) go foo (3, & wg) go foo (2, & wg) go foo (1, & wg) wg.Wait ()

canali

I canali consentono alle goroutine (e al tuo programma principale) di scambiare informazioni. Puoi creare un canale e passarlo a una goroutine. Il creatore può scrivere sul canale e la goroutine può leggere dal canale. 

Anche la direzione opposta funziona. Go fornisce anche una sintassi dolce per i canali con frecce per indicare il flusso di informazioni. Ecco un altro adattamento del nostro programma, in cui le goroutine ricevono un canale al quale scrivono quando sono terminate, e il programma principale attende di ricevere messaggi da tutte le goroutine prima di terminare.

pacchetto main import ("fmt" "time") func foo (d time.Duration, c chan int) d * = 1000000000 time.Sleep (d) fmt.Println (d) c <- 1  func main()  c := make(chan int) go foo(3, c) go foo(2, c) go foo(1, c) <- c <- c <- c  

Scrivi una Goroutine

È una specie di trucco. Scrivere una goroutine equivale a scrivere qualsiasi funzione. Guarda il foo () funzione sopra, che è chiamata nello stesso programma di una goroutine e anche una funzione regolare.

3. Gestione degli errori

Come ho detto prima, la gestione degli errori di Go è diversa. Le funzioni possono restituire più valori e, per convenzione, funzioni che possono fallire restituiscono un oggetto errore come ultimo valore restituito. 

C'è anche un meccanismo che assomiglia alle eccezioni tramite il panico() e recuperare() funzioni, ma è più adatto per situazioni speciali. Ecco un tipico scenario di gestione degli errori in cui il bar() la funzione restituisce un errore e il principale() la funzione controlla se c'è stato un errore e lo stampa.

pacchetto main import ("fmt" "errors") func bar () error return errors.New ("something is wrong") func main () e: = bar () se e! = nil fmt.Println ( e.Errore ()) 

Controllo obbligatorio

Se assegni l'errore a una variabile e non la controlli, Go si arrabbierà.

func main () e: = bar () main.go: 15: e dichiarato e non utilizzato 

Ci sono modi per aggirarlo. Non puoi assolutamente assegnare l'errore:

func main () bar ()

Oppure puoi assegnarlo al trattino basso:

func main () _ = bar ()

Supporto linguistico

Gli errori sono solo valori che puoi trasmettere liberamente. Go fornisce un piccolo supporto agli errori dichiarando l'interfaccia di errore che richiede solo un metodo chiamato Errore() che restituisce una stringa:

digita l'interfaccia di errore Error () string 

C'è anche il errori pacchetto che ti consente di creare nuovi oggetti di errore. Il FMT il pacchetto fornisce un Errorf () funzione per creare oggetti di errore formattati. Questo è tutto.

Interazione con le goroutine

Non è possibile restituire errori (o qualsiasi altro oggetto) da una goroutine. Le goroutine possono comunicare errori al mondo esterno attraverso altri mezzi. Il passaggio di un canale di errore a una goroutine è considerato una buona pratica. Le goroutine possono anche scrivere errori per registrare i file o il database o chiamare i servizi remoti.

Conclusione

Go ha visto un enorme successo e uno slancio negli ultimi anni. È il linguaggio go-to (vedi cosa ho fatto lì) per i moderni sistemi e database distribuiti. Ha convertito molti sviluppatori Python. 

Gran parte di esso è indubbiamente dovuto al sostegno di Google. Ma Go si regge definitivamente per i suoi meriti. Il suo approccio alla progettazione linguistica di base è molto diverso da altri linguaggi di programmazione contemporanei. Provaci. È facile da imparare e divertente da programmare.