C#类方法.如何失败并返回原因

本文关键字:返回 失败 类方法 何失败 | 更新日期: 2023-09-27 18:21:36

我相信有一种"好"的方法可以解决这个问题,但它一直困扰着我。我有一个方法应该返回一个对象,但它的参数有一定的先决条件。这些都超出了我的控制范围,可能会因为"业务逻辑"的原因而失败(如果你能原谅这个过时的术语的话)。

对象的返回值将为null,但我也想返回原因,因此调用代码基本上可以说"我没有取回我的对象,因为没有足够的信息来构建它"。

我觉得尝试接球不是正确的方法,但在这种情况下一直在使用它,因为没有更好的方法。我在stackoverflow、教科书和MSDN上阅读的所有内容似乎都集中在何时如何使用异常上,但不知何故,我未能想出解决这种情况的方法。

有人能提出一个更合适的模式吗?(第一个帖子所以…请原谅任何失礼行为)

下面是我一直在玩的一个示例:(注意//TODO注释下面的抛出新的Exception行)

public static Packet Parse(string packetString)
{
    Packet returnPacket = new Packet();
    StringBuilder output = new StringBuilder();
    try
    {
        using (XmlReader reader = XmlReader.Create(new StringReader(packetString)))
        {
            XmlWriterSettings ws = new XmlWriterSettings();
            ws.Indent = true;
            using (XmlWriter writer = XmlWriter.Create(output, ws))
            {
                string rootNodeString = string.Empty;
                // Parse the packet string and capture each of the nodes.
                while (reader.Read())
                {
                    //test root node is the correct opening node name
                    if (rootNodeString == string.Empty)
                    {
                        if (reader.NodeType != XmlNodeType.Element || reader.Name != PACKETROOT)
                        {
                            // TODO: I don't really think this should be an exception, but going with it for now for expediency, since XmlReader is doing the same anyway
                            throw new Exception(string.Format("The root node of a Packet must be <{0}>", PACKETROOT));
                        }
                        else
                        {
                            rootNodeString = reader.Name;
                        }
                    }
                    switch (reader.NodeType)
                    {
                        case XmlNodeType.Element:
                            Console.WriteLine(string.Format("start element = {0}", reader.Name));
                            break;
                        case XmlNodeType.Text:
                            Console.WriteLine(string.Format("text = {0}", reader.Value));
                            break;
                        case XmlNodeType.XmlDeclaration:
                        case XmlNodeType.ProcessingInstruction:
                            Console.WriteLine(string.Format("XmlDeclaration/ProcessingInstruction = {0},{1}", reader.Name, reader.Value));
                            break;
                        case XmlNodeType.Comment:
                            Console.WriteLine(string.Format("comment = {0}", reader.Value));
                            break;
                        case XmlNodeType.EndElement:
                            Console.WriteLine(string.Format("end element = {0}", reader.Name));
                            break;
                    }
                }
            }
        }
    }
    catch (XmlException xem)
    {
        Console.WriteLine(xem.Message);
        throw;
    }
    return returnPacket;
}

C#类方法.如何失败并返回原因

我有一个方法,它应该返回一个对象,但它的参数有一定的先决条件。这些超出了我的控制范围,可能会因"业务逻辑"原因而失败

如果前置条件确实是前置条件,则未能遵守该前置条件的调用者将出现错误。正确的做法是让被调用者抛出一个异常,最好是一个严重破坏整个进程的异常。这将强烈鼓励调用者修复他们的错误。

但根据你的描述,听起来你所拥有的并不是一个先决条件;前提条件是必须为真,如果它们不总是为真,则调用者有缺陷。相反,听起来你所拥有的是一种做得太多的方法;验证提供的参数是否被某些业务策略分类为有效,计算参数是否有效的结果。

因此,我倾向于将其分为两种方法。

第一个方法分析其参数,查看业务策略是否将其归类为有效,并返回一个报告对象,详细描述参数有效或无效的原因。

第二种方法计算结果。第二种方法可以以第一种方法验证参数为前提。

对象的返回值将为null,但我也想返回原因,因此调用代码基本上可以说"我没有取回我的对象,因为没有足够的信息来构建它"。

同样,这是更多的证据表明你试图做得太多了。您希望该方法的返回值既是描述为什么不满足业务规则的分析,也是业务流程的结果。这是两件非常不同的事情,因此应该用两种不同的方法来计算。

我在Noda Time中使用的方法是使用ParseResult<T>类型,这是解析操作的结果。它知道它是否成功,如果你向解析失败者询问结果,它会抛出异常,但否则不会抛出异常。(您目前无法获得在没有抛出的情况下会抛出的异常,但我稍后可能会添加它。)

这比抛出异常要好,因为这里的失败是所期望的——这并不是真正的异常。

它比使用int.TryParse等模式要好,因为它为您提供了一个封装关于解析操作的所有的单个结果值-不再干扰参数,并且在失败的情况下提供了更多的细节。

现在我还不清楚这在你的特定情况下是否合适,但当你基本上处理的数据可能是无效的,并且不表明你的系统的任何部分有任何问题时,合适的(IMO):这里没有什么令人惊讶的异常,只是有人给你提供了不好的输入:(

如果你测试的任何东西"都不应该发生",那么使用异常正是你应该做的。毕竟这是一种特殊情况。你为什么认为你不应该使用一个?

我同意Jon Skeet的观点。除非是真正的特殊情况,否则您不希望出现异常。用户输入验证是我最不建议使用异常的地方。

主要是我认为这会使逻辑更难遵循,因为您要么必须将所有调用封装在try/catch块中以保持所需的粒度级别,要么将错误逻辑推迟到代码的后面。使用返回类/结构可以确保对正确性的检查是明确的。

这可以极大地提高代码的可读性。请参阅下面的示例。

    public class ActionResult<T>
    {
        public T Result { get; set; }
        public bool IsSuccessful { get; set; }
        public string DetailMessage { get; set; }
        //Any other properties you might find useful like statuscode, etc.
    }
    public ActionResult<Packet> ParsePacket(string input)
    {
        Packet result = null;
        bool parseSuccess = true;
        string message = null;
        // Do your work, create packet and check conditions. 
        // Assign to your local variables.
        return new ActionResult<Packet> { 
            Result = result, 
            IsSuccessful = parseSuccess, 
            DetailMessage = message 
        };
    }
    public void SomeCallingMethodLikeGetPacket(string userInput)
    {
        ActionResult<Packet> parseResult = ParsePacket(userInput);
        if (!parseResult.IsSuccessful)
        {
            //Error handling.
        }
        else
        {
            Packet packet = parseResult.Result;
            //Do something with packet.
        }
    }

通过在存在无效输入时抛出异常,您实际上是在说,调用方法有责任确保数据有效,或者在数据无效时处理异常。这是完全可以接受的,尽管理想情况下应该将其纳入方法文档中。

如果你正在这样做,最好有一个方法来验证输入;通过提供这一点,调用者可以知道该方法将不起作用,而不需要遭受try/catch的性能打击。如果该类与一个或几个其他类紧密耦合,而这些类总是为它提供正确的输入,那么这就变得不那么重要了(但仍然不是一个坏主意)。

我还没有真正用C#开发过,但根据我使用其他语言(Java、PHP)的经验,我认为这是一个非常好的方法。如果你想区分你抛出的错误和XMLReader抛出的错误,你可以创建自己的自定义Exception来扩展Exception对象。

典型的模式是始终返回一个int,这是错误代码。实际结果通过outref参数传递。一些开发工厂确实要求将此作为必要条件。