Как бы я разработал метод TryParse, который предоставляет подробную информацию в случае ошибки синтаксического анализа?


9

При анализе пользовательского ввода обычно рекомендуется не генерировать и перехватывать исключения, а использовать методы проверки. В .NET BCL это будет разница между, например, int.Parse(создает исключение для недопустимых данных) и int.TryParse(возвращает falseнедопустимые данные).

Я создаю свой собственный

Foo.TryParse(string s, out Foo result)

метод, и я не уверен насчет возвращаемого значения. Я мог бы использовать boolкак собственный TryParseметод .NET , но это не дало бы указание на тип ошибки, на точную причину, почему s не может быть проанализирован в Foo. (Например, sможет иметь несоответствующие скобки, или неправильное количество символов, или Barбез соответствующего Bazи т. Д.)

Как пользователь API, я сильно не люблю методы, которые просто возвращают логическое значение успеха / неудачи, не сообщая мне, почему операция не удалась. Это делает отладку игрой в догадки, и я не хочу навязывать это клиентам моей библиотеки.

Я могу придумать много обходных путей для этой проблемы (вернуть коды состояния, вернуть строку ошибки, добавить строку ошибки в качестве параметра out), но у них у всех есть свои недостатки, и я также хочу оставаться в соответствии с соглашениями .NET Framework .

Таким образом, мой вопрос заключается в следующем:

Существуют ли в .NET Framework методы, которые (а) анализируют ввод без выдачи исключений и (б) все же возвращают более подробную информацию об ошибках, чем простое логическое значение true / false?


1
Эта ссылка не означает, что не рекомендуется создавать и ловить исключения. Есть моменты, которые лучше всего использовать Parse().
— Папараццо

Ответы:


5

Я бы порекомендовал использовать шаблон монады для вашего типа возврата.

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

Также обратите внимание, что Foo не должен выяснять, как его следует анализировать с пользовательского ввода, так как это напрямую связывает ваш уровень домена с уровнем UI, а также нарушает принцип единственной ответственности.

Вы также можете сделать класс результата разбора специфичным Fooвместо использования обобщений, в зависимости от вашего варианта использования.

Класс результата анализа foo может выглядеть примерно так:

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

Вот версия Monad:

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

Я не знаю ни одного метода в .net Framework, который возвращает подробную информацию об ошибке разбора.


Я понимаю ваш комментарий о привязке уровня пользовательского интерфейса, но в этом случае существует стандартизированное, каноническое строковое представление Foo, поэтому имеет смысл иметь Foo.ToStringи Foo.Parse.
— Хайнци

И, по поводу моего смелого вопроса, можете ли вы привести пример из .NET BCL, который использует этот шаблон?
— Хайнци

4
Как это монада?
— JacquesB

@Heinzi: Любой метод, который возвращает a, Func<T>будет соответствовать этим критериям, если вы включите в Tнеобходимую информацию. Возвращение подробной информации об ошибке в значительной степени зависит от вас. Вы рассматривали возможность использования Maybe<T>? Смотрите mikhail.io/2016/01/monads-explained-in-csharp
— Роберт Харви

@JacquesB: Мне было интересно, то же самое. Подпись метода совместима с модным поведением, но это все.
— Роберт Харви

1

Вы можете посмотреть на ModelState в рамках MVC. Он представляет собой попытку анализа некоторого ввода и может содержать набор ошибок.

Тем не менее, я не думаю, что есть повторяющийся шаблон для этого в .net BCL, так как исключения - к лучшему или худшему - установленный шаблон для сообщения об ошибках в .net. Я думаю, что вы должны просто пойти дальше и реализовать свое собственное решение, соответствующее вашей проблеме, например, ParseResultкласс с двумя подклассами, SuccessfulParseи FailedParse, где SuccessfulParseимеет свойство с анализируемым значением и FailedParseсвойство сообщения об ошибке. Объединение этого с сопоставлением с образцом в C # 7 может быть довольно элегантным.


1

Я столкнулся с похожими проблемами, связанными с желанием использовать TryParse/Convert/etc.метод, в котором мне иногда нужно знать, как и почему это не удалось.

Я получил вдохновение от того, как некоторые сериализаторы обрабатывают ошибки и используют события. Таким образом, синтаксис для моего TryX(..., out T)метода выглядит таким же чистым, как и любой другой, и надежно возвращает простой, falseкак предполагает шаблон.

Однако, когда мне нужно больше подробностей, я просто добавляю обработчик событий и получаю все необходимые результаты в пакете, настолько сложном или простом, насколько я хочу ( MyEventArgsниже). Добавьте его в список строк, добавьте ExceptionDispatchInfoи запишите исключения; пусть вызывающий абонент решит, если и как он хочет справиться с чем-то, что пойдет не так.

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;}
}
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.