portaldacalheta.pt
  • Principal
  • Conseils Et Outils
  • Agile
  • Kpi Et Analyses
  • Planification Et Prévision
La Technologie

Go Langage de programmation: un didacticiel d'introduction à Golang



Qu'est-ce que le langage de programmation Go?

Le relativement nouveau Aller au langage de programmation se trouve parfaitement au milieu du paysage, offrant beaucoup de bonnes caractéristiques et en omettant délibérément beaucoup de mauvaises. Il se compile rapidement, s'exécute rapidement, inclut un runtime et un garbage collection, dispose d'un système de type statique simple et d'interfaces dynamiques, et d'une excellente bibliothèque standard. C'est pourquoi tant de développeurs sont désireux d'apprendre la programmation Go.

Tutoriel Golang: illustration du logo



Aller et POO
La POO est l'une de ces fonctionnalités que Go omet délibérément. Il n'a pas de sous-classement, et il n'y a donc pas de diamants d'héritage, de super appels ou de méthodes virtuelles pour vous tromper. Pourtant, de nombreuses parties utiles de la POO sont disponibles d'autres manières.

* Les mixins * sont disponibles en incorporant des structures de manière anonyme, permettant à leurs méthodes d'être appelées directement sur la structure contenant (voir intégration ). La promotion des méthodes de cette manière s'appelle * forwarding *, et ce n'est pas la même chose que le sous-classement: la méthode sera toujours appelée sur la structure interne intégrée.

L'intégration n'implique pas non plus de polymorphisme. Alors que «A» peutavoirun `B`, ça ne veut pas dire çaesta «B» - les fonctions qui prennent un «B» ne prendront pas un «A» à la place. Pour cela, nous avons besoinles interfaces, que nous rencontrerons brièvement plus tard.

Pendant ce temps, Golang prend une position forte sur les fonctionnalités qui peuvent conduire à la confusion et aux bugs. Il omet les idiomes POO tels que l'héritage et le polymorphisme, en faveur de la composition et des interfaces simples. Il minimise la gestion des exceptions au profit d'erreurs explicites dans les valeurs de retour. Il existe exactement une façon correcte de mettre en page le code Go, appliquée par le gofmt outil. Etc.



Pourquoi apprendre Golang?

Go est aussi un excellent langage pour écrire programmes simultanés : programmes avec de nombreuses parties exécutées indépendamment. Un exemple évident est un serveur Web: chaque requête s'exécute séparément, mais les requêtes doivent souvent partager des ressources telles que des sessions, des caches ou des files d'attente de notification. Ça signifie programmeurs Go qualifiés doivent gérer l’accès simultané à ces ressources.



Bien que Golang dispose d'un excellent ensemble de fonctionnalités de bas niveau pour gérer la concurrence, leur utilisation directe peut devenir compliquée. Dans de nombreux cas, une poignée d'abstractions réutilisables sur ces mécanismes de bas niveau rend la vie beaucoup plus facile.

Dans le didacticiel de programmation Go d’aujourd’hui, nous allons examiner une de ces abstractions: un wrapper qui peut transformer toute structure de données en un service transactionnel . Nous utiliserons un Fund type à titre d'exemple - un magasin simple pour le financement restant de notre startup, où nous pouvons vérifier le solde et effectuer des retraits.



Pour démontrer cela dans la pratique, nous allons créer le service par petites étapes, faire un désordre en cours de route, puis le nettoyer à nouveau. Au fur et à mesure que nous progressons dans notre didacticiel Go, nous découvrirons de nombreuses fonctionnalités intéressantes du langage Go, notamment:

  • Types et méthodes de structure
  • Tests unitaires et benchmarks
  • Goroutines et canaux
  • Interfaces et typage dynamique

Créer un fonds simple

Écrivons du code pour suivre le financement de notre startup. Le fonds commence avec un solde donné, et l'argent ne peut être retiré (nous déterminerons les revenus plus tard).



Ce graphique représente un exemple simple de goroutine utilisant le langage de programmation Go.

Aller est délibérément ne pas un langage orienté objet: il n'y a pas de classes, d'objets ou d'héritage. Au lieu de cela, nous déclarerons un type de structure appelé Fund, avec une fonction simple pour créer de nouvelles structures de fonds et deux méthodes publiques.



fund.go

package funding type Fund struct { // balance is unexported (private), because it's lowercase balance int } // A regular function returning a pointer to a fund func NewFund(initialBalance int) *Fund { // We can return a pointer to a new struct without worrying about // whether it's on the stack or heap: Go figures that out for us. return &Fund{ balance: initialBalance, } } // Methods start with a *receiver*, in this case a Fund pointer func (f *Fund) Balance() int { return f.balance } func (f *Fund) Withdraw(amount int) { f.balance -= amount }

Tester avec des benchmarks

Ensuite, nous avons besoin d'un moyen de tester Fund. Plutôt que d’écrire un programme distinct, nous utiliserons Go’s package de test , qui fournit un cadre pour les tests unitaires et les benchmarks. La logique simple dans notre Fund Cela ne vaut pas vraiment la peine d’écrire des tests unitaires, mais comme nous parlerons beaucoup plus tard de l’accès simultané au fonds, il est logique d’écrire un benchmark.



Les benchmarks sont comme des tests unitaires, mais incluent une boucle qui exécute le même code plusieurs fois (dans notre cas, fund.Withdraw(1)). Cela permet au cadre de chronométrer la durée de chaque itération, en calculant la moyenne des différences transitoires par rapport aux recherches de disque, aux échecs de cache, à la planification des processus et à d'autres facteurs imprévisibles.

Le framework de test veut que chaque benchmark s'exécute pendant au moins 1 seconde (par défaut). Pour s'en assurer, il appellera le benchmark plusieurs fois, en passant à chaque fois une valeur «nombre d'itérations» croissante (le champ b.N), jusqu'à ce que l'exécution prenne au moins une seconde.



Pour l'instant, notre indice de référence déposera simplement de l'argent et le retirera un dollar à la fois.

fund_test.go

package funding import 'testing' func BenchmarkFund(b *testing.B) { // Add as many dollars as we have iterations this run fund := NewFund(b.N) // Burn through them one at a time until they are all gone for i := 0; i

Maintenant, exécutons-le:

$ go test -bench . funding testing: warning: no tests to run PASS BenchmarkWithdrawals 2000000000 1.69 ns/op ok funding 3.576s

Cela s'est bien passé. Nous avons effectué deux milliards (!) D'itérations, et la vérification finale du solde était correcte. Nous pouvons ignorer l'avertissement «aucun test à exécuter», qui fait référence aux tests unitaires que nous n'avons pas écrits (dans les exemples de programmation Go ultérieurs de ce didacticiel, l'avertissement est supprimé).

Accès simultané dans Go

Faisons maintenant le point de référence simultané, pour modéliser différents utilisateurs effectuant des retraits en même temps. Pour ce faire, nous allons engendrer dix goroutines et demander à chacun d’eux de retirer un dixième de l’argent.

Comment pourrions-nous structurer plusieurs goroutines concurrentes dans le langage Go?

Les goroutines sont la pierre angulaire de la concurrence dans le langage Go. Elles sont fils verts - threads légers gérés par le runtime Go, pas par le système d'exploitation. Cela signifie que vous pouvez en exécuter des milliers (ou des millions) sans frais généraux importants. Les goroutines sont engendrées avec le go mot clé, et commencez toujours par une fonction (ou un appel de méthode):

// Returns immediately, without waiting for `DoSomething()` to complete go DoSomething()

Souvent, nous souhaitons générer une courte fonction ponctuelle avec seulement quelques lignes de code. Dans ce cas, nous pouvons utiliser une fermeture au lieu d'un nom de fonction:

go func() { // ... do stuff ... }() // Must be a function *call*, so remember the ()

Une fois que toutes nos goroutines sont engendrées, nous avons besoin d'un moyen d'attendre qu'elles se terminent. Nous pourrions en construire un nous-mêmes en utilisant canaux , mais nous ne les avons pas encore rencontrés, ce serait donc sauter.

Pour l'instant, nous pouvons simplement utiliser le WaitGroup tapez dans la bibliothèque standard de Go, qui existe précisément dans ce but. Nous allons en créer un (appelé «wg») et appeler wg.Add(1) avant de donner naissance à chaque ouvrier, pour savoir combien il y en a. Ensuite, les travailleurs feront un rapport en utilisant wg.Done(). Pendant ce temps, dans la goroutine principale, nous pouvons simplement dire wg.Wait() pour bloquer jusqu'à ce que chaque travailleur ait terminé.

Dans les goroutines de travail dans notre prochain exemple, nous utiliserons defer appeler wg.Done().

defer prend un appel de fonction (ou méthode) et l'exécute immédiatement avant le retour de la fonction en cours, après que tout le reste est fait. Ceci est pratique pour le nettoyage:

func() { resource.Lock() defer resource.Unlock() // Do stuff with resource }()

De cette façon, nous pouvons facilement faire correspondre les Unlock avec son Lock, pour plus de lisibilité. Plus important encore, une fonction différée s'exécutera même s'il y a une panique dans la fonction main (quelque chose que nous pourrions gérer via try-finally dans d'autres langues).

Enfin, les fonctions différées s'exécuteront dans le sens inverse l'ordre dans lequel ils ont été appelés, ce qui signifie que nous pouvons bien faire un nettoyage imbriqué (similaire à l'idiome C de goto s et label s imbriqués, mais beaucoup plus soigné):

func() { db.Connect() defer db.Disconnect() // If Begin panics, only db.Disconnect() will execute transaction.Begin() defer transaction.Close() // From here on, transaction.Close() will run first, // and then db.Disconnect() // ... }()

OK, donc avec tout cela dit, voici la nouvelle version:

fund_test.go

package funding import ( 'sync' 'testing' ) const WORKERS = 10 func BenchmarkWithdrawals(b *testing.B) { // Skip N = 1 if b.N

Nous pouvons prédire ce qui va se passer ici. Les ouvriers exécuteront tous Withdraw l'un sur l'autre. À l'intérieur, f.balance -= amount lira le solde, en soustraira un, puis le réécrira. Mais parfois, deux ou plusieurs travailleurs liront tous les deux le même solde, et feront la même soustraction, et nous finirons avec le mauvais total. Droite?

$ go test -bench . funding BenchmarkWithdrawals 2000000000 2.01 ns/op ok funding 4.220s

Non, ça passe toujours. Que s'est-il passé ici?

Rappelez-vous que les goroutines sont fils verts - ils sont gérés par le moteur d’exécution Go, et non par le système d’exploitation. Le runtime planifie des goroutines sur le nombre de threads du système d'exploitation disponibles. Au moment de la rédaction de ce didacticiel sur le langage Go, Go n’essaie pas de deviner combien de threads de système d’exploitation il doit utiliser, et si nous en voulons plus d’un, nous devons le dire. Enfin, le runtime actuel ne prévient pas les goroutines - un goroutine continuera à fonctionner jusqu'à ce qu'il fasse quelque chose qui suggère qu'il est prêt pour une pause (comme interagir avec une chaîne).

Tout cela signifie que, bien que notre référence soit désormais simultanée, elle n’est pas parallèle . Un seul de nos nœuds de calcul fonctionnera à la fois, et il fonctionnera jusqu'à ce que ce soit terminé. Nous pouvons changer cela en disant à Go d'utiliser plus de threads, via le GOMAXPROCS variable d'environnement.

$ GOMAXPROCS=4 go test -bench . funding BenchmarkWithdrawals-4 --- FAIL: BenchmarkWithdrawals-4 account_test.go:39: Balance wasn't zero: 4238 ok funding 0.007s

C'est mieux. Maintenant, nous perdons manifestement certains de nos retraits, comme nous nous y attendions.

Dans cet exemple de programmation Go, le résultat de plusieurs goroutines parallèles n

Faites-en un serveur

À ce stade, nous avons différentes options. Nous pourrions ajouter un mutex explicite ou un verrou en lecture-écriture autour du fonds. Nous pourrions utiliser un comparateur et un échange avec un numéro de version. Nous pourrions tout faire et utiliser un CRDT schéma (peut-être en remplaçant le champ balance par des listes de transactions pour chaque client et en calculant le solde à partir de celles-ci).

Mais nous ne ferons aucune de ces choses maintenant, car elles sont désordonnées ou effrayantes ou les deux. Au lieu de cela, nous déciderons qu’un fonds doit être un serveur . Qu'est-ce qu'un serveur? C’est quelque chose dont vous parlez. Dans Go, les choses parlent via des canaux.

Les canaux sont le mécanisme de communication de base entre les goroutines. Les valeurs sont envoyées au canal (avec channel <- value), et peuvent être reçues de l'autre côté (avec value = <- channel). Les canaux sont «sans danger pour les goroutines», ce qui signifie que n'importe quel nombre de goroutines peut envoyer et recevoir d'eux en même temps.

Tampon
La mise en mémoire tampon des canaux de communication peut être une optimisation des performances dans certaines circonstances, mais elle doit être utilisée avec le plus grand soin (et une analyse comparative!).

Cependant, il existe des utilisations pour les canaux tamponnés qui ne concernent pas directement la communication.

Par exemple, un idiome de limitation commun crée un canal avec (par exemple) la taille de tampon «10», puis y envoie immédiatement dix jetons. N'importe quel nombre de goroutines de travail sont alors générés, et chacun reçoit un jeton du canal avant de commencer le travail, et le renvoie par la suite. Ensuite, quel que soit le nombre de travailleurs, seuls dix travailleront en même temps.

Par défaut, les chaînes Go sont sans tampon . Cela signifie que l'envoi d'une valeur à un canal bloquera jusqu'à ce qu'un autre goroutine soit prêt à la recevoir immédiatement. Go prend également en charge des tailles de tampon fixes pour les canaux (en utilisant make(chan someType, bufferSize)). Cependant, pour une utilisation normale, c'est généralement une mauvaise idée .

Imaginez un serveur web pour notre fonds, où chaque demande effectue un retrait. Lorsque les choses sont très occupées, le FundServer ne pourra pas suivre le rythme et les demandes qui tentent d’envoyer vers son canal de commande commenceront à se bloquer et à attendre. À ce stade, nous pouvons appliquer un nombre maximal de demandes dans le serveur et renvoyer un code d'erreur sensible (comme un 503 Service Unavailable) aux clients au-delà de cette limite. C'est le meilleur comportement possible lorsque le serveur est surchargé.

L'ajout de tampons à nos canaux rendrait ce comportement moins déterministe. Nous pourrions facilement nous retrouver avec de longues files d'attente de commandes non traitées basées sur des informations que le client a vues beaucoup plus tôt (et peut-être pour des requêtes qui avaient depuis expiré en amont). Il en va de même dans de nombreuses autres situations, comme l'application d'une contre-pression sur TCP lorsque le récepteur ne peut pas suivre l'expéditeur.

Dans tous les cas, pour notre exemple Go, nous nous en tiendrons au comportement sans tampon par défaut.

Nous utiliserons un canal pour envoyer des commandes à notre FundServer. Chaque travailleur de référence enverra des commandes au canal, mais seul le serveur les recevra.

Nous pourrions transformer directement notre type de fonds en une implémentation de serveur, mais ce serait compliqué - nous allions mélanger la gestion de la concurrence et la logique métier. Au lieu de cela, nous laisserons le type de fonds exactement tel qu’il est, et créerons FundServer un emballage séparé autour de lui.

Comme tout serveur, le wrapper aura une boucle principale dans laquelle il attend les commandes et répond à chacune à son tour. Il y a un autre détail que nous devons aborder ici: le type des commandes.

Un diagramme du fonds utilisé comme serveur dans ce didacticiel de programmation Go.

Pointeurs
Nous aurions pu faire en sorte que notre canal de commandes prenne des * pointeurs * vers des commandes (`chan * TransactionCommand`). Pourquoi pas nous?

Passer des pointeurs entre les goroutines est risqué, car l'un ou l'autre des goroutines pourrait le modifier. C'est aussi souvent moins efficace, car l'autre goroutine peut s'exécuter sur un cœur de processeur différent (ce qui signifie plus d'invalidation de cache).

Dans la mesure du possible, préférez transmettre des valeurs simples.

Dans la section suivante ci-dessous, nous allons envoyer plusieurs commandes différentes, chacune avec son propre type de structure. Nous voulons que le canal des commandes du serveur accepte l’une d’entre elles. Dans un langage POO, nous pourrions le faire via le polymorphisme: faire en sorte que le canal prenne une superclasse, dont les types de commandes individuels étaient des sous-classes. Dans Go, nous utilisons les interfaces au lieu.

Une interface est un ensemble de signatures de méthodes. Tout type qui implémente toutes ces méthodes peut être traité comme cette interface (sans être déclaré comme tel). Pour notre première exécution, nos structures de commande n'exposeront en fait aucune méthode, nous allons donc utiliser l'interface vide, interface{}. Puisqu'il n'a aucune exigence, tout value (y compris les valeurs primitives comme les entiers) satisfait l'interface vide. Ce n’est pas idéal - nous ne voulons accepter que les structures de commande - mais nous y reviendrons plus tard.

Pour l'instant, commençons avec l'échafaudage de notre serveur Go:

server.go

package funding type FundServer struct { Commands chan interface{} fund Fund } func NewFundServer(initialBalance int) *FundServer { server := &FundServer{ // make() creates builtins like channels, maps, and slices Commands: make(chan interface{}), fund: NewFund(initialBalance), } // Spawn off the server's main loop immediately go server.loop() return server } func (s *FundServer) loop() { // The built-in 'range' clause can iterate over channels, // amongst other things for command := range s.Commands { // Handle the command } }

Ajoutons maintenant quelques types de structure Golang pour les commandes:

type WithdrawCommand struct { Amount int } type BalanceCommand struct { Response chan int }

Le WithdrawCommand contient juste le montant à retirer. Il n’y a pas de réponse. Le BalanceCommand a une réponse, donc il inclut un canal pour l'envoyer. Cela garantit que les réponses iront toujours au bon endroit, même si notre fonds décide plus tard de répondre dans le désordre.

Nous pouvons maintenant écrire la boucle principale du serveur:

func (s *FundServer) loop() { for command := range s.Commands { // command is just an interface{}, but we can check its real type switch command.(type) { case WithdrawCommand: // And then use a 'type assertion' to convert it withdrawal := command.(WithdrawCommand) s.fund.Withdraw(withdrawal.Amount) case BalanceCommand: getBalance := command.(BalanceCommand) balance := s.fund.Balance() getBalance.Response <- balance default: panic(fmt.Sprintf('Unrecognized command: %v', command)) } } }

Hmm. C’est un peu moche. Nous activons le type de commande, en utilisant des assertions de type, et nous nous plantons peut-être. Allons de l'avant quand même et mettons à jour la référence pour utiliser le serveur.

func BenchmarkWithdrawals(b *testing.B) { // ... server := NewFundServer(b.N) // ... // Spawn off the workers for i := 0; i

C'était un peu moche aussi, surtout quand nous avons vérifié l'équilibre. Ça ne fait rien. Essayons:

$ GOMAXPROCS=4 go test -bench . funding BenchmarkWithdrawals-4 5000000 465 ns/op ok funding 2.822s

Bien mieux, nous ne perdons plus de retraits. Mais le code devient difficile à lire et les problèmes sont plus graves. Si jamais nous émettons un BalanceCommand puis oubliez de lire la réponse, notre serveur de fonds bloquera à jamais en essayant de l'envoyer. Nettoyons un peu les choses.

Faites-en un service

Un serveur est quelque chose à qui vous parlez. Qu'est-ce qu'un service? Un service est quelque chose à qui vous parlez avec une API . Au lieu que le code client fonctionne directement avec le canal de commande, nous allons rendre le canal non exporté (privé) et envelopper les commandes disponibles dans des fonctions.

type FundServer struct { commands chan interface{} // Lowercase name, unexported // ... } func (s *FundServer) Balance() int { responseChan := make(chan int) s.commands <- BalanceCommand{ Response: responseChan } return <- responseChan } func (s *FundServer) Withdraw(amount int) { s.commands <- WithdrawCommand{ Amount: amount } }

Maintenant, notre benchmark peut simplement dire server.Withdraw(1) et balance := server.Balance(), et il y a moins de chance de lui envoyer accidentellement des commandes non valides ou d'oublier de lire les réponses.

Voici à quoi pourrait ressembler l

Il y a encore beaucoup de passe-partout supplémentaire pour les commandes, mais nous y reviendrons plus tard.

Transactions

Finalement, l'argent s'épuise toujours. Convenons que nous arrêterons de retirer lorsque notre fonds sera réduit à ses dix derniers dollars et dépenserons cet argent sur une pizza commune pour célébrer ou compatir. Notre benchmark reflètera ceci:

// Spawn off the workers for i := 0; i

Cette fois, nous pouvons vraiment prédire le résultat.

$ GOMAXPROCS=4 go test -bench . funding BenchmarkWithdrawals-4 --- FAIL: BenchmarkWithdrawals-4 fund_test.go:43: Balance wasn't ten dollars: 6 ok funding 0.009s

Nous sommes revenus à notre point de départ: plusieurs employés peuvent lire le solde à la fois, puis tous le mettre à jour. Pour faire face à cela, nous pourrions ajouter une logique dans le fonds lui-même, comme un minimumBalance propriété, ou ajoutez une autre commande appelée WithdrawIfOverXDollars. Ce sont deux idées terribles. Notre accord est entre nous, pas une propriété du fonds. Nous devons le garder dans la logique applicative.

Ce dont nous avons vraiment besoin, c'est transactions , dans le même sens que les transactions de base de données. Étant donné que notre service n'exécute qu'une seule commande à la fois, c'est très simple. Nous ajouterons un Transact commande qui contient un rappel (une fermeture). Le serveur exécutera ce rappel dans sa propre goroutine, en passant le brut Fund. Le callback peut alors faire ce qu'il veut en toute sécurité avec le Fund.

Sémaphores et erreurs
Dans cet exemple suivant, nous faisons deux petites choses mal.

Tout d'abord, nous utilisons un canal `Done` comme sémaphore pour informer le code appelant de la fin de sa transaction. C'est bien, mais pourquoi le type de canal est-il «bool»? Nous n'enverrons jamais «true» que pour signifier «done» (que signifierait même l'envoi de «false»?). Ce que nous voulons vraiment, c'est une valeur à un seul état (une valeur sans valeur?). Dans Go, nous pouvons le faire en utilisant le type struct vide: `struct {}`. Cela a également l'avantage d'utiliser moins de mémoire. Dans l'exemple, nous allons nous en tenir à `bool` pour ne pas avoir l'air trop effrayant.

Deuxièmement, notre rappel de transaction ne renvoie rien. Comme nous le verrons dans un instant, nous pouvons extraire des valeurs du rappel pour appeler du code en utilisant des astuces de portée. Cependant, les transactions dans un système réel échoueraient probablement parfois, donc la convention Go consisterait à faire en sorte que la transaction renvoie une 'erreur' (puis vérifie si elle était 'nil' dans le code d'appel).

Nous ne le faisons pas non plus pour le moment, car nous n'avons aucune erreur à générer. // Typedef the callback for readability type Transactor func(fund *Fund) // Add a new command type with a callback and a semaphore channel type TransactionCommand struct { Transactor Transactor Done chan bool } // ... // Wrap it up neatly in an API method, like the other commands func (s *FundServer) Transact(transactor Transactor) { command := TransactionCommand{ Transactor: transactor, Done: make(chan bool), } s.commands <- command <- command.Done } // ... func (s *FundServer) loop() { for command := range s.commands { switch command.(type) { // ... case TransactionCommand: transaction := command.(TransactionCommand) transaction.Transactor(s.fund) transaction.Done <- true // ... } } }

Nos rappels de transaction ne renvoient rien directement, mais le langage Go facilite l'extraction directe des valeurs d'une clôture, nous allons donc le faire dans le benchmark pour définir le pizzaTime signaler lorsque l'argent manque:

pizzaTime := false for i := 0; i

Et vérifiez que cela fonctionne:

$ GOMAXPROCS=4 go test -bench . funding BenchmarkWithdrawals-4 5000000 775 ns/op ok funding 4.637s

Rien que des transactions

Vous avez peut-être repéré une opportunité de nettoyer un peu plus les choses maintenant. Puisque nous avons un Transact générique | commande, nous n’avons pas besoin de WithdrawCommand ou BalanceCommand plus. Nous allons les réécrire en termes de transactions:

func (s *FundServer) Balance() int { var balance int s.Transact(func(f *Fund) { balance = f.Balance() }) return balance } func (s *FundServer) Withdraw(amount int) { s.Transact(func (f *Fund) { f.Withdraw(amount) }) }

Maintenant, la seule commande que le serveur prend est TransactionCommand, donc nous pouvons supprimer le tout interface{} mess dans son implémentation, et lui faire accepter uniquement les commandes de transaction:

type FundServer struct { commands chan TransactionCommand fund *Fund } func (s *FundServer) loop() { for transaction := range s.commands { // Now we don't need any type-switch mess transaction.Transactor(s.fund) transaction.Done <- true } }

Bien mieux.

Il y a une dernière étape que nous pourrions franchir ici. Outre ses fonctions pratiques pour Balance et Withdraw, l'implémentation du service n'est plus liée à Fund. Au lieu de gérer un Fund, il pourrait gérer un interface{} et être utilisé pour envelopper n'importe quoi . Cependant, chaque rappel de transaction devra alors convertir le interface{} retour à une valeur réelle:

type Transactor func(interface{}) server.Transact(func(managedValue interface{}) { fund := managedValue.(*Fund) // Do stuff with fund ... })

C'est moche et sujet aux erreurs. Ce que nous voulons vraiment, ce sont des génériques à la compilation, afin que nous puissions «créer un modèle» sur un serveur pour un type particulier (comme *Fund).

Malheureusement, Go ne prend pas encore en charge les génériques. On s'attend à ce qu'il arrive éventuellement, une fois que quelqu'un a trouvé une syntaxe et une sémantique sensées. En attendant, la conception soigneuse de l'interface supprime souvent le besoin de génériques, et quand ils ne le sont pas, nous pouvons nous en tirer avec des assertions de type (qui sont vérifiées lors de l'exécution).

Avons-nous fini?

Oui.

systèmes de trading forex qui fonctionnent réellement

Eh bien, d'accord, non.

Par exemple:

  • Une panique dans une transaction tuera tout le service.

  • Il n'y a pas de délai d'attente. Une transaction qui ne revient jamais bloquera le service pour toujours.

  • Si notre Fonds agrandit de nouveaux champs et qu'une transaction plante à mi-chemin de leur mise à jour, nous aurons un état incohérent.

  • Les transactions peuvent divulguer les Fund gérés | objet, ce qui n’est pas bon.

  • Il n’existe aucun moyen raisonnable d’effectuer des transactions sur plusieurs fonds (par exemple, retirer d’un fonds et déposer dans un autre). Nous ne pouvons pas simplement imbriquer nos transactions, car cela permettrait des blocages.

  • Exécuter une transaction de manière asynchrone nécessite désormais une nouvelle routine et beaucoup de déconner. De même, nous souhaitons probablement pouvoir lire les Fund les plus récents | état d'un autre endroit pendant qu'une transaction de longue durée est en cours.

Dans notre prochain didacticiel sur le langage de programmation Go, nous examinerons quelques moyens de résoudre ces problèmes.

En relation: Logique bien structurée: un didacticiel Golang OOP

Comprendre les bases

Dans quoi la langue Go est-elle écrite?

La spécification du langage de programmation Go est un document écrit en anglais, tandis que la bibliothèque standard et le compilateur de Go sont écrits en Go lui-même.

À quoi sert Go?

Go est un langage de programmation à usage général et peut être utilisé pour une variété d'utilisations. Go peut être utilisé comme serveur Web dans le back-end et a été utilisé pour créer Docker, Kubernetes et la CLI Heroku.

Quelles entreprises utilisent Go?

Go est utilisé par un grand nombre d'entreprises technologiques réputées, notamment Google, Dropbox, Docker, Kubernetes, Heroku et bien d'autres.

Démystifier les crypto-monnaies, la blockchain et les ICO

Processus Financiers

Démystifier les crypto-monnaies, la blockchain et les ICO
Test de l'interface utilisateur Android et iOS avec Calabash

Test de l'interface utilisateur Android et iOS avec Calabash

La Technologie

Articles Populaires
Le talent n'est pas une marchandise
Le talent n'est pas une marchandise
Prototype with Ease - Un didacticiel InVision Studio
Prototype with Ease - Un didacticiel InVision Studio
Un mois dans la vie - Rôles intérimaires des directeurs financiers et meilleures pratiques
Un mois dans la vie - Rôles intérimaires des directeurs financiers et meilleures pratiques
Que fait un coach Agile et comment en devenir un?
Que fait un coach Agile et comment en devenir un?
Marché du vendeur - Questions de diligence raisonnable financière à poser
Marché du vendeur - Questions de diligence raisonnable financière à poser
 
Changer pour le bien ou le mal? Un guide de l'innovation UX
Changer pour le bien ou le mal? Un guide de l'innovation UX
Sécurité biométrique - La clé de l'authentification sans mot de passe ou une mode?
Sécurité biométrique - La clé de l'authentification sans mot de passe ou une mode?
Réflechissez bien. Lors de la définition des objectifs commerciaux de votre prochain projet UX
Réflechissez bien. Lors de la définition des objectifs commerciaux de votre prochain projet UX
Augmentez la croissance: effectuez votre propre analyse de cohorte avec ce code Open Source
Augmentez la croissance: effectuez votre propre analyse de cohorte avec ce code Open Source
Construire votre premier robot télégramme: un guide étape par étape
Construire votre premier robot télégramme: un guide étape par étape
Articles Populaires
  • vulnérabilités de protocole et/ou hacks
  • éléments et principes de conception visuelle
  • javascript obtient la date en millisecondes
  • 10 principes de l'art du design
  • comment trouver des clients consultants
Catégories
  • Conseils Et Outils
  • Agile
  • Kpi Et Analyses
  • Planification Et Prévision
  • © 2022 | Tous Les Droits Sont Réservés

    portaldacalheta.pt