Debería intentar ... atrapar ir dentro o fuera de un circuito?


185

Tengo un bucle que se parece a esto:

for (int i = 0; i < max; i++) {
    String myString = ...;
    float myNum = Float.parseFloat(myString);
    myFloats[i] = myNum;
}

Este es el contenido principal de un método cuyo único propósito es devolver la matriz de flotadores. Quiero que este método regrese nullsi hay un error, así que pongo el bucle dentro de un try...catchbloque, así:

try {
    for (int i = 0; i < max; i++) {
        String myString = ...;
        float myNum = Float.parseFloat(myString);
        myFloats[i] = myNum;
    }
} catch (NumberFormatException ex) {
    return null;
}

Pero también pensé en poner el try...catchbloque dentro del bucle, así:

for (int i = 0; i < max; i++) {
    String myString = ...;
    try {
        float myNum = Float.parseFloat(myString);
    } catch (NumberFormatException ex) {
        return null;
    }
    myFloats[i] = myNum;
}

¿Hay alguna razón, rendimiento o de otro tipo, para preferir uno sobre el otro?


Editar: El consenso parece ser que es más limpio poner el bucle dentro del try / catch, posiblemente dentro de su propio método. Sin embargo, todavía hay debate sobre cuál es más rápido. ¿Alguien puede probar esto y volver con una respuesta unificada?


2
No sé sobre el rendimiento, pero el código se ve más limpio con el intento de captura fuera del ciclo for.
Jack B Nimble

Respuestas:


132

ACTUACIÓN:

No hay absolutamente ninguna diferencia de rendimiento en el lugar donde se colocan las estructuras try / catch. Internamente, se implementan como una tabla de rango de código en una estructura que se crea cuando se llama al método. Mientras se ejecuta el método, las estructuras try / catch están completamente fuera de la imagen a menos que ocurra un lanzamiento, entonces la ubicación del error se compara con la tabla.

Aquí hay una referencia: http://www.javaworld.com/javaworld/jw-01-1997/jw-01-hood.html

La tabla se describe a mitad de camino hacia abajo.


Bien, entonces estás diciendo que no hay diferencia excepto la estética. ¿Puedes vincular a una fuente?
Michael Myers

2
Gracias. Supongo que las cosas podrían haber cambiado desde 1997, pero difícilmente lo harían menos eficiente.
Michael Myers

1
Absolutamente es una palabra dura. En algunos casos puede inhibir las optimizaciones del compilador. No es un costo directo, pero puede ser una penalización indirecta del rendimiento.
Tom Hawtin - tackline

2
Es cierto, supongo que debería haber reinado un poco en mi entusiasmo, ¡pero la información errónea me estaba poniendo de los nervios! En el caso de las optimizaciones, dado que no podemos saber de qué manera se vería afectado el rendimiento, creo que volvimos a probarlo (como siempre).
Jeffrey L Whitledge

1
no hay diferencia de rendimiento, solo si el código se ejecuta sin excepciones.
Onur Bıyık

72

Actuación : como dijo Jeffrey en su respuesta, en Java no hace mucha diferencia.

Generalmente , para facilitar la lectura del código, su elección de dónde capturar la excepción depende de si desea que el bucle siga procesándose o no.

En su ejemplo, regresó al detectar una excepción. En ese caso, pondría el try / catch alrededor del bucle. Si simplemente desea atrapar un valor malo pero continuar con el procesamiento, colóquelo dentro.

La tercera forma : siempre puede escribir su propio método ParseFloat estático y hacer que el manejo de excepciones se aborde en ese método en lugar de su bucle. ¡Hacer el manejo de excepciones aislado del bucle en sí!

class Parsing
{
    public static Float MyParseFloat(string inputValue)
    {
        try
        {
            return Float.parseFloat(inputValue);
        }
        catch ( NumberFormatException e )
        {
            return null;
        }
    }

    // ....  your code
    for(int i = 0; i < max; i++) 
    {
        String myString = ...;
        Float myNum = Parsing.MyParseFloat(myString);
        if ( myNum == null ) return;
        myFloats[i] = (float) myNum;
    }
}

1
Mira más cerca. Sale de la primera excepción en ambos. También pensé lo mismo al principio.
kervin

2
Era consciente, por eso lo dije en su caso, ya que él regresa cuando encuentra un problema, entonces usaría una captura externa. Para mayor claridad, esa es la regla que usaría ...
Ray Hayes

Buen código, pero desafortunadamente Java no tiene el lujo de salir de los parámetros.
Michael Myers

ByRef? ¡Hace tanto tiempo que no toco Java!
Ray Hayes

2
Alguien más sugirió TryParse que en C # /. Net le permite hacer un análisis seguro sin tener que lidiar con excepciones, eso ahora se replica y el método es reutilizable. En cuanto a la pregunta original, preferiría el bucle dentro de la captura, simplemente se ve más limpio, incluso si no hay ningún problema de rendimiento ..
Ray Hayes

46

Muy bien, después de que Jeffrey L Whitledge dijo que no había diferencia de rendimiento (a partir de 1997), fui y lo probé. Ejecuté este pequeño punto de referencia:

public class Main {

    private static final int NUM_TESTS = 100;
    private static int ITERATIONS = 1000000;
    // time counters
    private static long inTime = 0L;
    private static long aroundTime = 0L;

    public static void main(String[] args) {
        for (int i = 0; i < NUM_TESTS; i++) {
            test();
            ITERATIONS += 1; // so the tests don't always return the same number
        }
        System.out.println("Inside loop: " + (inTime/1000000.0) + " ms.");
        System.out.println("Around loop: " + (aroundTime/1000000.0) + " ms.");
    }
    public static void test() {
        aroundTime += testAround();
        inTime += testIn();
    }
    public static long testIn() {
        long start = System.nanoTime();
        Integer i = tryInLoop();
        long ret = System.nanoTime() - start;
        System.out.println(i); // don't optimize it away
        return ret;
    }
    public static long testAround() {
        long start = System.nanoTime();
        Integer i = tryAroundLoop();
        long ret = System.nanoTime() - start;
        System.out.println(i); // don't optimize it away
        return ret;
    }
    public static Integer tryInLoop() {
        int count = 0;
        for (int i = 0; i < ITERATIONS; i++) {
            try {
                count = Integer.parseInt(Integer.toString(count)) + 1;
            } catch (NumberFormatException ex) {
                return null;
            }
        }
        return count;
    }
    public static Integer tryAroundLoop() {
        int count = 0;
        try {
            for (int i = 0; i < ITERATIONS; i++) {
                count = Integer.parseInt(Integer.toString(count)) + 1;
            }
            return count;
        } catch (NumberFormatException ex) {
            return null;
        }
    }
}

Verifiqué el bytecode resultante usando javap para asegurarme de que nada estuviera en línea.

Los resultados mostraron que, suponiendo optimizaciones JIT insignificantes, Jeffrey tiene razón ; No hay absolutamente ninguna diferencia de rendimiento en Java 6, VM cliente Sun (no tenía acceso a otras versiones). La diferencia de tiempo total es del orden de unos pocos milisegundos durante toda la prueba.

Por lo tanto, la única consideración es lo que parece más limpio. Creo que la segunda forma es fea, así que me quedaré con la primera o con Ray Hayes .


Si el manejador de excepciones toma el flujo fuera del ciclo, creo que es más claro colocarlo fuera del ciclo, en lugar de colocar una cláusula catch aparentemente dentro del ciclo, que sin embargo regresa. Utilizo una línea de espacios en blanco alrededor del cuerpo "atrapado" de tales métodos "generales", para separar más claramente el cuerpo del bloque try que lo encierra todo .
Thomas W

16

Si bien el rendimiento puede ser el mismo y lo que "se ve" mejor es muy subjetivo, todavía hay una gran diferencia en la funcionalidad. Tome el siguiente ejemplo:

Integer j = 0;
    try {
        while (true) {
            ++j;

            if (j == 20) { throw new Exception(); }
            if (j%4 == 0) { System.out.println(j); }
            if (j == 40) { break; }
        }
    } catch (Exception e) {
        System.out.println("in catch block");
    }

El ciclo while está dentro del bloque try catch, la variable 'j' se incrementa hasta llegar a 40, se imprime cuando j mod 4 es cero y se lanza una excepción cuando j llega a 20.

Antes de cualquier detalle, aquí el otro ejemplo:

Integer i = 0;
    while (true) {
        try {
            ++i;

            if (i == 20) { throw new Exception(); }
            if (i%4 == 0) { System.out.println(i); }
            if (i == 40) { break; }

        } catch (Exception e) { System.out.println("in catch block"); }
    }

La misma lógica que la anterior, la única diferencia es que el bloque try / catch está ahora dentro del ciclo while.

Aquí viene la salida (mientras está en try / catch):

4
8
12 
16
in catch block

Y la otra salida (try / catch in while):

4
8
12
16
in catch block
24
28
32
36
40

Ahí tienes una diferencia bastante significativa:

mientras que en try / catch se escapa del ciclo

intentar / atrapar mientras mantiene el bucle activo


Por lo tanto, coloque try{} catch() {}el bucle si el bucle se trata como una sola "unidad" y al encontrar un problema en esa unidad hace que toda la "unidad" (o bucle en este caso) vaya hacia el sur. Ponga try{} catch() {}dentro del bucle si está tratando una única iteración del bucle, o un solo elemento en una matriz como una "unidad" completa. Si ocurre una excepción en esa "unidad", simplemente omitimos ese elemento y luego continuamos con la siguiente iteración del ciclo.
Galaxy

14

Estoy de acuerdo con todas las publicaciones de rendimiento y legibilidad. Sin embargo, hay casos en los que realmente importa. Otras personas mencionaron esto, pero podría ser más fácil de ver con ejemplos.

Considere este ejemplo ligeramente modificado:

public static void main(String[] args) {
    String[] myNumberStrings = new String[] {"1.2345", "asdf", "2.3456"};
    ArrayList asNumbers = parseAll(myNumberStrings);
}

public static ArrayList parseAll(String[] numberStrings){
    ArrayList myFloats = new ArrayList();

    for(int i = 0; i < numberStrings.length; i++){
        myFloats.add(new Float(numberStrings[i]));
    }
    return myFloats;
}

Si desea que el método parseAll () devuelva nulo si hay algún error (como en el ejemplo original), colocaría el try / catch en el exterior de esta manera:

public static ArrayList parseAll1(String[] numberStrings){
    ArrayList myFloats = new ArrayList();
    try{
        for(int i = 0; i < numberStrings.length; i++){
            myFloats.add(new Float(numberStrings[i]));
        }
    } catch (NumberFormatException nfe){
        //fail on any error
        return null;
    }
    return myFloats;
}

En realidad, probablemente debería devolver un error aquí en lugar de nulo, y en general no me gusta tener devoluciones múltiples, pero se entiende la idea.

Por otro lado, si desea que simplemente ignore los problemas y analice las cadenas que pueda, colocará el try / catch en el interior del bucle de esta manera:

public static ArrayList parseAll2(String[] numberStrings){
    ArrayList myFloats = new ArrayList();

    for(int i = 0; i < numberStrings.length; i++){
        try{
            myFloats.add(new Float(numberStrings[i]));
        } catch (NumberFormatException nfe){
            //don't add just this one
        }
    }

    return myFloats;
}

2
Es por eso que dije "Si simplemente quieres atrapar un valor malo pero seguir procesando, ponlo dentro".
Ray Hayes

5

Como ya se mencionó, el rendimiento es el mismo. Sin embargo, la experiencia del usuario no es necesariamente idéntica. En el primer caso, fallará rápidamente (es decir, después del primer error), sin embargo, si coloca el bloque try / catch dentro del bucle, puede capturar todos los errores que se crearían para una llamada dada al método. Al analizar una matriz de valores de cadenas en las que espera algunos errores de formato, definitivamente hay casos en los que le gustaría poder presentar todos los errores al usuario para que no tenga que intentar corregirlos uno por uno .


Esto no es cierto para este caso en particular (estoy regresando en el bloque catch), pero es bueno tenerlo en cuenta en general.
Michael Myers

4

Si falla todo o nada, entonces el primer formato tiene sentido. Si desea poder procesar / devolver todos los elementos que no fallan, debe usar el segundo formulario. Esos serían mis criterios básicos para elegir entre los métodos. Personalmente, si es todo o nada, no usaría la segunda forma.


4

Siempre y cuando sepa lo que necesita lograr en el ciclo, puede poner el try catch fuera del ciclo. Pero es importante comprender que el ciclo finalizará tan pronto como ocurra la excepción y que no siempre sea lo que desea. Esto es realmente un error muy común en el software basado en Java. Las personas necesitan procesar una serie de elementos, como vaciar una cola y confiar falsamente en una declaración externa try / catch que maneje todas las excepciones posibles. También podrían estar manejando solo una excepción específica dentro del ciclo y no esperar que ocurra ninguna otra excepción. Luego, si se produce una excepción que no se maneja dentro del ciclo, entonces el ciclo se "compensará", posiblemente finalice prematuramente y la instrucción catch externa maneja la excepción.

Si el ciclo tenía como función en la vida vaciar una cola, es muy probable que ese ciclo finalice antes de que esa cola se vacíe realmente. Falla muy común.


2

En sus ejemplos no hay diferencia funcional. Encuentro tu primer ejemplo más legible.


2

Debería preferir la versión externa sobre la versión interna. Esta es solo una versión específica de la regla, mueve cualquier cosa fuera del ciclo que puedas mover fuera del ciclo. Dependiendo del compilador IL y el compilador JIT, sus dos versiones pueden o no tener diferentes características de rendimiento.

En otra nota, probablemente deberías mirar float. TryParse o Convert.ToFloat.


2

Si coloca el try / catch dentro del bucle, seguirá haciendo bucles después de una excepción. Si lo coloca fuera del bucle, se detendrá tan pronto como se produzca una excepción.


Bueno, estoy devolviendo nulo por excepción, por lo que no seguiría procesándose en mi caso.
Michael Myers

2

Mi perspectiva sería que los bloques try / catch son necesarios para asegurar un manejo adecuado de las excepciones, pero crear tales bloques tiene implicaciones de rendimiento. Como los bucles contienen cálculos repetitivos intensivos, no se recomienda poner bloques try / catch dentro de los bucles. Además, parece que donde ocurre esta condición, a menudo se detecta "Excepción" o "RuntimeException". Se debe evitar la excepción RuntimeException atrapada en el código. Nuevamente, si trabaja en una gran empresa, es esencial registrar esa excepción correctamente o detener la ejecución de la excepción de tiempo de ejecución. Todo el punto de esta descripción esPLEASE AVOID USING TRY-CATCH BLOCKS IN LOOPS


1

configurar un marco de pila especial para el try / catch agrega una sobrecarga adicional, pero la JVM puede detectar el hecho de que está regresando y optimizar esto.

Dependiendo del número de iteraciones, la diferencia de rendimiento probablemente será insignificante.

Sin embargo, estoy de acuerdo con los demás en que tenerlo fuera del bucle hace que el cuerpo del bucle se vea más limpio.

Si existe la posibilidad de que alguna vez desee continuar con el procesamiento en lugar de salir si hay un número no válido, entonces querrá que el código esté dentro del bucle.


1

Si está adentro, entonces ganará la sobrecarga de la estructura try / catch N veces, en lugar de solo una vez en el exterior.


Cada vez que se llama a una estructura Try / Catch, agrega una sobrecarga a la ejecución del método. Solo un poco de memoria y procesadores necesarios para lidiar con la estructura. Si está ejecutando un bucle 100 veces, y por razones hipotéticas, digamos que el costo es de 1 tic por llamada de prueba / captura, entonces tener el Try / Catch dentro del bucle le cuesta 100 tics, en lugar de solo 1 tick si es fuera del circuito.


¿Puedes elaborar un poco sobre los gastos generales?
Michael Myers

1
Está bien, pero Jeffrey L Whitledge dice que no hay gastos adicionales. ¿Puede proporcionar una fuente que dice que hay?
Michael Myers

El costo es pequeño, casi insignificante, pero está ahí: todo tiene un costo. Aquí hay un artículo de blog de Programmer's Heaven ( tiny.cc/ZBX1b ) sobre .NET y una publicación del grupo de noticias de Reshat Sabiq sobre JAVA ( tiny.cc/BV2hS )
Stephen Wrighton

Ya veo ... Entonces, la pregunta es, ¿es el costo incurrido al ingresar al try / catch (en cuyo caso debe estar fuera del ciclo), o es constante durante la duración del try / catch (en cuyo caso debería estar dentro del bucle)? Dices que es el # 1. Y todavía no estoy totalmente convencido de que es un costo.
Michael Myers

1
Según este libro de Google ( tinyurl.com/3pp7qj ), "las últimas máquinas virtuales no muestran penalización".
Michael Myers

1

El punto principal de las excepciones es fomentar el primer estilo: permitir que el manejo de errores se consolide y se maneje una vez, no inmediatamente en cada sitio de error posible.


¡Incorrecto! El punto de excepciones es determinar qué comportamiento excepcional adoptar cuando ocurre un "error". Por lo tanto, elegir el bloque try / catch interno o externo realmente depende de cómo desee manejar el tratamiento de bucle.
gizmo

Eso se puede hacer igualmente bien con tratamientos de error previos a la excepción, como pasar banderas de "error ocurrido".
wnoise

1

ponlo dentro. Puede seguir procesando (si lo desea) o puede lanzar una excepción útil que le dice al cliente el valor de myString y el índice de la matriz que contiene el valor incorrecto. Creo que NumberFormatException ya le dirá el valor incorrecto, pero el principio es colocar todos los datos útiles en las excepciones que arroje. Piense en lo que sería interesante para usted en el depurador en este punto del programa.

Considerar:

try {
   // parse
} catch (NumberFormatException nfe){
   throw new RuntimeException("Could not parse as a Float: [" + myString + 
                              "] found at index: " + i, nfe);
} 

En el momento de necesidad, realmente apreciará una excepción como esta con tanta información como sea posible.


Sí, este es un punto válido también. En mi caso, estoy leyendo un archivo, por lo que todo lo que realmente necesito es la cadena que no se pudo analizar. Entonces hice System.out.println (ex) en lugar de lanzar otra excepción (quiero analizar la mayor parte del archivo como sea legal).
Michael Myers

1

Me gustaría agregar la mía 0.02csobre dos consideraciones competitivas cuando analizo el problema general de dónde ubicar el manejo de excepciones:

  1. La responsabilidad "más amplia" del try-catchbloque (es decir, fuera del bucle en su caso) significa que al cambiar el código en algún momento posterior, puede agregar por error una línea que es manejada por su catchbloque existente ; posiblemente sin querer. En su caso, esto es menos probable porque está capturando explícitamente unNumberFormatException

  2. Cuanto más "estrecha" sea la responsabilidad del try-catchbloque, más difícil será la refactorización. Particularmente cuando (como en su caso) está ejecutando una instrucción "no local" desde dentro del catchbloque (la return nulldeclaración).


1

Eso depende del manejo de fallas. Si solo quiere omitir los elementos de error, intente adentro:

for(int i = 0; i < max; i++) {
    String myString = ...;
    try {
        float myNum = Float.parseFloat(myString);
        myFloats[i] = myNum;
    } catch (NumberFormatException ex) {
        --i;
    }
}

En cualquier otro caso, preferiría probar afuera. El código es más legible, es más limpio. Tal vez sería mejor lanzar una IllegalArgumentException en el caso de error en lugar de devolver nulo.


1

Pondré mis $ 0.02. A veces terminas necesitando agregar un "finalmente" más adelante en tu código (porque ¿quién escribe su código perfectamente la primera vez?). En esos casos, de repente tiene más sentido tener el try / catch fuera del ciclo. Por ejemplo:

try {
    for(int i = 0; i < max; i++) {
        String myString = ...;
        float myNum = Float.parseFloat(myString);
        dbConnection.update("MY_FLOATS","INDEX",i,"VALUE",myNum);
    }
} catch (NumberFormatException ex) {
    return null;
} finally {
    dbConnection.release();  // Always release DB connection, even if transaction fails.
}

Porque si obtiene un error, o no, solo desea liberar su conexión de base de datos (o elegir su tipo favorito de otro recurso ...) una vez.


1

Otro aspecto no mencionado en lo anterior es el hecho de que cada try-catch tiene alguna impacto en la pila, lo que puede tener implicaciones para los métodos recursivos.

Si el método "exterior ()" llama al método "interno ()" (que puede llamarse a sí mismo de forma recursiva), intente localizar el try-catch en el método "externo ()" si es posible. Un ejemplo simple de "bloqueo de pila" que utilizamos en una clase de rendimiento falla a unos 6.400 cuadros cuando el try-catch está en el método interno, y a unos 11.600 cuando está en el método externo.

En el mundo real, esto puede ser un problema si está utilizando el patrón Compuesto y tiene estructuras anidadas grandes y complejas.


0

Si desea capturar la Excepción para cada iteración, o verificar en qué iteración se produce la Excepción y capturar todas las Excepciones en una iteración, coloque intentar ... atrapar dentro del ciclo. Esto no interrumpirá el ciclo si ocurre una Excepción y puede capturar cada Excepción en cada iteración a lo largo del ciclo.

Si desea romper el ciclo y examinar la excepción cada vez que se lanza, use try ... catch fuera del ciclo. Esto romperá el ciclo y ejecutará declaraciones después de catch (si las hay).

Todo depende de tu necesidad. Prefiero usar try ... catch dentro del bucle durante la implementación ya que, si se produce una excepción, los resultados no son ambiguos y el bucle no se romperá y ejecutará por completo.

Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.