Una parte de mi programa obtiene datos de muchas tablas y columnas de mi base de datos para su procesamiento. Algunas de las columnas pueden ser null
, pero en el contexto de procesamiento actual es un error.
Esto "teóricamente" no debería suceder, por lo que si lo hace, apunta a datos incorrectos o un error en el código. Los errores tienen diferentes severidades, dependiendo de qué campo es null
; es decir, para algunos campos, el procesamiento debe detenerse y alguien debe ser notificado; para otros, se debe permitir que el procesamiento continúe y simplemente notificar a alguien.
¿Hay alguna buena arquitectura o principios de diseño para manejar las null
entradas raras pero posibles ?
Las soluciones deberían ser posibles de implementar con Java, pero no utilicé la etiqueta porque creo que el problema es algo agnóstico al lenguaje.
Algunos pensamientos que tuve yo mismo:
Usando NOT NULL
Lo más fácil sería usar una restricción NOT NULL en la base de datos.
Pero, ¿qué sucede si la inserción original de los datos es más importante que este paso de procesamiento posterior? Por lo tanto, en caso de que el inserto pusiera un elemento null
en la tabla (ya sea por errores o incluso por alguna razón válida), no quisiera que el inserto falle. Digamos que muchas más partes del programa dependen de los datos insertados, pero no de esta columna en particular. Por lo tanto, prefiero arriesgarme al error en el paso de procesamiento actual en lugar del paso de inserción. Es por eso que no quiero usar una restricción NOT NULL.
Ingenuamente dependiendo de NullPointerException
Podría usar los datos como si esperara que siempre estuvieran allí (y ese debería ser realmente el caso), y capturar los NPE resultantes en un nivel apropiado (por ejemplo, para que el procesamiento de la entrada actual se detenga pero no todo el progreso del procesamiento) ) Este es el principio de "falla rápida" y a menudo lo prefiero. Si es un error, al menos obtengo un NPE registrado.
Pero luego pierdo la capacidad de diferenciar entre varios tipos de datos faltantes. Por ejemplo, para algunos datos faltantes, podría omitirlos, pero para otros, el procesamiento debería detenerse y notificar a un administrador.
Comprobando null
antes de cada acceso y lanzando excepciones personalizadas
Las excepciones personalizadas me permitirían decidir la acción correcta en función de la excepción, por lo que este parece ser el camino a seguir.
Pero, ¿qué pasa si me olvido de revisarlo en alguna parte? Además, abarroto mi código con verificaciones nulas que nunca o raramente se esperan (y definitivamente no forman parte del flujo de lógica de negocios).
Si elijo seguir este camino, ¿qué patrones son los más adecuados para el enfoque?
Cualquier pensamiento y comentario sobre mis enfoques son bienvenidos. También mejores soluciones de cualquier tipo (patrones, principios, mejor arquitectura de mi código o modelos, etc.).
Editar:
Hay otra restricción, ya que estoy usando un ORM para hacer el mapeo de DB a objeto de persistencia, por lo que hacer comprobaciones nulas en ese nivel no funcionaría (ya que los mismos objetos se usan en partes donde el nulo no hace ningún daño) . Agregué esto porque las respuestas proporcionadas hasta ahora mencionaron esta opción.
Respuestas:
Pondría las verificaciones nulas en su código de mapeo, donde construye su objeto a partir del conjunto de resultados. Eso coloca la verificación en un lugar y no permitirá que su código llegue a la mitad del procesamiento de un registro antes de detectar un error. Dependiendo de cómo funcione el flujo de su aplicación, es posible que desee realizar la asignación de todos los resultados como un paso de preprocesamiento en lugar de asignar y procesar cada registro de uno en uno.
Si está utilizando un ORM, deberá realizar todas las comprobaciones nulas antes de procesar cada registro. Recomendaría un
recordIsValid(recordData)
método de tipo, de esa manera puede (nuevamente) mantener toda la lógica de verificación nula y otra lógica de validación en un solo lugar. Definitivamente no mezclaría los cheques nulos con el resto de su lógica de procesamiento.fuente
Parece que insertar un valor nulo es un error, pero tiene miedo de aplicar este error en la inserción porque no desea perder datos. Sin embargo, si un campo no debería ser nulo pero sí, está perdiendo datos . Por lo tanto, la mejor solución es asegurarse de que los campos nulos no se guarden erróneamente en primer lugar.
Con este fin, haga cumplir que los datos sean correctos en el único repositorio autorizado y permanente para esos datos, la base de datos. Hágalo agregando restricciones no nulas. Entonces, su código puede fallar, pero estas fallas le notifican de inmediato los errores, lo que le permite corregir los problemas que ya están causando la pérdida de datos. Ahora que puede identificar fácilmente errores, pruebe su código y pruébelo dos veces. Podrá corregir errores que conducen a la pérdida de datos y, en el proceso, simplificar en gran medida el procesamiento posterior de los datos porque no tendrá que preocuparse por los nulos.
fuente
Con respecto a esta oración en la pregunta:
Siempre he apreciado esta cita (cortesía de este artículo ):
Básicamente: parece que estás respaldando la Ley de Postel , "sé conservador en lo que envías, sé liberal en lo que aceptas". Si bien es excelente en teoría, en la práctica este "principio de robustez" conduce a un software que no es robusto , al menos a largo plazo, y a veces también a corto plazo. (Compárese el artículo de Eric Allman, El principio de robustez reconsiderado , que es un tratamiento muy completo del tema, aunque se centra principalmente en los casos de uso del protocolo de red).
Si tiene programas que están insertando datos incorrectamente en su base de datos, esos programas están rotos y deben repararse . Empapelar el problema solo le permite seguir empeorando; Este es el equivalente en ingeniería de software de permitir que un adicto continúe su adicción.
Sin embargo, en términos pragmáticos, a veces es necesario permitir que continúe el comportamiento "roto", al menos temporalmente, especialmente como parte de una transición fluida de un estado laxo y roto a un estado estricto y correcto. En ese caso, desea encontrar una manera de permitir que las inserciones incorrectas tengan éxito, pero aún así permitir que el almacén de datos "canónicos" siempre esté en un estado correcto . Hay varias maneras de hacer esto:
Una forma de evitar todos estos problemas es insertar una capa de API que controle entre los programas que emiten escrituras y la base de datos real.
Parece que parte de su problema es que ni siquiera conoce todos los lugares que generan escrituras incorrectas, o que simplemente hay demasiados para que pueda actualizar. Es un estado aterrador, pero nunca se debería haber permitido que surgiera en primer lugar.
Tan pronto como obtenga más de un puñado de sistemas que tienen permiso para modificar datos en un almacén de datos de producción canónico, se encontrará en problemas: no hay forma de mantener centralmente nada sobre esa base de datos. Mejor sería permitir la menor cantidad posible de procesos para emitir escrituras, y usarlas como "guardianes" que puedan preprocesar los datos antes de insertarlos según sea necesario. El mecanismo exacto para esto realmente depende de su arquitectura específica.
fuente
" ¿Hay alguna buena arquitectura o principios de diseño para manejar las entradas nulas raras pero posibles? "
Respuesta simple: sí.
ETL
Realice un procesamiento inicial para garantizar que los datos sean de calidad suficiente para ingresar a la base de datos. Cualquier cosa en el archivo desplegable debe ser informada y cualquier dato limpio puede cargarse en la base de datos.
Como alguien que ha sido tanto cazador furtivo (dev) como guardián del juego (DBA), sé por amarga experiencia que los terceros simplemente no resolverán sus problemas de datos a menos que se vean obligados a hacerlo. Doblar constantemente hacia atrás y masajear los datos establece un precedente peligroso.
Mart / Repository
En este escenario, los datos sin procesar se introducen en la base de datos del repositorio y luego se envía una versión desinfectada a la base de datos de mart a la que las aplicaciones tienen acceso.
Valores predeterminados
Si puede aplicar valores predeterminados razonables a las columnas, debería hacerlo, aunque esto puede implicar algo de trabajo si se trata de una base de datos existente.
Fallar temprano
Es tentador simplemente abordar los problemas de datos en la puerta de enlace a la aplicación, el conjunto de informes, la interfaz, etc. Le recomiendo encarecidamente que no confíe únicamente en esto. Si conecta algún otro widget a la base de datos, posiblemente volverá a enfrentar los mismos problemas. Abordar los problemas de calidad de los datos.
fuente
Siempre que su caso de uso permita reemplazar NULL de forma segura por un buen valor predeterminado, puede hacer la conversión en las
SELECT
declaraciones SQL usandoISNULL
oCOALESCE
. Entonces en lugar deuno puede escribir
Por supuesto, eso solo funcionará cuando el ORM permita manipular las sentencias select directamente o proporcionar plantillas modificables para la generación. Uno debe asegurarse de que no haya errores "reales" enmascarados de esta manera, así que aplíquelo solo si el reemplazo por un valor predeterminado es exactamente lo que desea en caso de NULL.
Si puede cambiar la base de datos y el esquema, y su sistema db lo admite, puede considerar agregar una cláusula de valor predeterminado a las columnas específicas, como lo sugiere @RobbieDee. Sin embargo, esto también requerirá modificar los datos existentes en la base de datos para eliminar cualquier valor NULL insertado previamente, y eliminará la capacidad de distinguir entre datos de importación correctos e incompletos después.
Según mi propia experiencia, sé que usar ISNULL puede funcionar sorprendentemente bien: en el pasado tuve que mantener una aplicación heredada donde los desarrolladores originales se habían olvidado de agregar restricciones NOT NULL a muchas columnas, y no pudimos agregar fácilmente esas restricciones más adelante por algunas razones. Pero en el 99% de todos los casos, 0 como valor predeterminado para las columnas de números y la cadena vacía como valor predeterminado para las columnas de texto era totalmente aceptable.
fuente
El OP supone una respuesta que combina las reglas de negocio con los detalles técnicos de la base de datos.
Estas son todas las reglas de negocios. Las reglas comerciales no se preocupan por nulo per-se. Por lo que sabe, la base de datos podría tener un valor nulo, 9999, "BOO!" ... Es solo otro valor. Que, en un RDBMS, nulo tiene propiedades interesantes y usos únicos es discutible.
Lo único que importa es lo que significa "nulidad" para los objetos comerciales dados ...
Si.
Lanzar una excepción en la recuperación de datos no tiene sentido.
La pregunta es "¿debo almacenar datos" malos "? Depende:
fuente
Hay muchas formas de manejar nulos, por lo que pasaremos de la capa de base de datos a la capa de aplicación.
Capa de base de datos
Puedes prohibir los nulos ; aunque aquí no es práctico.
Puede configurar un valor predeterminado por columna:
insert
, por lo que no cubre la inserción nula explícitainsert
erróneamente perdió esta columnaPuede configurar un activador , de modo que, al insertarlos, los valores faltantes se calculen automáticamente:
insert
Capa de consulta
Puede omitir filas donde haya un inconveniente
null
:Puede proporcionar un valor predeterminado en la consulta:
Nota: instrumentar cada consulta no es necesariamente un problema si tiene alguna forma automatizada de generarlas.
Capa de aplicación
Puede verificar previamente la tabla para lo prohibido
null
:Puede interrumpir el procesamiento cuando encuentre un prohibido
null
:null
y cuáles noPuede omitir la fila cuando encuentre un prohibido
null
:null
y cuáles noPuede enviar una notificación cuando encuentre un prohibido
null
, ya sea uno a la vez o por lotes, que es complementario a las otras formas presentadas anteriormente. Sin embargo, lo que más importa es "¿entonces qué?", Especialmente si espera que la fila sea parcheada y necesita ser reprocesada, es posible que deba asegurarse de que tiene alguna forma de distinguir las filas ya procesadas de las filas que necesitan siendo reprocesado.Dada su situación, manejaría la situación en la aplicación y combinaría:
Si fuera posible, tendería a omitir para garantizar de alguna manera un mínimo de progreso, especialmente si el procesamiento puede llevar tiempo.
Si no necesita volver a procesar las filas omitidas, simplemente registrarlas debería ser suficiente y un correo electrónico enviado al final del proceso con el número de filas omitidas será una notificación adecuada.
De lo contrario, usaría una tabla lateral para que las filas se arreglen (y se vuelvan a procesar). Esta tabla lateral puede ser una referencia simple (sin clave foránea) o una copia completa: esta última, incluso si es más costosa, es necesaria si no tiene tiempo para abordar la información
null
antes de tener que limpiar los datos principales.fuente
Los nulos se pueden manejar en la traducción o asignación de tipos de bases de datos a tipos de idiomas. Por ejemplo, en C #, aquí hay un método genérico que maneja nulo para usted para cualquier tipo:
O, si quieres realizar una acción ...
Y luego, en la asignación, en este caso a un objeto de tipo "Muestra", manejaremos nulo para cualquiera de las columnas:
Finalmente, todas las clases de mapeo se pueden generar automáticamente en función de la consulta SQL o las tablas involucradas al observar los tipos de datos SQL y traducirlos a los tipos de datos específicos del idioma. Esto es lo que muchos ORM hacen por usted automáticamente. Tenga en cuenta que algunos tipos de bases de datos pueden no tener una asignación directa (columnas geoespaciales, etc.) y pueden necesitar un manejo especial.
fuente