Une partie de mon programme récupère les données de nombreuses tables et colonnes dans ma base de données pour le traitement. Certaines colonnes peuvent l'être null
, mais dans le contexte de traitement actuel, c'est une erreur.
Cela ne devrait "théoriquement" pas se produire, donc s'il le fait, cela indique des données incorrectes ou un bogue dans le code. Les erreurs ont différentes gravités, selon le champ null
; c'est-à-dire que pour certains champs, le traitement doit être interrompu et que quelqu'un doit en être informé, pour d'autres, le traitement doit être autorisé à se poursuivre et simplement informer quelqu'un.
Existe-t-il de bons principes d'architecture ou de conception pour gérer les null
entrées rares mais possibles ?
Les solutions devraient être possibles à implémenter avec Java mais je n'ai pas utilisé la balise car je pense que le problème est quelque peu indépendant du langage.
Quelques pensées que j'avais moi-même:
Utilisation de NOT NULL
Le plus simple serait d'utiliser une contrainte NOT NULL dans la base de données.
Mais que se passe-t-il si l'insertion originale des données est plus importante que cette étape de traitement ultérieure? Donc, dans le cas où l'insertion mettrait un null
dans la table (soit à cause de bogues, soit d'une raison valable), je ne voudrais pas que l'insertion échoue. Disons que de nombreuses autres parties du programme dépendent des données insérées, mais pas de cette colonne particulière. Je préfère donc risquer l'erreur dans l'étape de traitement actuelle au lieu de l'étape d'insertion. C'est pourquoi je ne veux pas utiliser de contrainte NOT NULL.
Naïvement dépendant de NullPointerException
Je pourrais simplement utiliser les données comme si je m'attendais à ce qu'elles soient toujours là (et cela devrait vraiment être le cas), et attraper les NPE résultants à un niveau approprié (par exemple pour que le traitement de l'entrée actuelle s'arrête mais pas la progression du traitement dans son ensemble) ). C'est le principe du «fail fast» et je le préfère souvent. S'il s'agit d'un bogue au moins, j'obtiens un NPE enregistré.
Mais je perds alors la capacité de différencier les différents types de données manquantes. Par exemple, pour certaines données manquantes, je pourrais les laisser de côté, mais pour d'autres, le traitement devrait être arrêté et un administrateur notifié.
Vérification null
avant chaque accès et levée d'exceptions personnalisées
Les exceptions personnalisées me permettraient de décider de l'action correcte en fonction de l'exception, donc cela semble être la voie à suivre.
Mais si j'oublie de le vérifier quelque part? De plus, j'encombre ensuite mon code avec des contrôles nuls qui ne sont jamais ou rarement attendus (et qui ne font donc certainement pas partie du flux de logique métier).
Si je choisis cette voie, quels modèles conviennent le mieux à l'approche?
Toutes les réflexions et commentaires sur mes approches sont les bienvenus. Aussi de meilleures solutions de toute nature (modèles, principes, meilleure architecture de mon code ou modèles, etc.).
Éditer:
Il y a une autre contrainte, en ce que j'utilise un ORM pour faire le mappage de la base de données à l'objet de persistance, donc faire des vérifications nulles à ce niveau ne fonctionnerait pas (car les mêmes objets sont utilisés dans des parties où le null ne fait aucun mal) . J'ai ajouté cela parce que les réponses fournies jusqu'à présent mentionnaient toutes deux cette option.
Réponses:
Je mettrais les vérifications nulles dans votre code de mappage, où vous construisez votre objet à partir du jeu de résultats. Cela place la vérification à un seul endroit et ne permettra pas à votre code de traiter à mi-chemin un enregistrement avant de frapper une erreur. Selon le fonctionnement de votre flux d'application, vous souhaiterez peut-être effectuer le mappage de tous les résultats en tant qu'étape de prétraitement au lieu de mapper et de traiter chaque enregistrement un par un.
Si vous utilisez un ORM, vous devrez effectuer toutes vos vérifications nulles avant de traiter chaque enregistrement. Je recommanderais une
recordIsValid(recordData)
méthode de type, de cette façon, vous pouvez (encore une fois) garder toute la logique de vérification nulle et de validation en un seul endroit. Je ne voudrais certainement pas mélanger les contrôles nuls avec le reste de votre logique de traitement.la source
Il semble que l'insertion d'une valeur nulle soit une erreur, mais vous avez peur d'imposer cette erreur lors de l'insertion, car vous ne voulez pas perdre de données. Cependant, si un champ ne doit pas être nul mais l'est, vous perdez des données . Par conséquent, la meilleure solution consiste à s'assurer que les champs nuls ne sont pas enregistrés par erreur en premier lieu.
À cette fin, assurez-vous que les données sont correctes dans le seul référentiel permanent faisant autorité pour ces données, la base de données. Faites-le en ajoutant des contraintes non nulles. Ensuite, votre code peut échouer, mais ces échecs vous avertissent immédiatement des bogues, vous permettant de corriger les problèmes qui vous font déjà perdre des données. Maintenant que vous pouvez facilement identifier les bogues, testez votre code et testez-le deux fois. Vous serez en mesure de corriger les bogues entraînant une perte de données et, ce faisant, de simplifier considérablement le traitement en aval des données, car vous n'aurez pas à vous soucier des valeurs nulles.
la source
En ce qui concerne cette phrase dans la question:
J'ai toujours apprécié cette citation (gracieuseté de cet article ):
Fondamentalement, il semble que vous approuviez la loi de Postel , "soyez conservateur dans ce que vous envoyez, soyez libéral dans ce que vous acceptez". Bien qu'il soit excellent en théorie, dans la pratique, ce «principe de robustesse» conduit à des logiciels qui ne sont pas robustes , du moins à long terme - et parfois à court terme également. (Comparez l'article d'Eric Allman The Robustness Principle Reconsidered , qui est un traitement très approfondi du sujet, bien que principalement axé sur les cas d'utilisation de protocoles de réseau.)
Si vous avez des programmes qui n'insèrent pas correctement des données dans votre base de données, ces programmes sont endommagés et doivent être corrigés . La résolution du problème ne fait que l'empirer; c'est l'équivalent en génie logiciel de permettre à un toxicomane de continuer sa dépendance.
De façon pragmatique, cependant, vous devez parfois permettre au comportement "cassé" de se poursuivre, au moins temporairement, en particulier dans le cadre d'une transition transparente d'un état cassé laxiste à un état strict et correct. Dans ce cas, vous souhaitez trouver un moyen de permettre aux insertions incorrectes de réussir, tout en permettant au magasin de données "canonique" d'être toujours dans un état correct . Il ya différentes manière de faire ceci:
Une façon de contourner tous ces problèmes consiste à insérer une couche API que vous contrôlez entre les programmes qui émettent des écritures et la base de données réelle.
Il semble qu'une partie de votre problème est que vous ne connaissez même pas tous les endroits qui génèrent des écritures incorrectes - ou qu'il y en a tout simplement trop pour que vous puissiez les mettre à jour. C'est un état effrayant, mais il n'aurait jamais dû se produire en premier lieu.
Dès que vous obtiendrez plus d'une poignée de systèmes autorisés à modifier les données dans un magasin de données de production canonique, vous aurez des ennuis: il n'y a aucun moyen de maintenir centralement quoi que ce soit à propos de cette base de données. Il serait préférable d'autoriser le moins de processus possible à émettre des écritures et à les utiliser en tant que «portiers» qui peuvent prétraiter les données avant de les insérer si nécessaire. Le mécanisme exact pour cela dépend vraiment de votre architecture spécifique.
la source
" Existe-t-il de bons principes d'architecture ou de conception pour gérer les entrées nulles rares mais possibles? "
Réponse simple - oui.
ETL
Effectuez un traitement initial pour vous assurer que les données sont de qualité suffisante pour entrer dans la base de données. Tout ce qui se trouve dans le fichier de dépôt doit être signalé et toutes les données propres peuvent être chargées dans la base de données.
En tant que quelqu'un qui a été à la fois braconnier (dev) et gardien de jeu (DBA), je sais par expérience amère que les tiers ne résoudront tout simplement pas leurs problèmes de données à moins qu'ils ne soient obligés de le faire. Se pencher constamment en arrière et masser les données à travers crée un dangereux précédent.
Mart / Dépôt
Dans ce scénario, les données brutes sont transmises à la base de données du référentiel, puis une version filtrée est envoyée à la base de données mart à laquelle les applications ont ensuite accès.
Les valeurs par défaut
Si vous pouvez appliquer des valeurs par défaut raisonnables aux colonnes, vous devriez, bien que cela puisse impliquer un certain travail s'il s'agit d'une base de données existante.
Échouer tôt
Il est tentant de simplement résoudre les problèmes de données au niveau de la passerelle vers l'application, la suite de rapports, l'interface, etc. Je vous conseille fortement de ne pas vous fier uniquement à cela. Si vous connectez un autre widget à la base de données, vous serez potentiellement confronté à nouveau aux mêmes problèmes. Traitez les problèmes de qualité des données.
la source
Chaque fois que votre cas d'utilisation permet de remplacer NULL en toute sécurité par une bonne valeur par défaut, vous pouvez effectuer la conversion dans les
SELECT
instructions Sql à l'aide deISNULL
ouCOALESCE
. Donc au lieu deon peut écrire
Bien sûr, cela ne fonctionnera que lorsque l'ORM permettra de manipuler directement les instructions de sélection ou de fournir des modèles modifiables pour la génération. Il faut s'assurer qu'aucune erreur "réelle" n'est masquée de cette façon, donc n'appliquez-la que si le remplacement par une valeur par défaut est exactement ce que vous voulez en cas de NULL.
Si vous pouvez modifier la base de données et le schéma, et que votre système db le prend en charge, vous pouvez envisager d'ajouter une clause de valeur par défaut aux colonnes spécifiques, comme suggéré par @RobbieDee. Cependant, cela nécessitera également de modifier les données existantes dans la base de données pour supprimer toutes les valeurs NULL précédemment insérées, et cela supprimera la possibilité de distinguer entre les données d'importation correctes et incomplètes par la suite.
D'après ma propre expérience, je sais que l'utilisation d'ISNULL peut étonnamment bien fonctionner - dans le passé, je devais maintenir une application héritée où les développeurs d'origine avaient oublié d'ajouter des contraintes NOT NULL à de nombreuses colonnes, et nous ne pouvions pas facilement ajouter ces contraintes plus tard pour quelques raisons. Mais dans 99% de tous les cas, 0 par défaut pour les colonnes de nombres et la chaîne vide par défaut pour les colonnes de texte était tout à fait acceptable.
la source
L'OP suppose une réponse qui associe les règles métier aux détails techniques de la base de données.
Ce sont toutes des règles commerciales. Les règles commerciales ne se soucient pas de nullité en soi. Pour tout ce qu'il sait, la base de données pourrait avoir null, 9999, "BOO!" ... C'est juste une autre valeur. Que, dans un SGBDR, null a des propriétés intéressantes et des utilisations uniques est sans objet.
La seule chose qui compte, c'est ce que la "nullité" signifie pour le ou les objets métier donnés ...
Oui.
Lancer une exception lors de la récupération des données n'a pas de sens.
La question est "dois-je stocker de" mauvaises "données"? Ça dépend:
la source
Il existe de nombreuses façons de gérer les valeurs nulles, nous allons donc passer de la couche de base de données à la couche d'application.
Couche de base de données
Vous pouvez interdire les null ; bien qu'ici ce ne soit pas pratique.
Vous pouvez configurer une valeur par défaut par colonne:
insert
, donc ne couvre pas l'insertion nulle expliciteinsert
colonne a manqué par erreurVous pouvez configurer un déclencheur , de sorte qu'à l'insertion, les valeurs manquantes soient automatiquement calculées:
insert
Couche de requête
Vous pouvez ignorer les lignes où un inconvénient
null
est présent:Vous pouvez fournir une valeur par défaut dans la requête:
Remarque: l'instrumentation de chaque requête n'est pas nécessairement un problème si vous disposez d'un moyen automatisé de les générer.
Couche d'application
Vous pouvez pré-vérifier le tableau pour les interdits
null
:Vous pouvez interrompre le traitement lorsque vous rencontrez un interdit
null
:null
et de celles qui ne peuvent pasVous pouvez sauter la ligne lorsque vous rencontrez un interdit
null
:null
et de celles qui ne peuvent pasVous pouvez envoyer une notification lorsque vous rencontrez une interdiction
null
, soit une à la fois, soit par lot, ce qui est complémentaire aux autres méthodes présentées ci-dessus. Cependant, ce qui importe le plus, c'est "et alors?" en cours de retraitement.Compte tenu de votre situation, je gérerais la situation au niveau de l'application et combinerais soit:
J'aurais tendance à simplement sauter si possible pour garantir en quelque sorte un minimum de progrès, surtout si le traitement peut prendre du temps.
Si vous n'avez pas besoin de retraiter les lignes ignorées, alors simplement les enregistrer devrait être suffisant et un e-mail envoyé à la fin du processus avec le nombre de lignes ignorées sera une notification appropriée.
Sinon, j'utiliserais une table d'appoint pour les lignes à corriger (et à retraiter). Cette table d'appoint peut être soit une simple référence (sans clé étrangère), soit une copie complète: cette dernière, même si elle est plus chère, est nécessaire si vous n'avez pas le temps de vous en occuper
null
avant de devoir nettoyer les données principales.la source
Les valeurs nulles peuvent être gérées lors de la traduction ou du mappage des types de base de données en types de langue. Par exemple en C #, voici une méthode générique qui gère null pour vous pour tout type:
Ou, si vous souhaitez effectuer une action ...
Et puis dans le mappage, dans ce cas à un objet de type "Sample", nous traiterons null pour n'importe laquelle des colonnes:
Enfin, toutes les classes de mappage peuvent être générées automatiquement en fonction de la requête SQL ou des tables impliquées en examinant les types de données SQL et en les convertissant en types de données spécifiques au langage. C'est ce que de nombreux ORM font pour vous automatiquement. Notez que certains types de bases de données peuvent ne pas avoir de mappage direct (colonnes géospatiales, etc.) et peuvent nécessiter une gestion spéciale.
la source