Partage
  • Partager sur Facebook
  • Partager sur Twitter

Fonction qui retourne un false ou un autre type

    29 avril 2022 à 20:41:22

    Bonsoir

    Dans mon programme j'ai mis beaucoup de assert() et sachant que c'est plus seulement moi mais d'autres utilisateurs qui devront aussi travailler avec, je veux retirer tous ces assert et faire un sorte que la fonction ayant un assert retourne false si la condition n'est pas respectée.

    std::string function{param1, param2){
        //  assert(param1 > param2 && "Erreur");
        if (!(param1 > param2))
            return false;
        ///
       
        ///
        return chaine_obtenue_apres_traitement;
    }

    S'il vous plait est-ce possible?

    • Partager sur Facebook
    • Partager sur Twitter
      29 avril 2022 à 20:49:00

      Oui, tu peux retourner std::optional<std::string> https://en.cppreference.com/w/cpp/utility/optional 

      Alternative : retourner std::pair<bool, std::string>, ou retourner une chaine vide (qui indiquera que la fonction a échoué), ou lancer une exception.

      • Partager sur Facebook
      • Partager sur Twitter
        29 avril 2022 à 21:02:59

        "assert", c'est pour les erreurs de programmation, ce n'est pas vraiment pour des retours "normals".
        • Partager sur Facebook
        • Partager sur Twitter
        Je recherche un CDI/CDD/mission freelance comme Architecte Logiciel/ Expert Technique sur technologies Microsoft.
          29 avril 2022 à 21:19:08

          Dans le code qu'il donne, le assert semble être utilisé pour vérifier une pré-condition. 

          Mais c'est vrai qu'il faut se poser la question de ce changement. Cela change les contraintes d'utilisation de ta fonction et ce n'est pas forcément une bonne chose. Pourquoi ne pas garder le code actuel ?

          • Partager sur Facebook
          • Partager sur Twitter
            29 avril 2022 à 22:14:31

            Salut,

            En fait, tu ne devrais justement pas retirer tes assertions.

            Car elles rentrent dans une approche que l'on appelle la "programmation par contrat", c'est à  dire que l'on "signe" un contrat avec l'utilisateur de "la fonctionnalité" qui dit en substance:

            Donne moi des données valides et cohérentes, tu obtiendra le résultat (correct et prédictible) que tu espères

            Elles permettent donc à ceux qui vont utiliser ta fonction de se rendre compte "le plus vite possible" qu'ils ont fait une c...rie en transmettant une valeur incorrecte à ta fonction, vu que, dans ce cas là, ta fonction "plantera" purement et simplement.

            Or, il est particulièrement intéressant de se rendre compte "le plus vite possible" de la connerie que l'on a pu faire à l'utilisation d'une fonction, parce que plus vite nous en aurons conscience, plus vite nous essayerons de la corriger, plus le code incriminé serait récent, et donc "clair dans la tête" de celui qui l'a écrit.  Ce qui facilite énormément les corrections ;)

            On peut effectivement distinguer trois grandes étapes durant lesquelles les éventuelles erreurs seront découvertes:

            • Lors de la compilation: ca, c'est l'idéal, car on peut estimer que le code incriminé vient tout juste d'être écrit, qu'avec un peu de chance, le curseur se trouve peut-être meme encore pour ainsi dire à  l'endroit où l'erreur est commise.
            • Lors des tests: c'est à  ce moment là que les assertions prennent tout leur sens. Non pas pour s'assurer que la fonction fera bien ce que l'on attend de sa part, mais bien pour s'assurer que l'utilisateur lui a fourni des valeurs correctes et cohérentes ;). Par contre, retrouver l'erreur sera déjà "plus difficile", car elle ne porte pas ** forcément ** -- comme les erreurs de compilation -- sur du code qui "vient tout juste d'être écrit".  On peut cependant espérer que ce code est "suffisamment récent" que pour qu'il soit encore "plus ou moins frais" dans la tête de celui qui l'a écrit.
            • Lors de la production: Ca, c'est le plus problématique des trois cas.  Car, s'il reste des erreurs lorsque  ton programme ou ta bibliothèque est mise en production, on n'a déjà aucune certitude qu'elles seront repérées par les utilisateurs, et on n'est encore moins sur qu'elles seront "remontées" (que l'utilisateur fera un rapport de bug). Si donc on prend connaissance d'une erreur qui survient en production, on est vraiment "mal barre", parce que, si cela se trouve, le code incriminé aura été écrit part quelqu'un qui aura quitté l'entreprise (pour cause de pension... ou  autre) depuis cinq ans.  Autant dire qu'il va falloir s'accrocher pour trouver le code qui doit être corrigé ;)

            Si bien que tout ce qui peut aider à mettre une erreur en évidence à l'occasion des deux premiers cas devrait être préservé "à tout prix", et  ce, d'autant plus que la manière dont les assert sont mis en oeuvre fera qu'ils seront tout  simplement remplacé par une "no op" (par rien du tout, en fait) une fois le code compilé en mode release ;)

            Par contre, tu peux aussi faire en sorte de rendre la cause du  problème plus explicite pour la personne qui verra le message émis lors des tests.  Mais bon, je vois que tu le fais visiblement déjà :D

            Tu peux aussi -- éventuellement -- envisager de lancer une exception comme l'a indiqué gbdivers.  Ce qui serait sans doute la "moins mauvaise idée" parmi les mauvaises idées visant à supprimer les assertions que  tu  pourrais avoir.

            En effet, même si  on dispose désormais d'un tag [[nodiscard]] pour indiquer au compilateur que  l'on s'attend quoi qu'il arrive à ce que l'utilisateur récupère la valeur de retour, cela ne donne en rien la certitude que l'utilisateur fera quoi que ce soit de la valeur récupérée.  Et cela garanti donc encore moins que l'utilisateur réagira "correctement" face à une valeur indiquant une erreur ;)

            Or, si  les paramètres d'appel de la fonction sont faux / incohérents / invalides, il ne faut clairement pas espérer que le résultat de cette fonction puisse être jugé ne serait ce que  comme "crédible".

            De là à estimer que la fonction ne sera même pas en mesure de fournir le moindre résultat, il n'y a qu'un pas qu'il est particulièrement censé de franchir dans de nombreuses circonstances.  On peut donc estimer "raisonnable" d'arrêter purement et simplement l'exécution de la fonction et de ... remonter la pile d'appels jusqu'à un endroit où l'erreur rencontrée pourra (éventuellement) être corrigée.

            Oui, mais, cela nous laisse malgré tout avec deux soucis majeurs:

            Primo, comme le dit si bien le terme, une exception a pour objectif de traiter des cas ... exceptionnels, c'est à dire, des cas sur lesquels le développeur de la fonction se rend bien compte qu'il n'a purement et simplement "aucune prise", qu'il ne peut pas éviter, même s'il espère qu'ils ne se présenteront jamais, et qui ne dépendent pas des données que l'utilisateur de la fonction pourrait lui avoir fournies.

            Par exemple:

            • si un fichier que l'on est occupé à lire contient la chaine de caractères "salut le monde" alors qu'on s'attendait à recevoir un entier
            • si le serveur distant auquel on a envoyé une demande ne répond pas dans les temps
            • si -- pour une raison quelconque -- on n'arrive pas à avoir l'accès à un fichier dont le nom et le chemin d'accès semblent pourtant "corrects"
            • si -- pour une raison comme une autre -- n'importe quel appel à un "service externe" (au programme) venait à ne pas fournir le résultat auquel on s'attend et que l'on espère

            On se rend donc "assez facilement" que le fait que  l'utilisateur de la fonction "laisse passer" une valeur  incorrecte lors de l'appel à la fonction ne fait "pas vraiment partie" de ces cas "sur lesquels le développeur n'a aucune prise" et qu'il s'agit en réalité ni plus ni moins que ... d'erreur de programmation.  Erreurs qui devront d'ailleurs être corrigées "le plus vite possible" ;)

            Secundo, j'ai mentionné plus haut que les assertions étaient transformées en "no op" en mode "release".  Et ca, c'est particulièrement intéressant pour ce qui est -- en définitive -- les "erreurs de programmation".

            On peut en effet considérer que ce n'est "dans l'idéal" pas au développeur de la fonction de s'assurer que l'utilisateur fourni des données valides et cohérentes. "Dans l'idéal", ce serait plutôt à l'utilisateur de la fonction de s'assurer que ce soit le cas avant de faire appel à la fonction.

            L'assertion peut donc être considérée que comme un "dernier rempart" permettant de garantir la validité et la cohérence des données transmises par l'utilisateur. Et ce,avec une idée bien précise en tête: celle de forcer l'utilisateur de la fonction à  faire "toutes les vérifications" qui relèvent de sa responsabilité avant de faire appel à la fonction. 

            Ou, si tu préfères, à corriger sa propre logique afin de s'assurer qu'il ne laissera passer que les données "valides et cohérentes" lors de l'appel de la fonction.

            Or, une fois que l'utilisateur a mis au point cette logique qui consiste à s'assurer de la cohérence des données qu'il s'apprête à fournir à  une fonction, ben, il n'y a -- normalement -- plus aucune raison pour  que la donnée transmise à la fonction ne soit pas (valide et) cohérente.  Si bien que le test au niveau même de la fonction en devient inutile.

            Voilà donc la deuxième raison qui fait qu'il serait quand même préférable de garder les assertions au lieu de les remplacer par des exceptions:  parce que, si tu choisi de lancer une exception lorsqu'un paramètre est invalide et / ou incohérent, le test de validité et de cohérence sur lequel se base le fait de lancer une exception devra ** toujours ** être exécuté, et ce, même si nous sommes d'accord sur le fait que la validité et la cohérence de l'information est du domaine de l'utilisateur de la fonction, et qu'il a donc (ou, du moins, qu'il devrait avoir)-- a priori -- déjà fait tous les tests avant même de faire appel à la fonction.  Et à plus forte raison si la fonction est en production ;)

            Ce qui revient à faire un test -- qui risque ** forcément ** de ralentir un peu les choses alors que l'on a toutes les raisons de croire que le résultat du  test sera toujours d'éviter le lancement d'une exception.  Ne serait-ce pas dommage de laisser ce test dans la version de production ?

            • Partager sur Facebook
            • Partager sur Twitter
            Ce qui se conçoit bien s'énonce clairement. Et les mots pour le dire viennent aisément.Mon nouveau livre : Coder efficacement - Bonnes pratiques et erreurs  à éviter (en C++)Avant de faire ce que tu ne pourras défaire, penses à tout ce que tu ne pourras plus faire une fois que tu l'auras fait
              30 avril 2022 à 1:07:31

              >Primo, comme le dit si bien le terme, une exception a pour objectif de traiter des cas ... exceptionnels, c'est à dire, des cas sur lesquels le développeur de la fonction se rend bien compte qu'il n'a purement et simplement "aucune prise", qu'il ne peut pas éviter, même s'il espère qu'ils ne se présenteront jamais, et qui ne dépendent pas des données que l'utilisateur de la fonction pourrait lui avoir fournies


              Dis comme ca les exceptions sont pas les bienvenues chez moi pour le moment. Les erreurs que je peux rencontrer sont essentiellement dues aux entrées de l'utilisateur.


              >L'assertion peut donc être considérée que comme un "dernier rempart" permettant de garantir la validité et la cohérence des données transmises par l'utilisateur. Et ce,avec une idée bien précise en tête: celle de forcer l'utilisateur de la fonction à  faire "toutes les vérifications" qui relèvent de sa responsabilité avant de faire appel à la fonction. 

              D'accord je vois, donc je peux(et peut-être je dois meme) laisser les assertions dans certaines fonctions.

              Mais j'ai mis des assertions peut-être où il faut pas.

              >Par contre, tu peux aussi faire en sorte de rendre la cause du  problème plus explicite pour la personne qui verra le message émis lors des tests.  Mais bon, je vois que tu le fais visiblement déjà :D

              Mes assertions renvoient bien un message tres clair sur l'origine, mais souvent c'est pas directement la cause de l'utilisateur. Y'a des fonctions où je mettais assert() car le probleme etait ainsi fait: si ceci arrive alors fin du programme, y'a telle erreur, arrêtons nous la. Mais maintenant j'en sais plus sur le probleme et: si ceci arrive, je dois oui connaitre que c'est un probleme mais refaire d'autres calculs ,si on peut le dire ainsi.

              C'est pourquoi je veux des return false, pour etre capable de faire des trucs du genre:

              auto var_a = maFonctionUne();
              auto var_b = maFonctionDeux(params...);
              
              if (var_b != false ) {
                  do ...
                  if (var_a != false) {
                      do ...
                  }
              }
              do ..
              // faire autre chose gerant l'erreur, affichage ? Re-execution si dans une boucle while 

              Bon j'explique pas tres bien mon idée mais c'est ce que je veux faire. je dois retirer certaines assertions et mettre un code pour gerer ces cas mais sans arrêter le programme et sans modifier le code qui est déjà la, puisque c'est deja stable.

              >Voilà donc la deuxième raison qui fait qu'il serait quand même préférable de garder les assertions au lieu de les remplacer par des exceptions:  parce que, si tu choisi de lancer une exception lorsqu'un paramètre est invalide et / ou incohérent, le test de validité et de cohérence sur lequel se base le fait de lancer une exception devra ** toujours ** être exécuté, et ce, même si nous sommes d'accord sur le fait que la validité et la cohérence de l'information est du domaine de l'utilisateur de la fonction, et qu'il a donc (ou, du moins, qu'il devrait avoir)-- a priori -- déjà fait tous les tests avant même de faire appel à la fonction.  Et à plus forte raison si la fonction est en production

              En tout cas je retires plus certaines assertions, et je mettrais les exception uniquement sur ces cas la que je ne peux pas gérer, même comme j'en vois aucun actuellement.
               

              • Partager sur Facebook
              • Partager sur Twitter
                30 avril 2022 à 1:41:34

                Assertions: parce que d'autres développeurs utiliseront incorrectement ta fonction. Et tu as autre chose à faire que de vérifier qu'ils respectent tes conditions d'appel. Genre tu ne vas pas prévoir dans la programmation qu'ils voudront acheter des croissants avec des billets de monopoly

                Exception: parce que des entrées et des facteurs externes au code ne permettent pas de réaliser le traitement attendu de te part. -> "Désolé il n'y a plus de croissants"

                Généralement, je préfère: acquisition des entrées externes (au programme), leur validation et restitution de la main si elles ne sont pas conformes (code de retour, exception...), puis appel de services suffisamment complexes pour qu'il n'y ait pas lieu de les compliquer encore plus avec la validation des entrées externes.

                Si tu cliques sur "blog" dans ma signature, il y a 3 billets sur le sujet.

                -
                Edité par lmghs 30 avril 2022 à 1:54:28

                • Partager sur Facebook
                • Partager sur Twitter
                C++: Blog|FAQ C++ dvpz|FAQ fclc++|FAQ Comeau|FAQ C++lite|FAQ BS| Bons livres sur le C++| PS: Je ne réponds pas aux questions techniques par MP.
                  30 avril 2022 à 2:03:08

                  J'ai pas encore fini de lire mais je trouves que c'est assez clair comme explications. Je risque revoir pas mal de mes fonctions pour y insérer des postconditions(première fois que j'entendais parler de ce mot) et les vérifier.

                  Luc Hermitte's Blog

                  • Partager sur Facebook
                  • Partager sur Twitter
                    30 avril 2022 à 5:14:49

                    Il faut comprendre que, jusqu'à présent, on a fourni des réponses en fonction de ce que l'on sait de ton programme.  C'est à dire, pas grand chose.  Les explications étaient donc forcément "générales" ;)

                    Maintenant qu'il semble clair (si j'ai bien saisi tes explications) que le programme doit continuer, même si un traitement particulier peut être jugé comme "inutile" dans un cadre particulier, j'aurais presque tendance à te proposer le patron "chain of responsability".

                    Il y a bien sur trente-six implémentations possibles de ce patron de conception.  L'idée générale étant toujours de décider -- sur base d'un critère défini par le(s) paramètre(s) si il nous revient "à nous" de traiter l'information, ou s'il faut "transmettre la patate" au suivant ;)
                    • Partager sur Facebook
                    • Partager sur Twitter
                    Ce qui se conçoit bien s'énonce clairement. Et les mots pour le dire viennent aisément.Mon nouveau livre : Coder efficacement - Bonnes pratiques et erreurs  à éviter (en C++)Avant de faire ce que tu ne pourras défaire, penses à tout ce que tu ne pourras plus faire une fois que tu l'auras fait

                    Fonction qui retourne un false ou un autre type

                    × Après avoir cliqué sur "Répondre" vous serez invité à vous connecter pour que votre message soit publié.
                    × Attention, ce sujet est très ancien. Le déterrer n'est pas forcément approprié. Nous te conseillons de créer un nouveau sujet pour poser ta question.
                    • Editeur
                    • Markdown