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
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 -
- Right tool for the job
- Maximize signal to noise ratio
- 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 comments - These 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
Comments
Post a Comment