2009-01-01 5 views
48

Hace poco estuve enseñando Python y descubrí las expresiones idiomáticas LBYL/EAFP con respecto a la comprobación de errores antes de la ejecución del código. En Python, parece que el estilo aceptado es EAFP, y parece funcionar bien con el lenguaje.LBYL vs EAFP en Java?

LBYL (L ook B ntes 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 (es E asier a A sk F orgiveness que P error):

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

Mi pregunta es la siguiente: Yo nunca habían oído hablar de la utilización de EAFP como la construcción de la validación de datos principal, que viene de un fondo de Java y C++. ¿Es EAFP algo que es aconsejable usar en Java? ¿O hay demasiada sobrecarga de excepciones? Sé que solo hay gastos generales cuando se lanza una excepción, por lo que no estoy seguro de por qué no se usa el método más simple de EAFP. ¿Es solo preferencia?

Respuesta

5

Personalmente, y creo que esto está respaldado por la convención, EAFP nunca es un buen camino a seguir. Puede verlo como un equivalente a lo siguiente:

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

en contraposición a:

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

Además, tenga en cuenta lo siguiente:

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 

Esto puede parecer mucho menos elegante (y sí, este es un ejemplo crudo - tengan paciencia conmigo), pero le da una granularidad mucho mayor en el manejo del error, en lugar de envolverlo todo en un try-catch para obtener ese NullPointerException, y luego intenta averiguar dónde y por qué lo obtuviste.

La forma en que lo veo EAFP nunca debe usarse, excepto en situaciones excepcionales. Además, desde que planteó el problema: sí, el bloque try-catch incurre en algún cargo adicional incluso si no se produce la excepción.

+32

Este tipo de EAFP depende en parte de si las excepciones que evalúa van a ocurrir muy a menudo. Si no son probables, entonces EAFP es razonable. Si son comunes, entonces LBYL puede ser mejor. La respuesta probablemente también dependa del paradigma de manejo de excepciones disponible. En C, LBYL es necesario. –

+5

Estoy de acuerdo con Jonathan. En Python, EAFP es una buena forma de hacerlo. – Jeff

+8

una "excepción común" no es una excepción, por lo que LBYL será preferido en ese caso, ¿no crees? –

10

Las excepciones se manejan de manera más eficiente en Python que en Java, que es al menos en parte por qué ves esa construcción en Python. En Java, es más ineficiente (en términos de rendimiento) usar excepciones de esa manera.

+2

mipadi, ¿verdad? tener alguna idea de cómo Python logra esto? – duffymo

+3

@duffymo Tuve la misma pregunta, y la encontré aquí: http://stackoverflow.com/questions/598157/cheap-exception-handling-in-python –

+2

@duffymo la mayor parte de la discusión se centra en LBYL vs EAFP, pero uno de las respuestas relacionadas con cómo las excepciones se implementan realmente en CPython: http://docs.python.org/c-api/intro.html#exceptions –

109

Si accede a los archivos, EAFP es más confiable que LBYL, porque las operaciones involucradas en LBYL no son atómicas, y el sistema de archivos puede cambiar entre el momento en que mira y el momento en que salta. En realidad, el nombre estándar es TOCTOU: hora de comprobación, hora de uso; Los errores causados ​​por una comprobación incorrecta son errores de TOCTOU.

Considere crear un archivo temporal que debe tener un nombre único. La mejor forma de averiguar si el nombre de archivo elegido aún existe es intentar crearlo, asegurándose de que utiliza opciones para garantizar que su operación falle si el archivo ya existe (en términos de POSIX/Unix, el indicador O_EXCL al open()).Si intenta comprobar si el archivo ya existe (probablemente usando access()), entonces, entre el momento en que dice "No" y la hora en que intenta crear el archivo, alguien o alguna otra cosa puede haber creado el archivo.

Por el contrario, suponga que intenta leer un archivo existente. Su verificación de que el archivo existe (LBYL) puede decir "está allí", pero cuando realmente lo abre, encuentra "no está allí".

En ambos casos, debe verificar la operación final, y el LBYL no ayudó automáticamente.

(Si va a jugar con los programas SUID o SGID, access() hace una pregunta diferente,. Que puede ser relevante para LBYL, pero el código todavía tiene que tener en cuenta la posibilidad de un fallo)

+3

Un gran ejemplo, Jonathan. Esto probablemente tiene mucho sentido para los desarrolladores de Java que se han ocupado de la programación concurrente y del idioma de bloqueo comprobado. –

44

Además de el costo relativo de las excepciones en Python y Java, tenga en cuenta que hay una diferencia en la filosofía/actitud entre ellos. Java intenta ser muy estricto con los tipos (y todo lo demás), lo que requiere declaraciones explícitas y detalladas de las firmas de clase/método. Supone que debe saber, en cualquier momento, exactamente qué tipo de objeto está utilizando y qué es capaz de hacer. En contraste, el "tipado de pato" de Python significa que no se sabe con certeza (y que no debería importar) cuál es el tipo de manifiesto de un objeto, solo se tiene que preocupar de que grazna cuando se lo pida. En este tipo de ambiente permisivo, la única actitud cuerda es suponer que las cosas funcionarán, pero prepárate para lidiar con las consecuencias si no lo hacen. La restricción natural de Java no encaja bien con un enfoque tan informal. (Esto no pretende desacreditar ni el enfoque ni el lenguaje, sino decir que estas actitudes forman parte de la expresión idiomática de cada idioma, y ​​copiar expresiones idiomáticas entre diferentes idiomas a menudo puede llevar a incomodidad y mala comunicación ...)

+5

Además, http://oranlooney.com/lbyl-vs-eafp/ proporciona un buen conjunto de ventajas y desventajas para cada enfoque. –

+0

No estoy de acuerdo con que la tipificación flexible en Python hace que sea más o menos sensato usar EAFP. Cuando pides perdón, pides perdón por los casos esperados. Por ejemplo, si un método "cancelar" va a cancelar un objeto, detectaremos las excepciones que esperamos que regrese. Esperamos que la cancelación no se realice porque el objeto tiene relaciones activas que evitan que se cancele, y queremos comunicarlo nuevamente a la IU. Si el método cancel falla debido a una división imprevista por cero scenerio, queremos que falle normalmente como cualquier otro error en el programa. –

1

Considere estos Fragmentos de código:

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 

Ambos se ven correctos, ¿no? Pero uno de ellos no lo es.

El primero, utilizando LBYL, falla debido a una distinción sutil entre isdigit y isdecimal; cuando se le llama con la cadena "①²³₅", lanzará un error en lugar de devolver correctamente el valor predeterminado.

La última, utilizando EAFTP, da como resultado un manejo correcto, por definición. No hay margen para una discrepancia de comportamiento, porque el código que necesita el requisito es el código que afirma ese requisito.

El uso de LBYL significa tomar lógica interna y copiarlos en cada call-site. En lugar de tener una codificación canónica de sus requisitos, tiene la oportunidad de equivocarse cada vez que llama a la función.

Vale la pena señalar que EAFTP no es sobre excepciones, y especialmente el código de Java no debería usar excepciones de forma generalizada. Se trata de dar el trabajo correcto al bloque correcto de código. Como ejemplo, usar los valores de retorno Optional es una forma perfectamente válida de escribir el código EAFTP, y es mucho más efectivo para garantizar la corrección que LBYL.