【问题标题】:Good practice - throw an exception to satisfy a function return良好做法 - 抛出异常以满足函数返回
【发布时间】:2013-11-13 14:50:16
【问题描述】:

构建一个自定义 IDataReader,它在 XML 中查找与特定元素名称匹配的值,如果找到,则返回这些值。 GetValue 函数——它必须返回一个由接口指定的对象,如下所示:

    public object GetValue(int i)
    {
        string SearchString = _columns[i];

        var searchedAttributeValue = from nm in _el.Attributes(SearchString) select nm;
        if (searchedAttributeValue.Count() > 0)
        {
            return ParseTypes(searchedAttributeValue.First().Value);
        }

        var searchedElementValue = from nm in _el.Elements(SearchString) select nm;
        if (searchedElementValue.Count() > 0)
        {
            return ParseTypes(searchedElementValue.First().Value);
        }
    }

显然,这不会构建,因为在没有有效返回的情况下可能会到达函数的末尾。

在这种情况下,如果发生这种情况,则意味着存在配置错误 - 用户正在寻找 XML 中不存在的元素或属性。

我发现(这对我来说是新的)你可以通过这样做来解决这个问题:

    public object GetValue(int i)
    {
        if (_el == null)
        {
            _el = XNode.ReadFrom(_reader) as XElement;
        }
        string SearchString = _columns[i];

        var searchedAttributeValue = from nm in _el.Attributes(SearchString) select nm;
        if (searchedAttributeValue.Count() > 0)
        {
            return ParseTypes(searchedAttributeValue.First().Value);
        }

        var searchedElementValue = from nm in _el.Elements(SearchString) select nm;
        if (searchedElementValue.Count() > 0)
        {
            return ParseTypes(searchedElementValue.First().Value);
        }

        throw new Exception("oh crap!");
    }

所以函数没有返回,它只是抛出一个错误。

但是,这种方法从根本上感觉有些错误。可以吗,为什么可以/不可以,有没有更好的方法来处理这种情况?

【问题讨论】:

  • 为什么不直接返回 null?毕竟,解析器无法解析真正异常的行为吗?
  • 如果你要扔,为什么要扔finally,而不是在函数的末尾?这似乎不必要地令人困惑,并且可能是错误的。 finally 不也是为了正常返回而执行的吗?
  • @svk 是的,刚刚意识到并进行了相应的编辑
  • 忍不住——看着你的网名,我可以想象你为什么喜欢这种方法:)
  • @matt-dillard 是的,对不起,见上文。作为一个eejit :)

标签: c# error-handling return


【解决方案1】:

除非你真的需要 catch 块,否则你不需要引入 try/catch/finally。您可以在初始方法的末尾添加 throw 语句。

在此过程中,我会开始使用FirstOrDefault(),遵循本地变量的常规 C# 命名约定,在查询表达式不是非常有用时停止使用它们,并抛出更具体的异常:

public object GetValue(int i)
{
    string searchString = _columns[i];

    var searchedAttribute = _el.Attributes(searchString).FirstOrDefault();
    if (searchedAttribute != null)
    {
        return ParseTypes(searchedAttribute.Value);
    }

    var searchedElement = _el.Elements(searchString).FirstOrDefault();
    if (searchedElement != null)
    {
        return ParseTypes(searchedElement.Value);
    }
    // Nothing found - oops.
    throw new SomeSpecificException("Some message here");
}

如果您需要 catch 块进行日志记录,我可能会尝试捕获 特定 异常,这可能意味着无论如何将其移至 ParseTypes(因为您不应该从其余的调用...)无论哪种方式,都将 throw 语句保留在末尾。

编辑:在设计方面,是否应该在找不到值时抛出异常实际上取决于期望。我们无法知道这是否表明系统有问题,或者只是完全正常的数据缺失。这应该决定你在这里的选择。特别是,如果每个调用者都试图捕获异常,那么这是一种设计味道,您应该返回 null 或使用类似 Dictionary<,>.TryGetValue 的方法。

【讨论】:

  • 谢谢 - 非常有帮助。问题仍然存在 - 是抛出异常还是返回 null 更好?
  • 我的错,因为我首先提出的问题很糟糕,这意味着早期的答案不太有用。对不起:(谢谢你们的建议。
【解决方案2】:

我喜欢使我的方法类似于 .NET 框架 Parse() 和 TryParse() 方法。在你的情况下,我会这样做:

public object GetValue(int i)
{
    // ...

    // fail
    throw new Exception("Cannot get value");
}

或者做:

public bool TryGetValue(int i, out object result)
{
    // ...

    // fail
    result = null;
    return false;
}

【讨论】:

    【解决方案3】:

    try catch 无关紧要。您可以简单地将代码重构为:

    public object GetValue(int i)
    {
    
       if (_el == null)
       {
          _el = XNode.ReadFrom(_reader) as XElement;
       }
         string SearchString = _columns[i];
    
       var searchedAttributeValue = from nm in _el.Attributes(SearchString) select nm;
       if (searchedAttributeValue.Count() > 0)
       {
          return ParseTypes(searchedAttributeValue.First().Value);
       }
    
       var searchedElementValue = from nm in _el.Elements(SearchString) select nm;
       if (searchedElementValue.Count() > 0)
       {
          return ParseTypes(searchedElementValue.First().Value);
       }
    
       throw new Exception("oh crap!");
    }
    

    这具有相同的最终结果。


    也就是说,抛出异常是昂贵的(计算上)。如果你想让它爆炸(完全停止处理,这是一个主要问题,它应该简单地死掉)然后抛出一个异常。

    如果它只是一种识别何时不满足 if 语句的方法,则可以将其更改为 TryParse 类型函数:

    public bool GetValue(int i, out object returnVal)
    {
    
       if (_el == null)
       {
          _el = XNode.ReadFrom(_reader) as XElement;
       }
         string SearchString = _columns[i];
    
       var searchedAttributeValue = from nm in _el.Attributes(SearchString) select nm;
       if (searchedAttributeValue.Count() > 0)
       {
          returnVal = ParseTypes(searchedAttributeValue.First().Value);
          return true;
       }
    
       var searchedElementValue = from nm in _el.Elements(SearchString) select nm;
       if (searchedElementValue.Count() > 0)
       {
          returnVal = ParseTypes(searchedElementValue.First().Value);
          return true;
       }
    
       return false;
    }
    

    然后

    if (GetValue(i, out value))
       //success
    else
       //it's failed.
    

    【讨论】:

      【解决方案4】:

      如果真的是异常情况,因为配置错误导致搜索不返回任何东西,那么一定要抛出异常。问题在于继续。如果找不到项目,客户端代码可以继续吗?如果可以,客户端代码将需要仔细检查您的代码引发的任何异常,以确定它是因为项目不存在还是因为其他无法继续的错误而引发的。

      另一种选择是返回一些指示未找到该值的内容,并允许调用代码处理它。您可以返回null 以向调用代码指示未找到任何项目,在这种情况下可能没问题。更好的解决方案可能是创建一个简单的Optional<T> 类,其中包含对象是否存在的指示(可能是HasValue),以及对象本身(如果存在)。调用代码可以轻松更具体地检查对象是否被返回并处理没有返回的情况,并且异常不需要额外的审查。

      编辑:一个更好的选择可能是其他人建议的ParseTryParse 范式,但我认为这个答案可能有一些用处,所以我把它留在这里:)

      【讨论】:

        【解决方案5】:

        这种方法有很多不好的做法:应始终避免使用catch (Exception)。只捕获您期望的异常。另外,throw Exception() 风格不好。总是抛出一些更具体的东西。但是对于您要查找的内容: 毕竟,您不需要 finally,只需将 throw new ArgumentException() 或其他内容作为函数的最后一行。这是一个很好的做法,如果代码曾经在那里运行,它确实是一个编程错误。

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2011-11-26
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2012-11-05
          • 1970-01-01
          相关资源
          最近更新 更多