Diseño de métodos relacionados con la base de datos, ¿cuál es mejor para devolver: verdadero / falso o fila afectada?

10

Tengo algunos métodos que realizan algunos cambios de datos en una base de datos (insertar, actualizar y eliminar). El ORM que estoy usando devuelve los valores int afectados por la fila para ese tipo de método. ¿Qué debo devolver para "mi método", para indicar el estado de éxito / fracaso de la operación?

Considere el código que está devolviendo un int:

A.1

public int myLowerLevelMethod(int id) {
    ...
    int affectedRows = myOrm.deleteById(id)
    ...

    return affectedRows;
}

Entonces uso:

A.2

public void myOtherMethod() {
    ...
    int affectedRows = myLowerLevelMethod(id)

    if(affectedRows > 0) {
        // Success
    } else {
        // Fail
    }
}

Compare con el uso de boolean:

B.1

public boolean myLowerLevelMethod(int id) {
    ...
    int affectedRows = myOrm.deleteById(id)
    ...

    return affectedRows > 0;
}

Entonces uso:

B.2

public void myOtherMethod() {
    ...
    boolean isSuccess = myLowerLevelMethod(id)

    if(isSuccess) {
        // Success
    } else {
        // Fail
    }
}

¿Cuál (A o B) es mejor? O pros / contras de cada uno?

Hoang Tran
fuente
En tu "A.2". Si las filas cero se ven afectadas, ¿por qué es un error si las filas cero deben verse afectadas? En otras palabras, si no hay un error en la base de datos, ¿por qué falla?
Jaydee
55
¿Existe una diferencia semántica entre "sin éxito" y "cero filas afectadas"? Por ejemplo, al eliminar todos los pedidos de un cliente, hay una diferencia entre "el cliente no existe" y "el cliente no tiene pedidos".
Cefalópodo
¿Considera que una eliminación con cero filas es inesperada? En ese caso tirar.
usr
@Arian Creo que esa es la verdadera pregunta para mí. Creo que elegí B, porque con A, mi código ahora contiene la comprobación de 0 en algunos lugares y de -1 en otros
Hoang Tran

Respuestas:

18

Otra opción es devolver un objeto de resultado en lugar de tipos básicos. Por ejemplo:

OperationResult deleteResult = myOrm.deleteById(id);

if (deleteResult.isSuccess()) {
    // ....
}

Con esto, si por alguna razón necesita devolver el número de filas afectadas, simplemente puede agregar un método en OperationResult:

if (deleteResult.isSuccess()) {
    System.out.println("rows deleted: " + deleteResult.rowsAffected() );
}

Este diseño permite que su sistema crezca e incluya una nueva funcionalidad (conocer las filas afectadas) sin modificar el código existente.

AlfredoCasado
fuente
2
+1 "Este diseño permite que su sistema crezca e incluya una nueva funcionalidad (conocer las filas afectadas) sin modificar el código existente" Esta es la forma correcta de pensar en esta categoría de preguntas.
InformadoA
Hice algo similar en mi antiguo proyecto. Tengo el objeto de solicitud y resultado de contenedor. Ambos usan Composiciones para incluir más detalles. y ambos tienen datos básicos también. En este caso, el objeto Resultado tiene un código de estado y un campo de mensaje.
InformadoA
Especialmente para un sistema que usa un ORM, yo llamaría a esto la mejor práctica. ¡El ORM en cuestión también puede incluir tales tipos de objeto de resultado!
Brian S
Solo mirando el ejemplo de OP, todo lo que puedo pensar es que deleteById devolverá 2 en algún momento :) así que definitivamente es un tipo personalizado, sí.
deadsven
Me gusta este enfoque. Creo que no bloquea, cubre mis dos enfoques y es modificable / expandible (en el futuro, si es necesario). El único inconveniente que se me ocurre es que es un poco más de código, especialmente cuando se trata de la mitad de mi proyecto. Marcaré esto como la respuesta. Gracias.
Hoang Tran
12

Es mejor devolver el número de filas afectadas porque proporciona información adicional sobre cómo se realizó la operación.

Ningún programador lo culpará porque él / ella tiene que escribir esto para verificar si tuvieron algunos cambios durante la operación:

if(affectedRows > 0) {
    // success
} else {
    // fail
}

pero te culparán cuando tengan que saber el número de filas afectadas y se darán cuenta de que no hay ningún método para obtener ese número.

Por cierto: si por "falla" te refieres a un error de consulta sintáctica (en cuyo caso el número de filas afectadas es obviamente 0), entonces lanzar la excepción sería más apropiado.

Poli
fuente
44
-1 por la razón que diste. Dar información adicional no siempre es una buena idea. A menudo, un mejor diseño llevaría a comunicarle a la persona que llama lo que necesita y nada más.
Arseni Mourzenko
1
@MainMa nada impide la creación de métodos sobrecargados. Además, en lenguajes nativos (familia C, por ejemplo), el número de filas se puede usar directamente en la lógica (0 es falso, cualquier otra cosa es verdadera).
PTwr
@PTwr: así que para hacer frente al hecho de que el método está devolviendo demasiada información, ¿sugiere crear una sobrecarga? Esto no parece correcto. En cuanto a "cero es falso, no cero es verdadero", este no es el punto de mi comentario, y es una mala práctica en algunos idiomas.
Arseni Mourzenko
1
Probablemente soy uno de los programadores mimados porque no creo que usar ningún truco pueda considerarse una buena práctica. De hecho, cada vez que tengo que lidiar con el código de otra persona, agradezco a quien lo escribió y no empleó ningún truco.
proskor
44
¡Siempre debe devolver el número de filas afectadas aquí! ¿Ya que no puede saber si el número de filas = 0 es un error, o si el número de filas = 3 es un éxito? Si alguien quiere insertar 3 filas y solo 2 se insertan, devolverá verdadero, ¡pero no está bien! Y si alguien quiere update t set category = 'default' where category IS NULLincluso 0 filas afectadas sería un éxito, porque ahora no hay ningún elemento sin categoría, ¡incluso si no hay filas afectadas!
Falco
10

No recomendaría ninguno de ellos. En cambio, no devuelva nada (nulo) en caso de éxito y arroje una excepción en caso de error.

Esto es exactamente por la misma razón por la que elijo declarar ciertos miembros de la clase privados. También hace que la función sea más fácil de usar. Más contexto operativo no siempre significa mejor, pero ciertamente significa más complejo. Cuanto menos prometas, más abstraes, más fácil es para el cliente entender y más libertad tienes para elegir cómo implementarlo.

La pregunta es cómo indicar éxito / error. En este caso, es suficiente para señalar el fallo lanzando una excepción y no devolver nada en caso de éxito. ¿Por qué debo proporcionar más de lo que el usuario necesita?

Pueden ocurrir fallas / situaciones excepcionales y luego tendrá que lidiar con ellas. Si usa try / catch para hacerlo o examina los códigos de retorno, es una cuestión de estilo / preferencia personal. Las ideas detrás de try / catch son: separar el flujo normal del flujo excepcional y dejar que las excepciones burbujeen hasta la capa donde se pueden manejar de la manera más adecuada. Entonces, como muchos ya han señalado, depende de si la falla es realmente excepcional o no.

proskor
fuente
44
-1 ¿Por qué no devolvería nada donde pudiera devolver algo, sin efectos secundarios negativos, que proporcione un contexto operativo adicional?
FreeAsInBeer
77
Exactamente por la misma razón que elijo declarar ciertos miembros de la clase privados. También hace que la función sea más fácil de usar. Más contexto operativo no siempre significa mejor, pero ciertamente significa más complejo. Cuanto menos prometas, más abstraes, más fácil es para el cliente entender y más libertad tienes para elegir cómo implementarlo.
proskor
1
Bueno, ¿qué datos necesita realmente obtener el usuario? La pregunta es cómo indicar éxito / error. En este caso, es suficiente para señalar el fallo lanzando una excepción y no devolver nada en caso de éxito. ¿Por qué debo proporcionar más de lo que el usuario necesita?
proskor
44
@proskor: las excepciones son para casos excepcionales. El "fracaso" en este escenario puede ser un resultado esperado. Por supuesto, presente esto como una alternativa potencial, pero no hay suficiente información aquí para hacer una recomendación.
Nick Barnes
1
-1 Las excepciones no deben ser parte del flujo normal del programa. No está claro qué significa "falla" en el contexto de su error, pero una excepción en el contexto de una llamada a la base de datos debe deberse a una excepción que ocurre en la base de datos. Afectar a cero filas no debería ser una excepción. Una consulta desglosada que no se puede analizar, hace referencia a una tabla inexistente, etc., sería una excepción porque el motor de la base de datos se atragantaría y arrojaría.
2

"¿Es esto mejor que eso?" No es una pregunta útil cuando las dos alternativas no hacen lo mismo.

Si necesita conocer el recuento de filas afectadas, debe usar la versión A. Si no tiene que hacerlo, puede usar la versión B, pero cualquier ventaja que pueda obtener en términos de menos esfuerzo de escritura de código ya ha desaparecido desde te tomaste la molestia de publicar ambas versiones en un foro en línea!

Mi punto es: qué solución es mejor depende completamente de cuáles son sus requisitos específicos para esta aplicación, y usted conoce esas circunstancias mucho mejor que nosotros. No existe una opción para toda la industria, segura de usar, de mejores prácticas, que no sea despedido, que sea mejor en general ; tienes que pensarlo tú mismo. Y para una decisión tan fácil de revisar como esta, tampoco necesita pasar tanto tiempo pensando.

Kilian Foth
fuente
Estoy de acuerdo en que esto depende mucho de los requisitos de la aplicación. Sin embargo, parece que este tipo de situación no es muy única, y no estoy buscando una bala de plata, sino solo la experiencia de otros tratando con lo mismo / similar (tal vez la pregunta es un poco engañosa, me gustan las sugerencias que no sean A / B)
Hoang Tran
1

Dos de los principios más importantes en el diseño de software mantenible son KISS y YAGNI .

  • BESO : Mantenlo simple, estúpido
  • YAGNI : No lo vas a necesitar

Casi nunca es una buena idea poner en lógica que no necesita de inmediato en este momento . Entre muchas otras personas, Jeff Atwood (cofundador de StackExchange) escribió sobre esto , y en mi experiencia él y otros defensores de estos conceptos tienen toda la razón.

Cualquier complejidad que agregue a un programa tiene un costo, pagado durante un largo período de tiempo. El programa se vuelve más difícil de leer, más complejo de cambiar y más fácil de introducir los errores. No caigas en la trampa de agregar cosas "por si acaso". Es una falsa sensación de seguridad.

Rara vez obtendrá un código correcto la primera vez. Los cambios son inevitables; agregar lógica especulativa para prepararse defensivamente para futuras contingencias desconocidas no lo protegerá de tener que refactorizar su código cuando el futuro sea diferente de lo que esperaba. El mantenimiento de la lógica innecesaria / contingente es más un problema de mantenimiento que la refactorización posterior para agregar la funcionalidad que falta.

Por lo tanto, dado que parece que todo lo que su programa necesita por ahora es saber si la operación tuvo éxito o no, su solución B propuesta (devolver un solo booleano) es el enfoque correcto. Siempre puede refactorizarlo más tarde si cambia el requisito. Esta solución es la más simple y tiene la menor complejidad (KISS) y hace justo lo que necesita y nada más (YAGNI).

Ben Lee
fuente
-1

Las filas completas o un estado de error

Considere devolver las filas completas, al menos como una opción de tiempo de ejecución. En las inserciones de DB puede que necesite inspeccionar los datos que se insertaron, ya que a menudo serán diferentes de lo que envió a la DB; Los ejemplos comunes incluyen ID de fila autogenerados (que la aplicación probablemente necesitará de inmediato), valores predeterminados determinados por DB y resultados de desencadenantes si los usa.

Por otro lado, si no necesita los datos devueltos, tampoco necesita el recuento de filas afectado, ya que no es útil para el resultado de 0. Si hay errores, debe devolver qué tipo de ocurrió un error, de una manera consistente con los principios de manejo de errores de su proyecto (excepciones, códigos numéricos de error, lo que sea); pero hay consultas válidas que afectarán correctamente 0 filas (es decir, "eliminar todos los pedidos caducados" si en realidad no hay ninguno).

Pedro es
fuente