2010-07-15 7 views
10

A menudo me encuentro en una situación en la que tengo un método donde algo puede salir mal, pero una excepción no sería correcto porque no es excepcional.Algo puede salir mal, pero no es una excepción

Por ejemplo:

Estoy diseñando un juego de monopolio. La clase Banco tiene un método buyHouse y un campo que cuenta el número de casas que quedan (hay 32 casas en monopolio). Algo que podría salir mal es que un jugador compre una casa cuando quedan 0. Cómo debería manejar esto. Aquí hay 3 enfoques que puedo idear.

1. public void buyHouse(Player player, PropertyValue propertyValue) 
{ 
    if(houseCount < 0) throw new someException; 
    .... 
    //Not really an exceptional situation 
} 

2. public boolean buyHouse(Player player, PropertyValue propertyValue) 
{ 
    if(houseCount < 0) return false; 
    .... 
    //This I think is the most normal approach but changing something 
    //and returning if it was a success seems bad practice to me. 
} 

3. public boolean housesLeft() 
{ 
    if(houseCount > 0) return true; 

    return false; 

    //Introducing a new method. But now I expect the client to call this method 
    //first before calling buyHouse(). 
} 

¿Qué harías?

+0

En lugar de si (algo que es verdad) return true; falso retorno; Prefiero hacer retVal booleano final; retVal = houseCount> 0; return (retVal); – TofuBeer

Respuesta

12

Haría 3 y 1 juntos. El uso correcto de la API es verificar si quedan casas antes de comprar una. Sin embargo, si el desarrollador olvidó hacerlo, ejecute una excepción de tiempo de ejecución.

Si se trata de una situación de subprocesos múltiples (donde muchas personas están comprando una casa al mismo tiempo) se vuelve más complicado. En ese caso, de hecho consideraría una excepción comprobada, si no es un método tryToBuyAHouse que devuelve un booleano, sino una excepción de tiempo de ejecución en el método buyHouse.

+2

Iba a decir esto también. Creo que una IllegalStateException es apropiada. –

+0

Gracias - esta parece ser la elección correcta. –

4

El significado de "excepcional" me parece bastante subjetivo. Significa lo que quieras que signifique. Estás diseñando la interfaz para la función, puedes decidir qué es excepcional y qué no.

Si no espera invocar buyHouse cuando houseCount es < = 0, entonces una excepción aquí está bien. Incluso si espera que se invoque, puede detectar la excepción en la persona que llama para manejar esa situación.

2

Cualquiera (1) o (2) es aceptable, dependiendo de si considera o no que "no hay casas para comprar" un resultado de rutina o una condición excepcional.

(3) es una mala idea, por varias razones:

  • se rompe la encapsulación (cliente tiene que saber demasiado sobre partes internas del banco)
  • usted todavía tiene que comprobar si hay errores y hacer (1) o (2) en caso de que los tornillos del cliente hasta
  • es problemático en situaciones de subprocesos múltiples
+0

No veo cómo 2 es diferente de tres, excepto que incluso peor: si se olvida de verificar, puede pensar que compró la casa cuando no lo hizo. – Yishai

+1

No creo que sea inapropiado esperar que el cliente sepa y juegue de acuerdo con las reglas del juego. –

1

me gustaría hacer algo como esto:

public boolean BuyHouse(Player player, PropertyValue propertyValue) { 
     // Get houseCount 
     if(houseCount <= 0) { 
     // Log this to your message queue that you want to show 
     // to the user (if it has a UI) 
     return false; 
     } 
     // Do other stuff if houses are left 
} 

PS: No estoy familiarizado con Java, C# utilizo

1

Esta pregunta es difícil de responder sin el contexto de qué entidad tiene una casa-. Desde una perspectiva de diseño general, hay una pequeña diferencia semántica para el que llama entre (1) y (2); ambos son try y check, pero tiene razón en que (1) debe evitarse para un estado totalmente esperado.

3

Si algo funciona como se esperaba 32 veces seguidas, y luego no funciona como se esperaba, creo que podría justificar que sea una condición excepcional si se tratara de un caso aislado.

Dada la situación que describes, creo que usar excepciones no es apropiado, ya que una vez que se hayan vendido 32 casas, el banco seguirá estando fuera de ellas (este es el nuevo estado "normal") y el procesamiento de excepciones es en realidad bastante lento en Java en comparación con el procesamiento normal.

Una cosa que podría hacer es reflejar más de cerca la interacción real. En Monopoly, el banquero simplemente le dirá que no puede comprar una casa si no queda ninguna.

Un posible modelo para esto es la siguiente:

public House buy(Player player, PropertyValue propertyValue) { 
    House propertyHouse = null; 
    if (houseCount > 0) { 
    propertyHouse = new House(player, propertyValue); 
    houseCount--; 
    } 

    return propertyHouse; 
} 

que también permitirá añadir el comportamiento al objeto Casa, y hacer que el flujo de solicitar/comprar una casa un poco más natural. Si no hay casas disponibles, no obtienes ninguna.

+0

Creo que esto va en la dirección correcta, pero preferiría un tipo de Opción (ver ej. Http://functionaljava.org) para forzar que verifique el resultado, evitando NPEs – Landei

+0

Esa es una dirección interesante, y puedo ver algunas ventajas ahí. ¿Has visto un uso significativo de este tipo de paradigma en la programación diaria, o es más una herramienta académica/de investigación? – mlschechter

1

usted decide regla & excepción aquí de usuario que utilice sus API/métodos:

housesLeft() se puede llamar para comprobar el número de casas se fue antes buyHouse() se llama. Llamar a buyHouse() cuando el número de casa que queda es cero es una excepción.

Es similar a verificar antes de acceder a cierto elemento de matriz, comprueba la longitud de la matriz antes de intentar acceder, de lo contrario se generará una excepción.

por lo que debe se parece a esto:

if (housesLeft() > 0) buyHouse(...); 

similar a

for (int i=0; i < arrayList.length; i++) System.out.println(arrayList[i]); 
+1

Este patrón es propenso a problemas de subprocesos múltiples. – samitgaur

+0

@samG: sí, es simple, pero se puede combinar con otro patrón relacionado a subprocesos. Creo que multi-threading no es una preocupación para bobjink aquí. – ttchong

+0

Creo que Yishai ha publicado una buena sugerencia más arriba para casos relacionados con múltiples subprocesos. – ttchong

2

Un par de otras opciones:

  • Su método podría aceptar un número de casas parámetros deseados , y devolver el número de casas compradas en realidad, después de verificar el saldo disponible del jugador y el entumecimiento er de casas disponibles. Regresar a cero sería una posibilidad perfectamente aceptable. Confía en el código de llamada para verificar cuántas casas recuperó realmente, por supuesto. (Esta es una variación al devolver un valor booleano, donde verdadero/falso indica que compró 1 o cero casas, por supuesto)

  • Una variación de ese tema sería devolver una colección de objetos House correspondientes al número de casas con éxito comprado, que podría ser una colección vacía. Presumiblemente, el código de llamada no podrá actuar como si tuviera más objetos House de los que le hubiera dado. (Esta es una variación al devolver un objeto House, con nulo que no representa casas compradas, y un objeto que representa 1 casa, y es a menudo parte de un enfoque de codificación general que prefiere colecciones vacías a referencias nulas)

  • Su método podría devuelve un objeto HousePurchaseTransaction que fue consultable para determinar el éxito o el fracaso de la transacción, su costo real, etc.

  • Una variación más rica en ese tema podría ser hacer HousePurchaseTransaction abstracto y derivar dos clases hijas: SuccessfulHousePurchase y FailedHousePurchase, por lo que podría asociar un comportamiento diferente con las dos condiciones de resultado. Instalar una casa en una calle puede requerir que pase un objeto 'SuccessfulHousePurchase' para continuar. (Esto evita el peligro de volver un nulo siendo la raíz de un error de referencia nula después, y es una variante del patrón de objeto nulo)

En realidad, sospecho que el enfoque adoptado dependerá de donde se terminó Asignar la responsabilidad de colocar las casas en el tablero, actualizar a los hoteles, hacer cumplir las reglas de construcción uniforme, limitar el número de casas compradas en una calle determinada, y así sucesivamente.

+1

Alguna buena sugerencia en la que no había pensado :) –

5

Esto es muy similar a la idea de hacer estallar un elemento de una pila vacía ... es excepcional. Estás haciendo algo que debería fallar.

Piense en situaciones excepcionales como casos en los que desea notificar al programador que algo ha salido mal y no quiere que lo ignoren. El uso de un valor de retorno booleano simple no es "correcto", ya que el programador puede ignorarlo. También es una buena idea tener un método que deba llamarse para verificar que haya casas disponibles. Pero recuerde que los programadores, en algunos casos, se olvidarán de llamarlo. En ese caso, la excepción sirve para recordarles que necesitan llamar al método para verificar que exista una casa antes de adquirirla.

Por lo tanto, proporcionaría el método para verificar que haya casas, y espero que las personas lo llamen y utilicen el valor de retorno verdadero/falso. En caso de que no llamen a ese método, o ignoren el valor de retorno, lanzaría una excepción para que el juego no se ponga en mal estado.

1

Recuerde que puede utilizar

return houseCount > 0; 

en lugar de

if(houseCount > 0) return true; 

return false; 
+0

Lo sé pero prefiero mi camino :) –

Cuestiones relacionadas