Bonne pratique pour écrire une fonction

Alban_
Modifié (August 2022) dans Informatique théorique
Bonjour,
j'ai une question "bonne pratique" (c'est en Python mais le langage n'a probablement aucune importance) : disons que je récupère dans un répertoire les images y figurant à l'aide d'une première fonction, puis que je fais un traitement sur l'une de ces images dans une seconde fonction (par exemple une pixellisation).
Pour être un peu plus concret, disons que ma première fonction récupère tous les fichiers d'extension bmp dans un répertoire passé en paramètre, puis renvoie l'image dont le nom vérifie un certain critère (peu importe, on peut imaginer que c'est le nom le plus court par exemple). Ma deuxième fonction appelle la première et récupère une image, puis fait le traitement. Mais évidemment mon répertoire pourrait être vide ou ne contenir aucune image (ou aucune ne vérifiant mon critère). Où vaut-il mieux traiter cette erreur et comment ? Je vois deux possibilités mais je ne sais pas si l'une est "meilleure" ou si ça n'a aucune importance dans le cas où mon répertoire ne contient pas d'image :
 - on renvoie un code erreur (par exemple une chaine disant 'erreur') avec la première fonction et la deuxième fonction prend en compte cela avec un if et affiche un message d'erreur
 - on renvoie quoi qu'il arrive quelque chose avec la première fonction (par exemple avec return()) et on gère l'erreur avec un try except dans la seconde fonction.
En fait, plus généralement, dans une fonction qui doit renvoyer quelque chose si tout va bien (l'immense majorité des cas), comment gérer le fait que sur certaines données, elle ne renvoie rien ? On ne renvoie juste rien et on laisse à une éventuelle seconde fonction le soin de gérer ce cas ou il vaut mieux le gérer immédiatement. Mais dans ce second cas, je ne trouve pas ma solution terrible puisqu'il faut être au courant que ma première fonction renvoie une chaîne ?

Réponses

  • Je pense qu'il vaut mieux que la fonction affiche une chaîne et renvoie soit rien, soit une image dédiée (avec "erreur" écrit dessus ?) plutôt qu'une chaîne. M'enfin, mon avis...
  • Lorsque je programmais je préférais que chaque fonction renvoie des données du même type (en PHP par exemple on est pas obligés de le faire...). Donc dans ce genre de situation, ma première fonction, celle qui est censée renvoyer une image, lève une exception si elle ne peut pas renvoyer une image.
    De plus pour faire propre je levais une exception adéquate à la situation, quitte à créer une exception personnalisée.

    La seconde fonction utilise le première à l'intérieur d'un try/catch et traite toutes les exceptions éventuellement levées par la première.
  • Alban_
    Modifié (August 2022)
    OK, j'ai l'impression que vous êtes sur le même conseil qui est plutôt de commencer par bien fignoler la première fonction, soit avec un code erreur simple mais compréhensible (image plutôt qu'une chaîne dans le contexte) soit en utilisant un mécanisme d'erreur.
    Bien sûr, ma question ne vient pas du fait de mes fonctions (anecdotiques) mais je me demandais comment une équipe de développeurs sur un gros projet pouvaient se prémunir de ce genre de soucis (ou alors ils perdent plein de temps à réparer des bugs de ce genre dans des cas pénibles non prévus au départ ?)
  • Dans les gros projets ça peut effectivement devenir un enfer si on est pas soigneux dès le départ. Les fonctions ne doivent pas connaître le mécanisme interne des fonctions qu'elles appellent. Par exemple si une fonction F1 appelle une fonction F2 qui renvoie une image, la fonction F1 ne doit pas savoir qu'en cas d'erreur F2 renvoie encore une image.

    Imagine que dans quelques mois tu doives remettre les mains dans le cambouis sur cet hypothétique gros projet, est-ce que tu vas te rappeler que la fonction F2 renvoyait quand même une image en cas d'erreur ? Probablement pas...

    Avec les exceptions c'est plus propre. Si je veux utiliser la fonction F2 dans quelques mois, je ne dois pas aller lire son code pour voir si elle me renvoie des trucs bizarres en cas d'erreur. Je regarde direct la liste des exceptions qu'elle lève dans tel ou tel cas et fini.

    En gros les fonctions doivent être des boîtes noires pour celui qui les utilise, on sait le type de données qu'elles renvoient et les exceptions qu'elles lèvent mais pas leur fonctionnement interne.
  • Question de néophyte car le sujet m'intéresse: qu'appelle-t-on "lever une exception" et que fait un "try except"?
    The fish doesnt think. The Fish doesnt think because the fish knows. Everything. - Goran Bregovic
  • Alban_
    Modifié (August 2022)
    D'accord, donc si tout tourne bien, le développeur de la fonction F1 doit penser à toutes les erreurs (impossibles, mais disons tendre vers cela) pour éviter que toute l'équipe passe des heures à relire le code de toutes les fonctions pour trouver le bug une fois le projet plus ou moins bouclé.
    Au début où j'apprenais Python, je trouvais justement que beaucoup de codes qu'on peut trouver sur internet mettent des try except un peu partout ce qui complique la lecture pour le néophyte que j'étais (et suis encore). Je précise que je me doutais que les personnes qui avaient écrit cela savaient ce qu'elles faisaient !
    Pour Soc, si tu connais un peu Python, je trouve cette page pas mal sur le mécanisme try except https://www.pierre-giraud.com/python-apprendre-programmer-cours/gestion-exception-try-except-else/
    Je ne sais pas si j'utilise exactement le bon vocabulaire, mais son texte tourne autour d'une fonction qui peut lever l'exception "division par 0".
  • Bonjour,

    Tu n'as pas besoin de deux fonctions, une seule suffit. Elle commence par vérifier si, dans le répertoire donné, existe au moins une image .bmp. Si ce n'est pas le cas, on termine. Sinon, elle charge chaque image l'une après l'autre et la traite. Il existe cependant des langages de programmation avec lesquels on parvient plus facilement au résultat recherché qu'avec d'autres, je pense en particulier à Php. Voici un exemple :

    $name = "monDossier/*.bmp";
    $images = scandir($name);
    foreach ($images as $image) {
        // traitement
    }

    S'il n'y a aucune image .bmp dans ce dossier, l'array $images est vide et la boucle n'est tout simplement pas parcourue.

  • Alban_
    Modifié (August 2022)
    Bien sûr, j'aurais pu faire une seule fonction. En fait, c'est une question qui m'est venue dans un autre contexte (dont je ne me souviens plus, d'où l'exemple bancal) où une fonction renvoyait en général un objet mais parfois aucun. Et j'avais l'impression de faire quelque chose de pas très propre.
    Je ne connais pas le PHP, mais ton exemple est suffisamment transparent pour que je comprenne. En Python aussi, la boucle vide ne serait pas parcourue.
    Par contre, si je n'ai besoin du traitement que sur un seul objet, ça va être long (pour peu que le traitement soit long).
  • raoul.S
    Modifié (August 2022)
    @Soc "lever une exception" signifie "déclencher" une exception... :mrgreen:

    Voici un exemple pour être plus clair. Si la fonction d'Alban_ reçoit le nom d'un répertoire et doit renvoyer l'image qui vérifie un certain critère (en supposant qu'elle n'en renvoie qu'une s'il y en a plusieurs) on aurait un code comme ça (peu importe le langage) : 

    function renvoie_image(nom_dossier: string){
        //on cherche l'image dont le nom vérifie un certain critère dans le dossier et on la met dans la variable img
        ....
        
        //si img est vide car on n'a rien trouvé on lève une exception
        if is_empty(img) {
            throw new MonExceptionImageVide(); //l'exécution de la fonction s'arrête
        }
    
        //si l'image est trouvée la variable img la contient et on la renvoie
        return img
    }

    On lève une exception avec "throw" en php, avec "raise" en python. La fonction "renvoie_image" déclenche (ou lève) une exception si elle ne peut pas renvoyer d'image. 

    Une fonction qui utiliserait la fonction renvoie_image le ferait ainsi : 
    function traiter_image(nom_dossier: string){
        try{
            img = renvoie_image(nom_dossier)
            
            //l'instruction ci-dessus n'a pas déclenché d'exception donc je peut continuer tout va bien
            ....
        } catch (MonExceptionImageVide e) {
    //ici je sais que la fonction renvoie_image n'a pas pu me renvoyer l'image et je sais aussi pour quelle raison // via le nom de l'exception, donc je peux faire les traitement adéquats ....
    }
    }
  • Merci! C'est toujours intéressant de mieux comprendre l'architecture des programmes.
    The fish doesnt think. The Fish doesnt think because the fish knows. Everything. - Goran Bregovic
  • Voyons comment ChatGPT – s'il était déjà sorti en août 2022 – aurait répondu à la question de @Alban_ :

    Q : écris un script Python qui cherche des images jpeg dans un répertoire de mon disque dur. S'il en trouve, il affiche leurs noms au moyen d'une boucle.

    GPT :

    Voici un exemple de script Python qui cherche des images JPEG dans un répertoire donné et affiche leurs noms à l'aide d'une boucle :

    import os
    
    directory = '/path/to/directory'
    
    for filename in os.listdir(directory):
        if filename.endswith('.jpg') or filename.endswith('.jpeg'):
            print(filename)

    Il utilise la fonction 'listdir' de la bibliothèque 'os' pour obtenir la liste des fichiers et dossiers dans le répertoire donné. Ensuite, il utilise une boucle 'for' pour parcourir cette liste et vérifie si chaque élément se termine par ".jpg" ou ".jpeg" à l'aide de la méthode 'endswith'. Si c'est le cas, il affiche le nom du fichier à l'aide de 'print'.

    Notez que ce script n'affiche que les fichiers qui se terminent par ".jpg" ou ".jpeg" et n'affiche pas les sous-répertoires. Si vous voulez également voir les sous-répertoires vous pouvez utiliser la fonction 'os.walk()' à la place de 'os.listdir()'.

    Effectuer une recherche dans les sous-dossiers du répertoire ciblé :

    Q : écris un script Python qui cherche des images bmp dans un répertoire de mon disque dur, ainsi que ses sous-dossiers. S'il en trouve, il affiche leurs noms au moyen d'une boucle.

    GPT :

    Voici un script Python qui peut parcourir un répertoire et ses sous-dossiers à la recherche de fichiers BMP, puis affiche leurs noms à l'aide d'une boucle :

    import os
    
    def search_bmp(path):
        for root, dirs, files in os.walk(path):
            for file in files:
                if file.endswith(".bmp"):
                    print(os.path.join(root, file))
    
    search_bmp("/path/to/search/")

    Il vous suffit de remplacer "/path/to/search/" par le chemin du répertoire où vous souhaitez rechercher des images BMP. La fonction 'os.walk()' parcourt récursivement tous les dossiers et sous-dossiers à partir du chemin spécifié, et la boucle interne vérifie si chaque fichier se termine par ".bmp". Si c'est le cas, le nom complet du fichier (chemin d'accès complet) est affiché à l'aide de 'print(os.path.join(root, file))'.

    Note: Si vous voulez juste voir les noms des fichiers sans le chemin, vous pouvez utiliser 'print(file)' à la place de 'print(os.path.join(root, file))'.

    Le principe est le même que celui que j'ai indiqué plus haut en utilisant Php : si aucune image jpeg ou bmp n'existe dans le répertoire directory, rien ne s'affiche.

    Variante :

    Q : modifie ce script pour qu'il affiche "aucune image BMP trouvée" si la recherche dans le répertoire ne donne aucun résultat.

    GPT :

    Voici un script modifié pour afficher "aucune image BMP trouvée" si la recherche dans le répertoire ne donne aucun résultat :

    import os
    
    def search_bmp(path):
        bmp_found = False
        for root, dirs, files in os.walk(path):
            for file in files:
                if file.endswith(".bmp"):
                    bmp_found = True
                    print(os.path.join(root, file))
        if not bmp_found:
            print("Aucune image BMP trouvée.")
    
    search_bmp("/path/to/search/")

    J'ai ajouté une variable 'bmp_found' qui est définie sur False au début de la fonction, puis est définie sur True si une image BMP est trouvée. Enfin, après la fin de la boucle principale, il y a une condition pour vérifier si la variable 'bmp_found' est toujours False, si c'est le cas, la fonction va afficher "Aucune image BMP trouvée."

    Rien à redire, ça coule de source et ça fonctionne parfaitement. Notez que sous Windows, le chemin "/path/to/search/" doit être échappé : "C:\\path\\to\\search".

  • Voici ce que le modèle text-davinci-003 du Playground d'OpenAI renvoie lorsqu'on lui pose la même question que ci-dessus, c'est-à-dire afficher le nom de toutes les images .jpg présentes dans un répertoire donné :

    # Importer les modules necessaires
    import os
    import glob
    
    # Définir le chemin du répertoire
    path = "C:\\Users\\Username\\...\\FolderName"
    
    # Rechercher toutes les images .jpg
    jpg_files = glob.glob(path + "/*.jpg")
    
    # Afficher le nom des images
    for file in jpg_files:
        print(os.path.basename(file))

    On notera la simplicité de ce code, proche de celui de Php (voir mon premier post ci-dessus), et en tout cas plus simple et plus facile à comprendre que celui proposé par ChatGPT dans le dernier exemple de mon précédent message. Je ne dis pas pour autant que le Playground est meilleur que ChatGPT, loin de là.

  • [Utilisateur supprimé]
    Modifié (February 2023)
    Une fonction qui doit renvoyer une image ne peut pas renvoyer une chaîne (ou j'ai mal compris la première solution). Par ailleurs une fonction qui doit renvoyer une image ne doit pas faire autre chose que renvoyer une image.
    Bref, la levée d'exception est un mécanisme présent dans pratiquement tous les langages "évolués" et est robuste. Il permet justement de ne pas réinventer la roue, la "réinvention" ayant de grandes chances de fournir une roue crevée, carrée ou voilée...
    Contrairement à ce qui a été préconisé, les bonnes pratiques conseillent de ne surtout pas faire effectuer plus d'une chose par une fonction (question de simplicité, ré-utilisabilité et maintenabilité). Donc une fonction qui récupère des fichiers d'un répertoire ne devrait pas effectuer un traitement dessus (surtout un traitement complexe comme un traitement d'images). D'ailleurs, dans les bonnes pratiques figurent le nommage explicite des variables et fonctions. Une fonction RecupererBMPRépertoire() ne doit pas s'appeler RecupererBMPRépertoirePuisVerifierQuIlYEnAAuMoinsUnPuisFairePixellisation()... :wink:.
  • "le développeur de la fonction F1 doit penser à toutes les erreurs"
    Une fonction c'est un contrat : je prends ces paramètres, je m'engage à fournir ce résultat. Si un problème survient dans le traitement, il y a différentes façons de le traiter : soit on recherche finement les causes d'erreur, soit on les regroupe toutes dans une seule case. Ça donnera in fine un message très explicite ou au contraire très vague.
    Tout dépend du temps dont on dispose pour écrire le code et des conséquences. Dans un avion ou une navette spatiale (pour ne pas dire un missile, thème plus d'actualité !), on va chercher la finesse parce que ne pas savoir pourquoi l'avion ne vire pas quand on actionne le joystick est plus que "embêtant". Pour le site de la SNCF, un gros message s'affichera en rouge sans qu'on sache bien pourquoi ça ne fonctionne pas...
  • Une fonction qui doit renvoyer une image ne peut pas renvoyer une chaîne
    Dans la plupart des posts qui ont précédé, il était question de simplement renvoyer le nom de chaque image. Ce qui importe est l'algorithme de détection des images dans le répertoire donné, pas le traitement qui s'ensuivra pour chacune d'elles. D'ailleurs, une fois que tu as collecté le nom des images il est très facile de les charger pour traitement, donc cette étape a été volontairement zappée.
  • Dans le premier message, j'ai lu : "puis renvoie l'image dont le nom vérifie un certain critère"...
    Mais peu importe, si le contrat est de renvoyer une chaîne de caractères avec un nom d'image, renvoyer une chaîne avec un nom d'erreur n'est pas (pour moi et pour un certain nombre de développeurs) une "bonne pratique".
    Ce qui ne veut pas dire que ce n'est pas utilisé, y compris en production, y compris dans des équipes de sociétés du CAC 40...
Connectez-vous ou Inscrivez-vous pour répondre.