Como eu projetaria um método TryParse que fornece informações detalhadas em caso de erro de análise?

9

Ao analisar a entrada do usuário, geralmente é recomendável não lançar e capturar exceções, mas usar métodos de validação. No .NET BCL, essa seria a diferença entre, por exemplo, int.Parse(lança uma exceção em dados inválidos) e int.TryParse(retorna falseem dados inválidos).

Estou projetando meu próprio

Foo.TryParse(string s, out Foo result)

método e não tenho certeza sobre o valor de retorno. Eu poderia usar boolcomo o próprio TryParsemétodo do .NET , mas isso não daria nenhuma indicação sobre o tipo de erro, sobre o motivo exato pelo qual s não poderia ser analisado em um arquivo Foo. (Por exemplo, spode ter parênteses sem correspondência, ou o número errado de caracteres, ou um Barsem um correspondente Baz, etc.)

Como usuário de APIs, não gosto de métodos que retornam um booleano de êxito / falha sem me dizer por que a operação falhou. Isso torna a depuração um jogo de adivinhação, e também não quero impor isso aos clientes da minha biblioteca.

Posso pensar em várias soluções alternativas para esse problema (retornar códigos de status, retornar uma string de erro, adicionar uma string de erro como um parâmetro out), mas todas elas têm suas respectivas desvantagens e também quero permanecer consistente com as convenções de o .NET Framework .

Assim, minha pergunta é a seguinte:

Existem métodos no .NET Framework que (a) analisam a entrada sem gerar exceções e (b) ainda retornam informações de erro mais detalhadas do que um simples booleano verdadeiro / falso?

Heinzi
fonte
11
Esse link não conclui, não é recomendável lançar e capturar exceções. Há momentos em que a melhor maneira é usar Parse().
paparazzo

Respostas:

5

Eu recomendaria usar o padrão de mônada para o seu tipo de retorno.

ParseResult<Foo> foo = FooParser.Parse("input");

Observe também que não deve ser responsabilidade do Foo descobrir como deve ser analisado a partir da entrada do usuário, pois isso vincula diretamente a camada do domínio à camada da interface do usuário e também viola o principal de responsabilidade única.

Você também pode criar uma classe de resultado de análise específica, em Foovez de usar genéricos, dependendo do seu caso de uso.

Uma classe de resultado de análise específica foo pode ser algo como isto:

class FooParseResult
{
     Foo Value { get; set; }
     bool PassedRequirement1 { get; set; }
     bool PassedRequirement2 { get; set; }
}

Aqui está a versão do Monad:

class ParseResult<T>
{
     T Value { get; set; }
     string ParseErrorMessage { get; set; }
     bool WasSuccessful { get; set; }
}

Não conheço nenhum método na estrutura .net que retorne informações detalhadas sobre erros de análise.

TheCatWhisperer
fonte
Entendo o seu comentário sobre a ligação da camada de interface do usuário, mas, neste caso, existe uma representação canônica padronizada de string do Foo, por isso faz sentido ter Foo.ToStringe Foo.Parse.
Heinzi 22/01
E, na minha pergunta em negrito, você pode me dar um exemplo da .NET BCL que usa esse padrão?
Heinzi 22/01
4
Como isso é uma mônada?
precisa saber é o seguinte
@ Heinzi: Qualquer método que retorne a Func<T>atenderia a esse critério, se você incluir Tas informações necessárias. O retorno de informações detalhadas sobre erros depende muito de você. Você já pensou em usar um Maybe<T>? Veja mikhail.io/2016/01/01/monads-explained-in-csharp
Robert Harvey
@ JacquesB: Eu estava meio que me perguntando a mesma coisa. A assinatura do método é compatível com o comportamento modânico, mas é isso.
Robert Harvey
1

Você pode ver o ModelState na estrutura MVC. Representa uma tentativa de análise de alguma entrada e pode ter uma coleção de erros.

Dito isto, não acho que exista um padrão recorrente para isso no .net BCL, já que as exceções são - para melhor ou pior - o padrão estabelecido para relatar condições de erro no .net. Eu acho que você deve seguir em frente e implementar sua própria solução adequada ao seu problema, por exemplo, uma ParseResultclasse com duas subclasses SuccessfulParsee FailedParse, onde SuccessfulParsepossui uma propriedade com o valor analisado e FailedParsepossui uma propriedade de mensagem de erro. Combinar isso com a correspondência de padrões no C # 7 pode ser bastante elegante.

JacquesB
fonte
1

Eu tive problemas semelhantes ao querer usar um TryParse/Convert/etc.método em que às vezes preciso saber como e por que ele falhou.

Acabei me inspirando em como alguns serializadores lidam com erros e usam eventos. Dessa forma, a sintaxe do meu TryX(..., out T)método parece tão limpa quanto qualquer outra e retorna com segurança um simples falsecomo o padrão implica.

No entanto, quando eu quero precisar de mais detalhes, basta adicionar um manipulador de eventos e obter os resultados necessários em um pacote tão complexo ou simples quanto eu desejar (a MyEventArgsseguir). Adicione-o a uma lista de strings, adicione ExceptionDispatchInfoe capture Exceptions; deixe o interlocutor decidir se e como deseja lidar com algo que der errado.

public class Program
{
    public static void Main()
    {
        var c = new MyConverter();

        //here's where I'm subscibing to errors that occur
        c.Error += (sender, args) => Console.WriteLine(args.Details);

        c.TryCast<int>("5", out int i);
    }
}

//here's our converter class
public class MyConverter
{
    //invoke this event whenever something goes wrong and fill out your EventArgs with details
    public event EventHandler<MyEventArgs> Error;

    //intentionally stupid implementation
    public bool TryCast<T>(object input, out T output)
    {
        bool success = true;
        output = default (T);

        //try-catch here because it's an easy way to demonstrate my example
        try
        {
            output = (T)input;
        }
        catch (Exception ex)
        {
            success = false;
            Error?.Invoke(this, new MyEventArgs{Details = ex.ToString()});
        }

        return success;
    }
}

//stores whatever information you want to make available
public class MyEventArgs : EventArgs
{
    public string Details {get; set;}
}
Chakrava
fonte