LBYL vs EAFP en Java?

Je me suis enseigné récemment Python et j’ai découvert les idiomes LBYL / EAFP en ce qui concerne la vérification des erreurs avant l’exécution du code. En Python, il semble que le style accepté soit l’EAFP, et il semble bien fonctionner avec le langage.

LBYL ( L ook Avant Y ou L eap):

def safe_divide_1(x, y): if y == 0: print "Divide-by-0 attempt detected" return None else: return x/y 

EAFP ( c’est plus que la préautorisation ):

 def safe_divide_2(x, y): try: return x/y except ZeroDivisionError: print "Divide-by-0 attempt detected" return None 

Ma question est la suivante: je n’avais jamais entendu parler de l’utilisation d’EAFP en tant que structure de validation de données principale, provenant d’un arrière-plan Java et C ++. Est-ce que l’EAFP est quelque chose de sage à utiliser en Java? Ou y a-t-il trop de frais généraux découlant des exceptions? Je sais qu’il n’y a qu’une surcharge lorsqu’une exception est réellement lancée, alors je ne suis pas sûr de savoir pourquoi la méthode plus simple d’EAFP n’est pas utilisée. Est-ce juste la préférence?

Personnellement, et je pense que cela est soutenu par une convention, l’EAFP n’est jamais une bonne solution. Vous pouvez le regarder comme un équivalent à ce qui suit:

 if (o != null) o.doSomething(); else // handle 

par opposition à:

 try { o.doSomething() } catch (NullPointerException npe) { // handle } 

De plus, considérez ce qui suit:

 if (a != null) if (b != null) if (c != null) a.getB().getC().doSomething(); else // handle c null else // handle b null else // handle a null 

Cela peut paraître moins élégant (et oui, c’est un exemple sommaire – mais je vous conseille), mais cela vous donne une granularité beaucoup plus grande dans le traitement de l’erreur, au lieu de l’encapsuler pour obtenir cette NullPointerException . puis essayez de trouver où et pourquoi vous l’avez.

À mon avis, l’EAFP ne devrait jamais être utilisé, sauf dans de rares cas. En outre, depuis que vous avez soulevé le problème: oui, le bloc try-catch induit une surcharge même si l’exception n’est pas levée.

Si vous accédez à des fichiers, EAFP est plus fiable que LBYL, car les opérations impliquées dans LBYL ne sont pas atomiques et le système de fichiers peut changer entre le moment où vous regardez et le moment où vous sautez. En fait, le nom standard est TOCTOU – Heure du contrôle, heure d’utilisation; Les bogues causés par une vérification inexacte sont des bogues de TOCTOU.

Envisagez de créer un fichier temporaire qui doit avoir un nom unique. La meilleure façon de savoir si le nom de fichier choisi existe encore est d’essayer de le créer – en vous assurant d’utiliser les options pour vous assurer que votre opération échoue si le fichier existe déjà (en termes POSIX / Unix, l’indicateur O_EXCL open() ). Si vous essayez de tester si le fichier existe déjà (probablement en utilisant access() ), alors entre le moment où cela dit “Non” et le moment où vous essayez de créer le fichier, quelqu’un ou quelque chose d’autre peut avoir créé le fichier.

À l’inverse, supposons que vous essayiez de lire un fichier existant. Votre vérification que le fichier existe (LBYL) peut dire “il est là”, mais quand vous l’ouvrez réellement, vous trouvez “il n’est pas là”.

Dans ces deux cas, vous devez vérifier l’opération finale – et le LBYL n’a pas aidé automatiquement.

(Si vous utilisez des programmes SUID ou SGID, access() pose une question différente, cela peut concerner LBYL, mais le code doit toujours tenir compte de la possibilité d’échec.)

Outre le coût relatif des exceptions en Python et Java, gardez à l’esprit qu’il existe une différence de philosophie / d’attitude entre eux. Java essaie d’être très ssortingct sur les types (et tout le rest), nécessitant des déclarations explicites et détaillées des signatures de classe / méthode. Cela suppose que vous sachiez à tout moment exactement quel type d’object vous utilisez et ce qu’il est capable de faire. En revanche, “Duck Typing” de Python signifie que vous ne savez pas avec certitude (et ne devrait pas vous en soucier) quel est le type de manifeste d’un object, vous n’avez besoin de vous en soucier que lorsque vous le lui demandez. Dans ce type d’environnement permissif, la seule attitude saine consiste à présumer que les choses vont fonctionner, mais soyez prêt à faire face aux conséquences si ce n’est pas le cas. La ressortingction naturelle de Java ne correspond pas bien à une approche aussi décontractée. (Ceci n’est pas destiné à dénigrer l’approche ou la langue, mais plutôt à dire que ces attitudes font partie de l’idiome de chaque langue, et la copie des idiomes entre différentes langues peut souvent conduire à la maladresse et à une mauvaise communication …)

Les exceptions sont gérées plus efficacement en Python qu’en Java, ce qui est au moins en partie la raison pour laquelle vous voyez cette construction en Python. En Java, il est plus inefficace (en termes de performances) d’utiliser les exceptions de cette manière.

Considérez ces extraits de code:

 def int_or_default(x, default=0): if x.isdigit(): return int(x) else: return default def int_or_default(x, default=0): try: return int(x) except ValueError: return default 

Ils ont tous les deux l’air correct, non? Mais l’un d’eux n’est pas.

Le premier, utilisant LBYL, échoue à cause d’une distinction subtile entre isdigit et isdecimal ; Lorsqu’elle est appelée avec la chaîne “①²³🄅₅”, elle génère une erreur plutôt que de renvoyer correctement la valeur par défaut.

Le dernier, en utilisant EAFTP, permet une gestion correcte, par définition. Il n’y a pas de place pour une incompatibilité de comportement, car le code qui nécessite cette exigence est le code qui affirme cette exigence.

Utiliser LBYL signifie prendre la logique interne et les copier dans chaque site d’appel. Plutôt que d’avoir un encodage canonique de vos besoins, vous avez la chance de perdre tout le temps que vous appelez la fonction.

Il convient de noter que l’EAFTP ne concerne pas les exceptions et que le code Java, en particulier, ne devrait pas utiliser les exceptions de manière généralisée. Il s’agit de donner le bon travail au bon bloc de code. À titre d’exemple, l’utilisation de valeurs de retour Optional est une manière parfaitement valable d’écrire du code EAFTP et est beaucoup plus efficace pour garantir l’exactitude que LBYL.