Making Visual Studio treat warnings the same way as errors
.NET projects have an interesting property that we can set. It’s named TreatWarningsAsErrors
. We can set it in *.csproj
files:
<PropertyGroup>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
<PropertyGroup>
Code language: HTML, XML (xml)
For years, I would say that this is great! The more strict code checking rules, the better, right? Code style, warnings, conventions. And in theory, there are nice benefits to that.
If we have strict automated rules to check everything, we save hours on code review discussions about tabs, spaces, spaces around braces etc. Automated reviewer will just reject that. No one needs to waste their attention on pointing out breaking conventions. We don’t have to annoy people by picking on minor issues.
Additionally, we get a super-fast feedback loop. With modern Visual Studio and its analyzers, we can see the squiggly red line indicating error a second after we wrote the code. We are incentivized by that red line to instantly fix the issue, because we’re trained to keep the code error-free as we work. Fast feedback loop is, in general, a great thing.
But then I landed in a project where I saw such strict rules applied. And I hated it. And I think I can articulate good arguments for not being too strict, and particularly, not treating warnings as errors.
Why I think that compiler should not treat warnings the same as errors
I’ll start with an example. This is a kind of error I’ve been seeing dozens times a day now:
Let’s assume that I agree that this code style rule is useful and that the team should not commit such lines to the repository. Should it be an error then? I think it should not, and here are my arguments:
- Build of the whole application will fail if there are any errors like that. It’s hard to test your code if it doesn’t compile. You can’t run unit tests. You can’t launch application and see if it works.
The nature of writing code is that before you commit your final solution, you add code. You move it, remove it, change it as you discover problems and so on. Maybe we gain immediate feedback on code style issues, but we loose quick feedback that comes from executing the code frequently and with minimum friction. - The IDE will constantly draw your attention to minor issues like “unused using statement”, or “two empty lines after each other”. If the editor shows the same kind of feedback (a red, squiggly line and an entry in the Errors window) for all issues, regardless of severity, you cannot focus on important issues, because all of them become equally important if they prevent the compilation.
- It’s just a waste of time to fix code style violations in a code that you will likely delete/rewrite in the coming hours.
Should we then just ignore all warnings and code style violations?
But if we don’t prevent developers from introducing new warnings and code style violations as early as possible, how to avoid having their number grow and accumulate as a technical debt? Let my try enumerate our options:
- On the one side of the spectrum, we can prevent code with minor issues from compiling instantly, right in the IDE like Visual Studio. We can do it with
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
, or by increasing severity of analyzer rules to ‘Error’. I already explained why I don’t like this idea. - On the other side of the spectrum, we can only check for code issues at the last moment, before we merge code to an integration branch. We can do it in Pull Request’s validation pipeline. Theoretically, you can do it even later, but it would be a really bad quality process.
I worked in such setups. I found them pretty annoying. Everything in your code seems to be fine in the IDE. Then you create a Pull Request and wait few minutes or few hours for the pipeline to complete. Only then you get feedback that you forgotten to add a comment to a public method. The issue here is a long feedback time between making a mistake and getting feedback about it. - Both of those extremes approaches seem to be inefficient. So I think it’s best to find something in between.
In reality, there don’t seem to be that many options in between to hook ourselves with the code validation. This is how I see our choices:
From that perspective, we could consider a git hook validating the code before each git push operation a good place to stop the bad code from going further. But from my experience, it has drawbacks. It’s hard to enforce setting up git hooks on the team. It might be inconvenient to try build solution on each commit to find out errors. The feedback might not be in a nice form. Maybe there are ways to do it, but I haven’t seen that working well yet.
How to best deal with warnings
I don’t have a definite answer, but taking the above into consideration, I would strive to make a setup where:
- Errors are errors, and warnings remain warnings.
- Errors are issues that make it impossible for compiler to produce a binary (library or executable)
- I would consider selectively increasing severity of some warnings to errors. I think of warnings that normally would not prevent compilation to succeed, but would very likely lead to runtime errors (e.g., conflicts of dependencies versions within the project)
- Warnings should remain warnings in the IDE. I’d be ok with a zero-warning policy enforced later, in the validation pipeline. Developer would still see orange squiggly line for warnings in the IDE, but would be able to ignore them until she’s ready to publish the Pull Request.
- Minor issues that don’t break the compilation, introduce no risks to runtime and don’t dramatically decrease other coders’ experience, should have lower severity (e.g. suggestion, hint). I’d allow the team to merged them to the integration branch. Therefore I’d consciously allow them to accumulate over time in the code base. I’d not say that few years ago, but I think this is the rational way to do.
Did this essay triggered some interesting thoughts in you? Please share in the comments whether you agree with me or not 🙂
I don’t agree.
I love having super strict code analysis blocking me from even building the code locally. It has taught me to simply write the code right from the start and has cured me of a number of bad habits. Early feedback is key to improving, and instant is as early as you can get.
If you want a well kept project there is no difference between severities. You write the code adhering to the standards you agreed on in the team, no more, no less.
Most people who use the option think they are writing proper code and error free code as much as possible, most times it is just delusion.
For example, lets assume you start coding properly with Nullable reference types, this means you will start to see warnings not from using NRT code but when implementing it and that it cannot cover all use cases.
As a result you get the most horrid code and a spam of null ignore ! exclamation marks and all.
Or better when new code warning is introduced that points out a new way due newer .NET version, and you suddenly get dozens of files that need to be changed. That happened before and will again.
Even better example, disallowing ObsoleteAttribute because of it. Seen it many times and so many times they try to justify not adding it to excluding list as well. Causing dozens of code saying not to use something, the fix to refactor it is in backlog and on a long list. In the mean time time people keep using the incorrect one and not migrating to the proper one. *facepalm*
The smaller the project, then sure you can start using it in the beginning, but be flexible and accept that some warnings are just warnings. Especially if you start introducing packages for like code styling, leave those as is, treating those as errors will only do harm in time.
While some languages like JS really need it because of the dozens of way you can write things, with .NET those are not really needed and the flavor of style depends on the code in the class. Some new languages have it from the get-go but even there it is not perfrect and some pressure to allow adjusting due to the horrible styling.
Example, there are some strict code style configurations demanding everything as expression when a single line is made. Fun and all, but depending on the amount and/or other code that becomes extremely unreadable. It is up to dev-er/ reviewers to see if still easy to read. Seen enough places where code styling rules were applied throughout and as a result most things became unreadable.