Como lidar com exceções que são absorvidas por uma biblioteca de terceiros?

5

Atualmente estou tendo um problema com um provedor de biblioteca de controle de terceiros. Eles têm uma cultura de ocultação de exceção que atrapalha a minha abordagem geral de fail-fast ao desenvolver software.

Um exemplo: seu controle de grade propõe um evento RowValidating , no qual é possível verificar a entrada de dados do usuário antes que ela seja confirmada no objeto subjacente. O argumento do evento tem uma propriedade booleana IsValid e uma propriedade% stringErrorText. Se o manipulador tiver a propriedade IsValid definida como False , a grade tratará a linha como inválida, apresentará o texto do erro seguido por "Deseja corrigir o valor?" em uma caixa de diálogo sim / não.

O problema é que se o manipulador lançar um Exception real durante o processo de validação, a grade o capturará e se comportará exatamente como descrito acima - apenas usará a propriedade Message da exceção capturada como o texto do erro de validação. A instância de exceção, mesmo que seja na verdade o MEU código que a causou, nunca poderá ser capturada posteriormente. Não há como eu lidar com isso além de colocar meu código de validação em um bloco try / catch que capture tudo.

Claro que não quero que meu código lance exceções, mas hey ... errare humanum est ! Se isso acontecer, prefiro muito mais que o app falhe e queime, em vez de esconder a exceção ! Para esses cenários, tenho um manipulador global que registrará a exceção não tratada logo antes da falha do aplicativo. É aqui que espero que qualquer um acabe.

Como minha empresa fez um grande investimento nesse provedor de terceiros (tanto de aprendizado quanto de código real em execução com ele), não posso simplesmente concorrer com outro fornecedor - muito menos fazer meu próprio controle de grade.

Eu tentei falar com o fornecedor para que isso seja resolvido, mas eles não farão nada com medo de causar mudanças de última hora para outros clientes (compreensível). Eles também não introduzem nenhum tipo de sinalizador booleano para contornar o comportamento (menos compreensível).

Neste momento, estou desconfortável com a implantação de um aplicativo no qual sei que há uma chance de ele ser executado em um estado corrompido. Eu também odeio ter que quebrar meu código de evento em um bloco try / catch que pode encontrar um Exception do qual é impossível se recuperar de forma graciosa.

Que tipo de solução posso usar para evitar ou corrigir esse problema?

TL; DR : o fornecedor terceirizado tira Exception das instâncias do meu próprio código, as soluções são feias e insatisfatórias, não sei o que fazer com isso.

    
por Crono 10.02.2015 / 18:47
fonte

5 respostas

2

Recentemente, resolvi um problema semelhante com uma biblioteca de terceiros. Permita-me reafirmar para ter certeza de que não estou interpretando mal sua situação. Você sabe como contornar isso, mas não gosta da repetição da solução alternativa e acha que isso obscurece seu código real?

Eu resolvi meu problema usando um decorador python que detecta uma exceção e a manipula apropriadamente. Dessa forma, é apenas uma% adicional@handle_errors para as definições de funções, não um bloco volumoso try-catch que é repetido em todos os lugares. Eu não estou muito familiarizado com a linha de produtos .net, mas sei que existem bibliotecas de programação orientadas a aspectos que você pode obter, ou você pode usar o padrão de decorador para alcançar uma redução semelhante em clichê.

Infelizmente, não há muito além disso que você possa fazer. É apenas o preço de bloqueio de fornecedor. No entanto, isso parece ser uma situação de baixa probabilidade e baixo impacto, com rotinas de validação que devem ser em grande parte apátridas e relativamente fáceis de testar completamente. Além disso, as exceções dificilmente são "ocultas" se forem apresentadas ao usuário. Esta não é a situação descrita no blog de Jeff Atwood.

    
por 10.02.2015 / 20:58
fonte
5

Como os controles da IU padrão da estrutura .NET não capturam exceções não tratadas por eles mesmos e oferecem alguns mecanismos para capturar essas exceções em um local central, concordo que é questionável por que um controle de terceiros deve se comportar de maneira diferente.

Vamos supor que, da natureza do seu aplicativo, no caso de uma falha grave, você tem 100% de certeza de poder sair do aplicativo a qualquer momento, mesmo em um evento de GUI arbitrário, sem o risco de deixar para trás muito dados inconsistentes. Então você pode considerar criar um wrapper funcional assim:

public class MyExit
{
  public void WhenUnhandledException(Action action)
  {
    try
    {
        action();  
    }
    catch(Exception ex)
    {
         // ... do some additional logging, if you like ...
         Environment.FailFast(ex.Message+"\n"+ex.StackTrace);
    }
  }
}

e use esta função em todo o código como este:

  void MyGUIExceptionHandler(object o, EventArgs e)
  {
     MyExit.WhenUnhandledException(() => {
       // add code, maybe using o and e
     });
  }

Dessa forma, você não precisa repetir o mesmo código try / catch / log / FailFast mais de uma vez, e você pode alterar sua estratégia fail-fast e logging depois, se quiser.

    
por 10.02.2015 / 23:31
fonte
2

O que há para fazer? Na minha opinião, a decisão de transformar uma exceção durante a validação em uma validação com falha é uma maneira correta de lidar com tais exceções.

  • Deixar a exceção passar e travar o aplicativo tem um risco significativo de perder o trabalho que o usuário estava fazendo, mesmo que a situação tenha sido causada por uma entrada incorreta do usuário e totalmente corrigível.
  • Em uma rotina de validação, você deve verificar apenas se a entrada é válida e não faz alterações no estado do aplicativo. Portanto, se uma exceção for lançada em uma rotina de validação, não deverá haver risco zero de que o estado do aplicativo esteja corrompido. Se você estiver fazendo mais do que apenas validação em seu manipulador, é problema seu garantir que não haja nenhum caminho com um estado de aplicativo inválido.
por 10.02.2015 / 20:06
fonte
2

Eu gosto muito de hard error & falhar rápido, eu acredito que eles são o único caminho certo e certo a seguir, mas eu tento não ser dogmático sobre eles.

Há casos em que a melhor coisa a fazer com uma exceção inesperada é registrá-lo e engoli-lo. Vou dar um exemplo mais simples do que a sua situação: suponha que você tenha uma coleção observável que, quando alterada, emite uma notificação para uma lista de observadores registrados. E suponha que um dos observadores lance uma exceção. É culpa da coleção? Não, pois não tem controle sobre quem se registra com ele. A culpa é do código que modificou a coleção? Certamente não, esse código provavelmente nem sabia que a coleção que estava modificando era um observável. É culpa de algum dos observadores restantes que estão registrados e também esperam receber suas notificações? Também não, como cada um deles não tem conhecimento de outros observadores, e nenhum controle sobre quem será notificado primeiro. Então, claramente, a única coisa que faz sentido para a coleção observável fazer é registrar a exceção, engoli-la e continuar invocando os observadores restantes na lista como se nada tivesse acontecido.

O criador dessa biblioteca está em situação semelhante e teve que tomar a mesma decisão.

O que eu faria no seu lugar é que eu cercaria todos os meus pontos de entrada com try-catch para ter certeza de que uma mensagem de erro significativa sempre será retornada à biblioteca que me chama.

Seria bom ter o suporte da linguagem para fazer coisas como essa (decoradores gerados automaticamente que captam exceções e invocam um substituível para lidar com eles) e a resposta de Karl Bielefeldt parece indicar que o Python tem esse mecanismo, mas aqueles de nós que não têm tais sutilezas à nossa disposição estão presos em fazê-lo à mão.

EDITAR:

Sua necessidade como desenvolvedor de saber que uma exceção não fatal ocorreu deve ser coberta principalmente ao registrar a exceção. (Você assiste seu log enquanto depura seu aplicativo, não é?)

Agora, se, como eu, você quiser eliminar a possibilidade de qualquer exceção não fatal rolar pelo log e passar despercebida, você pode fazer o que eu faço:

Eu tenho um método utilitário centralizado para engolir exceções que, imediatamente após o registro da exceção, realiza a seguinte chamada extremamente útil:

System.Diagnostics.Debugger.Break();

No campo, isso não faz nada. Mas em um ambiente de desenvolvimento, (durante a execução no depurador), isso entra no depurador, o que, teoricamente, permite examinar o estado da máquina e obter mais informações sobre a causa da exceção, mas, ainda mais importante, garante que eu notei que a exceção foi lançada.

É claro que isso pressupõe que, enquanto você desenvolve seu aplicativo, você nunca o executa, você sempre o depura. Esse é o modo correto de operação. Se por acaso você ainda não tiver esse hábito, recomendo vivamente que o adquira.

    
por 10.02.2015 / 22:04
fonte
-1

Então, aqui, você está escrevendo o manipulador que lança a exceção e a grade gerencia a própria exceção.

Eu vejo isso como bastante justo - afinal, você está escrevendo o código da biblioteca que gera estados de erro, e o chamador (isto é, a grade) captura e gerencia a exceção como ela gosta, neste caso mostrando uma mensagem para o usuário em um diálogo (o que é perfeitamente compreensível, dado o seu controle de interface do usuário).

Agora, posso entender que você gostaria que algumas exceções passassem e travassem o aplicativo ou permitissem capturá-las, e elas poderiam ter capturado exceções apenas com base em sua própria hierarquia, em vez de capturar todas as exceções, mas elas fizeram essa escolha. Você apenas terá que conviver com a impossibilidade de capturar exceções e lidar com elas dentro do manipulador (possivelmente definindo algum estado em outro lugar ou enviando uma mensagem alternativa para notificar sua inscrição).

    
por 11.02.2015 / 10:29
fonte