¿LBYL vs EAFP en Java?

Hace poco me enseñé Python y descubrí los modismos 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 idioma.

LBYL ( L ook B efore 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 más fácil que el permiso de emisión ):

 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: nunca había oído hablar del uso de EAFP como la construcción de validación de datos primaria, proveniente de un entorno Java y C ++. ¿Es EAFP algo que es sabio usar en Java? ¿O hay demasiada sobrecarga de las excepciones? Sé que solo hay una sobrecarga cuando realmente se lanza una excepción, así que no estoy seguro de por qué no se usa el método más simple de EAFP. ¿Es solo preferencia?

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

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

    Opuesto a:

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

    Además, considere 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 burdo, tenga paciencia conmigo), pero le brinda una granularidad mucho mayor en el manejo del error, en lugar de envolverlo todo en un try-catch para obtener esa NullPointerException , y luego trata de averiguar dónde y por qué lo conseguiste.

    Tal como lo veo, nunca se debe usar EAFP, excepto en situaciones raras. Además, ya que planteó el problema: sí, el bloque try-catch incurre en algunos gastos generales, incluso si la excepción no se produce.

    Si está accediendo a 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 que salta. En realidad, el nombre estándar es TOCTOU: tiempo de verificación, tiempo de uso; Los errores causados ​​por la comprobación inexacta son errores TOCTOU.

    Considere crear un archivo temporal que debe tener un nombre único. La mejor manera de averiguar si el nombre de archivo elegido todavía es intentar crearlo, asegurándose de que utiliza las opciones para asegurarse de que su operación falle si el archivo ya existe (en términos de POSIX / Unix, el indicador O_EXCL para open() ). Si intenta probar si el archivo ya existe (probablemente usando access() ), entonces entre el momento en que dice “No” y el momento en que intenta crear el archivo, es posible que alguien o alguien más haya creado el archivo.

    A la inversa, suponga que intenta leer un archivo existente. Tu cheque de que el archivo existe (LBYL) puede decir “está ahí”, pero cuando lo abres, encuentras “no está allí”.

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

    (Si está jugando con los progtwigs SUID o SGID, access() hace una pregunta diferente; puede ser relevante para LBYL, pero el código aún debe tener en cuenta la posibilidad de falla).

    Además del costo relativo de las excepciones en Python y Java, tenga en cuenta que existe una diferencia de filosofía / actitud entre ellas. Java intenta ser muy estricto con respecto a los tipos (y todo lo demás), requiriendo declaraciones explícitas y detalladas de firmas de clase / método. Supone que debe saber, en cualquier momento, exactamente qué tipo de objeto está usando y qué es capaz de hacer. En contraste, la “tipografía de pato” de Python significa que no está seguro (y no debería importarle) cuál es el tipo manifiesto de un objeto, solo tiene que preocuparse de que lo reproduzca cuando lo pida. En este tipo de ambiente permisivo, la única actitud sensata es suponer que las cosas funcionarán, pero prepárate para enfrentar 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 más bien decir que estas actitudes son parte del lenguaje de cada idioma, y ​​la copia de los idiomas entre diferentes idiomas a menudo puede llevar a la torpeza y la mala comunicación …)

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

    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 bien, ¿verdad? Pero uno de ellos no lo es.

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

    Lo último, usando EAFTP, resulta en un manejo correcto, por definición. No hay margen para una falta de coincidencia de comportamiento, porque el código que necesita el requisito es el código que afirma ese requisito.

    Usar LBYL significa tomar la lógica interna y copiarla en cada sitio de llamada. En lugar de tener una encoding canónica de sus requisitos, tiene la posibilidad de desordenar cada vez que llama a la función.

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