Je viens de finir un petit projet en Go la semaine dernière, un assembleur vers du MIPS simplifié. Voici un petit retour d'expérience, en espérant que ça serve !
NdM : merci à G.bleu pour son journal.
Sommaire
- Mise en situation
- Le programme
- Conclusion
Mise en situation
Pour mes études, j'ai un projet (le dernier avant la vie active !) de réalisation de microprocesseur MIPS « from scratch ».
Le CPU est designé sous Xilinx ISE (grosso modo un IDE dans lequel on peut réaliser des designs de composants à base de portes logiques). Par la suite, il sera chargé sur un FPGA, celui-ci connecté à un robot afin de lui faire suivre une ligne sur le sol.
De fait, une fois le CPU designé, il faut réaliser un programme en assembleur MIPS, puis le convertir en binaire afin de l'intégrer dans le design du CPU sous la forme d'un module en VHDL. En gros, en entrée du module arrive l'adresse du program counter et le module sort l'instruction correspondante.
Les plus attentifs auront déjà pointé du doigt le souci : comment convertir proprement le code assembleur MIPS en binaire ?
À la main, ne riez pas, c'est ce que m'a proposé mon prof quand je lui ai posé la question ! À sa décharge, les élèves suivant ce cours ne sont pas informaticiens mais plutôt orientés électronique.
Utiliser un assembleur déjà existant. La solution « ne pas réinventer la roue » de référence. Le problème : l'output sera en binaire (logique, me direz-vous) mais je veux du code binaire lisible ! (en gros mon output doit être 000101011100110 afin de pouvoir directement copier coller le code dans le fichier de mon module VHDL). Ajouter à cela que je ne veux pas de header ELF ou quoi que ce soit, juste la transcription du code que j'ai écrit. Je pense, bien-sûr, qu'il y a des solutions pour arriver à ce que je veux. Néanmoins, j'ai du temps en ce moment et apprendre un nouveau langage me semble plus formateur qu'apprendre les options d'un outil qui ne me servira plus par la suite !
Écrire un assembleur à la main. La solution que j'ai choisie, et qui me donne donc la possibilité de réaliser ce journal ! Un assembleur n'est pas aussi complexe qu'un compilateur (et de loin !), mais permet déjà de s'amuser sur un nouveau langage.
Le programme
Linus Torvald :
Show me the code !
Céans mon bon monsieur ! En espérant que tout le monde aime SourceForge…
Comme dit plus haut, il s'agit d'un assembleur MIPS « simplifié » :
Toute les instructions ne sont pas disponibles (pas de jump ou de subi par exemple). La raison est tout simplement que mon processeur ne prend pas en charge ces instructions et que je préfère avoir une erreur à la compilation si j'oublie ce détail que de devoir débugger une erreur qui n'en est pas une par la suite… Malgré tout, il est très simple d'ajouter ces fonctionnalités comme nous allons le voir.
Pas de header ELF pour le programme. Comme j'ai dit, l'idée est de copier la sortie de l'assembleur dans un fichier pour que mon cpu l'utilise "tel quel". Pas d'OS, pas de logique supérieure, rien ! Donc, pas besoin d'header.
Rentrons dans le code
J'ai divisé mon assembleur en 3 parties :
- Le parser/lexer : réalisé avec yacc pour le premier. On vérifie la grammaire et la sémantique afin de générer une liste d'instructions sous la forme d'un tableau de structures ainsi qu'une map faisant la conversion nom des label => adresse.
- Le binder : transforme chaque instruction du tableau précédent en une instruction binaire à proprement parler (avec vérification en fonction du contexte)
- Le main : qui wrap ces deux parties, les connecte entre elles et gère les options fournies par la ligne de commande.
Et Go dans tout ça ?
Il convient avant tout de parler un peu de la philosophie de Go avant d'aller plus loin.
En effet, l'idée est de fournir des outils tout prêts à l'utilisateur. On se base sur un maximum de règles standards et donc un minimum de configuration (voire en fait pas de configuration du tout dans bien des cas !).
Le makefile
L'exemple le plus frappant de ce concept est le makefile. Si dans les versions antérieures à la 1.0, Go possédait un simili makefile (il était déjà beaucoup plus simple qu'un makefile typique pour du C), tout cela est révolu !
Maintenant un projet Go n'a besoin pour compiler que de ses sources. Un coup de "go build" et tout se fait tout seul. Plus de gestion des dépendances, plus de problèmes de conflits d'includes (de toute façon, il n'y a pas d'include en Go)… voilà qui devrait intéresser, je pense, tous ceux qui se sont essayés à C++ et à ses célèbres erreurs de compilation hyper-verbeuses, pour cause de conflit de define pour avoir placé un include au mauvais endroit.
Petite remarque tout de même : Mon projet contient un makefile !
Bien que minimaliste, celui-ci est donc toujours présent.
La raison est multiple :
La commande "go build" construit votre binaire… et c'est tout ! J'aime pouvoir automatiser la génération de tarball, le nettoyage du projet, etc.
Mon projet utilise yacc (donc conversion du fichier parser.y en parser.go). De fait, go build ne met pas à jours parser.go si parser.y est mis à jour. D'où la nécessité de gérer cette dépendance
Les tests
De la même manière, réaliser des tests est simplissime. Pour écrire des tests pour le fichier foo.go, vous n'avez qu'à créer le fichier foo_test.go et… c'est tout ! Ce fichier se fera automatiquement compiler et ses fonctions commençant par Test seront exécutées à chaque lancement de "go test".
Dans le fichier, on importe le package "testing", et on appelle la méthode testing.T.Fail() ou testing.T.Error("C'est la dèche !") pour signaler que le test a échoué :
import "testing"
func TestFoo(t *testing.T) { // Les fonctions de test commencent par Test
// et respectent cette signature
if test_is_ok() != nil {
t.Error("t'es parti pour fixer ton code !") // Erreur avec message
}
if test_sans_message != nil {
t.Fail() // Erreur sans message
}
// Si aucun appel à Fail ou Error, alors le test est considéré comme réussit
}
Le multiplatforme !
Encore une très bonne nouvelle : Go est multiplatforme de base !
Voulant partager mon logiciel avec mes petits camarades (Je précise que je suis en Corée du Sud actuellement… pas la peine de dire quel OS utilise tout ce joli monde !), autant dire que cette fonctionnalité a fortement pesé dans la balance pour le choix de Go.
En outre, selon la doc il est possible de cross-compiler à partir de n'importe quelle platforme pour n'importe quelle autre juste en changeant ses variables locales comme GOOS ou GOARCH (et bien-sûr en compilant sa chaîne de compilation pour l'architecture cible). Toutefois, je n'ai pas testé cette possibilité, j'ai préféré rebooter sous windows (à ma décharge, j'aurais de toute façon dû le faire pour vérifier que mon binaire fonctionne bien !)
J'ai toutefois trouvé un peu bizarre que la gestion du retour à la ligne ne soit pas fournie comme en C++ (avec std::endl). De fait, on doit faire attention à ce léger détail et différencier les cas selon les OS à la main, ce qui est assez dommage.
Les outils en plus
En bonus, go fourni des outils des plus sympa :
go fmt
Cette commande permet de mettre à LA norme le code. Notez le "LA" majuscule, il n'y en a qu'une (certains diront qu'elle est horrible mais nous ne sommes pas vendredi, je laisse cela à d'autres). Du coup, pas de conflits à ce niveau, tous les codes go écrits pas tous les développeurs du monde auront la même forme.
Petit bémol pour ma part : cette norme utilise des indentations de 8 caractères et ne coupe pas le code à 80 colonnes. Résultat, celui-ci est bien souvent trop long à mon goût (ainsi qu'à celui de mon Emacs en multicolonnes. Là où je peux mettre 3 colonnes en C la plupart du temps, je suis limité à 2 en go… 33% d'espace perdu, snif !)go tool yacc
La commande go tool permet d'accéder à la foultitude d'outils intégré dans la commande go. Parmi eux se trouve yacc, le célèbre parser. Cette version est une réécriture en go de celui de plan9. Autant dire que sa présence à été déterminante dans mon choix d'utiliser go pour mon projet (un parser à la main… non merci !)
Écrivons un peu de code
Après tout ce temps à parler des outils, parlons du code, du vrai !
Le retour de plusieurs variables
En voilà une bonne idée ! Toute fonction peut renvoyer plusieurs variables au lieu d'une seule dans la plupart des langages.
De fait, on retrouve dans la lib standard de Go un bon nombre de fonctions renvoyant à la fois la valeur qu'on leur demande ainsi qu'un type "*Error" pouvant être soit "nil" (c'est-à-dire pointer sur rien) soit initialisé, signifiant alors une erreur. Plus besoin de "tricher" - comme en C - en donnant en argument de la fonction un pointeur sur la variable à compléter, puisque la variable de retour est déjà occupée par le code d'erreur.
De même, le parcours de tableau s'en trouve simplifié :
for i,elm := range array {
fmt.Println("l'élément", i, "a pour valeur", elm)
}
range est un mot clé permettant de parcourir un tableau en renvoyant, à chaque itération, la position ainsi que l'élément courant.
Notez aussi le ":=" permettant de déclarer "à l'arrache" des variable en fonction du type de la variable qu'on lui assigne. Un vrai bonheur pour gagner en lisibilité dans les cas triviaux.
La gestion des options
Encore une bonne nouvelle ! La gestion des passages d'options étant une fonctionnalité essentielle à 90% des logiciels, le package "flag" s'occupe de cela avec brio !
import "flag"
var f_input = flag.String("i", "", "Input file.")
var f_output = flag.String("o", "", "Output file. (stdout if nothing specified)")
var f_type = flag.String("t", "vhdl", "Type of output : binary, print, vhdl")
func main() {
flag.Parse()
fmt.Println("lecture du fichier", *f_input)
...
}
Ai-je vraiment besoin d'expliciter ? On déclare les flags en dehors des fonctions, on appel flag.Parse() avant d'utiliser les flags (notez qu'il s'agit de pointeurs, on met donc une "*" pour les déréférencer)
Remarquez que l'option --help/-h est gérée automatiquement !
Les conteneurs
Go possède de manière intégrée au langage les conteneurs les plus utilisés :
Map
Son nom est suffisamment explicite : une clé, une valeur.
Problème, selon moi : si la clé ne correspond à aucune valeur, la valeur nulle est renvoyée. De fait, dans mon programme, j'utilise une map pour faire la correspondance entre les labels et leur adresse réelle. Si je demande l'adresse d'un label inexistant, la map va me renvoyer la valeur 0, puisque c'est l'équivalent de la valeur nulle pour un int…
Problème : cette valeur peu tout à fait être valide dans le cas d'un label situé en début de code ! C'est d'autant plus étrange que le langage autorise le renvoi de plusieurs valeurs, comme nous l'avons vu précédemment…
Array
Un joli tableau unidimensionnel de taille constante… rien à redire.
Slice
Un type fourre-tout : c'est un genre de tableau à taille variable.
En réalité, le slice - comme son nom l'indique - représente un morceau de Array. De fait, plusieurs slices peuvent pointer en même temps sur le même Array, par exemple.
Je cherchais à utiliser un vecteur pour stocker chacune de mes instructions une fois parsées/lexées. J'ai eu la surprise de voir que le conteneur Vecteur avait été supprimé de la bibliothèque standard il y a quelques commits…
La réponse s'est trouvée sur la mailling list de Go : utiliser des slices !
Voici comment faire :
var s0 := []int{0, 1} // on créé un un slice sur un tableau contenant 0 et 1
var to_insert = 42
S0 = append(s, to_insert) // on utilise la fonction buildtin "append"
Simple ? Bon maintenant, voyons comment supprimer l'élément numéro i (en C++ j'aurais fait "vect.remove(i)")
s0 = append(s[:i - 1], s[i + 1:]...)
Pas glop ! Je pense qu'un peu de sucre syntaxique n'aurait pas été de trop pour cacher cette complexité inutile !
Pour ceux qui se posent la question, append ajoute des éléments à un slice. De fait, on doit transformer le slice s[i + 1:] en une suite d'élément avant de l'ajouter à s[:i - 1]. C'est ce qu'on fait avec la commande "s[i + 1:]…"
L'héritage
Dans Go l'héritage dans la grande tradition OO n'existe pas ! (et ce n'est pas pour me déplaire à titre personnel).
Le tout s'articule autour de trois concepts (que je n'illustrerai pas avec mon programme puisse que celui-ci n'en n'utilise pas le premier.)
Les structures
Plutôt que de déclarer des classes, on crée des structures. Celles-ci pouvant gérer l'héritage d'une façon amusante :
type plat struct {
name string
}
type choucroute struct {
plat
saucisses int
}
func main() {
ch := choucroute{plat{"choucroute"}, 4}
fmt.Println("Je vais me tapper une", ch.name, "avec ", ch.saucisses, "saucisses dedans !")
// ces deux lignes sont rigoureusement identiques
fmt.Println(ch.name)
fmt.Println(ch.plat.name)
}
On remarque donc que, en ajoutant dans choucroute une structure plat de manière anonyme (le nom "_" l'équivalent en Go de John Doe…), les éléments de plat sont intégrés dans choucroute !
Les méthodes
Une fois notre jolie structure déclarée, il est possible de lui adjoindre des méthodes :
func (ch Choucroute)manger() {
ch.saucisses--
fmt.Println("Miam ! Encore", ch.saucisses, "saucisses !")
}
func (_ Plat)manger() { // remarquez le "_" pour indiquer qu'on ne fera rien avec l'objet et qu'il n'est donc pas nécessaire de le nommer.
fmt.Println("Beark !")
}
Les interfaces
En Go, tout se base sur du Duck typing. De fait, afin de pouvoir manger à la fois un plat de seconde zone ou de la délicieuse choucroute, on peut déclarer une interface qui contiendra la fonction manger :
type mangerer interface {
manger()
}
func main() {
bouffe := []mangerer
bouffe = append(bouffe, choucroute{ "choucroute", 4 })
bouffe = append(bouffe, plat{ "bouts de tétons de mme Félipé" })
for _, pl := range bouffe {
pl.manger()
}
}
De fait, le duck typing faisant son travail, notre slice peut contenir n'importe quelle structure possédant une fonction ayant pour signature "manger()".
Au final, un gain de légèreté monstrueux comparé au C++ et à ses déclarations d'objets et d'héritage particulièrement verbeuses.
C'est beau, c'est simple, ça me fait pleurer !
Les autres fonctionnalités
Les goroutines
Bien sûr, je n'ai pas pu tout tester dans mon programme.
Je pense notamment à une des principales fonctionnalités mise en avant : la concurrence.
Mon programme n'utilise qu'un fil d'exécution. De fait, je n'ai pas pu utiliser les channels ni le mot clé go.
Toutefois, ayant déjà fait un peu joujou avec par le passé, j'ai trouvé l'idée vraiment excellente. Les channels permettant à une routine d'attendre qu'une autre lui envoie un objet pour continuer. De fait, on résout le soucis de synchronisation de manière beaucoup plus simple qu'en plaçant des mutex sur les ressources critiques !
Conclusion
Je suis globalement très satisfait de Go, j'ai l'impression d'un langage rapide, et ce dans tous les sens du terme :
Rapidité de compilation.
Quasi instantanée ! (dois-je comparer à un poid lourd comme C++ ?)Rapidité de développement.
Tout va très vite. Le duck typing permet de redéfinir ses structures très rapidement, on n'écrit que le minimum.
De même, tout le monde est convaincu de l'importance des tests, mais la flemme nous fait généralement (en particulier pour les petits projet comme le mien) tester à la main la fonctionnalité sur laquelle on travaille actuellement et basta !
De fait, le système de test intégré à Go est, pour moi, un pur bonheur, tant il est simple et efficace !Rapidité d'exécution.
Le débat est lancé ! Pour certains, le compilateur de Go est trop jeune et pas suffisamment optimisé (ce qui explique sa vitesse de compilation).
Les auteurs de Go ont notamment publié un article pour battre en brèche cette idée en montrant comment obtenir des performances proches du C++ en optimisant son code.
D'un autre côté, il s'agit d'un langage jeune, avec tous les problèmes inhérents :
Peu de bibliothèques tierces pour le moment. Au vu de la simplicité d'interfaçage de Go avec C, des projets de bindings de grosses bibliothèques fleurissent un peu partout, mais pour le moment rien de très stable/utilisable.
Un compilateur jeune.
En effet, pour le moment le runtime est compilée statiquement dans le logiciel. Cela explique la taille supérieure au mégaoctet du moindre "hello world". Par exemple, mon logiciel faisant dans les 800 lignes de Go se retrouve compilé dans un binaire de 1.6 Mo…
Enfin bon, vue la taille actuelle de nos disques durs et tant que Go n'aura pas pour vocation de tourner sur de l'embarqué, je ne suis pas sûr que ce soit un si gros point noir.
Aller plus loin
- Journal à l'origine de la dépêche (630 clics)
- gopiler (398 clics)
# quelques corrections de mise en forme :)
Posté par Thomas Debesse (site web personnel) . Évalué à 2. Dernière modification le 16 mai 2012 à 15:53.
Je suppose que
et
et
sont à retirer des blocs de codes ;)
[edit: Il semble que ce soit parce que le langage "go" ne soit pas reconnu, peut-être essayer avec le pis-aller "text" ?]
Il me semblait aussi avoir vu une faute en er/é mais je ne la retrouve plus.
Et merci pour ce journal/dépêche très intéressant, et bon courage pour tes étude ! :)
ce commentaire est sous licence cc by 4 et précédentes
[^] # Re: quelques corrections de mise en forme :)
Posté par claudex . Évalué à 3.
Il semblerait oui, mais le bug n'apparaît pas en prévisualisation.
« Rappelez-vous toujours que si la Gestapo avait les moyens de vous faire parler, les politiciens ont, eux, les moyens de vous faire taire. » Coluche
[^] # Re: quelques corrections de mise en forme :)
Posté par claudex . Évalué à 4.
Je crois que c'est bon.
« Rappelez-vous toujours que si la Gestapo avait les moyens de vous faire parler, les politiciens ont, eux, les moyens de vous faire taire. » Coluche
[^] # Re: quelques corrections de mise en forme :)
Posté par Thomas Debesse (site web personnel) . Évalué à 2.
cf. mon édit' : c'est peut-être parce que l'interpréteur de code ne gère pas le langage "go" ?
ce commentaire est sous licence cc by 4 et précédentes
[^] # Re: quelques corrections de mise en forme :)
Posté par claudex . Évalué à 4.
go est reconnu, c'est parce que lors de la rédaction, le rendu est fait par paragraphe séparé afin de permettre une édition à plusieurs, alors que lors de la publication tout est rendu en un seul bloc. Or, en convertissant le journal en dépêche je n'avais pas refermé tous les blocs de code.
« Rappelez-vous toujours que si la Gestapo avait les moyens de vous faire parler, les politiciens ont, eux, les moyens de vous faire taire. » Coluche
# maps
Posté par Sebastien . Évalué à 10.
pour ca, il y a le "comma-ok":
ou, plus go-ish:
-s
# Retour de fonction
Posté par mgoeminne (site web personnel) . Évalué à 2.
Merci pour ce témoignage très intéressant, bien que quelques coquilles/erreurs l'enlaidissent malheureusement.
Félicitation pour le projet. Ça me semble bien mien qu'une conversion à la main ;)
Euh… OK, de tels langages existent, mais je ne pense pas qu'ils soient majoritaires. Ou alors je commence vraiment à me faire vieux.
[^] # Re: Retour de fonction
Posté par claudex . Évalué à 5.
Il faut lire {Toute fonction peut renvoyer plusieurs variables {au lieu d'une seule dans la plupart des langages}}.
« Rappelez-vous toujours que si la Gestapo avait les moyens de vous faire parler, les politiciens ont, eux, les moyens de vous faire taire. » Coluche
[^] # Re: Retour de fonction
Posté par djano . Évalué à 3.
Oui, désolé, j'ai craqué sur les coquilles car ça pique les yeux. Je promets de faire un commentaire plus constructif.
réalisé
du projet
initialisé
essentielle
déréférencer
fourre-tout
suite d'élément
travaille
[^] # Re: Retour de fonction
Posté par BAud (site web personnel) . Évalué à 2.
Merci, corrigé (et une majorité de typos résiduelles).
[^] # Re: Retour de fonction
Posté par Marc-François . Évalué à 1.
suite d'éléments
# Debugger?
Posté par Damien (site web personnel) . Évalué à 1.
Y'a-t-il un debugger pour Go? à quoi ressemble-t-il ?
[^] # Re: Debugger?
Posté par Moonz . Évalué à 5.
gdb supporte Go.
# Voila des commentaires plus interessants
Posté par djano . Évalué à 4.
Bravo pour ce projet! Bravo de t’être lance le défi d'apprendre Go sur un problème qui ne le nécessitait pas.
J'aime: L'article sur le profiling en Go présente vraiment bien les outils disponibles de base. Je suis très impressionné, ils sont très puissants!
J'aime comment gopprof peut afficher le graphique graphviz des appels ou allocations (en les filtrant aussi!), comment il peut voir le code source annoté avec l'utilisation CPU ou l'allocation.
J'aime: je viens de voir que Go supporte la reflection, ce qui est parfaitement indispensable pour des programmes de taille conséquente pour automatiser certains chemins d’exécution de manière hyper generique.
J'aime pas: l'équivalent Go de "vect.remove(i)" en C++.
# Design = conception
Posté par Pierre Jarillon (site web personnel) . Évalué à 9.
Anglicisme, faux ami et faux sens… Désigner en français, c'est choisir, indiquer, montrer du doigt.
Le mot anglais design signifie :
- si c'est un nom : conception, dessin, modèle, projet, plan, style
- si c'est un verbe : concevoir, élaborer, dessiner, élaborer un plan, faire le plan.
[^] # Re: Design = conception
Posté par tyoup . Évalué à -2.
lire dizaïné et non dézinié ?
[^] # Re: Design = conception
Posté par gUI (Mastodon) . Évalué à 2.
Bin oui, il s'agit simplement d'un anglicisme, rien à voir avec un faux-ami. L'auteur a utilisé le terme anglais "design" pour, en effet, traiter de la conception du CPU.
C'est un usage devenu très courant dans les sciences (et surtout électronique/informatique).
En théorie, la théorie et la pratique c'est pareil. En pratique c'est pas vrai.
# Retours à la ligne
Posté par beagf (site web personnel) . Évalué à 6.
En C++, std::endl n'a pas grand chose à voir avec la gestion multi-plateforme du retour à la ligne. Un simple '\n' sera traduit par le bon retour à la ligne utilisé par la plateforme.
std::endl est identique à '\n' de ce point de vue, la différence est qu'il force un flush du buffer. Donc, il ne faut l'utiliser que si ce flush est désiré. Son nom est d'ailleurs à mon avis très mal choisi et amène à des codes inutilement lents quand un codeur pensant bien faire l'utilise pour écrire un fichier de sortie par petits fragments et au final perd tout le bénéfice du buffer de sortie.
Je ne connais pas Go mais à mon avis il y a de fortes chances pour que le '\n' soit correctement géré et donc s'adapte automatiquement à la plateforme.
# gestion des erreurs
Posté par devnewton 🍺 (site web personnel) . Évalué à 2.
Il n'y a pas d'exceptions en go? Ca doit être vite pénible de tester les erreurs à chaque appel…
Le post ci-dessus est une grosse connerie, ne le lisez pas sérieusement.
[^] # Re: gestion des erreurs
Posté par claudex . Évalué à 6.
Il y a un mécanisme différent avec panic et recover http://blog.golang.org/2010/08/defer-panic-and-recover.html mais je ne suis pas sûr d'avoir bien compris les subtilités (peut-être même pas les trucs évidents).
« Rappelez-vous toujours que si la Gestapo avait les moyens de vous faire parler, les politiciens ont, eux, les moyens de vous faire taire. » Coluche
[^] # Re: gestion des erreurs
Posté par devnewton 🍺 (site web personnel) . Évalué à 1.
Ca ne couvre qu'un cas d'usage des exceptions: quand quelque-chose se passe mal de manière exceptionnelle.
Je me demande comment faire en go pour les autres cas:
Le post ci-dessus est une grosse connerie, ne le lisez pas sérieusement.
[^] # Re: gestion des erreurs
Posté par TImaniac (site web personnel) . Évalué à 9.
En même temps, les exceptions ne sont censées être utilisées que pour ça, tous les autres cas d'usage sont des détournements et devraient être évités.
[^] # Re: gestion des erreurs
Posté par Antoine . Évalué à 2.
C'est parole d'évangile ?
[^] # Re: gestion des erreurs
Posté par reno . Évalué à 2.
Bah, vu que souvent le temps de génération&traitements d'une exception est long, coté performance c'est pas terrible de vouloir utiliser les exceptions pour autre chose que pour des cas exceptionnels..
D a une conception intéressante pour les exceptions: en plus du classique try .. catch, on peut définir des blocs (scope) qui sont exécutés en cas d'exception: l'avantage est que ça regroupe les traitements: http://dlang.org/exception-safe.html
Ça rend la programmation avec les exceptions beaucoup plus lisible AMHA, dommage que plus de langage n'offre pas ça..
[^] # Re: gestion des erreurs
Posté par rewind (Mastodon) . Évalué à 4.
ça s'appelle
defer
en Go[^] # Re: gestion des erreurs
Posté par reno . Évalué à 2.
S'il n'y a pas d'exception, je ne vois pas trop comment ça peut être équivalent..
[^] # Re: gestion des erreurs
Posté par devnewton 🍺 (site web personnel) . Évalué à 2.
Ca se juge au cas par cas (cf boost.graph). On ne cherche pas toujours la performance absolue, on préfère souvent la simplicité du code.
Ca existe en C++.
Le post ci-dessus est une grosse connerie, ne le lisez pas sérieusement.
[^] # Re: gestion des erreurs
Posté par reno . Évalué à 2.
Ah bon? Pourtant ça a été introduit dans D suite à une remarque d'un pro en C++ qui disait que ça serait bien a rajouter dans le C++, tu pense à quoi? Ou alors ça a été ajouté récemment?
Parce que si tu pense à RAII, c'est autre chose..
[^] # Re: gestion des erreurs
Posté par Batchyx . Évalué à 6.
[^] # Re: gestion des erreurs
Posté par Antoine . Évalué à 2.
Souvent ? Dans quel langage exactement ? Et pourquoi est-ce que les performances devraient prendre le pas sur le traitement correct des erreurs ?
C'est bien la mentalité du C, ça, et après on gâche une énergie folle à corriger les bugs et les trous de sécu que ça occasionne (mais le programme va plus vite).
[^] # Re: gestion des erreurs
Posté par wismerhill . Évalué à 4.
Il disait justement que ce n'est pas une bonne idée de l'utiliser pour autre chose que le traitement d'erreur.
[^] # Re: gestion des erreurs
Posté par barmic . Évalué à 7.
En C++ par exemple.
Non je pense qu'il a voulu dire que ça doit se limiter à la gestion d'erreur qui elles même devraient être exceptionnelles (sinon il vaut probablement mieux utiliser une approche proactive ce qui permet d'avoir un algo qui n'a pas de saut de n'importe où vers l'un des bloc catch puis de tenter de repartir de cela pour continuer).
Par exemple mkdir -p devrait probablement plutôt faire quelque chose comme :
que :
Tous les contenus que j'écris ici sont sous licence CC0 (j'abandonne autant que possible mes droits d'auteur sur mes écrits)
[^] # Re: gestion des erreurs
Posté par Batchyx . Évalué à 7.
Mauvais exemple: Ce n'est pas parce que exists() te dit que le répertoire n'existe pas que ton mkdir ne va pas échouer avec une erreur du type "le répertoire existe déjà", et inversement.
Au final, ton code deviendrai lourdingue :
Et c'est en supposant que les erreurs renvoyées par exists() aient la même sémantique que les erreurs renvoyées par mkdir. Si ce n'est pas le cas, il faudra les mettre dans des try séparés. Mais du coup, l'intérêt des exceptions de séparer le bon cas du mauvais cas est perdue.
Mais sinon, s'amuser à réimplémenter de façon buggée toutes les vérifications que peut faire le mkdir() actuel dans le code qui l'appelle, c'est pas vraiment une bonne idée, surtout si ton mkdir() change souvent (ici c'est pas le cas, mais avec d'autres fonctions …).
[^] # Re: gestion des erreurs
Posté par barmic . Évalué à 2.
Tu as raison je suis allé un peu vite.
J'ai pas compris ce que tu voulais dire.
Tous les contenus que j'écris ici sont sous licence CC0 (j'abandonne autant que possible mes droits d'auteur sur mes écrits)
[^] # Re: gestion des erreurs
Posté par Batchyx . Évalué à 4.
Ce que je veux dire, c'est que mkdir() va déjà vérifier toutes les préconditions avant de créer le répertoire. S'amuser à les réimplementer dans le code qui l'appelle pour éviter des exceptions, c'est bien joli, sauf que comme toutes les réimplementations, elles seront buggées, et si jamais les préconditions de mkdir() évoluent plus rapidement que ta réimplementation de ces préconditions, ça risque d'être drôle. Au final, ça n'est rien d'autre que la duplication de code. Pour rien.
Et je ne parle pas du cas ou la fonction à appeler n'est pas mkdir(), mais une fonction passée en paramètre.
[^] # Re: gestion des erreurs
Posté par barmic . Évalué à 2.
Tu as tout à fait raison mon exemple était mauvais c'est le premier qui m'est passé par l'esprit, j'essaierais de trouver mieux la prochaine fois.
Tous les contenus que j'écris ici sont sous licence CC0 (j'abandonne autant que possible mes droits d'auteur sur mes écrits)
[^] # Re: gestion des erreurs
Posté par devnewton 🍺 (site web personnel) . Évalué à 3.
Pourquoi?
La première fois que j'ai vu ça, c'est dans la très bonne bibliothèque boost.graph ( http://www.boost.org/doc/libs/1_49_0/libs/graph/doc/faq.html point 1 ) et ça me semble très propre.
Le post ci-dessus est une grosse connerie, ne le lisez pas sérieusement.
[^] # Re: gestion des erreurs
Posté par barmic . Évalué à 6.
C'est fait pour ça les exceptions. Elles drainent beaucoup les performances c'est pour ça qu'il vaut mieux éviter de les utiliser dans d'autres cas.
En principe c'est un cas exceptionnel, non ?
Si c'est pour voir ça (sic) :
Mais pour moi ça reste « quelque-chose se passe mal de manière exceptionnelle ».
Mauvais exemple utiliser les exception pour sortir autrement qu'en erreur c'est une mauvaise idée.
Tous les contenus que j'écris ici sont sous licence CC0 (j'abandonne autant que possible mes droits d'auteur sur mes écrits)
[^] # Re: gestion des erreurs
Posté par devnewton 🍺 (site web personnel) . Évalué à 2.
Justement, si tu vois ça, tu es certain que le développeur a voulu ignorer l'exception, pas qu'il a juste oublié.
Pourquoi?
Le post ci-dessus est une grosse connerie, ne le lisez pas sérieusement.
[^] # Re: gestion des erreurs
Posté par rewind (Mastodon) . Évalué à 2.
La vraie question, c'est : pourquoi a-t-il voulu ignorer une erreur potentielle ? Je vois principalement deux cas :
[^] # Re: gestion des erreurs
Posté par Moonz . Évalué à -3.
Très mal ? Ça segfault un peu plus loin, c’est tout.
Tu as un problème inattendu, le programme crash de manière inattendue. La vache de catastrophe horrible.
Alors je suis d’accord que dans certains cas, l’application ne doit jamais avoir de comportement indéfini, et dans ce cas oui, vérifier les exceptions/codes d’erreur est vital.
Je sais pas vous, mais 90% du code que je fais n’a pas de telles contraintes, et un crash si le disque dur grille et que
fopen
renvoieNULL
, c’est tout à fait acceptable. Par contre, le temps supplémentaire passé à coder les tests de vérification « est-ce quemalloc
a renvoyéNULL
», eux, sont déjà bien moins acceptables (parce que mine de rien, une gestion aussi fine des erreurs, c’est assez chronophage).[^] # Re: gestion des erreurs
Posté par devnewton 🍺 (site web personnel) . Évalué à 1.
Bref tu peux détecter la mauvaise pratique.
Le post ci-dessus est une grosse connerie, ne le lisez pas sérieusement.
[^] # Re: gestion des erreurs
Posté par wismerhill . Évalué à 4.
Ou que son IDE lui a rapporté une erreur de compilation et qu'il a cliqué sans réfléchir sur a solution "entourer l'instruction d'un try{}catch(){}".
[^] # Re: gestion des erreurs
Posté par barmic . Évalué à 2.
Ce n'est pas un usage « normal » des exceptions donc ça a tendance à induire en erreur. Utilisé comme ça c'est chercher à réinventer un goto avec un mécanisme plus sophistiqué et qui n'est pas prévu pour. De ce que j'en sais les exceptions sont assez lourdes que ce soit en C++ ou en Java (bien sûr en relatif), les utiliser comme comportement par défaut n'est donc pas une bonne idée. Tu as aussi de bonne chance de noyer ton traitement « normal » dans tes traitement d'erreur : un catch parmi les autres est le bon. Bien sur je présume que c'est le premier mais ce n'est pas vraiment distinguable. Tu risque fort d'avoir à faire de la gestion d'erreur dans ton catch qui n'est pas une erreur tu es donc repartis pour :
ou alors pour éviter la duplication des traitements d'erreur :
Tous les contenus que j'écris ici sont sous licence CC0 (j'abandonne autant que possible mes droits d'auteur sur mes écrits)
[^] # Re: gestion des erreurs
Posté par devnewton 🍺 (site web personnel) . Évalué à 4.
Ca se discute au cas par cas. Vaut-il mieux faire des centaines tests ou un bloc try catch?
C'est pour ça que les exceptions sont typées…
Plutôt que le cas "machin" imaginaire que tu proposes, regarde ce qui ce fait dans boost.graph.
Le post ci-dessus est une grosse connerie, ne le lisez pas sérieusement.
[^] # Re: gestion des erreurs
Posté par barmic . Évalué à 1.
Encore une fois, c'est bien pour la gestion des erreurs. Pour les cas nominaux c'est contre-intuitif et lourd.
Je n'ai pas dis que c'était impossible, juste que c'est contre-intuitif. Ce n'est pas parce que quelque chose est techniquement possible que c'est bien de le faire.
J'ai pas retrouvé la discussion à ce sujet dans leurs archives : http://lists.boost.org/Archives/boost/ ni d'exemples d'utilisation.
Tous les contenus que j'écris ici sont sous licence CC0 (j'abandonne autant que possible mes droits d'auteur sur mes écrits)
[^] # Re: gestion des erreurs
Posté par devnewton 🍺 (site web personnel) . Évalué à 1.
En gros c'est le pattern visitor pour parcourir un graphe, si tu trouves ce que tu cherchais dans le graphe, tu lèves une exception pour arrêter le parcours. L'avantage, c'est que l'algo de parcours n'a pas besoin de tester en permanence s'il doit s'arrêter ou continuer et les 'callbacks' n'ont pas besoin de retourner une valeur ou d'appeler une méthode pour dire stop.
Le post ci-dessus est une grosse connerie, ne le lisez pas sérieusement.
[^] # Re: gestion des erreurs
Posté par navaati . Évalué à 1.
Sinon t'as tout un tas de méthode bien plus propres comme les continuations (pas sûr que ça soit possible en C++ ceci-dit) ou un simple goto (on a sûrement là une de ses utilisations justifiées)…
[^] # Re: gestion des erreurs
Posté par GuieA_7 (site web personnel) . Évalué à 3.
Pas de continuation nativement en C++, mais c'est sûrement faisable via des hacks bien pointus (ou avec des threads mais pas terrible pour les performances du coup). Et un goto c'est bien pour rester dans la frame d'une fonction, pas pour remonter dans la pile d'appel, si ton algo est récursif par exemple.
Il y a une petite dizaine d'années ma prof de Ocaml nous conseillait d'utiliser une exception pour remonter le résultat d'une recherche récursive de manière efficace. Ceci dit je ne suis pas un pro en Ocaml (je n'en ai pas fait depuis) et c'est peut être un mauvais conseil.
[^] # Re: gestion des erreurs
Posté par barmic . Évalué à 2.
C'est quoi les continuations ? C'est pas simples à googliser comme terme.
Tous les contenus que j'écris ici sont sous licence CC0 (j'abandonne autant que possible mes droits d'auteur sur mes écrits)
[^] # Re: gestion des erreurs
Posté par GuieA_7 (site web personnel) . Évalué à 1.
(Désolé si ma définition n'est pas très rigoureuse). C'est une routine pour laquelle l'environnement va sauvegarder le contexte d’exécution entre chacun de ses appels, ce qui fait que d'un appel à l'autre l’exécution de cette routine se fait de manière continue. Bon je vais éclaircir avec un exemple en Python.
Si j'écris une fonction de ce style :
Tu vois bien que ce code est stupide : il va retourner '1' à chaque appel, et les 2 autres 'return' sont inutiles. En revanche si j'écris :
Grace à 'yield', Python va me générer tout seul une classe d'itérateur qui utilise mon code pour générer les valeurs successives. Je peux alors faire ça :
Ça aurait été faisable à la main évidemment, mais là c'est beaucoup plus court et simple. Ça permet rapidement de faire des choses complexes sans trop de se prendre la tête.
J'ai l'impression que les goroutines de Go justement permettent de faire des continuations (et c'est bien).
[^] # Re: gestion des erreurs
Posté par barmic . Évalué à 1. Dernière modification le 20 mai 2012 à 14:03.
Merci je connaissais mais sous un autre nom (je ne sais plus le quel).
De manière pas aussi simple ça doit pouvoir s'implémenter via un compteur déclaré en static de la finction en C et C++ :
C'est évidement plus lourd à écrire. Par contre il faut que les variables soient déclarée en static pour qu'elles soient sauvegardés et que le code puisse se être séparé (un yield dans un boucle peut devenir comliqué à passer dans cette forme).
Tous les contenus que j'écris ici sont sous licence CC0 (j'abandonne autant que possible mes droits d'auteur sur mes écrits)
[^] # Re: gestion des erreurs
Posté par GuieA_7 (site web personnel) . Évalué à 3.
Le problème du static c'est que le code ne va pas être réentrant. Il vaudrait mieux faire un vrai itérateur. En l’occurrence en Python à la main ça aurait pu donner un truc comme ça (facile à convertir en C++) :
On voit que le code avec les 'yield' est bien plus simple. On n'a pas par exemple à se soucier de mettre dans 'self' les valeurs qu'on veut garder dans le contexte, mais surtout la structure même du code est plus simple.
[^] # Re: gestion des erreurs
Posté par barmic . Évalué à 0.
C'est réentrant si tu utilise un type atomic sur le quel tu peux faire une opération du style inc_and_get(), mais c'est effectivement limité à des cas particuliers.
Tous les contenus que j'écris ici sont sous licence CC0 (j'abandonne autant que possible mes droits d'auteur sur mes écrits)
[^] # Re: gestion des erreurs
Posté par GuieA_7 (site web personnel) . Évalué à 6.
On s'est mal compris : mon itérateur en python ne peut pas non plus en l'état être utilisé par 2 threads qui appelleraient ses méthodes de manière concurrente (il faudrait utiliser un mutex). En revanche chaque thread peut instancier un itérateur et l'utiliser dans son coin pour aller de 1 à 3. Ce n'est pas le cas de ta fonction avec la variable statique.
[^] # Re: gestion des erreurs
Posté par Octabrain . Évalué à 1.
Call-with-current-continuation. En C, c'est setjmp/longjmp. Aussi, intéressant : Continuation-passing style
[^] # Re: gestion des erreurs
Posté par barmic . Évalué à 2.
Au fait en C++ et en Java, il n'est pas obligé de catcher les exceptions. En C++ c'est jamais obligatoire (on est même pas obligé de les déclarer dans le profil de la méthode). En Java les RuntimeExceptions ne sont pas obligé d'être catché (et je crois que c'est la même chose pour les Errors.
Tous les contenus que j'écris ici sont sous licence CC0 (j'abandonne autant que possible mes droits d'auteur sur mes écrits)
[^] # Re: gestion des erreurs
Posté par wismerhill . Évalué à 3.
Oui, les Error aussi car elles peuvent être lancées n'importe quand, typiquement un OutOfMemoryError sera lancée à l'instruction qui aura demandé le bloc mémoire de trop.
[^] # Re: gestion des erreurs
Posté par M . Évalué à 4.
Le defer c'est un destructeur (C++)/goto de cleanup (C) déguisé.
Le panic, recover sont un peu plus intéressant : le panic abort le programme en appelant tout les defer/destructeur de la backtrace. Le recover permet de catcher un panic et de rendre la main a l'appelant.
[^] # Re: gestion des erreurs
Posté par M . Évalué à 6.
Ce système fait affreusement pensé à errno qui est utilisé … en C.
[^] # Re: gestion des erreurs
Posté par Moonz . Évalué à 8.
Non, en C
errno
n’est pas renvoyé, c’est une variable globale.Et ce qu’on essaie de te dire, c’est que :
est plus clair que (version C)
[^] # Re: gestion des erreurs
Posté par Antoine . Évalué à 2.
Certes, mais
outvar = f(input)
serait encore mieux si f() avait la possibilité de lever une exception en cas d'erreur.[^] # Re: gestion des erreurs
Posté par G.bleu (site web personnel) . Évalué à 3.
C'est pas ce que pensent les développeurs de Go…
Erreur simple (cas le plus fréquent) => retour d'une interface Error et test de celle-ci
Erreur monstrueuse => panic/recover avec les defers pour un repli "en bon ordre"
[^] # Re: gestion des erreurs
Posté par lethom . Évalué à 2.
Finalement, je ne trouve pas ça stupide. La plupart du temps, les exceptions rajoutent un point inutile à mon avis. Au final, cette façon de faire me plaît plutôt bien.
Suivre le flux des commentaires
Note : les commentaires appartiennent à celles et ceux qui les ont postés. Nous n’en sommes pas responsables.