3

我知道人们说将异常用于流控制并仅在异常情况下使用异常是不好的,但有时将整个块包装在一个中不是更干净、更优雅try-catch吗?

例如,假设我有一个对话框窗口,TextBox用户可以在其中键入输入以以键值方式进行解析。

这种情况并不像你想象的那么做作,因为我继承了必须处理这种确切情况的代码(尽管不是农场动物)。

考虑一下这堵代码墙:

class Animals
{
    public int catA, catB;
    public float dogA, dogB;
    public int mouseA, mouseB, mouseC;
    public double cow;
}

class Program
{
    static void Main(string[] args)
    {
        string input = "Sets all the farm animals CAT 3 5 DOG 21.3 5.23 MOUSE 1 0 1 COW 12.25";

        string[] splitInput = input.Split(' ');

        string[] animals = { "CAT", "DOG", "MOUSE", "COW", "CHICKEN", "GOOSE", "HEN", "BUNNY" };

        Animals animal = new Animals();

        for (int i = 0; i < splitInput.Length; i++)
        {
            string token = splitInput[i];

            if (animals.Contains(token))
            {
                switch (token)
                {
                    case "CAT":
                        animal.catA = int.Parse(splitInput[i + 1]);
                        animal.catB = int.Parse(splitInput[i + 2]);
                        break;
                    case "DOG":
                        animal.dogA = float.Parse(splitInput[i + 1]);
                        animal.dogB = float.Parse(splitInput[i + 2]);
                        break;
                    case "MOUSE":
                        animal.mouseA = int.Parse(splitInput[i + 1]);
                        animal.mouseB = int.Parse(splitInput[i + 2]);
                        animal.mouseC = int.Parse(splitInput[i + 3]);
                        break;
                    case "COW":
                        animal.cow = double.Parse(splitInput[i + 1]);
                        break;
                }
            }
        }
    }
}

实际上有更多的农场动物和更多的处理。虽然很多事情都可能出错。用户可能输入错误数量的参数。用户可以以不正确的格式输入输入。用户可以指定数据类型无法处理的太大或太小的数字。

所有这些不同的错误都可以通过使用无例外地处理TryParse,检查用户尝试为特定动物使用多少参数,检查参数对于数据类型来说是太大还是太小(因为 TryParse 只返回 0),但每个人都应该产生同样的结果:

AMessageBox出现告诉用户输入的数据无效并修复它。我的老板不希望针对不同的错误使用不同的消息框。因此,与其做所有这些,为什么不将块包装在 atry-catch中并在 catch 语句中只显示该错误消息框并让用户再试一次?

也许这不是最好的例子,但想想任何其他情况,否则会有大量错误处理可以代替单个 try-catch。这不是更好的解决方案吗?

4

3 回答 3

3

使用 Try-Catch-Finalize 与使用 TryParse 不同。TryParse 不会停止代码的执行,它不会抛出异常,而是简单地跳过错误并使变量保持未初始化状态。

编写安全代码比错误处理和“很多可能出错”要多得多,这就是限制派上用场的时候。为了正确执行和良好结果,我们限制用户的自由,下一步是错误处理。

于 2012-11-03T15:22:44.577 回答
0

只是为了好玩,我为您的案例拼凑了一个类似解析器组合器的快速示例。在解析半复杂语法时,解析器组合器非常强大。此外,解析器组合库可以扩展为支持解析器失败的人类可读错误。

也许它可以为您提供处理任务的不同方法的想法。

使用一些解析器原语解析“猫”看起来像这样:

static IParseResult<Cat> ParseCat(this ParserState ps)
{
    var value = new Cat();
    var result = 
            ps.AttemptTo (() => ps.ParseToken("CAT"))
        &&  ps.AttemptTo (ps.ParseDouble, out value.A)
        &&  ps.AttemptTo (ps.ParseDouble, out value.B)
        ;

    return ps.Result(result, value);
}

狗、牛和老鼠类似。解析动物是所有这些的组合:

    static IParseResult<Animal> ParseAnimal(this ParserState ps)
    {
        Animal value = null;
        var result =
                ps.AttemptTo(ps.ParseCat    , out value)
            ||  ps.AttemptTo(ps.ParseDog    , out value)
            ||  ps.AttemptTo(ps.ParseMouse  , out value)
            ||  ps.AttemptTo(ps.ParseCow    , out value)
            ||  ps.AttemptTo(ps.ParseUnknown, out value)
            ;

        return ps.Result(result, value);
    }

最后,我们喜欢将输入解析为尽可能多的动物:

    public static IParseResult<Animal[]> ParseAnimals(this ParserState ps)
    {
        Animal[] value;
        var result = ps.AttemptMany(ps.ParseAnimal, out value);
        return ps.Result(result, value);
    }

完整样本:

namespace ParseTest
{
    using System;
    using System.Collections.Generic;
    using System.Globalization;
    using System.Threading;
    using System.Linq;

    static class Program
    {

        static void Main(string[] args)
        {
            Thread.CurrentThread.CurrentCulture = CultureInfo.InvariantCulture;

            var input = "Sets all the farm animals CAT 3 5 DOG 21.3 5.23 MOUSE 1 0 1 COW 12.25";

            Console.WriteLine(input);

            var ps = new ParserState(input.Split(' '));

            var result = ps.ParseAnimals();

            if (result.Success)
            {
                foreach (var animal in result.Value.Where(v => !(v is Unknown)))
                {
                    Console.WriteLine(animal);
                }
            }
            else
            {
                Console.WriteLine("Parse failure");
            }

        }
    }


    // ParserState tracks where we are in the input
    class ParserState
    {
        public readonly string[] Tokens;
        public int CurrentPosition;

        public ParserState(string[] tokens) 
        {
            Tokens = tokens ?? new string[0];
        }


        public string Peek
        {
            get
            {
                if (CurrentPosition < 0)
                {
                    return "";
                }

                if (CurrentPosition >= Tokens.Length)
                {
                    return "";
                }

                return Tokens[CurrentPosition];
            }
        }

        public string PeekAndAdvance ()
        {
            var result = Peek;

            ++CurrentPosition;

            return result;
        }

        public bool EndOfStream
        {
            get { return CurrentPosition >= Tokens.Length; }
        }

    }
    struct Empty
    {

    }

    // ParseResult are the result of parsers, it might be a succcess or failure
    interface IParseResult<out T>
    {
        ParserState State { get; }
        bool Success { get; }
        T Value { get; }
    }

    class ParseResult<T> : IParseResult<T>
    {
        readonly ParserState m_state;
        readonly bool m_success;
        readonly T m_value;

        public ParseResult(ParserState state, bool success, T value)
        {
            m_state = state;
            m_success = success;
            m_value = value;
        }

        public ParserState State
        {
            get { return m_state; }
        }

        public bool Success
        {
            get { return m_success; }
        }

        public T Value 
        {
            get { return m_value; }
        }
    }

    static class ParserExtensions
    {
        public static IParseResult<T> Result<T>(this ParserState ps, bool success, T value)
        {
            return new ParseResult<T>(ps, success, value);
        }

        public static IParseResult<T> Success<T>(this ParserState ps, T value)
        {
            return ps.Result(true, value);
        }

        public static IParseResult<T> Failure<T>(this ParserState ps)
        {
            return ps.Result(true, default(T));
        }

        public static bool AttemptTo<T>(this ParserState ps, Func<IParseResult<T>> action)
        {
            T value;
            return ps.AttemptTo(action, out value);
        }

        // Attempts to run a parser, 
        // if the parser fails the parser state is returned to its previous state
        public static bool AttemptTo<T>(this ParserState ps, Func<IParseResult<T>> action, out T value)
        {
            value = default(T);
            if (ps.EndOfStream)
            {
                return false;
            }

            var currentPos = ps.CurrentPosition;

            var result = action();

            if (result.Success)
            {
                value = result.Value;
            }
            else
            {
                ps.CurrentPosition = currentPos;
            }

            return result.Success;
        }

        // Attempts to run a parser many times and build an array of values, 
        // if the parser fails the parser state is returned to its previous state
        // and the result is returned
        public static bool AttemptMany<T>(this ParserState ps, Func<IParseResult<T>> action, out T[] values)
        {
            values = new T[0];
            if (ps.EndOfStream)
            {
                return true;
            }

            var parsedValues = new List<T>();

            var currentPos = ps.CurrentPosition;

            IParseResult<T> result;

            while ((result = action ()).Success)
            {
                currentPos = ps.CurrentPosition;
                parsedValues.Add(result.Value);
            }

            ps.CurrentPosition = currentPos;

            values = parsedValues.ToArray();

            return true;
        }

        static IParseResult<Empty> ParseToken(this ParserState ps, string token)
        {
            var value = new Empty();
            var result = ps.PeekAndAdvance().Equals(token, StringComparison.OrdinalIgnoreCase);
            return ps.Result(result, value);
        }

        static IParseResult<double> ParseDouble(this ParserState ps)
        {
            double value;
            var result = double.TryParse(ps.PeekAndAdvance(), out value);
            return ps.Result(result, value);
        }

        static IParseResult<Cat> ParseCat(this ParserState ps)
        {
            var value = new Cat();
            var result = 
                    ps.AttemptTo (() => ps.ParseToken("CAT"))
                &&  ps.AttemptTo (ps.ParseDouble, out value.A)
                &&  ps.AttemptTo (ps.ParseDouble, out value.B)
                ;

            return ps.Result(result, value);
        }

        static IParseResult<Dog> ParseDog(this ParserState ps)
        {
            var value = new Dog();
            var result =
                    ps.AttemptTo(() => ps.ParseToken("DOG"))
                &&  ps.AttemptTo(ps.ParseDouble, out value.A)
                &&  ps.AttemptTo(ps.ParseDouble, out value.B)
                ;

            return ps.Result(result, value);
        }

        static IParseResult<Mouse> ParseMouse(this ParserState ps)
        {
            var value = new Mouse();
            var result =
                    ps.AttemptTo(() => ps.ParseToken("MOUSE"))
                &&  ps.AttemptTo(ps.ParseDouble, out value.A)
                &&  ps.AttemptTo(ps.ParseDouble, out value.B)
                &&  ps.AttemptTo(ps.ParseDouble, out value.C)
                ;

            return ps.Result(result, value);
        }

        static IParseResult<Cow> ParseCow(this ParserState ps)
        {
            var value = new Cow();
            var result =
                    ps.AttemptTo(() => ps.ParseToken("COW"))
                &&  ps.AttemptTo(ps.ParseDouble, out value.A)
                ;

            return ps.Result(result, value);
        }

        static IParseResult<Unknown> ParseUnknown(this ParserState ps)
        {
            ps.PeekAndAdvance();
            return ps.Success(new Unknown());
        }

        static IParseResult<Animal> ParseAnimal(this ParserState ps)
        {
            Animal value = null;
            var result =
                    ps.AttemptTo(ps.ParseCat    , out value)
                ||  ps.AttemptTo(ps.ParseDog    , out value)
                ||  ps.AttemptTo(ps.ParseMouse  , out value)
                ||  ps.AttemptTo(ps.ParseCow    , out value)
                ||  ps.AttemptTo(ps.ParseUnknown, out value)
                ;

            return ps.Result(result, value);
        }

        public static IParseResult<Animal[]> ParseAnimals(this ParserState ps)
        {
            Animal[] value;
            var result = ps.AttemptMany(ps.ParseAnimal, out value);
            return ps.Result(result, value);
        }
    }

    class Animal
    {

    }

    class Cat : Animal
    {
        public double A;
        public double B;

        public override string ToString()
        {
            return new {Type = "CAT", A, B}.ToString();
        }
    }

    class Dog : Animal
    {
        public double A;
        public double B;

        public override string ToString()
        {
            return new { Type = "DOG", A, B }.ToString();
        }
    }

    class Mouse : Animal
    {
        public double A;
        public double B;
        public double C;

        public override string ToString()
        {
            return new { Type = "MOUSE", A, B, C }.ToString();
        }
    }

    class Cow : Animal
    {
        public double A;

        public override string ToString()
        {
            return new { Type = "COW", A}.ToString();
        }
    }

    class Unknown : Animal
    {
        public override string ToString()
        {
            return new { Type = "UNKNOWN"}.ToString();
        }
    }

}

PS。FParsec 是我最后一次检查 .NET 的最佳解析器组合库:http ://www.quanttec.com/fparsec/tutorial.html

PS。MicroParser 是 FParsec 的 C# 变体,但旨在更小,同时牺牲解析巨型字符串的能力:http: //microparser.codeplex.com/

于 2012-11-03T16:57:17.220 回答
0

这取决于您需要对错误消息进行多少控制。使用“包罗万象”的方法,您将能够告诉用户的只是“出了点问题,再试一次”。

使用一个大的 try-catch 块并捕获Exception您将隐藏代码中的其他错误,与 int 解析不完全相关。您至少应该尝试缩小预期异常的类型。

还要考虑到这TryParse将使您继续进行其余的解析,这意味着您可以跟踪并记录所有问题,然后以详细的方式将它们通知给用户。这取决于您要完成的工作。

于 2012-11-03T15:26:18.867 回答