Retour d'expérience sur Go

Posté par (page perso) . Édité par baud123, Xavier Claude, Nÿco et B16F4RV4RD1N. Modéré par Xavier Claude. Licence CC by-sa
52
16
mai
2012
Golang

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

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.

  • # quelques corrections de mise en forme :)

    Posté par (page perso) . Évalué à 2. Dernière modification le 16/05/12 à 15:53.

    Je suppose que

    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 gestions des passages d'options étant une fonctionnalité essentiel à 90% des logiciels, le package "flag" s'occupe de cela avec brio !
    
    

    et

    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 :
    
    

    et

    #### Les interfaces
    
    En Go, tout se base sur du Duck typing. De fait afin de pouvoir manger à la fois un plat de seconde zone où de la délicieuse choucroute, on peut déclarer une interface qui contiendra la fonction manger :
    
    

    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 (page perso) . É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 (page perso) . É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 (page perso) . É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 (page perso) . É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 . Évalué à 10.

    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.

    pour ca, il y a le "comma-ok":

    _, ok := my_map[my_key]
    if ok {
      fmt.Printf("err: the key [%v] is already in the map!\n", my_key)
    }
    
    

    ou, plus go-ish:

    if _,ok := my_map[my_key]; ok {
      fmt.Printf("err: the key [%v] is already in the map!\n", my_key)
    }
    
    

    -s

  • # Retour de fonction

    Posté par (page perso) . É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 ;)

    Toute fonction peut renvoyer plusieurs variables au lieu d'une seule dans la plupart des langages.

    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 (page perso) . É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 . Évalué à 3.

      Oui, désolé, j'ai craqué sur les coquilles car ça pique les yeux. Je promets de faire un commentaire plus constructif.

      • Le parser/lexer : Réaliser avec yacc pour le premier.

      réalisé

      do projet

      du projet

      ainsi qu'un type "*Error" pouvant être soit "nil" (c'est à dire pointer sur rien) soit initialiser

      initialisé

      étant une fonctionnalité essentiel à 90%

      essentielle

      on met donc une "*" pour les déférencer)

      déréférencer

      Un type four tout

      fourre-tout

      en une suit d'élément

      suite d'élément

      sur laquelle on travail actuellement

      travaille

  • # Debugger?

    Posté par (page perso) . Évalué à 1.

    Y'a-t-il un debugger pour Go? à quoi ressemble-t-il ?

  • # Voila des commentaires plus interessants

    Posté par . É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 (page perso) . Évalué à 9.

    Le CPU est designé sous Xilinx
    De fait, une fois le CPU designé

    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 . Évalué à -2.

      lire dizaïné et non dézinié ?

      • [^] # Re: Design = conception

        Posté par . É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).

  • # Retours à la ligne

    Posté par (page perso) . Évalué à 6.

    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.

    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 (page perso) . Évalué à 2.

    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

    Il n'y a pas d'exceptions en go? Ca doit être vite pénible de tester les erreurs à chaque appel…

    http://devnewton.bci.im

    • [^] # Re: gestion des erreurs

      Posté par (page perso) . É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 (page perso) . É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:

        • on ne veut pas sans arrêt vérifier les erreurs.
        • on veut forcer l'appelant à gérer une erreur.
        • on veut sortir d'un traitement complexe (par exemple un parcours dans un graphe).

        http://devnewton.bci.im

        • [^] # Re: gestion des erreurs

          Posté par (page perso) . Évalué à 9.

          Ca ne couvre qu'un cas d'usage des exceptions: quand quelque-chose se passe mal de manière exceptionnelle.

          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 . Évalué à 2.

            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.

            C'est parole d'évangile ?

            • [^] # Re: gestion des erreurs

              Posté par . Évalué à 2.

              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.

              C'est parole d'évangile ?

              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 . Évalué à 4.

                ça s'appelle defer en Go

                • [^] # Re: gestion des erreurs

                  Posté par . É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 (page perso) . É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..

                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.

                dommage que plus de langage n'offre pas ça

                Ca existe en C++.

                http://devnewton.bci.im

                • [^] # Re: gestion des erreurs

                  Posté par . Évalué à 2.

                  Ca existe en C++.

                  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 . Évalué à 6.

                    int bad = open("blah", O_RDONLY);
                    BOOST_SCOPE_EXIT ( (&bad) ) {
                        if (bad != -1)
                            close(bad);
                    } BOOST_SCOPE_EXIT_END
                    do_something(bad);
                    
                    
              • [^] # Re: gestion des erreurs

                Posté par . É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

                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 . Évalué à 4.

                  Et pourquoi est-ce que les performances devraient prendre le pas sur le traitement correct des erreurs ?

                  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 . Évalué à 7.

                  Dans quel langage exactement ?

                  En C++ par exemple.

                  Et pourquoi est-ce que les performances devraient prendre le pas sur le traitement correct des erreurs ?

                  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 :

                  try {
                      if(!exist(dir)) {
                          mkdir(dir);
                      }
                  catch(...) {}
                  
                  

                  que :

                  try {
                      mkdir(dir);
                  }
                  catch (IOException e) {
                      if (/* exception dut à préexistence de dossier */) { /* OSEF */ }
                      else { /* Erreur */ }
                  }
                  
                  

                  Les logiciels sous licence GPL forcent leurs utilisateurs à respecter la GPL (et oui, l'eau, ça mouille).

                  • [^] # Re: gestion des erreurs

                    Posté par . É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 :

                    try {
                        if(!exist(dir)) {
                            mkdir(dir);
                        }
                    }
                    catch (IOException e) {
                        if (/* exception dut à préexistence de dossier */) { /* OSEF */ }
                        else { /* Erreur */ }
                    }
                    
                    

                    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 . Évalué à 2.

                      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.

                      Tu as raison je suis allé un peu vite.

                      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 …).

                      J'ai pas compris ce que tu voulais dire.

                      Les logiciels sous licence GPL forcent leurs utilisateurs à respecter la GPL (et oui, l'eau, ça mouille).

                      • [^] # Re: gestion des erreurs

                        Posté par . É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 . É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.

                          Les logiciels sous licence GPL forcent leurs utilisateurs à respecter la GPL (et oui, l'eau, ça mouille).

          • [^] # Re: gestion des erreurs

            Posté par (page perso) . É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.

            http://devnewton.bci.im

        • [^] # Re: gestion des erreurs

          Posté par . Évalué à 6.

          Ca ne couvre qu'un cas d'usage des exceptions: quand quelque-chose se passe mal de manière exceptionnelle.

          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.

          on ne veut pas sans arrêt vérifier les erreurs.

          En principe c'est un cas exceptionnel, non ?

          on veut forcer l'appelant à gérer une erreur.

          Si c'est pour voir ça (sic) :

          try {
              appel();
          }
          catch(Throwable th) { /* OSEF */ }
          
          

          Mais pour moi ça reste « quelque-chose se passe mal de manière exceptionnelle ».

          on veut sortir d'un traitement complexe (par exemple un parcours dans un graphe).

          Mauvais exemple utiliser les exception pour sortir autrement qu'en erreur c'est une mauvaise idée.

          Les logiciels sous licence GPL forcent leurs utilisateurs à respecter la GPL (et oui, l'eau, ça mouille).

          • [^] # Re: gestion des erreurs

            Posté par (page perso) . Évalué à 2.

            Si c'est pour voir ça (sic)

            Justement, si tu vois ça, tu es certain que le développeur a voulu ignorer l'exception, pas qu'il a juste oublié.

            Mauvais exemple utiliser les exception pour sortir autrement qu'en erreur c'est une mauvaise idée.

            Pourquoi?

            http://devnewton.bci.im

            • [^] # Re: gestion des erreurs

              Posté par . É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é.

              La vraie question, c'est : pourquoi a-t-il voulu ignorer une erreur potentielle ? Je vois principalement deux cas :

              • Parce que c'est une feignasse : dans ces cas là, il vaut mieux avoir quelqu'un qui n'ignore pas les problèmes potentiels et qui fait ce qu'il faut. C'est comme ceux qui ne regardent jamais les codes de retour des appels systèmes en C. Parce que le jour où ça passe par là, ça fait généralement très mal.
              • Parce que cette erreur n'est pas importante : et alors, si on peut la récupérer, pourquoi envoyer une exception ? Là, c'est le programmeur de l'appel qui est fautif, mais comme bien souvent c'est le même, il est doublement fautif : il envoie une exception inutile et il ne la traite même pas.
              • [^] # Re: gestion des erreurs

                Posté par . Évalué à -3.

                Parce que le jour où ça passe par là, ça fait généralement très mal.

                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 renvoie NULL, c’est tout à fait acceptable. Par contre, le temps supplémentaire passé à coder les tests de vérification « est-ce que malloc 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 (page perso) . Évalué à 1.

                La vraie question, c'est : pourquoi a-t-il voulu ignorer une erreur potentielle ?

                Bref tu peux détecter la mauvaise pratique.

                http://devnewton.bci.im

            • [^] # Re: gestion des erreurs

              Posté par . Évalué à 4.

              Justement, si tu vois ça, tu es certain que le développeur a voulu ignorer l'exception, pas qu'il a juste oublié.

              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 . Évalué à 2.

              Mauvais exemple utiliser les exception pour sortir autrement qu'en erreur c'est une mauvaise idée.

              Pourquoi?

              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 :

              try {
                  machin;
              } catch (Resultat &r) {
                  try {
                      /* tout se passe bien */
                  } catch (...) {
                      /* Erreur */
                  }
              } catch (...) {
                  /* Erreur */
              }
              
              

              ou alors pour éviter la duplication des traitements d'erreur :

              try {
                  try {
                      machin;
                  } catch (Resultat &r) {
                      /* tout se passe bien */
                  }
              } catch (...) {
                  /* Erreur */
              }
              
              

              Les logiciels sous licence GPL forcent leurs utilisateurs à respecter la GPL (et oui, l'eau, ça mouille).

              • [^] # Re: gestion des erreurs

                Posté par (page perso) . Évalué à 4.

                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.

                Ca se discute au cas par cas. Vaut-il mieux faire des centaines tests ou un bloc try catch?

                Tu as aussi de bonne chance de noyer ton traitement « normal » dans tes traitement d'erreur : un catch parmi les autres est le bon.

                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.

                http://devnewton.bci.im

                • [^] # Re: gestion des erreurs

                  Posté par . Évalué à 1.

                  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.

                  Ca se discute au cas par cas. Vaut-il mieux faire des centaines tests ou un bloc try catch ?

                  Encore une fois, c'est bien pour la gestion des erreurs. Pour les cas nominaux c'est contre-intuitif et lourd.

                  Tu as aussi de bonne chance de noyer ton traitement « normal » dans tes traitement d'erreur : un catch parmi les autres est le bon.

                  C'est pour ça que les exceptions sont typées…

                  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.

                  Plutôt que le cas "machin" imaginaire que tu proposes, regarde ce qui ce fait dans boost.graph.

                  J'ai pas retrouvé la discussion à ce sujet dans leurs archives : http://lists.boost.org/Archives/boost/ ni d'exemples d'utilisation.

                  Les logiciels sous licence GPL forcent leurs utilisateurs à respecter la GPL (et oui, l'eau, ça mouille).

                  • [^] # Re: gestion des erreurs

                    Posté par (page perso) . Évalué à 1.

                    J'ai pas retrouvé la discussion à ce sujet dans leurs archives : http://lists.boost.org/Archives/boost/ ni d'exemples d'utilisation.

                    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.

                    http://devnewton.bci.im

                    • [^] # Re: gestion des erreurs

                      Posté par . É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 (page perso) . É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 . Évalué à 2.

                        C'est quoi les continuations ? C'est pas simples à googliser comme terme.

                        Les logiciels sous licence GPL forcent leurs utilisateurs à respecter la GPL (et oui, l'eau, ça mouille).

                        • [^] # Re: gestion des erreurs

                          Posté par (page perso) . É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 :

                          def foo():
                              return 1
                              return 2
                              return 3
                          
                          

                          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 :

                          def foo():
                              yield 1
                              yield 2
                              yield 3
                          
                          

                          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 :

                          it = foo()
                          print it.next() # '1'
                          print it.next() # '2'
                          
                          

                          Ç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 . Évalué à 1. Dernière modification le 20/05/12 à 14:03.

                            Ç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.

                            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++ :

                            int foo () {
                                static int count = 0;
                                switch(count++) {
                                  case 0:
                                    return 1;
                                  case 1:
                                    return 2;
                                  default:
                                    return 3;
                                }
                            }
                            
                            

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

                            Les logiciels sous licence GPL forcent leurs utilisateurs à respecter la GPL (et oui, l'eau, ça mouille).

                            • [^] # Re: gestion des erreurs

                              Posté par (page perso) . É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++) :

                              class Foo:
                                  def __init__(self):
                                      self._count = 1
                              
                                  def __iter__(self): #pour pouvoir faire "for i in Foo():"
                                      return self
                              
                                  def next(self):
                                      count = self._count
                              
                                      if count > 3:
                                          raise StopIteration
                              
                                      self._count += 1
                              
                                      return count
                              
                              

                              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 . É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.

                                Les logiciels sous licence GPL forcent leurs utilisateurs à respecter la GPL (et oui, l'eau, ça mouille).

                                • [^] # Re: gestion des erreurs

                                  Posté par (page perso) . É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 . Évalué à 1.

                          Call-with-current-continuation. En C, c'est setjmp/longjmp. Aussi, intéressant : Continuation-passing style

            • [^] # Re: gestion des erreurs

              Posté par . Évalué à 2.

              Si c'est pour voir ça (sic)

              Justement, si tu vois ça, tu es certain que le développeur a voulu ignorer l'exception, pas qu'il a juste oublié.

              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.

              Les logiciels sous licence GPL forcent leurs utilisateurs à respecter la GPL (et oui, l'eau, ça mouille).

              • [^] # Re: gestion des erreurs

                Posté par . Évalué à 3.

                En Java les RuntimeExceptions ne sont pas obligé d'être catché (et je crois que c'est la même chose pour les Errors.

                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 . Évalué à 4.

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

        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 . Évalué à 6.

      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.

      Ce système fait affreusement pensé à errno qui est utilisé … en C.

      • [^] # Re: gestion des erreurs

        Posté par . É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 :

        outvar1, outvar2 = f(input)
        
        

        est plus clair que (version C)

        outvar1 = f(input, &outvar2)
        
        
        • [^] # Re: gestion des erreurs

          Posté par . É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 (page perso) . É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 (page perso) . É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 à ceux qui les ont postés. Nous n'en sommes pas responsables.