IdentifiantMot de passe
Loading...
Mot de passe oublié ?Je m'inscris ! (gratuit)
Navigation

Inscrivez-vous gratuitement
pour pouvoir participer, suivre les réponses en temps réel, voter pour les messages, poser vos propres questions et recevoir la newsletter

C Discussion :

Goto, une instruction pas comme les autres


Sujet :

C

  1. #41
    Inactif  

    Homme Profil pro
    Développeur informatique
    Inscrit en
    Décembre 2012
    Messages
    63
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Cher (Centre)

    Informations professionnelles :
    Activité : Développeur informatique
    Secteur : Arts - Culture

    Informations forums :
    Inscription : Décembre 2012
    Messages : 63
    Points : 95
    Points
    95
    Billets dans le blog
    1
    Par défaut Pour mémoire vous devriez citer l'exemple du concepteur du langage pascal...
    Pour mémoire vous devriez citer l'exemple du concepteur du langage pascal Niklaus Wirth, qui avait prédit que l'on puisse se passer de la fonction GoTo a travers des structures élaborées de programmation.

    D'autre part, les programmeurs devraient aussi connaître le langage de l'assembleur et de constater la présence des instructions Jump et ses déclinaisons.

  2. #42
    Expert éminent
    Avatar de Matthieu Vergne
    Homme Profil pro
    Consultant IT, chercheur IA indépendant
    Inscrit en
    Novembre 2011
    Messages
    2 264
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France

    Informations professionnelles :
    Activité : Consultant IT, chercheur IA indépendant
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : Novembre 2011
    Messages : 2 264
    Points : 7 760
    Points
    7 760
    Billets dans le blog
    3
    Par défaut
    Pour ma part, un langage de programmation est, au même titre qu'un langage naturel, voué à communiquer une idée en vue de faire exécuter une action par un interpréteur. Dans une simple conversation, ce serait tout simplement de faire comprendre à l'autre ce à quoi on pense, dans le cas d'un programme, c'est d'exécuter une fonctionnalité particulière. À ce titre, l'utilisation du goto est à bannir pour une raison purement linguistique : c'est un opérateur ayant une sémantique trop large, et donc trop peu informative sur les réelles intentions. C'est comme si on utilisait le terme "chose" ou "truc" pour parler. Avec un ou deux dans la phrase, si on suit bien on peut arriver à recoller les morceaux et savoir de quoi on parle. Avec ce genre de termes utilisés à tout bout de champs, on ne comprends plus rien et on n'a plus qu'à prier pour que ce qu'on dise soit bien compris comme on le souhaite par l'interpréteur. Dit autrement, dès lors où on a des structures sémantiquement plus précises qui correspondent à nos besoins, choisir d'utiliser néanmoins un goto est équivalent à choisir de manquer de clarté, même avec parcimonie. C'est de la mauvais fainéantise.

    Dans l'exemple d'Obsidian :
    Citation Envoyé par Obsidian Voir le message
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    x=1; goto debut;
    while (x<=5)
    {
        putchar ('-');
        debut:
        printf ("%d",x);
        x++;
    }
    Plusieurs l'ont fait remarquer : ton goto peut être remplacé par un code tout aussi complexe où tu fait ton printf avant la boucle, sur le modèle d'une initialisation suivie d'une répétition.
    Citation Envoyé par Obsidian Voir le message
    Dans cet exemple, tu saisis deux fois « printf() » mais tu n'établis aucune relation logique entre les deux[...]C'est également plus pénible pour le développeur : même s'il reconnaît ce que tu es en train de faire, il ne peut pas savoir s'il y a bien une liaison implicite entre tes deux printf. Ça devient critique lorsque tu fais la maintenance d'un grand programme : lorsque tu en vient à modifier ce printf pour le mettre à jour ou le remplacer par autre chose, tu introduis automatiquement un bug si tu ne penses pas à traiter l'autre.
    La maintenance te gène du fait de la duplication de code ? Met ton printf dans une fonction et appelle cette fonction, ce qui a du sens car on veut bel et bien faire exactement la même chose avant et dans la boucle (clin d'oeil à gl : "Commençons par réfléchir aux responsabilités, aux rôles des différentes fonctions, à l'objectif à atteindre plutôt que de se focaliser sur telle ou telle règle arbitraire."). Tu n'aimes pas le fait d'avoir un appel de fonction pour quelque chose d'aussi simple ? C'est une optimisation basique qu'un compilateur est tout à fait à même de faire : cette fonction n'est utilisée qu'ici, elle est courte (1 ligne contre 1 ligne) et donc il est tout à fait pertinent de remplacer l'appel de fonction par la ligne correspondante à la compilation. En revanche, avec ton goto, tu dis au compilateur que tu veux commencer au milieu de la boucle sans dire pourquoi, empêchant le compilateur de faire son boulot d'optimisation. Et du point de vue du développeur, quand il voit que tu appelles une fonction, il se fiche pas mal du contenu de cette fonction : elle est là pour faire un boulot particulier. Tant qu'elle le fait correctement, tout va bien. Décider de remplacer l'appel d'une fonction par son code juste pour économiser des lignes, c'est faire une optimisation syntaxique qui n'a pas lieu d'être car n'apporte rien au dév si ce n'est de satisfaire son égo en pensant à tord qu'il a aidé le compilo : pourquoi un dév serait meilleur, pour faire des optimisations aussi simples, qu'un compilo fait par toute une communauté ? Mais je reviens sur ce point plus loin.

    Et encore, ici on pinaille sur le bien fondé de faire une fonction pour un printf, mais cette logique est généralisable : quand tu as plusieurs lignes qui se suivent au sein de la boucle que tu veux répéter lors d'une initialisation, c'est que cet ensemble de lignes à un rôle précis, qui peut être avantageusement traduit en une fonction bien nommée (sinon c'est que tu ne maitrise pas ton propre code, donc revoit ta conception) qui est appelée avant et dans la boucle. Au même titre que refuser d'utiliser le goto par principe est critiquable, refuser d'avoir deux fois la même ligne de code par principe est tout aussi critiquable : si la sémantique de ton programme est de répéter une même opération, tu la répète. Si tu veux la répéter de manière homogène, tu utilises des structures qui te le permette (for, while, ...), si tu veux la répéter de manière hétérogène alors tu conçoit chaque répétition différente, et si tu a des répétitions différentes au sein desquelles tu as des répétitions homogènes, alors tu combine. Je pense ce coup-ci à cela :
    Citation Envoyé par Obsidian Voir le message
    Ensuite, dans le cas présent, il n'y a qu'une seule instruction. Que se passerait-il si tu devais sauter les cinquante premières instructions d'une boucle qui en compte cent ? Pour adopter le modèle que tu nous présentes, tu serais obligé de déclarer une fonction pour les appeler facilement avant puis dans la boucle. Et si tu retrouves le même cas de figure trente fois dans ton programme, tu dois déclarer trente fonctions locales. C'est idiot.
    Oui c'est idiot : quand tu en viens à devoir répéter, tu factorise : si ça devient le genre d'opération que tu utilises à tout bout de champs, alors crée ta propre fonction avec callback/classe qui te permet d'exécuter une initialisation et une boucle qui répète. Si c'est une structure centrale, pourquoi ne pas y mettre un peu d'effort pour avoir quelque chose de solide qui te simplifie la tâche partout plutôt que de faire des copier-coller de codes, de surcroit avec des goto qui auront tôt fait de te faire aller sur le label d'une autre boucle sans faire sourciller le compilateur, parce que tu auras oublié de renommer correctement tes labels ?

    Si tu persistes à vouloir garder tes lignes exclusivement dans la boucle en utilisant un goto, c'est que tu continues à croire que deux codes sémantiquement différents (afficher un début de séquence VS afficher une suite de séquence) mais au code similaire (printf VS "-" + printf) sont sémantiquement identiques. Ce qui est sémantiquement identique, c'est la notion d'affichage d'un élément d'une séquence (le printf) qui a donc ses raisons de se retrouver dans une fonction qui lui est dédiée. Ta solution avec goto c'est typiquement le genre de code sémantiquement sale qui est considéré comme propre par un abus d'une autre règle : ne pas répéter le code. Ce n'est pas parce que j'utilise exactement le même code que ça correspond exactement à la même sémantique : si le contexte est suffisamment différent pour justifier que changer le code dans un cas (bug fixing, changement du cahier des charges ou que sais-je) n'implique pas de changer le code dans l'autre, alors on n'a aucune raison de factoriser le code, aussi similaires soient-ils syntaxiquement. Si on le fait, on prend le risque qu'un changement normal dans un cas implique un bug dans l'autre, parce qu'on aura mis en commun ce qui n'avait pas de raison de l'être (c'est pareil par coïncidence, et non par besoin). L'optimisation de la syntaxe, c'est le boulot du compilo, le boulot du programmeur c'est l'optimisation de la sémantique, que le compilo n'est pas capable de faire à notre place. Et c'est ça qui permet d'avoir un code lisible pour un autre dév sans avoir besoin de foutre des commentaires au sein du code (je parle pas d'entêtes, mais bien de commentaires qui redisent en français ce que le code dit en C/Java/...).

    Je note aussi une erreur rapide, probablement d'inattention :
    Citation Envoyé par Obsidian Voir le message
    le compilateur n'a aucun moyen de savoir que ton premier printf() est en fait censé faire partie de la même boucle, ce qui casse complètement le principe-même de la programmation structurée.
    Qu'il est censé appliquer la même fonction de la même manière, OK, mais qu'il est censé faire partie de la boucle, pas du tout ! C'est ta propre conception qui te dit que "c'est dans la boucle", mais ce n'est pas la seule valable. C'est qu'une unique phrase, donc je passe vite, mais ça me semble représentatif de pourquoi tu as du mal à concevoir que ton goto n'a rien de nécessaire : tu fais passer ta conception devant celle des autres en abusant d'un principe différent (réduire la répétition de code).

    Dans le cas de l'AFD :
    Citation Envoyé par Obsidian Voir le message
    2. Les automates finis à états. Le goto est par nature l'âme d'un AFD, puisqu'il s'agit de sauter d'un état prédéterminé à un autre. Si le cheminement du traitement est déterminé à l'avance, il est tout-à-fait possible d'écrire :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    etat1:
        traitement1();
        if (condition) goto etat2;
        if (condition) goto etat3;
        if (condition) goto etat5;
        goto etat6;
     
    etat2:
        traitement2();
        if (condition) goto etat6;
        if (condition) goto etat4;
        if (condition) goto etat1;
        goto etat2;
     
    …
    Là ça semble faire sens d'utiliser un goto, parce qu'on se fiche complètement du contexte autre que l'état courant et la condition : pour tel état+condition, tu vas là, point final. Mais ce n'est vrai que si dans ton programme, tout ce que tu as c'est cet automate et rien d'autre. Si ton programme vise à utiliser cet automate pour faire autre chose (il est rare qu'on lance un automate juste pour dire de le faire tourner) alors tes gotos te font prendre le risque de voyager hors du scope de ton automate. Pourquoi prendre ce risque ? Pourquoi ne pas prendre/faire une bibliothèque qui gère exclusivement les automates (e.g. en objet : des classes Etat, Transition, Automate et ce qu'il faut de fonctions haut niveau -et goto-free- pour les utiliser simplement) pour ensuite les utiliser dans ton programme en affichant clairement la sémantique (que sont tes états, tes transitions, etc.) plutôt que de les cacher dans des suites de if-goto ou même if-then ou switch à décrypter ? Tu y réponds presque :
    Citation Envoyé par Obsidian Voir le message
    … dans de telles conditions, on va avoir tendance à écrire un switch() et à stocker l'état en cours dans une variable, ce qui transpose inutilement au runtime ce qui est statique à la base.
    Alors là soit. C'est statique, donc on le code en dur : on utilise un langage tout con fait pour encoder des AFD, inutile d'utiliser un langage aussi large que C ou Java pour quelque chose d'aussi spécifique. Si tu mélanges le code en dur de ton automate au code du reste de ton programme, alors tu ne poses aucune différence sémantique entre ton programme et ton AFD, et donc n'importe qui cherchant à déboguer ton code aura donc toutes les raisons du monde de transformer ton automate en autre chose qu'un automate, parce que de son point de vue il aura besoin d'introduire par exemple la notion de mémoire et donc, plutôt que d'utiliser un autre automate pour encoder cette mémoire, il préfèrera sauvegarder les X états précédemment visités ou intégrer des variables globales. Alors dis-moi, qu'est ce qui est important ? D'avoir un AFD ou d'avoir une simple structure de conditions que tu peux remanipuler (et donc représentable en AFD ou non selon les choix de conception) ? Dans le premier cas, ton code en dur est à jeter car non maintenable (c'est un bloc monolithique qui devrait être factorisé). Dans le second on se fiche pas mal d'avoir un AFD, et tous tes états/transitions peuvent être représentés différemment selon qu'on n'ait qu'une seule condition (attente jusqu'à condition satisfaite), plusieurs indépendantes (suite de if-elsif), plusieurs avec points communs (imbrications de if), etc. Bref, les cas d'écoles hyper particuliers c'est joli, mais ça fait pas des arguments.

    Citation Envoyé par Obsidian Voir le message
    goto, c'est comme l'utilisateur root sous Unix : c'est l'instruction omni-potente. Il faut juste veiller à ne pas l'utiliser pour se sortir d'une impasse (et c'est surtout ça qu'il faut surveiller à mon avis) mais ça permet d'implémenter facilement ce qui n'a pas été prévu au départ.
    Je dirais plutôt le contraire : à n'utiliser que pour se sortir d'une impasse, dans le sens où tant qu'on peut résoudre un problème sans sortir l'outil dangereux, il faut se prémunir de le sortir. Mais j'imagine que c'était l'idée de base, et dans ce sens je suis à moitié d'accord. L'analogie est pertinente, mais le contexte a une différence majeure : quand je fais la maintenance moi-même et quand je fais un script censé s'en charger. Quand je le fais à la main, une fois, je peux me permettre d'utiliser un outil dangereux, en en étant conscient et donc en apportant une attention particulière au moment où je l'utilise. Si je le fait plusieurs fois, je vais finir par augmenter le risque de faire des bêtises, et donc, sachant que je serai surement amener à recommencer, je vais chercher/faire un outil adapté à ce besoin ayant moins de risque. Quand je fais un programme, l'idée est la même, sauf qu'on saute directement à l'étape répétition. Se permettre d'utiliser des outils dangereux à ce niveau est donc se tirer une balle dans le pied dès le départ, car on s'ouvre toutes les portes pour qu'un problème surgisse un jour ou un autre, si une situation qu'on n'a pas prévu arrive (ce qui est toujours plausible, nous ne sommes que des humains après tout).

    Tout au mieux, ton outil dangereux mais bien pratique fournit une rustine efficace, mais pas une solution. Si tu ne reviens pas dessus pour faire une correction en bonne et due forme, tu ne fais que remplacer ton problème par un autre problème potentiel qui, si ça se trouve, sera encore plus dur à résoudre quand tu auras oublié d'où ça vient.

    Les break et continue, c'est sémantiquement plus précis, mais tout aussi dangereux. Un break est avantageusement remplacé par un return, clair et sans ambiguité : la boucle a un but précis, donc on la factorise dans une fonction au nom représentatif, et plutôt que d'avoir un break qui te dit que tu vas sauter certaines lignes et reprendre quelque part plus loin, tu as un return qui te dit que ta fonction est terminée, point barre. Tu ne commences pas à chercher midi à 14h, tu sors de la fonction. Et du point de vue de l'appelant, pas d'ambiguité non plus : tu appelles une fonction et quand elle a fini tu as ton résultat, peu importe comment elle le génère (c'est son boulot, pas celui de l'appelant), et tu poursuis à la ligne qui suit, pas quelque part plus loin. Et avec un peu de chance, ça te donnera une fonction prête à l'emploi si tu veux recommencer ailleurs, mais ça c'est l'argument marketing. Là je pense surtout à ce genre de fonctionnalité, avantageusement remplacée par un appel de fonction et le break par un return :
    Citation Envoyé par valkirys Voir le message
    tester le contenue d'une "matrice" à N dimension implique N boucles avec un break si on a trouvé ce que l'on cherche, rien de compliqué a priori :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    search: {
        for (Type type : types) {
            for (Type t : types2) {
                if (some condition) {
                    // Do something and break...
                    break search;
                }
            }
        }
    }
    Pour le principe :
    Citation Envoyé par Obsidian Voir le message
    Et c'est bien le problème : l'objectif des codeurs n'est plus d'écrire le code le propre possible en fonction du contexte mais bien d'éviter les goto par principe.
    Il y a aussi ce qu'on appelle le "sens commun", la "tradition", ou que sais-je : quand on apprend que quelque chose est plus risqué qu'avantageux, quelqu'un de bien attentionné s'arrangera pour que ça rentre dans le crane des générations suivantes. C'est de là qu'on pose des principes, méthodes, chartes, etc. Libre à chacun de les remettre en cause, mais s'ils sont là c'est qu'ils ont des raisons d'être. On peut reprocher à quelqu'un qu'il critique sur la seule base d'un principe plutôt que d'un argumentaire, mais pas qu'il choisit de s'appuyer sur un principe sans savoir ce qu'il y a derrière, surtout si celui-ci a fait ses preuves. Sinon l'éducation n'aurait aucun sens, on devrait toujours tout remettre en cause et on n'avancerai pas.

    Citation Envoyé par Obsidian Voir le message
    Il est intéressant, lorsque l'on développe du logiciel, d'essayer de transposer cela au reste de l'industrie et, en particulier, de penser à la manière dont on réaliserait la fonction de façon mécanique plutôt que logicielle : dans le cas présent, si je veux imprimer sur une feuille une suite de motifs séparés par des tirets, il me suffit de construire un simple rouleau d'imprimerie et de le « déphaser » de manière à ce qu'il commence par le motif plutôt que par le tiret. La solution que tu nous proposes consisterait, elle, à mettre en place une machine spéciale dédiée pour imprimer le premier motif uniquement avant de faire passer normalement la feuille dans le rouleau.
    Il faut comparer ce qui est comparable. Là, tu parles d'une chaine en dur (rouleau d'imprimerie), qui n'aurait donc pas besoin d'un déphasage, juste d'avoir directement le bon contenu. Dans nos deux cas, on gère une variable (x) associée à une chaine en dur ("-"). Cela implique d'avoir 2 parties (mécaniques), la partie constante ("-") et la partie variable (x) qui seront chacune imprimée à la suite de l'autre. Dans le cas du goto, c'est équivalent à mettre une feuille de papier buvard lors de l'impression du premier tiret, de façon à ce que le premier élément vraiment imprimé soit x. Dans le cas de l'initialisation, ça revient à forcer l'impression du x d'abord, puis à lancer la machine. Perso, je préfère garder le contrôle plutôt que de devoir calculer précisément quand je dois mettre et retirer le buvard. Mes doigts s'en porteront mieux.

    Citation Envoyé par Obsidian Voir le message
    C'est intéressant parce qu'en général, on en arrive à dire « de toutes façons le compilateur va optimiser tout cela ». Personne ne nous le garantit, d'une part, et cela revient à dire que le vrai travail est en fait mené par les personnes qui ont conçu le compilateur. Et même alors, le compilateur sera à même de faire cette optimisation que s'il est capable de reconnaître le modèle. Il faut donc que celui-ci soit défini au départ et fasse partie des motifs qui lui ont été enseignés. En toute rigueur, c'est surtout ce travail que le programmeur devrait faire, et utiliser les goto si le langage ne propose pas de lui-même ce modèle.
    Ce que connait le compilo (i.e. ceux qui le font) c'est le modèle du langage. C'est à dire toute la partie syntaxique. Et eux ont vocation à la maitriser. Faire par soi-même de l'optimisation syntaxique revient à faire passer son orgueil devant la compétence de ceux qui ont fait le compilo. Là où le programmeur a tout le mérite, c'est quand il s'assure que ce qu'il écrit représente la sémantique qu'il a en tête : ici je suis censé faire la même chose que là, donc je vais utiliser la même fonction plutôt que de copier coller le code. Ici je veux afficher, tout comme là, mais bien que je peux faire exactement la même chose rien ne me dit que c'est censé être tout à fait pareil, donc je copie colle le code plutôt que de factoriser en une fonction commune. Comme ça si quelqu'un décide de changer l'un, on évite d'avoir un changement non voulu sur l'autre.
    Site perso
    Recommandations pour débattre sainement

    Références récurrentes :
    The Cambridge Handbook of Expertise and Expert Performance
    L’Art d’avoir toujours raison (ou ce qu'il faut éviter pour pas que je vous saute à la gorge {^_^})

  3. #43
    Membre à l'essai
    Homme Profil pro
    Développeur informatique
    Inscrit en
    Mai 2008
    Messages
    4
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 35
    Localisation : France, Val de Marne (Île de France)

    Informations professionnelles :
    Activité : Développeur informatique
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : Mai 2008
    Messages : 4
    Points : 13
    Points
    13
    Par défaut
    C'est toujours très agréable de lire quelqu'un qui à eu le courage de décrire précisément son opinion juste avant d'avoir à le faire soi-même.

  4. #44
    Invité
    Invité(e)
    Par défaut
    Citation Envoyé par Matthieu Vergne Voir le message
    Les break et continue, c'est sémantiquement plus précis, mais tout aussi dangereux. Un break est avantageusement remplacé par un return, clair et sans ambiguité : la boucle a un but précis, donc on la factorise dans une fonction au nom représentatif, et plutôt que d'avoir un break qui te dit que tu vas sauter certaines lignes et reprendre quelque part plus loin, tu as un return qui te dit que ta fonction est terminée, point barre. Tu ne commences pas à chercher midi à 14h, tu sors de la fonction. Et du point de vue de l'appelant, pas d'ambiguité non plus : tu appelles une fonction et quand elle a fini tu as ton résultat, peu importe comment elle le génère (c'est son boulot, pas celui de l'appelant), et tu poursuis à la ligne qui suit, pas quelque part plus loin. Et avec un peu de chance, ça te donnera une fonction prête à l'emploi si tu veux recommencer ailleurs, mais ça c'est l'argument marketing. Là je pense surtout à ce genre de fonctionnalité, avantageusement remplacée par un appel de fonction et le break par un return.
    Pour le break c'est vrai mais le continue ne peut pas vraiment être remplacé par une fonction.

    Citation Envoyé par pvincent Voir le message
    L'exemple donné par Obsdian http://www.developpez.net/forums/d14...tion-autres/#4
    ne me semble pas très heureux: j'écrirai plutôt ceci:
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    for (x = 1; x < 5; x++) {
        printf ("%d-",x);
     }
    printf ("%d" ,x++);
    mea culpa j'ai écrit une horreur un peu plus haut

  5. #45
    Expert éminent
    Avatar de Matthieu Vergne
    Homme Profil pro
    Consultant IT, chercheur IA indépendant
    Inscrit en
    Novembre 2011
    Messages
    2 264
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France

    Informations professionnelles :
    Activité : Consultant IT, chercheur IA indépendant
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : Novembre 2011
    Messages : 2 264
    Points : 7 760
    Points
    7 760
    Billets dans le blog
    3
    Par défaut
    Pour le continue, je pense que c'est plus une question d'organisation du code et d'abus de la règle "ne pas répéter le code". Je vais encore créer des fonctions, mais pour ceux qui voient l'usinagaz à 365 fonctions arriver, lisez quand même, j'y réponds en fin de post.

    Typiquement, tu as des choses du genre :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    for(...) {
    // code commun
    if (cas1) {
     // code specifique 1
    } else if (cas2) {
     // code specifique 2
     continue;
    } else if (cas3) {
     // code specifique 3
    } else {
     // code specifique 4
     continue;
    }
    // code specifique 1&3
    }
    Les continue servent surtout à factoriser un code qui ne s'applique que dans certains cas, ici 1 et 3 et pas 2 et 4. Le problème, c'est que quand tu lis le code spécifique 1&3 tu ne sais pas que ça concerne uniquement les cas 1 et 3 sans bien lire tes if avant pour voir les continue (à moins que tu mettes des commentaires, ce qui est encore un effort supplémentaire). Du coup, niveau débogage/maintenabilité, c'est pas le top. Ensuite, parlons sémantique : si ce code 1&3 doit être exécuté dans le cas 1 et le cas 3, alors tu peux le mettre directement dans les cas correspondant :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    for(...) {
    // code commun
    if (cas1) {
     // code specifique 1
     // code specifique 1&3
    } else if (cas2) {
     // code specifique 2
     continue;
    } else if (cas3) {
     // code specifique 3
     // code specifique 1&3
    } else {
     // code specifique 4
     continue;
    }
    }
    Dès lors, c'est évident, les continue deviennent inutiles :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    for(...) {
    // code commun
    if (cas1) {
     // code specifique 1
     // code specifique 1&3
    } else if (cas2) {
     // code specifique 2
    } else if (cas3) {
     // code specifique 3
     // code specifique 1&3
    } else {
     // code specifique 4
    }
    }
    Maintenant, pourquoi préfère-t-on avoir les continue et le code factorisé ? Pour ne pas répéter le code. Très bien, alors pourquoi ne pas faire une fonction ?
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    for(...) {
    // code commun
    if (cas1) {
     // code specifique 1
     fun();
    } else if (cas2) {
     // code specifique 2
    } else if (cas3) {
     // code specifique 3
     fun();
    } else {
     // code specifique 4
    }
    }
    ...
    fun() {
    // code specifique 1&3
    }
    Dans certain cas, ça peut être trop compliqué parce qu'on a des arguments à faire passer et parfois plusieurs résultats à récupérer. Sauf que de par mon expérience, ce genre de problèmes est rarement (pour ne pas dire jamais) difficile à résoudre : une classe faisant office de structure de données et hop, le tour est joué. Plutôt que d'utiliser 50 variables, on utilise 50 champs dans 1 structure, on passe la structure en argument de notre fonction et une fois exécutée la structure est modifiée en conséquence (ou on récupère une nouvelle version en retour de fonction) avec toutes les données nécessaires dedans. Selon le traitement qu'on fait, on préfèrera avoir plusieurs structures de données pour mieux séparer différentes sémantiques, voire avec des méthodes déjà intégrées à ces classes pour des traitement très spécifiques. Dès lors, on disposera d'un ensemble d'objets bien pratiques qui, si bien nommés, seront bien plus parlant qu'un ensemble de variables distribuées au milieu du code.

    Du coup, qu'on ait cette difficulté ou non, une fonction fait généralement très bien l'affaire et, contrairement au cas des continue qui nous font couper notre code en morceau, la fonction s'exécute bien à l'intérieur des cas qui en ont besoin, et non en dehors avec la nécessité de bien relire ce qui est au dessus pour bien comprendre quels cas sont concernés. Et surtout, du fait qu'on ne répète qu'une ligne, ça diminue grandement l'envie de factoriser ce code en utilisant des continue, ce qui nous permet de garder des cas atomiques qu'on pourra corriger à volonté.

    Une autre raison qui fait que c'est mauvais de factoriser avec des continue : ce n'est pas généralisable. Si j'ai un code commun à 1 et 3 et un autre commun à 2 et 4, je ne peux en factoriser que 1 en utilisant les continue, alors que les autres je suis obligé de répéter le code dans les deux cas concernés. Et si j'en ai d'autres encore, des cas avec des parties communes, pareil : je ne peux le faire que pour 1 factorisation, le reste je dois le répéter. Si je veux éviter ça, il me faut par exemple imbriquer mes if :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    for(...) {
    // code commun
    if (cas1 || cas3) {
     if (cas1) {
      // code specifique 1
     } else if (cas3) {
      // code specifique 3
     }
     // code specifique 1&3
    } else {
     if (cas2) {
      // code specifique 2
     } else {
      // code specifique 4
     }
     // code specifique 2&4
    }
    }
    Et là on voit bien que, bien que plus complexe que notre cas de base, on n'a pas besoin de continue. Mais la structure est plus complexe et on doit tester plusieurs fois nos cas. Des fois on peut optimiser mais ça peut être au détriment de la lisibilité. Et si on a vraiment une structure complexe, les imbrications à la chaine... Mais au moins c'est généralisable : on peut imbriquer autant que c'est nécessaire et, si on s'organise bien (conditions claires, des fonctions pour éviter d'avoir trop d'imbrications, etc.) on peut avoir quelque chose d'assez propre. Mais faut être bon pour y arriver (et motivé pour le relire derrière).

    Une autre façon, généralisable et qui évite une telle complexité, est d'utiliser les fonctions comme décrite au début :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    for(...) {
    // code commun
    if (cas1) {
     // code specifique 1
     fun13();
    } else if (cas2) {
     // code specifique 2
     fun24();
    } else if (cas3) {
     // code specifique 3
     fun13();
    } else {
     // code specifique 4
     fun24();
    }
    }
    ...
    fun13() {
    // code specifique 1&3
    }
     
    fun24() {
    // code specifique 2&4
    }
    Encore une fois, on ne répète qu'une ligne, donc ça donne pas envie de tout factoriser (salement). Ensuite, même si on a des parties communes 1&2 et 3&4 par exemple, on peut les factoriser tout aussi facilement et lisiblement (avec les if imbriqués par contre, là ça devient vraiment galère). Et au final, en appliquant cette méthode, qu'est-ce qu'on a dans notre code ? Ni plus ni moins qu'une suite de cas précis ayant chacun leur processus, avec des parties communes bien en vues dans des fonctions spécialisées. Ca donne un code clair et modulaire.

    Et pour ceux qui lancent l'argument "ouais mais tu finis par avoir 300 fonctions dans une seule classe", je leur dirait que c'est encore qu'une question d'organisation de code : j'ai parlé de structures de données en début de post, et typiquement ces structures de données son liées à des processus spécifiques, processus qu'on retrouve généralement dans ces fonctions. Pour quelqu'un d'habitué à faire de l'objet, ça doit le faire tilter : tes fonctions fun13() et fun24(), c'est pas ici que tu vas les avoir, c'est dans les classes de tes structures de données. Si on as 50 fonctions dans une classe, c'est probablement parce qu'on y fait beaucoup de choses différentes. Je ne serai pas étonné qu'il y ait un paquet de concepts qui soit avantageusement représentés par des classes spécifiques, avec leurs fonctions, pour alléger significativement le code.

    Alors pourquoi on le fait pas et on utilise des continue ? Parce qu'on se limite très souvent à faire de l'optimisation syntaxique à l'arrache : je vois un code identique dans plusieurs cas, je factorise ce code là, sans même chercher à savoir si c'est censé être pareil (sémantiquement) ou si c'est juste une coïncidence (un copier-coller rapide et pratique qui devra probablement évolué différemment un jour ou l'autre). Après, peut-être que je verrai qu'il y en d'autres qui ont du code en commun, mais comme j'arriverai plus à le factoriser parce que les continue c'est pas généralisable, je me consolerai en me disant que j'en ai au moins factorisé 1 donc je n'ai rien à me reprocher. Et voilà comment on dort tranquille avec du code sale {^_^}.
    Site perso
    Recommandations pour débattre sainement

    Références récurrentes :
    The Cambridge Handbook of Expertise and Expert Performance
    L’Art d’avoir toujours raison (ou ce qu'il faut éviter pour pas que je vous saute à la gorge {^_^})

  6. #46
    Expert éminent
    Avatar de Pyramidev
    Homme Profil pro
    Développeur
    Inscrit en
    Avril 2016
    Messages
    1 469
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Haute Garonne (Midi Pyrénées)

    Informations professionnelles :
    Activité : Développeur

    Informations forums :
    Inscription : Avril 2016
    Messages : 1 469
    Points : 6 102
    Points
    6 102
    Par défaut
    Je up car on parle de goto en C dans un autre fil.

    Comme certains l'ont déjà dit, le principal intérêt de goto en C, c'est de gérer les erreurs.

    Voici un exemple de code illustratif avec des goto :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
        if(code = allouer_A(/* paramètres */))
            return code;
     
        if(code = allouer_B(/* paramètres */))
            goto cleanup_A;
     
        if(code = allouer_C(/* paramètres */))
            goto cleanup_B;
     
        if(code = allouer_D(/* paramètres */))
            goto cleanup_C;
     
        code = faireDesTrucs(/* paramètres */);
     
        liberer_D();
     
    cleanup_C:
        liberer_C();
     
    cleanup_B:
        liberer_B();
     
    cleanup_A:
        liberer_A();
     
        return code;
    Bien sûr, on peut faire sans goto, mais ce serait moins élégant.
    Le code équivalent sans goto serait une pyramide de if :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
        code = allouer_A(/* paramètres */);
        if(!code) {
            code = allouer_B(/* paramètres */);
            if(!code) {
                code = allouer_C(/* paramètres */);
                if(!code) {
                    code = allouer_D(/* paramètres */);
                    if(!code) {
                        code = faireDesTrucs(/* paramètres */);
                        liberer_D();
                    }
                    liberer_C();
                }
                liberer_B();
            }
            liberer_A();
        }
        return code;
    Ici, la pyramide a 5 niveaux d'indentation. Quand on passe à 10, c'est encore pire.

    Remarque : une autre possibilité est de déclarer plein de booléens pour savoir quelles ressources libérer, mais ce n'est pas mieux que les goto :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
        code = allouer_A(/* paramètres */)
        const bool faudra_liberer_A = !code;
     
        if(!code) code = allouer_B(/* paramètres */);
        const bool faudra_liberer_B = !code;
     
        if(!code) code = allouer_C(/* paramètres */);
        const bool faudra_liberer_C = !code;
     
        if(!code) code = allouer_D(/* paramètres */);
        const bool faudra_liberer_D = !code;
     
        if(!code) code = faireDesTrucs(/* paramètres */);
     
        if(faudra_liberer_D) liberer_D();
        if(faudra_liberer_C) liberer_C();
        if(faudra_liberer_B) liberer_B();
        if(faudra_liberer_A) liberer_A();
        return code;

  7. #47
    Expert éminent
    Avatar de Matthieu Vergne
    Homme Profil pro
    Consultant IT, chercheur IA indépendant
    Inscrit en
    Novembre 2011
    Messages
    2 264
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France

    Informations professionnelles :
    Activité : Consultant IT, chercheur IA indépendant
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : Novembre 2011
    Messages : 2 264
    Points : 7 760
    Points
    7 760
    Billets dans le blog
    3
    Par défaut
    Perso, je préfère les exceptions. Quant à l'argument de la pyramide, c'est purement esthétique et donc subjectif. Au moins l'indentation permet de voir clairement que si on foire à tel niveau c'est là qu'on saute. Les gotos, c'est un scope complètement manuel, donc faut chercher. Dit autrement, c'est de l'automatisation en moins. Et je pense que plus d'automatisation passe mieux quand on est plus organisé. Donc mon avis est que les goto permettent de faire du code moins bien conçu.
    Site perso
    Recommandations pour débattre sainement

    Références récurrentes :
    The Cambridge Handbook of Expertise and Expert Performance
    L’Art d’avoir toujours raison (ou ce qu'il faut éviter pour pas que je vous saute à la gorge {^_^})

  8. #48
    Membre chevronné
    Avatar de lilington
    Homme Profil pro
    Développeur informatique
    Inscrit en
    Juin 2005
    Messages
    681
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 42
    Localisation : Chine

    Informations professionnelles :
    Activité : Développeur informatique
    Secteur : High Tech - Multimédia et Internet

    Informations forums :
    Inscription : Juin 2005
    Messages : 681
    Points : 1 944
    Points
    1 944
    Par défaut
    Citation Envoyé par Pyramidev Voir le message
    Comme certains l'ont déjà dit, le principal intérêt de goto en C, c'est de gérer les erreurs.
    En faite c'est beaucoup plus simple, l'interdiction de l'utilisation du goto (ou le combat pour le dediaboliser) est un combat d'ayatolla, de bien pensant qui impose leur point de vue.
    comme je disais c'est plus simple que ca:
    a l'origine un article "Go To Statement Considered Harmful" ou le monsieur se plaint de faite que le code devient spagetti et illisible. Il deconseil son utilisation et d'autre meme preconise de ne pas l'enseigner au debutant. Je suis d'accord jusque la. mais la ou ca devient du n'importe quoi c'est quand on le diabolise.
    la reaction de certains etait de montrer qu'on pouvait faire pire sans goto (comme tu l'as montre) notemment dans la gestion des erreurs.

    les imbrications successives et les booleans a n'en plus finir rende le code aussi illisible sinon meme pire. mais en bon extremiste les gens veulent montrer qu'il on raison et diront que no goto c'est mieux dans tout les cas.

    ca c'est la situation actuelle.

    dans mon cas personnel je ne parle pas de goto a un gar qui debute en C. mais j'utilise de GOTO dans mon propre code (c'est tres rares) pour d'iverse raison. et je le relis sans probleme ET mes collegues n'ont aucun probleme pour lire un code comme tu l'as fait toi meme. seule la mauvaise fois ou la certitude d'avoir raison ferai dire que c'est mal.

    dernier point il y a des gens qui ferme le fichier des qu'ils voient goto dans ton code sans meme lire parcequ'on leur a dit que GOTO c'est mal sans meme savoir pourquoi.

    on donne pas une tronconneuse a un gosse de 5 ans. mais entre les mais d'un bucheron c'est un outils magnifique. c'est la meme chose pour goto. celui qui sait POURQUOI il est diabolise et connait la puissance de GOTO saura l'utiliser sans encombre et ecrirait un code claire pour qui veut le lire sans arriere penser.

    Citation Envoyé par Matthieu Vergne Voir le message
    Perso, je préfère les exceptions.
    En C c'est un complique, et en C++ c'est un peu mal foutu je suis pas expert C++ mais j'ai des collegues qui font des trucs pour contourner ses limitations et en plus ton mot cle c'est PREFERE du coup je debat pas avec les preferences tu as le droit et tu n'as ni tort ni raison, c'est un choix de gout.

    Citation Envoyé par Matthieu Vergne Voir le message
    Quant à l'argument de la pyramide, c'est purement esthétique et donc subjectif...
    non pas esthetique mais plus lisible. avec la piramide et une indentation tres profonde il faut scroller et chercher, ca fatigue les yeux. avec un goto et partant du principe que le lecteur sait comment aujourd'hui on l'utilise il ne lira meme pas car il sait dejat que c'est a la fin et que apres on sort de la fonction. Mainteant comme je le dis si tu fait 10 niveau de IF je te demanderai pas de changer ton codes et faite je t'en parlerai meme pas sauf si je n'arrive pas a le lire.
    Citation Envoyé par Matthieu Vergne Voir le message
    Et je pense que plus d'automatisation passe mieux quand on est plus organisé. Donc mon avis est que les goto permettent de faire du code moins bien conçu.
    la encore c'est un probleme de lisibilite et non de conception. c'est comme choisir entre if(valeur) et if(valeur != 0) si on comprend le langage par exemple ici le C on fait un choix, parcontre ca peut gener certain moi en l'occurence je prefere les details donc j'ecrit toujours if(valeur != 0) . Et d'autre diront que il faut abolire l'un ou l'autre (car ils ont la science absolut).

    Pour finir si il y a une chose que j'aimerai imposer sur ce sujet c'est : si le code est lisible et comprehensible les choix du codeur c'est pas votre problemes goto ou pas temps c'est pas un code spagetti et que c'est facile a lire pourquoi critiquer?
    malheuresement chacun a raison donc debat il y aura toujours
    ex:linux vs windows, vi vs emac, ou le meilleur: Des gens qui regarde d'autre gens entrain de jouer... faut vraiment rien avoir a faire de ca vie VS je regarde d'autre gens jouer au foot
    Petit lien vers mon premier jeux SDL2/C
    http://store.steampowered.com/app/72..._Soul_Of_Mask/
    la suite? ca vient,ca vient!

Discussions similaires

  1. Envoie formulaire depuis flash(pas comme les autres)
    Par TobyKaos dans le forum Flash
    Réponses: 1
    Dernier message: 22/11/2007, 15h38
  2. Une macro pas comme les autres:)
    Par Lucie75 dans le forum Macros et VBA Excel
    Réponses: 2
    Dernier message: 07/08/2007, 12h11
  3. Tâche planifiée pas comme les autres
    Par casavba dans le forum VBA Outlook
    Réponses: 2
    Dernier message: 07/08/2007, 12h08
  4. requete selection mais pas comme les autres
    Par adil_math2006 dans le forum MS SQL Server
    Réponses: 2
    Dernier message: 28/06/2007, 13h44
  5. Une horloge pas comme les autres
    Par laurent2101 dans le forum Flash
    Réponses: 3
    Dernier message: 12/06/2007, 16h13

Partager

Partager
  • Envoyer la discussion sur Viadeo
  • Envoyer la discussion sur Twitter
  • Envoyer la discussion sur Google
  • Envoyer la discussion sur Facebook
  • Envoyer la discussion sur Digg
  • Envoyer la discussion sur Delicious
  • Envoyer la discussion sur MySpace
  • Envoyer la discussion sur Yahoo