Skip to main content

Clean Coding Principles in CSharp

A code shall be easy to read and understand. In this post, I am outlining basic principles  about clean coding after researching through expert recommended books, trainings and based on my experience.

A common example to start with is a variable declaration like -

int i 

The above statement did not clarify the purpose of variable i. However, the same variable can be declared as - 

int pageNumber

The moment we declared the variable as int pageNumber, our brain realized that the variable is going to store the value for number of pages. We have set the context in our brain now and it is ready to understand what the code is going to do next with these page numbers. This is one of the basic advantages of clean coding.

Reasons for clean coding

Reading clean code is easier - Every code is revisited after certain amount of time either by the same or different developer who created it. In both the cases, if the code is unclean, its difficult to understand and update it.

To avoid sloppy code - Sloppy code injects technical debt. Being sloppy, slows us down causing long term cost

To avoid being a bad example - No one wants to be a bad example within the team. Dirty code make us one such example.

Code like an Author

Write your code as if you are an author. Read each line of your code multiple times to make it easy to understand. 

An author divides his story into chapters, headings and paragraphs. Similarly, a developer shall divide his code properly in namespace, classes and methods.

Clean Coding Principles

Basically, there are 3 principles to clean coding -

  1. Right tool for the job
  2. Maximize signal to noise ratio
  3. Self-document the code

Right tool for the job

In simple words, keep only ONE LANGUAGE PER FILE. Avoid using one language to write another language via strings in a file. E.g. 

Putting html in JavaScript file to inject it dynamically is wrong. Markup (html) shall always be in .html files.

HTML in sql db or sql queries is incorrect. 

Putting styles inline is incorrect.

Dynamic sql in C# string is incorrect. Use stored procedures instead of dynamic Sql queries in c# or Java. 

Dynamic js in c# strings is incorrect e.g. Google analytics code in .cs files

Storing the code in correct file formats has following advantages-

Code remains cached in browser

E.g if js in html, then js downloaded on each refresh

Code colored for readability

Code is syntax checked

Clear separation of concerns

Enhanced reusability

Avoid string parsing on every request as files get cached

MAXIMIZE SIGNAL TO NOISE RATIO ( SNR)

Signal-to-noise ratio in science and engineering is the ratio of desired signal to undesired signals (noise). In coding terms, it means, each identifier should be meaningful within the domain of the application. 

E.g.  Signal - Product , Noise - ProductFactory, ProductBuilder

Following rules ensure a healthy SNR - 

RULE OF 7 - Humans can hold 7 items in memory at one instant of time. Hence, to make our code easy to understand, we should not have more than 7 - 

Number of parameters per methods

Number of methods per class

Number of variables currently in scope

The DRY Principle (Don’t Repeat Yourself) - According to this, developers should not copy and paste duplicate code at multiple places as it -

decreases signal to noise ratio

increases the number of code lines

creates a maintenance problem as bug fix needs to be done in multiple places where the code is duplicated. 

SELF DOCUMENTING CODE

A self-documented code is the code that itself explains what the original developer tried to do without needing any additional comments. Such code has clear -

content

layers of abstraction

formatting for readability

less comments

Self documented code is ensured by-

following naming conventions

writing clean classes

writing clean methods

writing clean comments

writing clean conditions 

Naming Conventions

Naming convention is very important part of clean coding. From variables to methods to classes to your application itself, names should justify the purpose.

E.g. List<decimal> p = new List<decimal>

List<decimal> prices = new List<decimal>

In the above example, the latter clearly justifies the purpose of the list in comparison to former. 

Naming the Classes

Good class names are -  

Nouns

Specific - makes class less cohesive

Have single responsibility

E.g. - User, Account

Bad class name examples - WebSiteBO, Utility, Common, MyFunctions, JimmysUtils, *manager/*processor/*info

Note - Avoid generic suffixes that add no value to the name

E.g. - Product is a better class name than ProductInfo or ProductManager

Naming the Methods

The following guidelines are important to name the methods efficiently -

Make sure method names are comprehensive and tell all what the developer is trying to do. Developers can find the correct name for a class/method using - 

Rubber Ducking Method -  Verbalize what each class/method does to find the suitable name. E.g. A method responsible for GETting the REGISTERED USERS shall be named as GetRegisteredUsers()

Other good e.g. - IsValidSubmission() , ImportDocument() , SendEmail() 

Bad e.g. - go() , complete() , get() , process() , doIt() , start() , on_init() , page_load() etc. 

Ensure that within a class/method, we should describe only one thing. If a class/method is doing more than one thing, it's better to split them into multiple. 

E.g. – GetUser() should only be responsible for getting the user details and should not create user session. It should be done in a separate method as these are separate concerns and should be done separately

Note - One should reconsider the name of method if it has -

Presence of And, If, Or in method name - It is a sign to create 2 methods

Presence of Abbreviations in name - regUsr, regisUser

Little bit of extra storage to make things more understandable doesn't hurt anything 

Naming Booleans

Booleans are important to condition logic formation in every application. Hence, utmost care needs to be taken in naming them. 

Good boolean e.g. - IsOpen, done, isActive, loggedin

Bad boolean e.g.- open, start, status, login

Implementing CONDITIONALS cleanly

Good conditions convey clear intent and logic. Use the right tool (right data type) to store conditionals. While implementing conditions, take care to - 

Compare Boolean Implicitly

Bad Example -

if(loggedin==true)

Good Example -

if(loggedIn)

Assign Booleans Implicitly

Bad E.g. – 

bool goingToChipotleForLunch
if(cashInWallet>6)
goingToChipotleForLunch = true

Good E.g. – 

bool goingToChipotleForLunch = cashInWallet>6;

Be Positive in implementing the conditions. 

E.g. - if(loggedIn) makes more sense than if(!IsNotLoggedIn)

Ternaries are beautiful when implementing conditions - 

Bad - 

if(isSpeaker)
registrationFee=0
else registrationFee=50

Good -

int registrationFee = isSpeaker?0:50

Be Strongly typed, not STRINGLY typed - When strings are used for comparison, there are chances of failure at runtime whenever the string changes. 

E.g. -

if(employeeType=="manager")//fails at runtime if mistyped

It's better to implement the above condition as -

if(employee.Type==EmployeeType.Manager) // no typos

Using enums is preferrabld in such cases as they are searchable (using show all references in Visual Studio)

Avoid Magic Numbers - Numbers don’t convey any meaning without context. 

Bad e.g.- 

if(age>21){} /// why 21
if(status==2)//use enums here

Good e.g.- 

const int legalDrivingAge=21
if(age>legalDrivingAge){}
if(status==Status.active)

Magic strings and numbers further complicate the conditionals. Developers shall use intermediate variables to provide the context. 

Bad e.g. - 

if(car.Year>1980
&&(car.Make=="Ford"||car.Make=="Chevrolet"...

Good e.g - 

bool eligibleforPension = employee.Age>55
&& employee.YearsEmployed>10
&& employee.IsRetired===true;

Encapsulate Complex Conditions - Create methods for complex conditionals

E.g. 

ValidFileRequest()
{
return (fileExt==".mp4"||fileExt==".mpg")
}

rather than - 

if((fileExt==".mpy"||fileExt==".mpg")

Prefer Polymorphism over switch

Bad - 

switch(user.Status)
{
case Status.Active:....//active user logic
break;
case Status.InActive:....//inactive user logic
break
}

good -

public abstract class User{
pubic void login()
}
public class ActiveUser:User{
public override void Login(){
}
}
public class InActiveUser:User{
public override void Login(){
}
}

Be Declarative

Bad e.g. - 

foreach(var user in users){
if(user.AccountBalance<minAccountBalance
&& user.Status==Status.Active)
{
matchingUser.Add(user);
}
}

Good e.g. - 

return users.
where(u=>u.AccountBalance<minAccountBalance)
.where(u=>u.status==Status.Active)


Table Driven Methods - Try to use tables when there are too many conditions.

bad - 
if(age<20){}
else if (age<30{}
else if (age <40){}
else if (age<50){}

good -

Store this data in db table and execute a db query to get the results - 

insuranceRateId		Maimum Age	Rate
1 20 346
2 30 444
return Repository.GetInsuranceRate(age)


WRITE CLEAN METHODS

We should create a function - 

To avoid duplication of code 

To prevent excessive Indentation:

Extract methods from too long code by picking most deeply rooted code and moving it out. High cyclomatic complexity hurts readability, hinders testing and increases bug risk.

A clean method should - 

fail fast - throw exception when unexpected occurs.

return early - return statement should not be too deep in code

clarify intent (method names should provide high level summary)

always do one thing. If a method does many things, create separate methods for each part.

Some miscellaneous points -

Mayfly variables Mayfly is a fly with shortest life on earth. Similarly, sefine the variables when needed, just in time. Don't describe variables at top inside a method.

Bad e.g. – 

pubic string GetAddress(){
bool a=false
int b=0
string c=string.Empty
...


Function parameters- Avoid too many parameters to a method. Avoid flag arguments with bool data type as they reflect 2 intents which should be split in 2 functions.

Signs that a method is too long-

If scrolling required

Hard time to name a method means its doing too many things. Extract methods out from it

Multiple conditionals

Hard to digest with multiple layers of abstraction

WRITING CLEAN CLASSES

Classes are created to model an object. When developers put unrelated things in a class, it becomes low on cohesion. In such class, methods won’t relate with each other. Classes with low cohesion should be split up in to more classes. 

Advantages of Cohesiveness in class - 

enhances readability,

increases reuse

avoids attracting the lazy

To implement cohesiveness in classes, watch for -

standalone methods

fields used only by one method

classes that change often.Such need to be extracted into separate classes

E.g. bad class - single class with methods

edit vehicle options

update pricing 

schedule maintenance

send maintenance reminder

select financing

calculate monthly payment

Good class - separate classes

Vehicle

edit vehicle options

update pricing 

VehicleMaintenance

schedule maintenance

send maintenance reminder

VehicleFinance

select financing

calculate monthly payment

This reduces risk of breaking the logic when modifications are done

It makes it easy to include new feature.

A few bonus points - 

Names and cohesion -

Broad names lead to poor cohesion

Classes with generic names grow hugely

Class should have specific name to promote cohesiveness

Primitive Obsession - Never send too many parameters to a method. Try to combine related parameters in an object - 

e.g. 

good - 

private voide SaveUser(User user)

bad - 

private void SaveUser(stirng firstName, string lastName, string State, string zip, sting eyeColor, string phone ... )

Proximity Principles - Make code readable top to bottom. Keep related actions together. 

The Outline Rule-  

Collapsed code should read like an outline. 

High level methods should delegate to low level methods. 

As such, some methods shall become just a sequence of method calls. 

WRITING CLEAN COMMENTS

Over-reliance on comments to understand the code is a code smell. There should be a clear reason to justify the presence of comments in code. 

Developers should prefer expressive code over comments i.e. only use comments when code alone is not enough to understand the implementation. 

Not all comments add value. Some common examples of useless comments are - 

Redundant commentsThese comments repeat exactly what code says

E.g 

int i=1//set i=1
///calculate total charges
private void CalculateTotalCharges(){}

Avoid requiring comment for every method

Intent Comments -

e.g. bad - 

//assure users account is deactivated
if(user.Status==2)

good –

 if(user.Status==Status.Inactive)

Rather than such comments, developers should try to 

declare intermediate variable

declare a constant or enum

extract condition to a function

Apology Comments – Never put them

E.g. – 

//sorry, this crashes sometimes

Zombie code comments - These are the sections of commented code in our application

Divider comments - avoid using comments to separate sections

Brace tracker comments – No need for these.

e.g. 

 }//closes for loop.. 

Bloated header comments – These are comments at top of the file telling author etc. Don’t repeat yourself in code files e.g. author and created date available from source control. No need to mention them in code files. 

Change comments – Change metadata belongs in source control and not in comments. Developers should prevent comments like - 

//Defect log - defect #5274 - checking for null now


Clean Comments - These are the comments useful in understanding and maintaining the application. E.g.-

ToDo comments - to tell what todo soon 

E. g. //TODO refractor out duplication

Summary comments - describes intent at general level

E.g. - //encapsultes logic for calculating retiree benefits

Documentation - these are useful for 3rd parties. 

E.g. - //see microsoft.com/api for documentation

STAYING CLEAN

Refactor only the code you are working on. If code is difficult to comprehend or change, clean it. While cleaning, ensure -

Code Reviews

Proactive cleanliness

Set guidelines

Assure readability

Add tests for regression protection to assure bugs don’t get introduced

Please like and subscribe the blog for similar posts. Thank you!!

Comments

POPULAR POSTS

Sitecore PowerShell Script to create all language versions for an item from en version

  We have lots of media items and our business wants to copy the data from en version of media item to all other language versions defined in System/Languages. This ensures that media is available in all the languages. So, we created the below powershell script to achieve the same -  #Get all language versions defined in System/Languages $languages = Get-ChildItem /sitecore/System/Languages -recurse | Select $_.name | Where-Object {$_.name -ne "en"} | Select Name #Ensuring correct items are updated by comparing the template ID  $items = Get-ChildItem -Path "/sitecore/media library/MyProjects" -Recurse | Where-Object {'<media item template id>' -contains $_.TemplateID} #Bulk update context to improve performance New-UsingBlock (New-Object Sitecore.Data.BulkUpdateContext) { foreach($item in $items){    foreach($language in $languages){ $languageVersion = Get-Item -Path $item.Paths.Path -Language $language.Name #Check if language versi

Export Sitecore media library files to zip using SPE

If you ever require to export Sitecore media files to zip (may be to optimize them), SPE (Sitecore Powershell Extension) has probably the easiest way to do this for you. It's as easy as the below 3 steps -  1. Right click on your folder (icons folder in snap)>Click on Scripts> Click on Download 2. SPE will start zipping all the media files placed within this folder. 3. Once zipping is done, you will see the Download option in the next screen. Click Download Zip containing the media files within is available on your local machine. You can play around with the images now. Hope this helps!! Like and Share ;)

Make Sitecore instance faster using Roslyn Compiler

When we install the Sitecore instance on local, the first load is slow. After each code deploy also, it takes a while for the Sitecore instance to load and experience editor to come up. For us, the load time for Sitecore instance on local machines was around 4 minutes. We started looking for ways to minimize it and found that if we update our Web.config to use Roslyn compiler and include the relevant Nugets into the project, our load times will improve. We followed the simple steps - Go to the Project you wish to add the NuGet package and right click the project and click 'Manage NuGet Packages'. Make sure your 'Package Source' is set to nuget.org and go to the 'Browse' Tab and search Microsoft.CodeDom.Providers.DotNetCompilerPlatform. Install whichever version you desire, make sure you note which version you installed. You can learn more about it  here . After installation, deploy your project, make sure the Microsoft.CodeDom.Providers.DotNetCompilerPlatform.d

Experience of a first time Sitecore MVP

The Journey I have been working in Sitecore for almost 10 years now. When I was a beginner in Sitecore, I was highly impressed by the incredible community support. In fact, my initial Sitecore learning path was entirely based on community written blogs on Sitecore. During a discussion with my then technology lead Neeraj Gulia , he proposed the idea that I should start giving back to developer community whenever I get chance. Just like I have been helped by many developers via online blogs, stackoverflow etc., I should also try to help others. Fast forward a few years and I met  Nehemiah Jeyakumar  (now an MVP). He had a big archive of his technical notes in the form Sitecore blogs. I realized my first blog dont have to be perfect and it can be as simple as notes to a specific problem for reference in future. That's when I probably created my first blog post on Sitecore. At that time, I didn't knew about the Sitecore MVP program. Over the years, I gained more confidence to write