CodeIt.Right Rules, Explained Part 5
Editorial note: I originally wrote this post for the SubMain blog. You can check out the original here, at their site. You can also read a lot more, both on that blog and in their documentation, about CodeIt.Right’s analysis rules.
Today, I’ll do another installment of the CodeIt.Right Rules, Explained series. This is post number five in the series. And, as always, I’ll start off by citing my two personal rules about static analysis guidance, along with the explanation for them.
- Never implement a suggested fix without knowing what makes it a fix.
- Never ignore a suggested fix without understanding what makes it a fix.
It may seem as though I’m playing rhetorical games here. After all, I could simply say, “learn the reasoning behind all suggested fixes.” But I want to underscore the decision you face when confronted with static analysis feedback. In all cases, you must actively choose to ignore the feedback or address it. And for both options, you need to understand the logic behind the suggestion.
In that spirit, I’m going to offer up explanations for three more CodeIt.Right rules today.
Mark ISerializable Types with “Serializable” Attribute
If you run across this rule, you might do so while writing an exception class. For example, the following small bit of code in a project of mine triggers it.
public class GithubQueryingException : Exception
public GithubQueryingException(string message, Exception ex) : base(message, ex)
It seems pretty innocuous, right? Well, let’s take a look at what went wrong.
The rule actually describes its own solution pretty well. Slap a serializable attribute on this exception class and make the tool happy. But who cares? Why does it matter if you don’t mark the exception as serializable?
To understand the issue, you need awareness of a concept called “application domains” within the .NET framework. Going into much detail about this would take us beyond the scope of the post. But suffice it to say, “application domains provide an isolation boundary for security, reliability, and versioning, and for unloading assemblies.” Think two separate processes running and collaborating.
If some external process will call your code, it won’t access and deal with your objects the same way that your own code will. Instead, it needs to communicate by serializing the object and passing it along as if over some remote service call. In the case of the exception above, it lacks the attribute marking it explicitly as serializable, in spite of implementing that interface. So bad things will happen at runtime. And this warning exists to give you the heads up.
If you’ll only ever handle this exception within the same app domain, it won’t cause you any heartburn. But, then again, neither will adding an attribute to your class.
Do Not Handle Non-CLS-Compliant Exceptions
Have you ever written code that looks something like this?
In essence, you want to take a stab at doing something and return true if it goes well and false if anything goes wrong. So you write code that looks something like the above.
If you you have, you’ll run afoul of the CodeIt.Right rule, “do not handle non-cls-compliant exceptions.” You might find this confusing at first blush, particularly if you code exclusively in C# or Visual Basic. This would confuse you because you cannot throw exceptions not compliant with the common language specification (CLS). All exceptions you throw inherit from the Exception class and thus conform.
However, in the case of native code written in, say, C++, you can actually throw non-CLS-compliant exceptions. And this code will catch them because you’ve said “catch anything that comes my way.” This earns you a warning.
The CodeIt.Right warning here resembles one telling you not to catch the general exception type. You want to be intentional about what exceptions you trap, rather than casting an overly wide net. You can fix this easily enough by specifying the actual exception you anticipate might occur.
Async Methods Should Return Task or Task<T>
As of .NET Framework 4.5, you can use the async keyword to allow invocation of an asynchronous operation. For example, imagine that you had a desktop GUI app and you wanted to populate a form with data. But imagine that acquiring said data involved doing an expensive and time consuming call over a network.
With synchronous programming, the call out to the network would block, meaning that everything else would grind to a halt to wait on the network call… including the GUI’s responsiveness. That makes for a terrible user experience. Of course, we solved this problem long before the existence of the async keyword. But we used laborious threading solutions to do that, whereas the async keyword makes this more intuitive.
Roughly speaking, designating a method as “async” indicates that you can dispatch it to conduct its business while you move on to do other things. To accomplish this, the method synchronously returns something called a Task, which acts as a placeholder and a promise of sorts. The calling method keeps a reference to the Task and can use it to get at the result of the method, once the asynchronous operation completes.
But that only works if you return a Task or Task<T>. If, instead, you create a void method and label it asynchronous, you have no means to get at it later and no means to explicitly wait on it. There’s a good chance this isn’t what you want to do, and CodeIt.Right lets you know that. In the case of an event handler, you might actually want to do this, but better safe than sorry. You can fix the violation by returning a non-parameterized Task rather than declaring the method void.
Until Next Time
This post covered some interesting language and framework features. We looked at the effect of crossing app domain boundaries and what that does to the objects whose structure you can easily take for granted. Then we went off the beaten path a little by looking at something unexpected that can happen at the intersection of managed and native code. And, finally, we delved into asynchronous programming a bit.
As we wander through some of these relatively far-reaching concerns, it’s nice to see that CodeIt.Right helps us keep track. A good analysis tool not only helps you catch mistakes, but it also helps you expand your understanding of the language and framework.