Uma parte do meu programa busca dados de muitas tabelas e colunas no meu banco de dados para processamento. Algumas das colunas podem estar null
, mas no contexto de processamento atual, isso é um erro.
Isso "teoricamente" não deve acontecer, portanto, se isso ocorrer, ele indica dados incorretos ou um bug no código. Os erros têm gravidades diferentes, dependendo de qual campo é null
; ou seja, para alguns campos o processamento deve ser interrompido e alguém notificado, para outros o processamento deve continuar e apenas notificar alguém.
Existem bons princípios de arquitetura ou design para lidar com as null
entradas raras, mas possíveis ?
As soluções devem ser possíveis de implementar com Java, mas eu não usei a tag porque acho que o problema é um pouco independente da linguagem.
Alguns pensamentos que eu tinha:
Usando NOT NULL
O mais fácil seria usar uma restrição NOT NULL no banco de dados.
Mas e se a inserção original dos dados for mais importante que esta etapa de processamento posterior? Portanto, caso a inserção coloque um null
na tabela (por causa de bugs ou talvez por algum motivo válido), eu não gostaria que a inserção falhasse. Digamos que muitas outras partes do programa dependam dos dados inseridos, mas não dessa coluna em particular. Portanto, prefiro arriscar o erro na etapa de processamento atual em vez da etapa de inserção. É por isso que não quero usar uma restrição NOT NULL.
Ingenuamente, dependendo de NullPointerException
Eu poderia apenas usar os dados como se esperasse que eles estivessem sempre lá (e esse deveria realmente ser o caso) e capturar os NPEs resultantes em um nível apropriado (por exemplo, para que o processamento da entrada atual pare, mas não todo o progresso do processamento ) Esse é o princípio do "falhar rápido" e eu geralmente prefiro. Se for um bug, pelo menos, recebo um NPE registrado.
Mas, então, perco a capacidade de diferenciar entre vários tipos de dados ausentes. Por exemplo, para alguns dados ausentes, eu poderia deixar de fora, mas para outros, o processamento deve ser interrompido e um administrador notificado.
Verificando null
antes de cada acesso e lançando exceções personalizadas
Exceções personalizadas me permitem decidir a ação correta com base na exceção, portanto esse parece ser o caminho a seguir.
Mas e se eu esquecer de verificar em algum lugar? Além disso, desorganizo meu código com verificações nulas que nunca ou raramente são esperadas (e, portanto, definitivamente não fazem parte do fluxo da lógica de negócios).
Se eu optar por seguir esse caminho, quais padrões são mais adequados para a abordagem?
Quaisquer pensamentos e comentários sobre minhas abordagens são bem-vindos. Também melhores soluções de qualquer tipo (padrões, princípios, melhor arquitetura do meu código ou modelos etc.).
Editar:
Há outra restrição: eu estou usando um ORM para fazer o mapeamento do banco de dados para o objeto de persistência, portanto, fazer verificações nulas nesse nível não funcionaria (como os mesmos objetos são usados em partes em que o nulo não causa nenhum dano) . Eu adicionei isso porque as respostas fornecidas até agora mencionaram essa opção.
Respostas:
Eu colocaria as verificações nulas no seu código de mapeamento, onde você constrói seu objeto a partir do conjunto de resultados. Isso coloca a verificação em um só lugar e não permitirá que seu código passe pela metade do processamento de um registro antes de ocorrer um erro. Dependendo de como o fluxo do aplicativo funciona, convém executar o mapeamento de todos os resultados como uma etapa de pré-processamento, em vez de mapear e processar cada registro, um de cada vez.
Se você estiver usando um ORM, precisará executar todas as suas verificações nulas antes de processar cada registro. Eu recomendaria um
recordIsValid(recordData)
método -type, para que você possa (novamente) manter toda a lógica de verificação nula e outra validação em um só lugar. Definitivamente, não misturaria as verificações nulas com o restante da sua lógica de processamento.fonte
Parece que inserir um nulo é um erro, mas você tem medo de aplicar esse erro na inserção porque não deseja perder dados. No entanto, se um campo não deve ser nulo, mas sim, você está perdendo dados . Portanto, a melhor solução é garantir que os campos nulos não sejam salvos erroneamente em primeiro lugar.
Para esse fim, imponha que os dados estejam corretos no repositório permanente autorizado e autorizado para esses dados, o banco de dados. Faça isso adicionando restrições não nulas. Em seguida, seu código poderá falhar, mas essas falhas notificarão você imediatamente sobre erros, permitindo corrigir problemas que já estão causando a perda de dados. Agora que você pode facilmente identificar erros, teste seu código e duas vezes. Você poderá corrigir os erros que levam à perda de dados e, no processo, simplificar bastante o processamento posterior dos dados, porque não precisará se preocupar com valores nulos.
fonte
Em relação a esta sentença na pergunta:
Eu sempre apreciei esta citação (cortesia deste artigo ):
Basicamente: parece que você está endossando a Lei de Postel , "seja conservador no que envia, seja liberal no que aceita". Embora seja ótimo em teoria, na prática, esse "princípio de robustez" leva a um software que não é robusto , pelo menos a longo prazo - e às vezes também a curto prazo. (Compare o artigo de Eric Allman, O princípio da robustez reconsiderado , que é um tratamento muito completo do assunto, embora principalmente focado nos casos de uso de protocolos de rede.)
Se você possui programas que estão inserindo dados incorretamente no banco de dados, esses programas estão danificados e precisam ser corrigidos . Cobrir o problema apenas permite que ele continue piorando; este é o equivalente da engenharia de software de permitir que um viciado continue seu vício.
Pragmaticamente falando, no entanto, às vezes você precisa permitir que o comportamento "quebrado" continue, pelo menos temporariamente, especialmente como parte de uma transição contínua de um estado frouxo e frouxo para um estado estrito e correto. Nesse caso, você deseja encontrar uma maneira de permitir que as inserções incorretas tenham êxito, mas ainda permita que o armazenamento de dados "canônico" esteja sempre em um estado correto . Existem várias maneiras de fazer isso:
Uma maneira de contornar todos esses problemas é inserir uma camada de API que você controla entre os programas que emitem gravações e o banco de dados real.
Parece que parte do seu problema é que você nem conhece todos os lugares que estão gerando gravações incorretas - ou simplesmente existem muitos deles para atualizar. É um estado assustador, mas nunca deveria ter sido permitido surgir.
Assim que você tiver mais de um punhado de sistemas com permissão para modificar dados em um armazenamento de dados de produção canônico, você estará com problemas: não há como manter centralmente nada sobre esse banco de dados. Melhor seria permitir que o menor número de processos possível emitisse gravações e os usasse como "gatekeepers" que podem pré-processar os dados antes de inseri-los conforme necessário. O mecanismo exato para isso realmente depende da sua arquitetura específica.
fonte
" Existem bons princípios de arquitetura ou design para lidar com as entradas nulas raras, mas possíveis? "
Resposta simples - sim.
ETL
Realize algum processamento inicial para garantir que os dados tenham qualidade suficiente para entrar no banco de dados. Qualquer coisa no arquivo suspenso deve ser relatada novamente e todos os dados limpos podem ser carregados no banco de dados.
Como alguém que tem sido caçador (dev) e detentor de jogos (DBA), sei por experiência amarga que terceiros simplesmente não resolverão seus problemas de dados, a menos que sejam forçados a fazê-lo. Constantemente curvando-se para trás e massageando dados através de conjuntos de um precedente perigoso.
Mart / Repositório
Nesse cenário, os dados brutos são enviados para o banco de dados do repositório e, em seguida, uma versão higienizada é enviada para o banco de dados do mercado a que os aplicativos têm acesso.
Valores padrão
Se você pode aplicar valores padrão sensíveis às colunas, deve fazê-lo, embora isso possa envolver algum trabalho, se este for um banco de dados existente.
Falhar cedo
É tentador simplesmente resolver problemas de dados no gateway para o aplicativo, conjunto de relatórios, interface etc. Recomendamos que você não confie apenas nisso. Se você conectar algum outro widget ao banco de dados, será possível enfrentar os mesmos problemas novamente. Resolva os problemas de qualidade dos dados.
fonte
Sempre que seu caso de uso permitir substituir NULL com segurança por um bom valor padrão, você poderá fazer a conversão nas
SELECT
instruções Sql usandoISNULL
ouCOALESCE
. Então, ao invés dealguém pode escrever
Obviamente, isso só funcionará quando o ORM permitir manipular diretamente as instruções de seleção ou fornecer modelos alteráveis para a geração. Deve-se garantir que nenhum erro "real" seja mascarado dessa maneira; portanto, aplique-o apenas se a substituição por um valor padrão for exatamente o que você deseja no caso de NULL.
Se você conseguir alterar o banco de dados e o esquema, e o sistema db suportar isso, considere adicionar uma cláusula de valor padrão às colunas específicas, conforme sugerido por @RobbieDee. No entanto, isso também exigirá modificar os dados existentes no banco de dados para remover quaisquer valores NULL inseridos anteriormente e removerá a capacidade de distinguir os dados de importação corretos e incompletos posteriormente.
Pela minha própria experiência, eu sei que usar o ISNULL pode funcionar surpreendentemente bem - no passado, eu tive que manter um aplicativo legado em que os desenvolvedores originais haviam esquecido de adicionar restrições NOT NULL a muitas colunas e não pudemos adicioná-las facilmente mais tarde. por algumas razões. Mas em 99% de todos os casos, 0 como padrão para colunas numéricas e a sequência vazia como padrão para colunas de texto eram totalmente aceitáveis.
fonte
O OP está assumindo uma resposta que combina as regras de negócios com os detalhes técnicos do banco de dados.
Isso é todas as regras de negócios. As regras de negócios não se importam com nulo per se. Pelo que se sabe, o banco de dados pode ter nulo, 9999, "BOO!" ... É apenas outro valor. Que, em um RDBMS, null tem propriedades interessantes e usos exclusivos é discutível.
A única coisa que importa é o que "nulidade" significa para o (s) objeto (s) de negócios especificado (s) ...
Sim.
Lançar uma exceção na recuperação de dados não faz sentido.
A questão é "devo armazenar dados 'ruins'"? Depende:
fonte
Existem várias maneiras de lidar com nulos, portanto, passaremos da camada do banco de dados para a camada do aplicativo.
Camada de banco de dados
Você pode proibir nulos ; embora aqui seja impraticável.
Você pode configurar um padrão por coluna:
insert
, portanto, não cobre inserção nula explícitainsert
coluna erroneamente errouVocê pode configurar um gatilho para que, após a inserção, os valores ausentes sejam calculados automaticamente:
insert
Camada de consulta
Você pode pular linhas onde um inconveniente
null
está presente:Você pode fornecer um valor padrão na consulta:
Nota: instrumentar cada consulta não é necessariamente um problema se você tiver alguma maneira automatizada de gerá-las.
Camada de aplicação
Você pode verificar previamente a tabela como proibido
null
:Você pode interromper o processamento ao encontrar um proibido
null
:null
e quais não podemVocê pode pular a linha ao encontrar um proibido
null
:null
e quais não podemVocê pode enviar uma notificação ao encontrar um proibido
null
, um de cada vez ou por lote, que é complementar às outras formas apresentadas acima. O que importa mais, no entanto, é "o que é então?", Mais notavelmente, se você espera que a linha seja corrigida e precise ser processada novamente, talvez seja necessário garantir que você tenha alguma maneira de distinguir as linhas já processadas das linhas que precisam de sendo processado novamente.Dada a sua situação, eu lidaria com a situação no aplicativo e combinaria:
Eu tenderia a pular, se possível, de alguma forma, garantir um pouco de progresso, especialmente se o processamento levar algum tempo.
Se você não precisar reprocessar as linhas ignoradas, simplesmente registrá-las deve ser suficiente e um e-mail enviado no final do processo com o número de linhas ignoradas será uma notificação adequada.
Caso contrário, eu usaria uma tabela lateral para as linhas serem corrigidas (e processadas novamente). Essa tabela lateral pode ser uma referência simples (sem chave estrangeira) ou uma cópia completa: a última, mesmo que mais cara, é necessária se você não tiver tempo para resolver a questão
null
antes de limpar os dados principais.fonte
Nulos podem ser manipulados na tradução ou mapeamento de tipos de banco de dados para tipos de idioma. Por exemplo, em C #, aqui está um método genérico que lida com nulo para você para qualquer tipo:
Ou, se você deseja executar uma ação ...
E então no mapeamento, neste caso para um objeto do tipo "Amostra", manipularemos null para qualquer uma das colunas:
Por fim, todas as classes de mapeamento podem ser geradas automaticamente com base na consulta ou nas tabelas SQL envolvidas, observando os tipos de dados SQL e convertendo-os para os tipos de dados específicos do idioma. Isso é o que muitos ORMs fazem por você automaticamente. Observe que alguns tipos de banco de dados podem não ter um mapeamento direto (colunas geoespaciais etc.) e podem precisar de tratamento especial.
fonte