-
Notifications
You must be signed in to change notification settings - Fork 231
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Remove S4457 from SonarWay #6132
Comments
The rule is overly cautious. If the criterium is fail-fast, then the correct rule implementation would be to not allow From a debugging perspective, this pattern where you wrap calls to an async method with a synchronous method to perform parameter validation and have argument exceptions thrown without having to observe tasks, is actually more than overly cautious. It's downright counter-productive to the intent of clarity towards consuming developers as well. (I guess you could say the synchronous wrapper is actually... exceptionally bad.) |
Hey @MxNbrt ,
Here is an example: DoWorkAsync(null); // fire-and-forget
// async-await used,
// treated as a special method for the async state machine,
// re-written by the compiler.
static async Task DoWorkAsync(int? maybeValue)
{
if (!maybeValue.HasValue)
{
throw new ArgumentNullException(nameof(maybeValue));
}
await Task.Delay(42); // Actual work happens here
} In this snippet, the "fire-and-forget" line will not throw an exception. This is because exceptions raised in asynchronous methods are placed on the returned task. If said task is not awaited, the exception is also not propagated to the call site, which could lead to potential bugs. The correct approach would be something like the following: DoWorkAsync(null); // fire-and-forget
// no async-await used, treated as a normal method
static Task DoWorkAsync(int? maybeValue)
{
if (!maybeValue.HasValue)
{
throw new ArgumentNullException(nameof(maybeValue));
}
return Task.Delay(42); // Actual work happens here
} In this case, the fire-and-forget method call will raise as expected.
Notice that the I hope the snippets provided above clarify the utility of this rule. |
The 'but fire-and-forget tasks' argument is, respectfully, bullshit. The whole point of fire-and-forget is that you kick something off, and then forget about it. Meanwhile, to pander to a very specific corner case that maybe makes up 1% of the total case, this style rule enforces work on 99% of the other cases. To needlessly restructure code to comply with the rule's silly wishes; to explicitly mark each individual occurrence of a rule violation as 'won't fix' in a central Sonarqube instance; or to add suppressions to each individual occurrence in code. It's a rule that, when activated, has in practicality a 99% false positive ratio needing workarounds. Moreover, the proposed fix allowing In fact, it's been possible for years for users to write their own async method builder and to tell the runtime to use that. Unity has libraries like Uni.AsyncRx that do this so you can tune performance characteristics to be more in line with what you'd expect from a game engine. And there are more than likely other examples of this, esp. in performance-sensitive context. In other words: your rule's proposed style improvement hinges on an internal implementation detail you cannot guarantee. S4457 is wrong; stupid and backwards and if you have no plans to remove it; then at the very least it should be disabled by default and should contain a big fat comment warning about the caveats. Microsoft actually has a style rule that handles unawaited - i.e. fires-and-forget - tasks, btw. It warns users when they execute an async method without either awaiting it; or assigning its result anywhere. Explicit fire-and-forget tasks can satisfy the rule by assigning the result to a discard, e.g. _ = DoFireAndForgetAsync(someData); |
Hello @gregory-paidis-sonarsource Thanks for clarifying, what your thoughts were when implementing the rule. Nonetheless i have to agree to @rjgotten and think that the fire-and-forget pattern should itself be a rule that is being violated here. The fire-and-forget pattern is bad and should only be used in a very small percentage of use cases. Thats why you should get rid of S4457 or at least disable it as a default. Thanks and have a nice day |
S4457 tells me that my async code should be splitted into synchronous data validation and asynchronous data processing. E.g the compliant solution looks like this:
My problem here is that last line which returns the Task directly instead of awaiting it. This conflicts with Microsofts recommendation
"Prefer async/await over directly returning Task" for async code which we followed everywhere in our code
So the correct solution according to Microsoft would be to make the whole method async and return await reader.SkipLinesInternalAsync(linesToSkip). After i got confused, I did what every developer would do and ended up at this stackoverflow answer which also claimed that the rule is "overly cautious".
So now I am not quite sure which coding style to follow and whether the rule S4457 is even reasonable.
Can you please clearify what is the purpose of S4457 and which code style would be best to follow?
Thank you very much
The text was updated successfully, but these errors were encountered: