Как бы я разработал метод 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?

Heinzi
источник
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, который возвращает подробную информацию об ошибке разбора.

TheCatWhisperer
источник
Я понимаю ваш комментарий о привязке уровня пользовательского интерфейса, но в этом случае существует стандартизированное, каноническое строковое представление 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 может быть довольно элегантным.

JacquesB
источник
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;}
}
Chakrava
источник