TryFoo应该抛出异常吗?

Jim*_*hel 8 .net exception

.NET Framework中的一个常见模式是TryXXX模式(我不知道这是否是他们真正称之为的模式),其中被调用的方法尝试执行某些操作,True如果成功False则返回,或者操作失败.一个很好的例子是通用Dictionary.TryGetValue方法.

这些方法的文档说它们不会抛出异常:失败将在方法返回值中报告.到目前为止都很好.

我最近遇到了两种不同的情况,其中实现TryXXX模式的.NET Framework方法引发了异常.请参阅System.Random构造函数中的Bug?Uri.TryCreate抛出UriFormatException?详情.

在这两种情况下,该TryXXX方法都会调用其他方法来引发意外异常,并且这些异常会被转义.我的问题:这是否打破了不抛出异常的隐含合同?

换句话说,如果你正在写作TryFoo,你能保证异常无法逃脱,通过这样写吗?

public bool TryFoo(string param, out FooThing foo)
{
    try
    {
        // do whatever
        return true;
    }
    catch
    {
        foo = null;
        return false;
    }
}
Run Code Online (Sandbox Code Playgroud)

这很诱人,因为这可以保证不会逃避任何例外,从而兑现隐含的合同.但这是一个隐藏的bug.

根据我的经验,我的直觉是,这是一个非常糟糕的主意.但是,如果TryFoo让一些异常逃脱,那么它真正说的是"我不会抛出任何我知道如何处理的异常",然后合同的整个想法"我不会抛出异常"被抛出窗口.

那么,你有什么看法?应该TryFoo处理所有异常,还是只处理它预期发生的异常?你的理由是什么?

ojr*_*rac 5

这取决于.TryParseFoo应该捕获所有解析异常,例如null或格式错误的字符串.但也有不相关的例外(ThreadAbortException比如说)不应该自动安静.

在Python中(跟我一起),你可以通过捕获KeyboardInterrupt异常来解决很多麻烦,这基本上意味着你的程序即使你用Ctrl-C也能继续运行.

我的经验法则是你应该TryFooFoo没有清理输入的情况下使用.如果发现TryFooFoo您的输入无关的错误,则会过于激进.


JP *_*oto 5

这不是不抛出异常的保证.在Dictionary上考虑这个简单的例子......

var dic = new Dictionary<string, string>() { { "Foo", "Bar" } };

string val = String.Empty;
string key = null;

dic.TryGetValue(key, out val); // oops
Run Code Online (Sandbox Code Playgroud)

我发送了一个null键,我得到一个NullArgumentException.反射器因此显示代码......

private int FindEntry(TKey key)
{
  if (key == null)
  {
    ThrowHelper.ThrowArgumentNullException(ExceptionArgument.key);
   }
   // more stuff
}
Run Code Online (Sandbox Code Playgroud)

在我看来,合同的意思是"如果你试图做的事情失败了,我就不会抛出异常",但这远非"我不会抛出任何例外".既然你可能没有编写框架,你可能会妥协这样的事情......

catch( Exception ex )
{
  Logger.Log( ex );
  Debug.Assert( false );
  foo = null;
  return false;
}  
Run Code Online (Sandbox Code Playgroud)

...并且不处理具有该catch块的TryXXX故障情况(因此您没有一堆简单的日志内容).在这种情况下,您至少会揭示错误的性质并在开发期间识别它.时间以及在运行时记下它.