2009-03-24 5 views
7

En un proyecto Java actual que tenemos un código similar al siguiente ejemplo:cómo ejecutar mejor un conjunto de métodos, incluso si una excepción ocurre

try { 
    doSomeThing(anObject); 
} 
catch (SameException e) { 
    // Do nothing or log, but don't abort current method. 
} 

try { 
    doOtherThing(anObject); 
} 
catch (SameException e) { 
    // Do nothing or log, but don't abort current method. 
} 

// ... some more calls to different method ... 

try { 
    finallyDoYetSomethingCompletelyDifferent(anObject); 
} 
catch (SameException e) { 
    // Do nothing or log, but don't abort current method. 
} 

Como se puede ver, varios método diferente se denominan con la exacta mismo objeto y para cada llamada se captura la misma excepción y se maneja de la misma manera (o de una manera muy similar). La excepción no se vuelve a lanzar, pero solo se puede registrar y luego descartar.

La única razón por la que hay un try-catch alrededor de cada método, es ejecutar siempre todos los métodos, sin importar si un método ejecutado anteriormente falló.

No me gusta el código anterior. Ocupa mucho espacio, es muy repetitivo (especialmente el registro realizado en el bloque catch, no se presenta aquí) y simplemente se ve mal.

Puedo pensar en otras formas de escribir este código, pero tampoco me gustan mucho. Las siguientes opciones vinieron a la mente:

secuencia de bucle interruptor/de-caso paradigmático

(Ver Wikipedia o The Daily WTF)

for (int i = 0; i <= 9; i++) { 
    try { 
     switch (i) { 
      case 0: 
       doSomeThing(anObject); break; 
      case 1: 
       doOtherSomeThing(anObject); break; 
      // ...More cases... 
      case 9: 
       doYetSomethingCompletelyDifferent(anObject); break; 
     } 
    } 
    catch (SameException e) { 
     // Do nothing or log, but don't abort current method. 
    } 
} 

Esto es obviamente mal código, muy propenso a errores y parece amateur.

Reflexión

utilizar la reflexión para llegar Method objetos para los métodos para llamar y los almacenan en una lista en el orden en que se supone que deben ser ejecutados. Luego itere sobre esta lista y llame al método usando anObject como único parámetro. La excepción se maneja dentro del bucle.

No me gusta este enfoque, ya que los errores (por ejemplo, los errores ortográficos en los nombres de los métodos) solo aparecen durante el tiempo de ejecución y la API de reflexión es algo hablador.

Functor

crear una clase Functor así:

private class Functor 
{ 
    void doStuff(MyObject object) throws SameException; 
} 

continuación, crear una lista de objetos Functor que llaman a los métodos. De esta manera:

List<Functor> functors = new ArrayList<Functor>(); 

functors.add(new Functor() { 
    @Override 
    public void execute(MyObject anObject) { 
     doSomeThing(anObject); 
    } 
}); 

functors.add(new Functor() { 
    @Override 
    public void execute(MyObject anObject) { 
     doOtherSomeThing(anObject); 
    } 
}); 

Más tarde, recorrer esta lista y llamar execute() en cada objeto Functor. Puedo resumir mi sentimiento acerca de este enfoque con dos palabras: Código hinchado.


Como realmente no me gustan los cuatro enfoques, me gustaría discutir este problema aquí. ¿Cuál crees que es el mejor enfoque? ¿Cómo resolvió problemas similares en el pasado? ¿Hay quizás una solución mucho más simple que extrañé por completo?

Respuesta

5

El enfoque de functor es el mejor para mí, es una lástima que Java no tenga una mejor manera de representar cierres o delegados.Eso es básicamente lo que eres realmente después, y en C# (y en muchos otros idiomas) sería trivial.

Puede reducir la hinchazón física mediante el uso de un poco algo como:

Functor[] functors = new Functor[] { 
    new Functor() { @Override public void execute(MyObject anObject) { 
     doSomeThing(anObject); 
    }}, 
    new Functor() { @Override public void execute(MyObject anObject) { 
     doSomeOtherThing(anObject); 
    }} 
}; 

El espacio en blanco colapsando aquí bien puede estar en contra de la guía de estilo que está utilizando, pero creo que hace que el código sea más fácil de hecho, lea, ya que puede ver la carne más fácilmente.

mejor empezar a ejercer presión para cierres en Java 8;)

+0

La hinchazón es aún peor que eso: usted (y él) olvidó la cláusula throws. –

+0

¡Guau, eres rápido! :) También pensé en cierres/delegados. Pero, como dijiste, la construcción más similar que Java tiene para ofrecer son funtores. Quizás tenga razón sobre el uso de un formato de código que es mejor para leer, pero no necesariamente respeta la guía de estilo que utilizamos. –

+0

@mmyers En el código original, la excepción capturada es una RuntimeException. Entonces esto no es un problema en este caso particular. (En otros puede ser, por supuesto.) –

6

desearía que se enfoque refactorización (o "Por qué hemos llegado hasta aquí, en primer lugar?"):

considerar por qué el individuo los métodos pueden arrojar la excepción después de hacer "cosas" con myObject y esa excepción puede ignorarse de forma segura. Como la excepción escapa al método, myObject debe estar en un estado desconocido.

Si es seguro ignorar la excepción, seguramente debe ser la forma incorrecta de comunicar que algo salió mal en cada método.

En su lugar, tal vez sea cada método que necesita realizar alguna sesión de error. Si no usa registradores estáticos, puede pasar un registrador a cada método.

+0

Desafortunadamente, la excepción original es del tipo de excepción de VestraintViolation lanzada por (creo) Hibernate. Esto no puede ser cambiado. Podríamos atrapar y manejar la excepción en cada método. Pero eso no mejoraría el código. Idealmente, me gustaría tener solo una cláusula catch para todas las llamadas. –

+0

Pero eso significaría que no se escribió nada en la base de datos, entonces, ¿por qué intentar continuar con la transacción? Dependiendo de la base de datos subyacente, ya estará marcado para la reversión. – PeterR

+0

A menos que esté tomando la excepción para decir "El objeto ya existe en la tabla, entonces ignoramos la falla al insertar". La forma correcta es primero probar y cargar el objeto en la memoria caché de la sesión de Hibernates, luego ejecutar guardar (o guardarOrUpdate). Lo sé porque he estado allí yo mismo :) – PeterR

2

Estoy de acuerdo con PeterR. Me cuesta creer que realmente quieras continuar la ejecución de algo después de que se haya lanzado una excepción. Si lo haces, entonces algo excepcional probablemente no haya sucedido realmente.

Dicho de otra manera, las excepciones no se deben utilizar para el registro o control de flujo. Solo deben usarse cuando ocurre algo excepcional que el código en el nivel donde se lanzó la excepción no puede tratar.

Como tal, internalizaría los mensajes de registro y eliminaría las excepciones que se lanzan.

Como mínimo, creo que debe volver atrás y volver a entender qué intenta hacer el código y qué valor comercial o reglas se están implementando. Como dijo PeterR, intenta entender "¿por qué llegamos aquí en primer lugar?" parte del código y qué significa exactamente las excepciones.

2

¿Los métodos que llama están bajo su control? Si es así, en este caso especial volver códigos de error (u objetos) podría ceder el paso a un mejor diseño general que el uso de excepciones:

handle(doSomething(anObject)); 
handle(doOtherThing(anObject)); 
// some more calls to different methods 
handle(finallyDoYetSomethingCompletelyDifferent(anObject)); 

con

private void handle(ErrorCode errorCode) { 
    // Do something about it 
} 

y

private ErrorCode doSomething(Object anObject) { 
    // return ErrorCode describing the operation's outcome 
} 

Esto parece menos detallado, aunque no SECO.

Alternativamente, se utiliza algún mecanismo AOP para interceptar las llamadas a doSomething, doOtherThing y finallyDoYetSomethingCompletelyDifferent con un Consejo Alrededor de ese primeros mangos y luego descarta la excepción. Combine eso con RuntimeExceptions y un pointcut basado en una buena anotación descriptiva y capture perfectamente lo que parece ser una especie de preocupación transversal oculta.

Confieso que me gusta el enfoque de Functor, aunque.

EDIT: Acabo de ver su comentario en una de las respuestas. Probablemente vaya con el enfoque de AOP en ese caso.

3

En la primera mirada, estoy de acuerdo con PeterR: si es seguro ignorar la excepción tan fácilmente, tal vez ese método no debería arrojar una excepción.

Sin embargo, si está seguro de que eso es exactamente lo que quiere, por ejemplo, tal vez se trabaja con métodos de una biblioteca de 3 ª parte que insisten en lanzar excepciones específicas, optaría por el siguiente enfoque:

  1. crear una interfaz que contiene todos los métodos que pueden ser llamados:

      
    public interface XyzOperations { 
        public void doSomething(Object anObject); 
        public void doOtherThing(Object anObject); 
        ... 
        public void finallyDoYetSomethingCompletelyDifferent(Object anObject); 
    
  2. crear una clase de implementación por defecto para los métodos de métodos apropiados, posiblemente refactorización ellos de algún otro lugar:

     
        public class DefaultXyzOperations implements XyzOperations { 
        ... 
        } 
    
  3. uso de una clase Java Proxy para crear un proxy dinámico en XyzOperations que delegar todos los métodos para DefaultXyzOperations, pero, tendría el control de excepciones centralizada en su InvocationHandler. No elaborarán las siguientes, pero es un esquema básico:

     
        XyzOperations xyz = (XyzOperations)Proxy.newProxyInstance(
         XyzOperations.class.getClassLoader(), 
         new Class[] { XyzOperations.class }, 
         new InvocationHandler() { 
          public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { 
           try { 
             method.invoke(new DefaultXyzOperations(), args); 
           } 
           catch(SameException e) { 
            // desired exception handling 
           } 
          } 
         }); 
    
  4. uso esa instancia de proxy a partir de entonces, sólo tiene que llamar a los métodos deseados

Como alternativa, se puede usar AspectJ o similares Solución AOP para agregar consejos sobre todos los métodos de XyzOperations y hacer el manejo de excepciones allí.

Si prefiere introducir una nueva dependencia, o escribir proxies manualmente depende de su preferencia personal y la cantidad total de código donde necesita dicho comportamiento.

1

Dejando de lado el problema de la refactorización, AspectJ (o similar) parece la forma más fácil de atrapar/informar de forma transparente estas excepciones. Debería poder configurarlo de forma que entrelazara el try/catch alrededor de las llamadas al método incluso si agrega nuevas (el bloque de código de arriba sería, sospecho, bastante frágil si alguien modifica ese código sin entenderlo por completo). la razón detrás de ella)

0

Si se va la ruta diferente estilo de código, que se pegaba con un simple:

try { doSomeThing(anObject); } catch (SameException e) { Log(e); } 
try { doOtherThing(anObject); } catch (SameException e) { Log(e); } 
// ... some more calls to different method ... 

actualización: no veo cómo va con una sintaxis como el enfoque de la Functor reduce cualquiera del código involucrado. Como mencionó Jon, Java no admite una sintaxis simple para reducirlo aún más. Si fuera C# hay muchas variaciones que puedes hacer, todo alrededor del hecho de que no hay mucha sintaxis adicional para componer métodos como eso, es decir, una expresión como actions.Add (() => doSomething (anObject));

+0

Otros no están de acuerdo, pero estoy contigo Freddy. El código hace lo que se quiere de él, y solo la estética y el deseo de complicar las cosas de otra manera. Si hubiera cientos de estas llamadas, quizás Functors o un refactor. Pero por el momento, déjalo en paz. –

0

Déjame explicar un poco más el enfoque de functor ...

Dependiendo de la complejidad de su aplicación, a veces vale la pena mover parte de la lógica de su empresa a un archivo conf, donde se puede expresar de manera más adecuada y breve. De esta manera, separa los detalles técnicos (la creación/invocación del functor, el manejo de excepciones) de la lógica de negocios: la definición de qué métodos y en qué orden se van a llamar.

En la forma más simple podría ser algo como esto:

mypackage.Action1 
mypackage.Action2 
... 

donde accionX es una clase que implementa la clase Functor (o interfaz).

Cuestiones relacionadas