Book Image

Clean Code in C#

By : Jason Alls
Book Image

Clean Code in C#

By: Jason Alls

Overview of this book

Traditionally associated with developing Windows desktop applications and games, C# is now used in a wide variety of domains, such as web and cloud apps, and has become increasingly popular for mobile development. Despite its extensive coding features, professionals experience problems related to efficiency, scalability, and maintainability because of bad code. Clean Code in C# will help you identify these problems and solve them using coding best practices. The book starts with a comparison of good and bad code, helping you understand the importance of coding standards, principles, and methodologies. You’ll then get to grips with code reviews and their role in improving your code while ensuring that you adhere to industry-recognized coding standards. This C# book covers unit testing, delves into test-driven development, and addresses cross-cutting concerns. You’ll explore good programming practices for objects, data structures, exception handling, and other aspects of writing C# computer programs. Once you’ve studied API design and discovered tools for improving code quality, you’ll look at examples of bad code and understand which coding practices you should avoid. By the end of this clean code book, you’ll have the developed skills you need in order to apply industry-approved coding practices to write clean, readable, extendable, and maintainable C# code.
Table of Contents (17 chapters)

The need for coding standards, principles, and methodologies

Most software today is written by multiple teams of programmers. As you know, we all have our own unique ways of coding, and we all have some form of programming ideology. You can easily find programming debates regarding various software development paradigms. But the consensus is that it does make our lives easier as programmers if we do all adhere to a given set of coding standards, principles, and methodologies.

Let's review what we mean by these in a little more detail.

Coding standards

Coding standards set out several dos and don'ts that must be adhered to. Such standards can be enforced through tools such as FxCop and manually via peer code reviews. All companies have their own coding standards that must be adhered to. But what you will find in the real world is that when the business expects a deadline to be met, those coding standards can go out of the window as the deadline can become more important than the actual code quality. This is usually rectified by adding any required refactoring to the bug list as technical debt to be addressed after the release.

Microsoft has its own coding standards, and the majority of the time these are the adopted standards that are modified to suit each business' needs. Here are some examples of coding standards found online:

When people across teams or within the same team adhere to coding standards, your code base becomes unified. A unified code base is much easier to read, extend, and maintain. It is also likely to be less error-prone. And if errors do exist, they are more likely to be found more easily, since the code follows a standard set of guidelines that all developers adhere to.

Coding principles

Coding principles are a set of guidelines for writing high-quality code, testing and debugging that code, and performing maintenance on the code. Principles can be different between programmers and programming teams.

Even if you are a lone programmer, you will do yourself an honorable service by defining your own coding principles and sticking to them. If you work in a team, then it is very beneficial to all to agree on a set of coding standards to make working on shared code easier.

Throughout this book, you will see examples of coding principles such as SOLID, YAGNI, KISS, and DRY, all of which will be explained in detail. But for now, SOLID stands for Single Responsibility Principle, Open-Closed Principle, Liskov Substitution, Interface Segregation Principle, and Dependency Inversion Principle. YAGNI stands for You Ain't Gonna Need It. KISS stands for Keep It Simple, Stupid, and DRY stands for Don't Repeat Yourself.

Coding methodologies

Coding methodologies break down the process of developing software into a number of predefined phases. Each phase will have a number of steps associated with it. Different developers and development teams will have their own coding methodologies that they follow. The main aim of coding methodologies is to streamline the process from the initial concept, through the coding phase, to the deployment and maintenance phases.

In this book, you will become accustomed to Test-Driven Development (TDD) and Behavioral-Driven Development (BDD) using SpecFlow, and Aspect-Oriented Programming (AOP) using PostSharp.

Coding conventions

It is best to implement the Microsoft C# coding conventions. You can review them at https://docs.microsoft.com/en-us/dotnet/csharp/programming-guide/inside-a-program/coding-conventions.

By adopting Microsoft's coding conventions, you are guaranteed to write code in a formally accepted and agreed-upon format. These C# coding conventions help people to focus on reading your code and spend less time focusing on the layout. Basically, Microsoft's coding standards promote best practices.

Modularity

Breaking large programs up into smaller modules makes a lot of sense. Small modules are easy to test, are more readily reused, and can be worked on independently from other modules. Small modules are also easier to extend and maintain.

A modular program can be divided into different assemblies and different namespaces within those assemblies. Modular programs are also much easier to work on in team environments as different modules can be worked on by different teams.

In the same project, code is modularized by adding folders that reflect namespaces. A namespace must only contain code that is related to its name. So, for instance, if you have a namespace called FileSystem, then types related to files and directories should be placed in that folder. Likewise, if you have a namespace called Data, then only types related to data and data sources should be located in that namespace.

Another beautiful aspect of correct modularization is that if you keep modules small and simple, they are easy to read. Most of a coder's life apart from coding is spent reading and understanding code. So the smaller and more correctly modularized the code is, then the more easier it is to read and understand the code. This leads to a greater understanding of the code and improves developer take-up and use of the code.

KISS

You may be the super genius of the computer programming world. You may be able to produce code that is so sexy that other programmers can only stare at it in awe and end up drooling on their keyboard. But do those other programmers know what the code is by just looking at it? If you found that code in 10 weeks' time when you head deep into a mountain of different code with deadlines to meet, would you be able to explain with absolute clarity what your code does and the rationale behind your choice of coding method? And have you considered that you may have to work on that code further down the road?

Have you ever programmed some code, gone away, and then looked at it more than a few days later and thought to yourself, I didn't write this rubbish, did I? What was I thinking!? I know I've been guilty of it and so have some of my ex-colleagues.

When programming code, it is essential to keep the code simple and in a human-readable format that even newbie junior programmers can understand. Often juniors are exposed to code to read, understand, and then maintain. The more complex the code, the longer it takes for juniors to get up to speed. Even seniors can struggle with complex systems to the point that they leave to find work elsewhere that's less taxing on the brain and their well-being.

For example, if you are working on a simple website, ask yourself a few questions. Does it really need to use microservices? Is the brownfield project you are working on really complicated? Is it possible to simplify it to make it easier to maintain? When developing a new system, what are the minimum number of moving parts you need to write a robust, maintainable, and scalable solution that performs well?

YAGNI

YAGNI is a discipline in the agile world of programming that stipulates that a programmer should not add any code until it is absolutely needed. An honest programmer will write failing tests based on a design, then write just enough production code for the tests to work, and finally, refactor the code to remove any duplication. Using the YAGNI software development methodology, you keep your classes, methods, and overall lines of code to an absolute minimum.

The primary goal of YAGNI is to prevent the over-engineering of software systems by computer programmers. Do not add complexity if it is not needed. You must remember to only write the code that you need. Don't write code that you don't need, and don't write code for the sake of experimentation and learning. Keep experimental and learning code in sandboxed projects specifically for those purposes.

DRY

I said Don't Repeat Yourself! If you find that you are writing the same code in multiple areas, then this is a definite candidate for refactoring. You should look at the code to see if it can be genericized and placed in a helper class for use throughout the system or in a library for use by other projects.

If you have the same piece of code in multiple locations, and you find the code has a fault and needs to be modified, you must then modify the code in other areas. In situations like this, it is very easy to overlook code that requires modification. The result is code that gets released with the problem fixed in some areas, but still existing in others.

That is why it is a good idea to remove duplicate code as soon as you encounter it, as it may cause more problems further down the road if you don't.

SOLID

SOLID is a set of five design principles that intend to make software easier to understand and maintain. Software code should be easy to read and extend without having to modify portions of the existing code. The five SOLID design principles are as follows:

  • Single Responsibility Principle: Classes and methods should only perform a single responsibility. All the elements that form a single responsibility should be grouped together and encapsulated.
  • Open/Closed Principle: Classes and methods should be open for extension and closed for modification. When a change to the software is required, you should be able to extend the software without modifying any of the code.
  • Liskov Substitution: Your function has a pointer to a base class. It must be able to use any class derived from the base class without knowing it.
  • Interface Segregation Principle: When you have large interfaces, the clients that use them may not need all the methods. So, using the Interface Segregation Principle (ISP), you extract out methods to different interfaces. This means that instead of having one big interface, you have many small interfaces. Classes can then implement interfaces with only the methods they need.
  • Dependency Inversion Principle: When you have a high-level module, it should not be dependent upon any low-level modules. You should be able to switch between low-level modules freely without affecting the high-level module that uses them. Both high-level and low-level modules should depend upon abstractions.

An abstraction should not depend upon details, but details should depend upon abstractions.

When you declare variables, you should always use static types such as an interface or abstract class. Concrete classes that implement the interface or inherit from the abstract class can then be assigned to the variable.

Occam's Razor

Occam's Razor states the following: Entities should not be multiplied without necessity. To paraphrase, this essentially means that the simplest solution is most likely the correct one. So, in software development, the breaking of the principle of Occam's Razor is accomplished by making unnecessary assumptions and employing the least simple solution to a software problem.

Software projects are usually founded upon a collection of facts and assumptions. Facts are easy to deal with but assumptions are something else. When coming up with a software project solution to a problem, you normally discuss the problem and potential solutions as a team. When choosing a solution, you should always choose the project with the least assumptions as this will be the most accurate choice to implement. If there are a few fair assumptions, the more assumptions you are having to make, the more likely it is that your design solution is flawed.

A project with less moving parts has less that can go wrong with it. So, by keeping projects small with as few entities as possible by not making assumptions unless they are necessary, and only dealing with facts, you adhere to the principle of Occam's Razor.