我有代码:
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
答案 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}}。
而且,我还要指出,这种方法同时解决了代码分析警告,和使实现简单,是内置参数验证的单个方法。两全其美。 :)