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

12

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

Ejemplo básico

Una simple, así que no pierdo a todo el mundo :).

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 yo soy el que codifica la parte de la interfaz de usuario, y otra persona está escribiendo el código de consulta de la base de datos.

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 que falta.

¿Qué pasa si la consulta está mal codificada y no maneja este caso? ¿Qué sucede si la persona que escribió la consulta maneja un resultado incompleto y cuando intentas mostrar la información, todo se bloquea porque tu 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, usted no es responsable de este accidente". Pero, sigue siendo tu parte del código que está fallando.

Otro ejemplo

Digamos que ahora soy yo quien escribe la consulta. Las especificaciones no dicen lo mismo que anteriormente, 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 de "selección" para asegurarme de proporcionarle información completa al tipo de UI?

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 mostrarlo, pero no maneja este caso en su código?

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

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

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

    
pregunta rdurand 24.05.2013 - 11:16

5 respuestas

14

Lo que estás hablando aquí es límites de confianza . ¿Confías en el límite entre tu aplicación y la base de datos? ¿Confía la base de datos en que los datos de la aplicación siempre están pre-validados?

Esa es una decisión que se debe tomar 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 confiarán felizmente en las API de terceros para que hagan lo que usted espera que hagan, todo el tiempo, todo el tiempo.

    
respondido por el pdr 24.05.2013 - 11:53
5

El principio de robustez "Sé conservador en lo que envías, sé liberal en lo que aceptas" es lo que buscas. . 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.

    
respondido por el Doc Brown 24.05.2013 - 11:58
1

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

  • El "caso feliz": alimenta la entrada válida de tu aplicación y asegúrate de que produzca la salida correcta.
  • Los casos de error: alimenta las entradas no válidas de tu aplicación y asegúrate de que las maneje correctamente.

Para hacer esto, no puede usar el componente de su colega: en su lugar, use burla , es decir, reemplace el resto de la aplicación con módulos "falsos" que puede controlar desde el marco de prueba . La forma exacta en que se hace esto depende de la forma en que los módulos interactúan puede ser suficiente con solo llamar a los métodos de su módulo con argumentos codificados, y puede llegar a ser tan complejo como escribir un marco completo que conecte las interfaces públicas de los otros módulos con el entorno de prueba.

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

En su caso de "Ejemplo básico", para probar su código por unidad, escriba una clase simulada que simule la capa de base de datos. Sin embargo, su clase simulada no va realmente a la base de datos: simplemente la carga 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ías 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 DB real se porta mal? Por ejemplo, podría lanzar 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 tenemos que hacer que MockDB lance 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 tus pruebas unitarias. Para la prueba de integración, no usa la clase MockDB; en su lugar, encadenan 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 cuanto 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 complique. 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.

    
respondido por el tdammers 24.05.2013 - 12:27
1

Hay 3 principios principales que intento codificar por:

  • DRY

  • KISS

  • YAGNI

La eliminación de todo esto es que corre el riesgo de escribir un código de validación que esté duplicado en otro lugar. Si las reglas de validación cambian, estas deberán actualizarse en varios lugares.

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

Cualquier código adicional (incluso si nunca cambia) es una sobrecarga, ya que será necesario escribirlo, leerlo, almacenarlo y probarlo.

Si todo lo anterior es cierto, sería una negligencia para usted no hacer ninguna validación. Para mostrar un nombre completo en la aplicación, necesitaría algunos datos básicos, incluso si no valida los datos en sí.

    
respondido por el Robbie Dee 24.05.2013 - 15:25
1

En palabras sencillas.

No hay 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 imponer la integridad de los datos, que incluye lanzar 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. Deja que el sistema de base de datos lo haga por ti.
  4. Las bases de datos deben proteger la integridad de los datos y generar errores .
  5. La lógica de negocios 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 hamster triste al usuario.

De nuevo:

  • Base de datos - > errores de lanzamiento
  • Lógica de negocios - > captura errores y lanza excepciones
  • Capa de presentación - > validar, lanzar excepciones o mostrar triste mensajes.
respondido por el Tulains Córdova 24.05.2013 - 21:30

Lea otras preguntas en las etiquetas