¿Por qué tener un método que devuelve un bool / int y tiene el objeto real como parámetro de salida?

12

Veo el siguiente patrón de código por todas partes en la base de código de mi empresa (aplicación .NET 3.5):

bool Foo(int barID, out Baz bazObject) { 
    try { 
            // do stuff
            bazObject = someResponseObject;

            return true;
    }
    catch (Exception ex) { 
        // log error
        return false;
    }
}

// calling code
BazObject baz = new BazObject();
fooObject.Foo(barID, out baz);

if (baz != null) { 
    // do stuff with baz
}

Estoy tratando de entender por qué haría esto en lugar de que el Foométodo simplemente tome la ID y devuelva un Bazobjeto, en lugar de devolver un valor que no se utiliza y que el objeto real sea un parámetro de referencia o salida.

¿Hay algún beneficio oculto de este estilo de codificación que me falta?

Wayne Molina
fuente
En su ejemplo, bazser nully el boolser devuelto nofalse son equivalentes. nunca es así, a menos que se actualice antes de que se lance , cuando se devuelva nunca lo será . Sería de gran ayuda enormemente si la especificación de estaban disponibles. De hecho, este es quizás el problema más grave exhibido por este código. new BazObject()nullbazObjectExceptionFoofalsebaznullFoo
Steve Powell
Mi memoria es borrosa ya que esto fue hace mucho tiempo, pero creo que había estropeado el ejemplo y estaba comprobando si "baz" era falso, no si era nulo. En cualquier caso, el patrón me pareció realmente arcaico, como si fuera de VB6 y el desarrollador nunca se molestó en mejorar su código (lo que no hizo)
Wayne Molina

Respuestas:

11

Usualmente usas ese patrón para poder escribir código como este:

if (Foo(barId, out bazObject))
{
  //DoStuff with bazobject
}

se usa en CLR para TryGetValue en la clase de diccionario, por ejemplo. Evita cierta redundancia, pero los parámetros de salida y referencia siempre me parecieron un poco desordenados.

Homde
fuente
1
+1, esta es la razón por la cual, aunque tiendo a devolver un objeto con el booleano y el objeto en lugar de devolverlos por separado
pdr
Voy a marcar esto como la respuesta, ya que explica la razón, aunque el consenso parece ser bastante desactualizado, excepto en circunstancias específicas.
Wayne Molina
Esta respuesta justifica el uso de este patrón. Pero si está TODO SOBRE su base de código, probablemente sea una mala práctica como el lado de Sean.
Codismo
11

Este es el antiguo código de estilo C, antes de que hubiera excepciones. El valor de retorno indicó si el método fue exitoso o no, y si lo fuera, el parámetro se llenaría con el resultado.

En .net tenemos excepciones para ese propósito. No debería haber ninguna razón para seguir este patrón.

[editar] Obviamente, hay implicaciones de rendimiento en el manejo de excepciones. Tal vez eso tenga algo que ver con eso. Sin embargo, en ese fragmento de código ya se está lanzando una excepción. Sería más limpio dejarlo subir por la pila hasta que quede atrapado en un lugar más apropiado.

Sean Edwards
fuente
No. No puedo estar de acuerdo con eso. Nunca use una excepción para una condición esperada. Si analiza una cadena a un int, por ejemplo, siempre es posible que alguien pase una cadena no analizable. No debe lanzar una excepción en ese caso
pdr
Eso no es lo que yo dije. Si lee el código que OP publicó, explícitamente está ocurriendo un caso de excepción, pero el método lo detecta y devuelve falso en su lugar. Esta pregunta está relacionada con el manejo de errores, no con las condiciones esperadas.
Sean Edwards
Sí, parece que se usa principalmente en métodos que cargan datos de la base de datos, por ejemplo, if (baz.Select())pero la mayoría de las veces el valor de retorno simplemente se descarta y el valor se compara con nulo o alguna propiedad.
Wayne Molina
@Sean, mira lo que estás diciendo, solo me opongo a la frase "En .NET tenemos excepciones para ese propósito". Las excepciones tienen un propósito completamente diferente para mí
pdr
1
Ok, déjame ser claro. Es cierto que no solo debe agregar un booleano a cada método para tener en cuenta los errores de codificación en argumentos no válidos. Pero cuando la entrada a un método proviene de un usuario en lugar de un desarrollador , debería poder tratar de manejar la entrada sin lanzar una excepción si se equivocaron.
pdr
2

Dado el fragmento de código, parece completamente inútil. Para mí, el patrón de código inicial sugeriría que nulo para BazObject sería un caso aceptable y el retorno de bool es una medida para determinar un caso de falla de forma segura. Si el siguiente código era:

// calling code
BazObject baz = new BazObject();
bool result = fooObject.Foo(barID, out baz);

if (result) { 
    // do stuff with baz
    // where baz may be 
    // null without a 
    // thrown exception
}

Esto tendría más sentido para mí hacerlo de esta manera. Quizás este es un método que alguien antes usaba para garantizar que se pasara baz por referencia sin comprender cómo funcionan realmente los parámetros de objeto en C #.

Joel Etherton
fuente
Creo que fue escrito por un miembro actual del equipo. Quizás eso es algo de lo que debería preocuparme O_O
Wayne Molina
@Wayne M: Menos preocupación que una posible oportunidad de capacitación :)
Joel Etherton
1

A veces, este patrón es útil cuando a usted, la persona que llama, no le importa si el tipo devuelto es una referencia o un tipo de valor. Si está llamando a dicho método para recuperar un tipo de valor, ese tipo de valor necesitará un valor no válido establecido (por ejemplo, double.NaN) o necesitará alguna otra forma de determinar el éxito.

Kevin Hsu
fuente
Eso tiene sentido. Parece un poco raro, pero eso suena como una razón válida.
Wayne Molina
0

La idea es devolver un valor que indique si el proceso fue exitoso, permitiendo un código como este:

Baz b;
if (fooObject.foo(id, out b)) {
   // do something with b
}
else {
   Error.screamAndRun("object lookup/whatever failed! AAaAAAH!");
}

Si el objeto no puede ser nulo (lo que parece correcto en su ejemplo), es mejor hacerlo de la siguiente manera:

Baz b = fooObject.foo(id);
if (b != null) {
   // do something with b
}
else {
   Error.screamAndRun("object lookup/whatever failed! AAaAAAH!");
}

Si el objeto puede ser nulo, las excepciones son el camino a seguir:

try {
   Baz b = fooObject.foo(id);
}
catch (BazException e) {
   Error.screamAndRun("object lookup/whatever failed! AAaAAAH!");
}

Es un patrón común utilizado en C, donde no hay excepciones. Permite que la función devuelva una condición de error. Las excepciones son generalmente una solución mucho más limpia.

Michael K
fuente
Particularmente porque el código ya está lanzando una excepción.
David Thornley