¿Debe un getter lanzar una excepción si su objeto tiene un estado inválido?

54

A menudo me encuentro con este problema, especialmente en Java, incluso si creo que es un problema general de POO. Es decir: al generar una excepción se revela un problema de diseño.

Supongamos que tengo una clase que tiene un campo String name y un campo String surname .

Luego usa esos campos para componer el nombre completo de una persona para mostrarlo en algún tipo de documento, digamos una factura.

public void String name;
public void String surname;

public String getCompleteName() {return name + " " + surname;}

public void displayCompleteNameOnInvoice() {
    String completeName = getCompleteName();
    //do something with it....
}

Ahora quiero reforzar el comportamiento de mi clase lanzando un error si se llama a displayCompleteNameOnInvoice antes de que el nombre haya sido asignado. Parece una buena idea, ¿no?

Puedo agregar un código de aumento de excepciones al método getCompleteName . Pero de esta manera, estoy violando un contrato 'implícito' con el usuario de la clase; en general, los captadores no deben lanzar excepciones si sus valores no están establecidos. Ok, esto no es un captador estándar ya que no devuelve un solo campo, pero desde el punto de vista del usuario, la distinción puede ser demasiado sutil como para pensar en ello.

O puedo lanzar la excepción desde dentro de displayCompleteNameOnInvoice . Pero para hacerlo, debería probar directamente los campos name o surname y al hacerlo violaría la abstracción representada por getCompleteName . Es responsabilidad de este método verificar y crear el nombre completo. Incluso podría decidir, basando la decisión en otros datos, que en algunos casos es suficiente el surname .

Entonces la única posibilidad parece cambiar la semántica del método getCompleteName a composeCompleteName , que sugiere un comportamiento más "activo" y, con ello, la capacidad de lanzar una excepción.

¿Es esta la mejor solución de diseño? Siempre estoy buscando el mejor equilibrio entre simplicidad y corrección. ¿Hay una referencia de diseño para este problema?

    
pregunta AgostinoX 02.11.2014 - 16:00

6 respuestas

149

No permita que su clase se construya sin asignar un nombre.

    
respondido por el DeadMG 02.11.2014 - 16:56
74

La respuesta provista por DeadMG casi la nila, pero permítame expresarla de manera un poco diferente: evite tener objetos con un estado no válido. Un objeto debe ser "completo" en el contexto de la tarea que cumple. O no debería existir.

Entonces, si quieres fluidez, usa algo como el Builder Pattern . O cualquier otra cosa que sea una reificación separada de un objeto en construcción en oposición a la "cosa real", donde se garantiza que este último tiene un estado válido y es el que realmente expone las operaciones definidas en ese estado (por ejemplo, getCompleteName ).

    
respondido por el back2dos 02.11.2014 - 18:39
39

No tiene un objeto con un estado no válido.

El propósito de un constructor o constructor es establecer el estado del objeto en algo que sea consistente y utilizable. Sin esta garantía, los problemas surgen con el estado incorrectamente inicializado en los objetos. Este problema se complica si se trata de una concurrencia y algo puede acceder al objeto antes de que haya terminado de configurarlo.

Entonces, la pregunta para esta parte es "¿por qué está permitiendo que el nombre o el apellido sean nulos?" Si eso no es algo que sea válido en la clase para que funcione correctamente, no lo permita. Tener un constructor o constructor que valide correctamente la creación del objeto y, si no es correcto, plantea el problema en ese punto. También puede utilizar uno de los @NotNull anotaciones que existe para ayudar a comunicar que "esto no puede ser nulo" y aplicar En codificación y análisis.

Con esta garantía en su lugar, es mucho más fácil razonar sobre el código, lo que hace, y no tener que lanzar excepciones en lugares extraños o poner demasiadas comprobaciones en las funciones de obtención.

Captadores que hacen más.

Hay un poco de información sobre este tema. Tienes la cantidad de lógica en Getters y ¿Qué debería permitirse dentro de getters y setters? desde aquí y captadores y configuradores que realizan lógica adicional sobre el desbordamiento de pila. Este es un problema que surge una y otra vez en el diseño de la clase.

El núcleo de esto viene de:

  

en general, los captadores no deben lanzar excepciones si sus valores no están establecidos

Y tienes razón. Ellos no deberían. Deben parecer 'tontos' al resto del mundo y hacer lo que se espera. Poner demasiada lógica ahí lleva a problemas donde se viola la ley de menos asombro. Y seamos realistas, realmente no quieres estar envolviendo a los captadores con un try catch porque sabemos cuánto nos encanta hacer eso en general.

También hay situaciones en las que must utiliza un método getFoo como el marco de JavaBeans y cuando tiene algo de EL llamando a la espera de obtener un bean (de modo que <% bar.foo %> realmente llame a getFoo() en la clase - dejando de lado el 'debería el bean estar haciendo la composición o ¿Debería dejarse eso a la vista? 'porque uno puede fácilmente llegar a situaciones donde uno u otro puede ser claramente la respuesta correcta)

Tenga en cuenta también que es posible que un determinado captador se especifique en una interfaz o que haya sido parte de la API pública expuesta anteriormente para la clase que se está refaccionando (una versión anterior solo tenía 'completeName' que se devolvió y una refactorización lo dividió en dos campos).

Al final del día ...

Haz lo que sea más fácil de razonar. Dedicará más tiempo a mantener este código del que dedicará a diseñarlo (aunque cuanto menos tiempo dedique a diseñar, más tiempo dedicará a su mantenimiento). La opción ideal es diseñarlo de tal manera que no tarde mucho tiempo en mantenerlo, pero tampoco se quede sentado pensando en ello durante días, a menos que esta sea una opción de diseño que tienen días de implicaciones más tarde.

Si intentas mantenerte con la pureza semántica del getter que es private T foo; T getFoo() { return foo; } , tendrás problemas en algún momento. A veces, el código simplemente no se ajusta a ese modelo y las contorsiones por las que uno pasa para tratar de mantenerlo así no tiene sentido ... y, en última instancia, hace que sea más difícil de diseñar.

A veces, acepte que los ideales del diseño no se pueden realizar de la forma en que los quiere en el código. Las pautas son pautas, no grilletes.

    
respondido por el user40980 02.11.2014 - 22:20
5

No soy un tipo de Java, pero esto parece adherirse a las dos restricciones que presentaste.

getCompleteName no lanza una excepción si los nombres no están inicializados, y displayCompleteNameOnInvoice lo hace.

public String getCompleteName()
{
    if (name == null || surname == null)
    {
        return null;
    }
    return name + " " + surname;
}

public void displayCompleteNameOnInvoice() 
{
    String completeName = getCompleteName();
    if (completeName == null)
    {
        //throw an exception.
    }
    //do something with it....
}
    
respondido por el Rotem 02.11.2014 - 17:22
4

Parece que nadie está respondiendo tu pregunta.
A diferencia de lo que a la gente le gusta creer, "evitar objetos no válidos" no suele ser una solución práctica.

La respuesta es:

Sí, debería.

Ejemplo sencillo: FileStream.Length en C # / .NET , que arroja NotSupportedException si el archivo no se puede buscar.

    
respondido por el Mehrdad 04.11.2014 - 08:48
1

Tienes el nombre completo del cheque al revés.

getCompleteName() no tiene que saber qué funciones lo utilizarán. Por lo tanto, no tener un name al llamar a displayCompleteNameOnInvoice() no es responsabilidad de getCompleteName() s.

Pero, name es un requisito previo claro para displayCompleteNameOnInvoice() . Por lo tanto, debería asumir la responsabilidad de verificar el estado (O debería delegar la responsabilidad de verificar a otro método, si lo prefiere).

    
respondido por el Krumia 05.11.2014 - 10:43

Lea otras preguntas en las etiquetas

Comentarios Recientes

runStructureDefineThrowError falló (sí / no) La condición NoCancels en Throwback.Xml representa una condición de error, en la que una supuesta condición posterior se da por vencida y lanza una RuntimeException. null La gramática permite que null sea igual a nada, string y string [], pero no a otros tipos XML. x La gramática agrega las reglas de análisis XmlXML de forma predeterminada. En lugar de reglas de análisis XML, incluye un mapeo entre XmlXMLDataRegExpTk y los elementos del elemento XMLDocument por nombre,... Lee mas