Código de fortalecimiento con manejo de excepciones posiblemente inútil

12

¿Es una buena práctica implementar un manejo de excepciones inútil, en caso de que otra parte del código no esté codificada correctamente?

Ejemplo básico

Una simple, así que no pierdo a todos :).

Digamos que estoy escribiendo una aplicación que mostrará la información de una persona (nombre, dirección, etc.), los datos que se extraen de una base de datos. Digamos que soy yo quien codifica la parte de la interfaz de usuario, y alguien más está escribiendo el código de consulta DB.

Ahora imagine que las especificaciones de su aplicación dicen que si la información de la persona está incompleta (digamos que falta el nombre en la base de datos), la persona que codifica la consulta debe manejar esto devolviendo "NA" para el campo faltante.

¿Qué sucede si la consulta está mal codificada y no maneja este caso? ¿Qué sucede si el tipo que escribió la consulta maneja un resultado incompleto y cuando intenta mostrar la información, todo falla, porque su código no está preparado para mostrar cosas vacías?

Este ejemplo es muy básico. Creo que la mayoría de ustedes dirá "no es su problema, no son responsables de este bloqueo". Pero, sigue siendo tu parte del código la que falla.

Otro ejemplo

Digamos ahora que soy yo quien escribe la consulta. Las especificaciones no dicen lo mismo que arriba, pero el tipo que escribe la consulta "insertar" debe asegurarse de que todos los campos estén completos al agregar una persona a la base de datos para evitar insertar información incompleta. ¿Debo proteger mi consulta "select" para asegurarme de proporcionarle al chico de la interfaz de usuario información completa?

Las preguntas

¿Qué pasa si las especificaciones no dicen explícitamente "este tipo es el encargado de manejar esta situación"? ¿Qué sucede si una tercera persona implementa otra consulta (similar a la primera, pero en otra base de datos) y usa su código de UI para mostrarla, pero no maneja este caso en su código?

¿Debo hacer lo que sea necesario para evitar un posible bloqueo, incluso si no soy yo quien debe manejar el mal caso?

No estoy buscando una respuesta como "(s) él es el responsable del accidente", ya que no estoy resolviendo un conflicto aquí, me gustaría saber, si protejo mi código contra situaciones no es mi responsabilidad ¿manejar? Aquí, un simple "si está vacío hacer algo" sería suficiente.

En general, esta pregunta aborda el manejo de excepciones redundante. Lo pregunto porque cuando trabajo solo en un proyecto, puedo codificar 2-3 veces un manejo de excepciones similar en funciones sucesivas, "por si acaso", hice algo mal y dejé pasar un mal caso.

rdurand
fuente
44
Estás hablando de "pruebas", pero hasta donde yo entiendo tu problema te refieres a "pruebas que se aplican en producción", esto se llama mejor "validación" o "manejo de excepciones".
Doc Brown
1
Sí, la palabra apropiada es "manejo de excepciones".
Rdurand
cambió la etiqueta incorrecta entonces
Doc Brown
Lo remito a The DailyWTF : ¿está seguro de que desea hacer este tipo de pruebas?
gbjbaanb
@gbjbaanb: Si entiendo su enlace correctamente, no es de eso de lo que estoy hablando. No estoy hablando de "pruebas estúpidas", estoy hablando de duplicar el manejo de excepciones.
Rdurand

Respuestas:

14

De lo que estás hablando aquí es de límites de confianza . ¿Confía en el límite entre su aplicación y la base de datos? ¿La base de datos confía en que los datos de la aplicación siempre se validan previamente?

Esa es una decisión que debe tomarse en cada aplicación y no hay respuestas correctas o incorrectas. Tiendo a equivocarme al llamar a demasiados límites un límite de confianza, otros desarrolladores felizmente confiarán en API de terceros para hacer lo que esperas que hagan, todo el tiempo, siempre.

pdr
fuente
5

El principio de robustez "Sea conservador en lo que envía, sea liberal en lo que acepta" es lo que busca. Es un buen principio: EDITAR: siempre que su aplicación no oculte ningún error grave, pero estoy de acuerdo con @pdr en que siempre depende de la situación si debe aplicarlo o no.

Doc Brown
fuente
Algunas personas piensan que el "principio de robustez" es una mierda. El artículo da un ejemplo.
@MattFenwick: gracias por señalarlo, es un punto válido, he cambiado un poco mi respuesta.
Doc Brown
2
Este es un artículo aún mejor que señala los problemas con el "principio de robustez": joelonsoftware.com/items/2008/03/17.html
hakoja
1
@hakoja: honestamente, conozco bien este artículo, se trata de los problemas que obtienes cuando comienzas a no seguir el principio de robustez (como algunos chicos de MS intentaron con versiones más nuevas de IE). Sin embargo, esto se aleja un poco de la pregunta original.
Doc Brown
1
@DocBrown: que es exactamente por qué nunca deberías haber sido liberal en lo que aceptas. La robustez no significa que deba aceptar todo lo que se le arroje sin quejarse, solo que debe aceptar todo lo que se le arroje sin estrellarse.
Marjan Venema
1

Depende de lo que esté probando; pero supongamos que el alcance de su prueba es solo su propio código. En ese caso, debe probar:

  • El "caso feliz": alimente la entrada válida de su aplicación y asegúrese de que produzca la salida correcta.
  • Los casos de falla: alimente las entradas no válidas de su aplicación y asegúrese de que las maneje correctamente.

Para hacer esto, no puede usar el componente de su colega: en su lugar, use burlas , es decir, reemplace el resto de la aplicación con módulos "falsos" que puede controlar desde el marco de prueba. Cómo exactamente hace esto depende de la interfaz de los módulos; puede ser suficiente simplemente llamar a los métodos de su módulo con argumentos codificados, y puede volverse tan complejo como escribir un marco completo que conecte las interfaces públicas de los otros módulos con el entorno de prueba.

Sin embargo, ese es solo el caso de prueba de unidad. También desea pruebas de integración, donde pruebe todos los módulos en concierto. Una vez más, desea probar tanto el caso feliz como los fracasos.

En su caso de "Ejemplo básico", para probar el código de la unidad, escriba una clase simulada que simule la capa de la base de datos. Sin embargo, su clase de prueba realmente no va a la base de datos: simplemente la precarga con las entradas esperadas y las salidas fijas. En pseudocódigo:

function test_ValidUser() {
    // set up mocking and fixtures
    userid = 23;
    db = new MockDB();
    db.fixedResult = { firstName: "John", lastName: "Doe" };
    db.expectedCall = { method: 'getUser', params: { userid: userid } };
    userController = new UserController(db);
    expectedResult = "John Doe";

    // run the actual test
    actualResult = userController.displayUserAsString(userid);

    // check assertions
    assertEquals(expectedResult, actualResult);
    db.assertExpectedCall();
}

Y así es como probaría los campos faltantes que se informan correctamente :

function test_IncompleteUser() {
    // set up mocking and fixtures
    userid = 57;
    db = new MockDB();
    db.fixedResult = { firstName: "John", lastName: "NA" };
    db.expectedCall = { method: 'getUser', params: { userid: userid } };
    userController = new UserController(db);

    // let's say the user controller is specified to leave "NA" fields 
    // blank
    expectedResult = "John";

    // run the actual test
    actualResult = userController.displayUserAsString(userid);

    // check assertions
    assertEquals(expectedResult, actualResult);
    db.assertExpectedCall();
}

Ahora las cosas se vuelven interesantes. ¿Qué pasa si la clase real de DB se porta mal? Por ejemplo, podría arrojar una excepción por razones poco claras. No sabemos si lo hace, pero queremos que nuestro propio código lo maneje con gracia. No hay problema, solo necesitamos hacer que nuestro MockDB arroje una excepción, por ejemplo, agregando un método como este:

class MockDB {
    // ... snip
    function getUser(userid) {
        if (this.fixedException) {
            throw this.fixedException;
        }
        else {
            return this.fixedResult;
        }
    }
}

Y luego nuestro caso de prueba se ve así:

function test_MisbehavingUser() {
    // set up mocking and fixtures
    userid = 57;
    db = new MockDB();
    db.fixedException = new SQLException("You have an error in your SQL syntax");
    db.expectedCall = { method: 'getUser', params: { userid: userid } };
    userController = new UserController(db);

    // run the actual test
    try {
        userController.displayUserAsString(userid);
    }
    catch (DatabaseException ex) {
        // This is good: our userController has caught the raw exception
        // from the database layer and wrapped it in a DatabaseException.
        return TEST_PASSED;
    }
    catch (Exception ex) {
        // This is not good: we have an exception, but it's the wrong kind.
        testLog.log("Found the wrong exception: " + ex);
        return TEST_FAILED;
    }
    // This is bad, too: either our mocking class didn't throw even when it
    // should have, or our userController swallowed the exception and
    // discarded it
    testLog.log("Expected an exception to be thrown, but nothing happened.");
    return TEST_FAILED;
}

Estas son sus pruebas unitarias. Para la prueba de integración, no utiliza la clase MockDB; en su lugar, encadena ambas clases reales juntas. Todavía necesitas accesorios; por ejemplo, debe inicializar la base de datos de prueba a un estado conocido antes de ejecutar la prueba.

Ahora, en lo que respecta a las responsabilidades: su código debe esperar que el resto de la base de código se implemente según la especificación, pero también debe estar preparado para manejar las cosas con gracia cuando el resto se arruina. No es responsable de probar otro código que no sea el suyo, pero es responsable de hacer que su código sea resistente al mal comportamiento del código en el otro extremo, y también es responsable de probar la resistencia de su código. Eso es lo que hace la tercera prueba anterior.

tdammers
fuente
¿Leíste los comentarios debajo de la pregunta? El OP escribió "pruebas", pero lo dijo en el sentido de "verificaciones de validación" y / o "manejo de excepciones"
Doc Brown
1
@tdammers: perdón por el malentendido, quise decir, de hecho, manejo de excepciones ... Gracias de todos modos por la respuesta completa, el último párrafo es lo que estaba buscando.
Rdurand
1

Hay 3 principios principales que trato de codificar:

  • SECO

  • BESO

  • YAGNI

El problema es que te arriesgas a escribir un código de validación que esté duplicado en otra parte. Si las reglas de validación cambian, éstas deberían actualizarse en varios lugares.

Por supuesto, en algún momento en el futuro, puede volver a formatear su base de datos (sucede) en cuyo caso podría pensar que sería ventajoso tener el código en más de un lugar. Pero ... estás codificando algo que puede no suceder.

Cualquier código adicional (incluso si nunca cambia) está sobrecargado, ya que deberá escribirse, leerse, almacenarse y probarse.

Todo lo anterior es cierto, sería negligente de su parte no hacer ninguna validación en absoluto. Para mostrar un nombre completo en la aplicación, necesitaría algunos datos básicos, incluso si no valida los datos en sí.

Robbie Dee
fuente
1

En palabras simples.

No existe tal cosa como "la base de datos" o "la aplicación" .

  1. Una base de datos puede ser utilizada por más de una aplicación.
  2. Una aplicación puede usar más de una base de datos.
  3. El modelo de base de datos debe exigir la integridad de los datos, lo que incluye arrojar un error cuando un campo requerido no se incluye en una operación de inserción, a menos que se defina un valor predeterminado en la definición de la tabla. Esto debe hacerse incluso si inserta la fila directamente en la base de datos sin pasar por la aplicación. Deje que el sistema de base de datos lo haga por usted.
  4. Las bases de datos deben proteger la integridad de los datos y arrojar errores .
  5. La lógica empresarial debe detectar esos errores y lanzar excepciones a la capa de presentación.
  6. La capa de presentación debe validar la entrada, manejar excepciones o mostrar un hámster triste al usuario.

De nuevo:

  • Base de datos-> errores de lanzamiento
  • Lógica empresarial-> detectar errores y lanzar excepciones
  • Capa de presentación-> validar, lanzar excepciones o mostrar mensajes tristes.
Tulains Córdova
fuente