将异步方法分为两种进行代码分析?

时间:2019-07-05 22:02:02

标签: c# async-await sonarqube

我有代码:

public async Task DeleteColorSchemeAsync(ColorScheme colorScheme)
{
    if (colorScheme == null)
        throw new ArgumentNullException(nameof(colorScheme));

    if (colorScheme.IsDefault)
        throw new SettingIsDefaultException();

    _dbContext.ColorSchemes.Remove(colorScheme);
    await _dbContext.SaveChangesAsync();
}

一个代码分析器建议我将此方法分为两种方法:

  

将此方法分为两个部分,一个处理参数检查,另一个处理异步代码

我以下列方式拆分此代码时是否正确?

public async Task DeleteColorSchemeAsync(ColorScheme colorScheme)
{
    if (colorScheme == null)
        throw new ArgumentNullException(nameof(colorScheme));

    if (colorScheme.IsDefault)
        throw new SettingIsDefaultException();

    await DeleteColorSchemeInternalAsync(colorScheme);
}

private async Task DeleteColorSchemeInternalAsync(ColorScheme colorScheme)
{
    _dbContext.ColorSchemes.Remove(colorScheme);
    await _dbContext.SaveChangesAsync();
}

编译器有什么不同?它看到了两种异步方法,与我的第一种方法有什么不同?

使用的代码工具分析器:sonarqube

1 个答案:

答案 0 :(得分:2)

假设您要遵循代码分析建议,那么我将不采用第一种方法async。相反,它只能执行参数验证,然后返回调用第二个的结果:

public Task DeleteColorSchemeAsync(ColorScheme colorScheme)
{
    if (colorScheme == null)
        throw new ArgumentNullException(nameof(colorScheme));

    if (colorScheme.IsDefault)
        throw new SettingIsDefaultException();

    return DeleteColorSchemeInternalAsync(colorScheme);
}

private async Task DeleteColorSchemeInternalAsync(ColorScheme colorScheme)
{
    _dbContext.ColorSchemes.Remove(colorScheme);
    await _dbContext.SaveChangesAsync();
}

所有这些,我认为没有足够的理由来拆分这种方法。 SonarQube的规则Parameter validation in "async"/"await" methods should be wrapped恕我直言,过于谨慎。

编译器对async方法使用与迭代器方法相同的转换。使用迭代器方法时,在单独的方法中进行参数验证很有价值,因为否则,直到调用者尝试获取序列中的第一个元素(即,由编译器生成的MoveNext()方法时),参数验证才会完成被称为)。

但是对于async方法,该方法中直到第一个await语句的所有代码,包括任何参数验证,都将在对该方法的初始调用时执行。

SonarQube规则似乎是基于这样一个问题:在观察到Task之前,不会观察到async方法中生成的任何异常。没错但是async方法的典型调用顺序是对返回的await Task进行async,它会在完成后立即观察到异常,这当然会在生成异常时发生,并且会发生同步(即不会产生线程)。

我承认这不是一成不变的。例如,您可能会发起一定数量的Task.WhenAll()呼叫,然后使用例如async观察其完成情况。如果没有立即进行参数验证,您将在意识到其中一个调用无效之前结束所有任务的启动。这确实违反了“快速失败”(SonarQube规则所针对的)的一般原则。

但是,另一方面,参数验证失败几乎总是归因于用户代码不正确。即发生这种情况并不是因为数据输入问题,而是因为代码编写不正确。在这种情况下,“快速失败”有点奢侈。无论如何,对我而言更重要的是,以自然,易于遵循的方式编写代码,并且我认为将所有内容都用一种方法可以更好地实现该目标。

因此,在这种情况下,不必遵循SonarQube的建议。您可以将async方法保留为单一方法,保持原来的方式,而不会损害代码。比迭代器方法方案(具有相似的参数pro和con)更是如此,恕我直言,将验证保留在public Task DeleteColorSchemeAsync(ColorScheme colorScheme) { if (colorScheme == null) throw new ArgumentNullException(nameof(colorScheme)); if (colorScheme.IsDefault) throw new SettingIsDefaultException(); _dbContext.ColorSchemes.Remove(colorScheme); return _dbContext.SaveChangesAsync(); } 方法中与将其删除到包装方法中一样,同样重要。

但是,如果您确实选择遵循SonarQube的建议,那么我上面提供的示例将比您拥有的方法更好,恕我直言(并且确实更符合SonarQube文档的详细建议)。

我会注意到,实际上,还有一种更简单的方式来表达代码:

async

即完全不要执行async您的代码不需要await,因为只有一个async,它发生在方法的最后。由于您的代码实际上并不需要将控制返回给它,因此实际上并不需要使它Task。只需完成您需要做的所有同步工作(包括参数验证),然后返回原本要等待的{{1}}。

而且,我还要指出,这种方法同时解决了代码分析警告,使实现简单,是内置参数验证的单个方法。两全其美。 :)