Pourquoi il faut spécifier l’exception qu’on gère 21


On utilise beaucoup la gestion des exceptions en Python, mais le langage permet de faire des trucs apocryphes comme:

try:
    # un code qui va chier des bulles
except: # je les attrappe tous, comme les pokemons
    pass

Ceci n’est ni plus ni moins que l’équivalent de @ pour silent les erreurs en PHP.

C’est caca.

Et la raison pour laquelle j’oppose une aversion si mesurée mais néanmoins portée par des arguments forts, c’est que c’est le grand kiff de Max d’en mettre partout, et que la semaine dernière ça lui a valu un debuggage en prod.

Depuis le dernier déploiement, les logs d’un des workers d’un serveur d’encoding affichaient en boucle :

“Error accessing Database, check your connexion”

Du coup Max a cherché d’ou venait ce problème de database par les cas usuels : port ? Permission ? Network ? Mise à jour de lib de l’ORM ? Daemonisation ?

Le snippet qui provoquait ce message était (en simplifié) :

try:
    new_model = Model.objects.create(params)
except:
    self.logger.error("Error accessing Database, check your connexion")
    # + trucs pour gérer le bouzin

La raison du try / except est que parfois, sur ce serveur, et seulement sur ce serveur, la connection avec la DB distante droppait. Bug aléatoire ou de config ? Parfois il est plus simple de ne pas résoudre le problème et laisser le truc foirer de temps en temps, et juste garder un recovery. Ça évite un spécial case pour un seul serveur, et ça évite de migrer le serveur.

Sauf que ce try / except était générique, et il attrapait tout, même les rhumes.

Je lui ai demandé de virer le try / except pour voir ce qui se passait vraiment, et…

raise MissingDependency("The 'solr' backend requires the installation of 'pysolr'. Please refer to the documentation.")

Le truc qui n’a RIEN à voir avec la base de données. Solr est notre moteur de recherche, et c’est juste qu’à la création d’un objet de modèle Django, un signal est propagé pour mettre à jour l’index de Solr. Le nouveau déploiement était incomplet : il manquait une lib. La lib était importée à ce moment là, ça levait une exception, qui était attrapée bêtement par le try/except générique.

Bref, le code a été remplacé par

try:
    new_model = Model.objects.create(params)
except DataBaseError:
    self.logger.error("Error accessing Database, check your connexion")
    # + trucs pour gérer le bouzin

Si on l’avait eu depuis le début, l’erreur aurait été apparente tout de suite et rapidement corrigée.

Ne faites JAMAIS de try/except sans préciser l’exception que vous allez gérer. La seule raison pour laquelle cette feature existe, c’est qu’elle est pratique à utiliser dans un shell. En fait, même DataBaseError est trop générique comme Exception si vous voulez vraiment un controle fin.

Notez également qu’un except: pass est rarement une bonne idée. Au moins prenez le temps de logger ce qui se passe dans la clause except. Un bon log, c’est le début du bonheur.

21 thoughts on “Pourquoi il faut spécifier l’exception qu’on gère

  • N

    Et pourquoi ne pas plutôt utiliser un truc générique qui va exposer l’exception, du genre:

    try:

    except Exception, e:
    print "%s" % e

  • Sam Post author

    C’est une solution (qu’il faut coupler à un log du stacktrace complet sinon ça sert à rien), et ça dépend ce qu’on veut:

    – on veut que l’appli continue de tourner en mode dégradé jusqu’à ce qu’on répare: on peut utiliser except Exception, e.
    – on veut éviter que l’appli fasse des conneries dès que ça merde (ou gérer un nettoyage / une compensation pour chaque type d’erreur), il faut préciser l’exception.

  • Policier Moustachu

    J’ai une application de test en charge où j’ai plusieurs threads qui exécutent tous le même code. Si il y en a un qui crève il va bloquer tous les autres qui s’attendent à des points de rdv. Donc je fais des try génériques (avec log du stack trace).

    Par contre c’est du jython. C’est comme du python mais acheté à Marakech.

  • desfrenes

    Qu’on me brûle avec les hérétiques (https://github.com/desfrenes/Tags/blob/master/tagz.py#L76), tant pis pour moi (mais quand même: https://github.com/desfrenes/Tags/blob/master/tagz.py#L3 )

    Avant dans ce script je spécifiais bien comme il faut toutes les exceptions possibles… mais il y en avait vraiment trop de générées par la lib qui lisait les tags, et pas forcément prévisibles (à chaque fois que je relançais l’indexation je retombais sur une nouvelle).

    Ça me rappelle à une époque en basic: “on error resume next” :-)

  • Morgotth

    Et ce que le problème ne serait pas également dans votre utilisation des logs ?
    Pour des erreurs comme celle la qui engendre une erreur critique, j’aime bien faire :

    try:
        new_model = Model.objects.create(params)
    except:
        self.logger.exception("Error accessing Database, check your connexion")
        self.tout_fou_lcamp()

    exception étant du même niveau que error mais avec toute la stacktrace de l’exception à la suite !

    Le log sera verbeux mais bon, mieux vaut trop que trop peu ;)

    PS: rien n’empêche d’insérer l’exception avec un traitement spécifique par fille exception !

  • Sam Post author

    @Morgotth: dans ce cas précis, non, car on ne veut pas le stacktrace. On ne cherche pas à debugger l’exception via les logs. On veut soit que ça tourne si c’est de la DB sans nous faire chier, soit que ça s’arrête net. Dans d’autres cas, oui c’est clair.

  • Krysztof von Murphy

    C’est encore plus drôle dans du PL/SQL (Oracle) :

    BEGIN
    (logique complexe de mise à jour
    de données de stock, financières...)
    EXCEPTION
    WHEN OTHERS THEN NULL ;
    END ;
    COMMIT ;

    Toute erreur sera passée à la trappe, et la cohérence des données n’est pas garantie, pour ne pas dire qu’elle devient complètement aléatoire.

    J’appelle ça du sabotage. J’ai vu ça dans les spécifiques de l’ERP d’une usine, dans du code qui revenait d’Inde (ben oui, c’était moins cher que les prestataires locaux embedded dans l’équipe client et qui ont facturé 6 mois de plus pour remettre d’équerre).

  • desfrenes

    @kontre: je ne les ai plus en tête précisément. Je rejouerai le script sur mes fichiers de test en journalisant les types d’exceptions.

    Par contre ton post me donne la raison pour laquelle j’avais parfois du mal à arrêter ce script, merci pour ça ^^

  • Sam Post author

    @Lujeni: ce n’est pas le but de l’article, mais je pourrais faire un article sur les exceptions en général si ça intéresse du monde.

    @Krysztof von Murphy: le prix de l’offshore, les gens ne le comprennent qu’une fois qu’ils n’ont plus d’argent pour payer quelqu’un qui connait son métier. C’est d’autant plus dur qu’il y a de très mauvais pros en France, et de très bon offshore, du coup l’évaluation par un commercial au dela du prix du devis, ben…

  • Anucunnilinguiste

    Le off-shore dans l’industrie, c’est la dernière pièce de l’édifice qui consiste à tuer le savoir faire de nos ingénieurs.

    Cela dit dans le groupe sidérurgique qui m’emploie, nous nous sommes séparés de l’off-shore en Inde. Les programmeurs ont un niveau moyen et surtout ils ne connaissant pas les règles métier.
    Du coup les programmes nous sont renvoyés pour correction et les coûts explosent : on a donc stoppé notre collaboration et encore je ne vous parle pas des tentatives de passage de SI en full .net, on a dû faire machine arrière tellement ça tient pas la charge ;)

  • Anucunnilinguiste

    @Sam

    J’aimerais bien un article sur les Exceptions, ça pourrait le faire vu que c’est le truc qui me casse les couilles le plus souvent en Python ;)

Leave a comment

Des questions Python sans rapport avec l'article ? Posez-les sur IndexError.