首页 > 解决方案 > 如果一个方法在逻辑上必须返回一个值,我是否应该返回一个默认值以满足编译器错误,即并非所有代码路径都返回一个值?

问题描述

以下方法会产生此编译错误:

'SqlDeadlockHelper.ExecuteAsync(Func<Task>)':并非所有代码路径都返回值

根据方法的内容,我认为这在逻辑上是不可能的。如果我添加return default为最后一行,我很好(或者我应该抛出一个异常)。我应该得到这个错误吗?

    public static async Task<T> ExecuteAsync<T>(Func<Task<T>> func)
    {
        int tryCount = 0;

        do
        {
            try
            {
                tryCount++;
                return await func();
            }
            catch (Exception ex)
            {
                bool isRetryException = IsRetrySqlException(ex);
                bool retryHasBeenExhausted = tryCount >= MaxRetries;

                if (!isRetryException) { throw; }

                if (retryHasBeenExhausted) { throw new SqlHelperException("message here", ex); }

                // Logging here
            }

            // Wait for a period of time before retrying.
            SpinWait.SpinUntil(() => false, RetryDelayInMilliseconds);
        }
        while (tryCount <= MaxRetries);

        //return default; // This solves the problem, but logically we can't ever get here.
    }

这是另一个整体的同步方法。我可以清理它以删除我们认为不相关的部分,但现在这就是全部。

    protected void ExecuteWithExceptionRetry(params SqlExceptionNumber[] sqlExceptionNumbers)
    {
        // determine the exception numbers to retry
        List<SqlExceptionNumber> retryExceptionNumbers = new List<SqlExceptionNumber>(this._exceptionNumbers);
        if ((sqlExceptionNumbers != null) && (sqlExceptionNumbers.Count() > 0))
        {
            retryExceptionNumbers.Clear();
            retryExceptionNumbers.AddRange(sqlExceptionNumbers);
        }

        // make sure there are retry exceptions to look for
        if (retryExceptionNumbers.Count == 0)
        {
            retryExceptionNumbers.AddRange(from SqlExceptionNumber e in Enum.GetValues(typeof(SqlExceptionNumber)) where e.IsDefault() select e);
        }

        int tryCount = 0;
        int maxRetries = this.MaxRetryCount;

        do
        {
            try
            {
                // increment the try count...
                tryCount++;

                this.Action();
                
                return; // If here, execution was successful, so we can return.
            }
            catch (Exception exception)
            {
                bool isRetryException = IsRetrySqlException(exception, retryExceptionNumbers.ToArray());
                bool hasRetryBeenExhausted = tryCount >= maxRetries;

                if (!isRetryException)
                    throw;

                if (hasRetryBeenExhausted)
                {
                    throw new SqlRetryHelperRetryExceededException(
                        string.Format(CultureInfo.InvariantCulture,
                            "SQL exception thrown during query execution, retries have been exhausted after {0} attempt(s).",
                            maxRetries),
                        exception);
                }

                // need to keep retrying so log the current exception and keep going
                string details = (this.LogRetryDetails)
                    ? string.Format(CultureInfo.InvariantCulture, "Exception Detail: {0}", exception.ToString())
                    : string.Empty;

                string errorMessage = String.Format(CultureInfo.InvariantCulture,
                    "SQL exception thrown during query execution, will try {0} more time(s). {1}", maxRetries - tryCount, details);
                _logger.Warning(errorMessage);
            }

            // wait for a period of time before retrying
            if (this.RetryDelayInMilliseconds.HasValue)
            {
                SpinWait.SpinUntil(() => false, this.RetryDelayInMilliseconds.Value);
            }
        }
        while (tryCount <= maxRetries);
    }

标签: c#async-await

解决方案


有一天,宇宙将改进它传递给您的 idiotproof 逻辑的白痴,MaxRetries 将小于 tryCount 而 do 循环不会..

..那么您将处于您确定永远不会发生的代码路径上

您可能会断言您在某处有一些代码可以防止这些变量中的每一个都使得 MaxRetries 低于 tryCount,但是 C# 并没有深入考虑代码;它只是预见到此循环可能无法运行,因为它由几个变量控制。您可能会指出 MaxRetries 是一个具有受控范围的 int,如果它有“这个值”,而 tryCount 有“那个值”,那么这个 bool 就是 blah,那个 bool 就是 blahblah,这意味着它将不得不循环。您已经对其他地方可能发生的事情的所有可能性进行了更深入的思考,而不是 C# 对其分析所做的事情。

但是假设你很满意它永远不会发生。然后你公司雇佣的新孩子改变了 MaxRetries 属性,所以它每次调用时都会返回一个随机数,突然之间你的“永远不会到达那里”代码很有可能在很多情况下,路径实际上确实到达了那里。C# 仍然没有深入考虑任何事情;它不会查看代码中分配或使用它的每个位置,也不会查看它可能具有的所有可能值,以及您是否使用它们的子集,这意味着永远不会命中此方法的结尾

这就是抛出异常的目的;如果您确定代码可以达到这一点的唯一方法是开发人员搞砸了,或者用户输入了一个如此疯狂的值,您永远无法想象如何处理它。抛出一个让他们知道的异常这样他们就可以轻松修复配置等

抛出异常是返回默认值(可能为空)的一种可接受的替代方法,如果您正在查看日志以找出您的永不失败代码失败的原因,则完全可取。看到“应该不可能达到这个”的异常点;tryCount 是 x,MaxRetries 是 y。调查日志中的原因”优于“未将对象引用设置为对象的实例”X 小时和 Y 方法调用远离生成 null 的位置

从您想尝试最多 maxretries 次的评论中,将计数结构如下更易读:

    public static async Task<T> ExecuteAsync<T>(Func<Task<T>> func)
    {

        for(int tryCount = 0; tryCount < MaxRetries; tryCount++)
        {
            try
            {
                return await func();
            }
            catch (Exception ex)
            {
                if(!IsRetrySqlException(ex))
                   throw;
            }

            // Wait for a period of time before retrying.
            SpinWait.SpinUntil(() => false, RetryDelayInMilliseconds);
        }
        
        //retries exhausted
        throw new SqlHelperException("message here", ex);
    }

推荐阅读