How do you write the best code possible? What characteristics should that code have?
Assuming you can answer these questions, how do you ensure only good code is committed to your project and get notified when the quality declines?
These are hard questions with no simple or straightforward answers.
Code Quality is a fascinating subject, which comprises a mixture of knowledge, experience, guidelines, principles, patterns and conventions. And if that wasn’t enough, they all should be carefully adapted to each particular team and use case.
The subject gets even more confusing when you start considering tooling. However, choosing the right tools for each team and use case will make achieving quality code simpler.
This article contains an introduction to the Code Quality subject in the .Net ecosystem, with a brief look at tools included in Visual Studio and other third-party commercial tools.
What makes good code?
Let’s start the discussion by talking about desirable characteristics in good code. Let me start by one that it’s often forgotten, maybe because its obviousness.
Good code should work.
And by “work” I mean meeting the requirements without bugs.
You might have written a masterpiece, an elegant, readable, fully decoupled, extensible and 100% covered-by-tests piece of code. However, if it doesn’t meet the requirements, none of that matters at the end of the day.
Of course, there are many ways of simply reaching a solution that “works”.
However, we might consider some of them better than others.
Broadly speaking and without any specific context or requirements, I can enumerate a few characteristics I appreciate in good code:
- Simple, but not simplistic
- readable and easy to understand
That’s quite a lot to take in and you could spend days reading through passionate debates on internet like this one, this other one or this series of articles on DotNetCurry.
However, it all comes to a simple fact:
code spends most of its life being read and modified most of the time by developers other than the authors.
Even the author could be considered a different person if he comes back after a few months, as he stares perplexed at what his younger-self did.
How do you write good code then?
Luckily, or not, lots has been written about the subject and over the years, lots of people have worked on the subject and tried to provide guidance either general-purpose or aimed at specific problems.
Most of the time, someone else has found the same problem before you or at least something close enough!
We have multiple programming paradigms like Object Oriented Programming (OOP), Functional Programming (FP), Aspect Oriented Programming (AOP). Languages used to be closely aligned with these, but these days the lines are blurring.
Traditional OOP languages like C# keep getting bigger influences from functional programming, while the functional languages themselves, including F#, enjoy a second childhood.
We also have methodologies like Test Driven Development (TDD), Behavior Driven Development (BDD) or Object Oriented Design (ODD) and more specific paradigms, practices and processes like Domain Driven Design (DDD), unit testing, event driven programming or reactive programming.
This is just scratching the surface of the options out there. Fortunately, information is anything but scarce, which is handy as you need to invest time and understand them well enough to evaluate them.
Then you will find programming principles.
Most of you will have heard about the SOLID principles, but it’s also good to know about Don’t Repeat Yourself (DRY), Keep it Simple Stupid (KISS), You Aren’t Gonna Need It (YAGNI), Tell, Don’t Ask or Avoid Premature Optimization.
The key here is to understand the trade-offs and reasons behind them, using these principles as a guide as you make decisions. Even if you decide to break one of them, at least you thought it through before reaching a conscious choice.
Finally, there is an enormous number of patterns of different levels. The list might start with classics like Singleton, Decorator, Composite, Repository or Unit of Work.
You might then find more complex patterns like Model-View-Controller (MVC), Model-View-View-Model (MVVM), Event Sourcing or Command-Query-Response Separation (CQRS).
In the end, we could put high level patterns/architectures like Layered, Client-Server, Microservices or Serverless architectures.
As you can see, there is no shortage of information or guidance to help you write good code.
In fact, there is so much information out there that you might have a hard time choosing where to start from. Personally, I would first get my basics right and then start getting deeper depending on your career and preferences.
Before we move on, I would just mention a few books I found very helpful and in case you do as well. These are Code Complete (2nd edition) by Steve McConnell, Agile Principles Patterns and Practices in C# by Robert C. Martin and Patterns of Enterprise Application Architecture by Martin Fowler.
I have also found most things coming from Mark Seemann (aka Ploeh) as a fascinating read on functional programming.
Make sure your code works
Of course, you would like to know that your code works.
Unfortunately, there is no way other than testing, even if it’s manual testing. But we are lazy, and prefer automating as much as possible especially when we are talking about repetitive work.
That’s when you start considering automated testing and planning your testing strategy.
You will consider testing at different levels like unit tests, integration tests or end to end tests. You might also need tests for your non-functional requirements like performance, security, or resiliency.
In short, anything important for your app should be tested.
Each of these have their own particular philosophy, requiring different considerations and approaches. Just writing these tests is hard, but finding the right level of testing, while writing good tests is an art!
Of course, they come with their own frameworks, patterns, and tools to make sure you are never bored.
Spread the love across your team
Let’s say you have a pretty good idea about how to write good code and what shape it should have.
Most likely you don’t work alone but as part of a team.
The team needs to agree how they will write code, which principles, patterns, processes, and even styles everyone should follow.
This isn’t an easy task, but isn’t over once a decision is made.
Now everyone needs to make sure they keep following that decision or agree to change the things that aren’t working.
Your source control tool becomes way more than a code repository, sitting at the center of team and allowing them to work in parallel on multiple features.
Code reviews, these days often in the form of Pull Requests (PRs) are critical. They are one of the best ways of catching deviations from the agreed approaches, bugs, and simply learning from each other.
Your tests give everyone a safety net regardless of the area they are working on.
Finally, static code analysis tools ensure a consistent style across the entire code base and end endless discussions primarily based on personal preference like spacing or usage of brackets in single line ifs.
Deploy working code
Feedback is important.
When you write code, you would like to know as soon as possible whether the code works and doesn’t break existing functionality.
You would also like that code to be available as soon as possible in User Acceptance Testing (UAT) or Production so the bigger feedback loop and acceptance of the code can begin.
That’s why you need a good Continuous Integration/Delivery pipeline in place.
You should be able to push your code, test it, and deploy to any environments you require, and this process should be as short as possible.
You might even have a gated check-in process that doesn’t commit any code to your master/dev trunk unless it builds and passes your unit tests.
There is no shortage of tools and options in this area either, which depending on the company might get deep into Devops territory.
When things go wrong
Let’s face it, things won’t always go according to a plan.
And when that happens, you want to have the right tools and information available so you can trace and fix the issue as soon as possible.
This is why you want to master your debuggers, have different profiles in your toolbelt for different purposes (CPU, memory usage, network, database, etc.) that integrate logging, monitoring, diagnostic frameworks and platforms within your app, and have a means of visualizing and correlating that data.
But above all, once you find the issue and locate it in your code, you don’t want to be faced with a mess.
You want to see good code there, easy to understand and fix, covered by tests that give you decent confidence that nothing else was broken by the change.
You are not alone
Maybe you are part of a very small team (might be just you), or maybe you work for an old-school company where most of the previous concepts are buzzwords at most.
Progressing as a professional developer is hard, but doing so on your own and/or without the support of your company is even harder.
Luckily you are not alone and these days it’s easy to reach out to the wider community.
There are many things you can try.
For example, join meetups in your area, local development groups, get involved with Open Source Software (OSS), try to help or just ask questions in Stackoverflow, follow online tutorials, courses and videos, read blogs, etc.
You don’t need to do them all, but I am sure you can find one that works for you.
Code Quality Tools included in Visual Studio
There are many tools out there to help you in any of the areas described above.
Some are official tools provided by Microsoft, others are open source offerings, while others are commercial tools requiring an extra investment.
If you are developing .Net applications, it is very likely that you are using Visual Studio.
It then makes sense to start the tools discussion going through the tools included in Visual Studio. Bear in mind these might be available or not depending on your version of Visual Studio, check the comparison page for more information.
- Of course, you get a powerful IDE with Intellisense. It also brings helpful navigation features like peek definition, find all references or view the call hierarchy of a method.
- All versions include a powerful debugger, with additional tools like Intellitrace included in the Enterprise version. You also have a CPU and memory usage available in the Performance Profiler.
- When it comes to testing, all versions come with a test runner and unit testing support. Additional features like code coverage, coded UI tests or web load testing can be found in the Professional and especially the Enterprise version. These features keep growing on every release, for example VS 2017 has recently added Live Unit Testing support on its Enterprise version.
- It also comes with decent support for static code analysis. All versions allow you to use Code Analysis, formerly FxCop, and select the rule set your code should comply with as part of the build. Predefined rule sets from Microsoft are available. You can also generate code metrics, which generates metrics like lines of code and cyclomatic complexity in order to generate a maintainability index that will highlight areas needed attention.
- Basic refactoring support is also available in all versions, with common refactorings like extracting interface, extracting a method or renaming a member available. Code Snippets is a related offering which lets you define small snippets for quickly creating new methods, interfaces, properties, whatever you can think of.
- Let’s not forget Team Foundation Server (TFS), included within the Professional and Enterprise versions, which provides on premises support for Source Control, Project Management and Continuous Integration.
If you are interested, check this article on the DNC magazine describing some of these tools.
If you are interested in .Net Core, bear in mind some tools like Code Metrics might not support it yet.
It is also worth mentioning some of Microsoft’s online and Azure offerings related with developer tools:
- With Visual Studio Team Services, you have a cloud base offering providing Source Control, Agile Project Management and Continuous Integration. It’s an excellent tool with free and pay-for tiers, read more about it in this article of the DNC magazine.
- Application Insights lets you easily monitor your applications, visualize the captured data and detect/diagnose issues.
As you can see, there is no shortage of tools out-of-the-box, with many included in every edition of Visual Studio.
While this doesn’t cover everything that Microsoft offers to .Net developers, you might still need extra/better tools.
The following section will give you a glimpse of the breadth of 3rd party offerings.
A brief look at the many Third Party tools
I will start this section by mentioning alternatives to Visual Studio as IDE, although if you are targeting the full .Net framework, this is your best choice.
You might have already discovered Visual Studio Code, which works great as a lightweight editor for .Net Core applications. Check this introductory article in the DNC magazine.
A more fully-featured IDE supporting not just .Net Core but also the full .Net framework is the promising Rider from JetBrains, available as preview and due to release this year.
Speaking of JetBrains, they provide the most popular productivity tool, ReSharper, but that’s not the only offering:
- ReSharper is a Visual Studio extension which offers advanced refactorings, navigations, snippets, on the fly analysis, quick fixes, and test running capabilities.
- JustCode is another Visual Studio Extension from Telerik to boost code productivity with some clean code refactoring and cleaning features.
- CodeRush is a similar offering, also in the form of an extension. It is focused on productivity through refactorings, code navigation, code analysis, snippets, and test runners.
- There is an enormous number of extensions of different sizes for Visual Studio in the realm of “productivity tools”. These might be from small focused extensions like Open Command Line to larger ones like the Web Tools or Productivity Power Tools.
There is no shortage of tools either on the testing side of things
- You might be interested in alternative frameworks or test runners like NUnit or xUnit.
- Commercial tools like NCrunch offer alternative test runners that provides lots of additional information about the code under test and the results, including code coverage.
- As you might suspect, there is also no shortage of alternative code coverage tools like NCover or dotCover.
A different category of tools is aimed at code quality in a less focused way.
These typically contain static code analysis, integration with test runners and code coverage tools and some form of reporting or dashboards that collects and presents all this information:
- SonarQube is a popular open source offering available on many languages including C#. Read more about it in this article of the DNC magazine.
- NDepend, is a powerful commercial tool and allows your team to gauge the overall health of your .NET application, irrespective of its complexity.
- The built-in debugger in Visual Studio is quite powerful but that doesn’t mean you cannot find additional tools to help you debugging:
- OzCode adds many useful debugging features to Visual Studio, particularly for LINQ expressions.
- Fiddler is very common tool used for web debugging.
- SysInternals tools like Process Monitor, Process Explorer or Debug View are particularly handy when debugging running systems.
- You can disassemble dll’s with Ildasm and inspect the intermediate code (MSIL), which is bundled with Visual Studio. Or you can use tools like .Net Reflector or dotPeek and even recreate the original C# source code.
Another category of tools are the profilers, which can be of major help when diagnosing certain issues like performance bottlenecks or memory leaks.
- Besides the tools included in Visual Studio, JetBrains offers a specific performance profiler named dotTrace and a specific memory profiler named dotMemory.
- Redgate offers popular alternatives for both performance and memory profiling with their ANTS memory profiler and ANTS performance profiler.
- Glimpse and Mini-Profiler are two very popular NuGet packages in ASP.Net that provide insights and performance data directly on the browser while you run the site. Mini-Profiler can also be adapted to other scenarios and store its data into other sources like SQL server.
Before we finish the discussion, let’s take a quick look at popular non-Microsoft alternatives for Source Control, Project Management and Continuous Integration:
- The most popular cloud based Version Control offering is GitHub of course. You can find powerful alternatives in Bitbucket and GitLab to mention but a few.
- There is no shortage of Continuous Integration tools. Jenkins is the most popular one, while TeamCity also has a lot of traction in the .Net world, both of which offer on premises and cloud hosting, with Azure support available. AppVeyor is another interesting cloud based offering.
- Finally, I cannot avoid mentioning Jira as a very powerful project management tool.
This lengthy list of tools represents a breadth of open source and commercial offerings in the .Net landscape. However, this doesn’t (and cannot) contain every tool found out there.
Pick any area, dig online and you will find many specialized tools both commercial and open source which might be worth investigating.
3rd Party tools - InDepth
In the near future, we will covering up some commercial tools that help ensure Code quality and gauge the overall health of your .NET application.
We will be starting with NDepend.
NDepend combines the capabilities of multiple tools with the sole aim of helping you avoiding mistakes and code smells, write better code, and get a clear picture of the current quality of your code base as well as the trend.
On every build, your code will be analyzed and information for every method will be extracted, including code metrics, call/reference chains, dependency graphs and more. A set of rules can be defined and enforced based on that data using a LINQ-like syntax. This LINQ syntax can also be used to explore your code and is the basis of the many different visualizations provided by the tool.
It integrates with external tools like reflectors, code coverage, and Continuous Integration servers means you can define your Quality Gates and prevent bad code from being committed and your code base from deteriorate.
There is much more to it, so if it sounds interesting I will invite you to read the follow-up article to be published soon on this website which will be focused on its new 2017 version.
Update: The NDepend article has been published at www.dotnetcurry.com/general-topics/1361/third-party-tool-ndepend
Every good coding practice, pattern, principle, and tool mentioned through the article for improving code quality, is valuable. Whether they are the right ones for your team or project is something you need to decide for yourself.
Be agile with your coding practices and patterns. Decide within your team what makes most sense now. Once agreed, commit yourselves to try for a reasonable period after which you will re-evaluate. That way you will keep what works and change what doesn’t.
As for the tools, you should be able to try most if not all of them before you commit to any extra investment. Download them, try them, and strictly evaluate their usefulness to your team or project.
We all like shiny new tools, but you are responsible of making sure it is one you really need.
Thanks to Craig Berntson for technically reviewing this article.