¿Cómo diseñaría un método TryParse que proporcione información detallada en caso de un error de análisis?

9

Al analizar la entrada del usuario, generalmente se recomienda no lanzar y capturar excepciones, sino usar métodos de validación. En .NET BCL, esta sería la diferencia entre, por ejemplo, int.Parse(produce una excepción en datos no válidos) y int.TryParse(devuelve falsedatos no válidos).

Estoy diseñando el mío

Foo.TryParse(string s, out Foo result)

método y no estoy seguro sobre el valor de retorno. Podría usar boolcomo el propio TryParsemétodo de .NET , pero eso no daría ninguna indicación sobre el tipo de error, sobre la razón exacta por la que s no se puede analizar en a Foo. (Por ejemplo, spodría tener paréntesis sin igual, o el número incorrecto de caracteres, o un Barsin un correspondiente Baz, etc.)

Como usuario de API, no me gustan mucho los métodos que solo devuelven un Booleano de éxito / fracaso sin decirme por qué falló la operación. Esto hace que la depuración sea un juego de adivinanzas, y tampoco quiero imponer eso a los clientes de mi biblioteca.

Puedo pensar en muchas soluciones a este problema (códigos de estado de retorno, devolver una cadena de error, agregar una cadena de error como parámetro de salida), pero todos tienen sus desventajas respectivas, y también quiero mantener la coherencia con las convenciones de .NET Framework .

Por lo tanto, mi pregunta es la siguiente:

¿Existen métodos en .NET Framework que (a) analicen la entrada sin generar excepciones y (b) sigan devolviendo información de error más detallada que un simple booleano verdadero / falso?

Heinzi
fuente
1
Ese enlace no concluye, no se recomienda lanzar y atrapar excepciones. Hay momentos en que la mejor manera es usarlo Parse().
paparazzo el

Respuestas:

5

Recomendaría usar el patrón de mónada para su tipo de retorno.

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

Tenga en cuenta también que no debería ser responsabilidad de Foo averiguar cómo se debe analizar a partir de la entrada del usuario, ya que esto vincula directamente su capa de dominio a su capa de interfaz de usuario y también viola el principio de responsabilidad única.

También puede hacer una clase de resultado de análisis específica para, en Foolugar de usar genéricos, según su caso de uso.

Una clase de resultado de análisis específico foo podría verse así:

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

Aquí está la versión de Monad:

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

No conozco ningún método en el marco .net que devuelva información detallada de error de análisis.

TheCatWhisperer
fuente
Entiendo su comentario sobre el enlace de la capa UI, pero en este caso existe una representación de cadena canónica estandarizada de Foo, por lo que tiene sentido tener Foo.ToStringy Foo.Parse.
Heinzi
Y, wrt mi pregunta en negrita, ¿me puede dar un ejemplo de .NET BCL que utiliza este patrón?
Heinzi
44
¿Cómo es eso una mónada?
JacquesB
@Heinzi: Cualquier método que devuelva un Func<T>cumpliría ese criterio, si incluye en Tla información que necesita. Devolver información detallada sobre errores depende en gran medida de usted. ¿Has considerado usar un Maybe<T>? Ver mikhail.io/2016/01/monads-explained-in-csharp
Robert Harvey
@JacquesB: Me preguntaba lo mismo. La firma del método es compatible con el comportamiento de Modanic, pero eso es todo.
Robert Harvey
1

Podrías mirar ModelState en el marco MVC. Representa un intento de análisis de alguna entrada y puede tener una colección de errores.

Dicho esto, no creo que haya un patrón recurrente para esto en .net BCL, ya que las excepciones son, para bien o para mal, el patrón establecido para informar condiciones de error en .net. Creo que debería seguir adelante e implementar su propia solución que se adapte a su problema, por ejemplo, una ParseResultclase con dos subclases SuccessfulParsey FailedParse, donde SuccessfulParsetiene una propiedad con el valor analizado y FailedParsetiene una propiedad de mensaje de error. Combinar esto con la coincidencia de patrones en C # 7 podría ser bastante elegante.

JacquesB
fuente
1

Me he encontrado con problemas similares al querer usar un TryParse/Convert/etc.método en el que a veces necesito saber cómo y por qué falló.

Terminé inspirándome en cómo algunos serializadores manejan errores y usan eventos. De esta manera, la sintaxis de mi TryX(..., out T)método se ve tan limpia como cualquier otra y devuelve de manera confiable un simple falsecomo lo implica el patrón.

Sin embargo, cuando quiero necesitar más detalles, solo agrego un controlador de eventos y obtengo los resultados que necesito en un paquete tan complejo o simple como quiero (el MyEventArgssiguiente). Agréguelo a una lista de cadenas, agregue ExceptionDispatchInfoy capture Excepciones; deje que la persona que llama decida si quiere lidiar con algo que sale mal y cómo lo hará.

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
fuente