Every time you stumbled across a piece of code where you either needed a disproportionate long time to understand what is going on there or where you thought to yourself this just doesnt look right. Then you already encountered code which most likely is not clean. It is not preferrable if this happens to you while reading through code some one else wrote but it is also not uncommon that the code you yourself wrote causes this kind of feelings or thoughts when reading it again a few weeks or months after writing it. At this point you should consider starting to do some research about the topic Clean Code.
What is Clean Code?
Clean Code is the term which describes the practice to write code as simple, as readable and as maintainable as possible. One important goal Clean Code wants to achieve is that everyone understands what the code does without putting too much effort into actually understanding it. To accomplish all of that Clean Code offers best practices, guidelines and suggestions which accumulated over time from all the experience by masters of the craft.
Some time ago I read a statement which said: “A good developer writes code he or she understands, but a great developer writes code everyone understands“. I was not able to find the source of this sentence but ever since this phrase stuck to my mind and motivated me to write code as clean as possible for everyone, not just for me.
I want to state some of the descriptions about clean code from minds greater than mine which were collected in the book Clean Code: A Handbook of Agile Software Craftsmanship (Robert C. Martin)
I like my code to be elegant and efficient. The logic should be straightforward to make it hard for bugs to hide, the dependencies minimal to ease maintenance, error handling complete according to an articulated strategy, and performance close to optimal so as not to tempt people to make the code messy with unprincipled optimizations. Clean code does one thing well.Bjarne Stroustrup, inventor of C++ and author of The C++ Programming Language
Clean code is simple and direct. Clean code reads like well-written prose. Clean code never obscures the designer’s intent but rather is full of crisp abstractions and straightforward lines of control.Grady Booch, author of Object Oriented Analysis and Design with Applications
Clean code can be read, and enhanced by a developer other than its original author. It has unit and acceptance tests. It has meaningful names. It provides one way rather than many ways for doing one thing. It has minimal dependencies, which are explicitly defined, and provides a clear and minimal API. Code should be literate since depending on the language, not all necessary information can be expressed clearly in code alone.“Big” Dave Thomas, founder of OTI, godfather of the Eclipse strategy
I could list all of the qualities that I notice in clean code, but there is one overarching quality that leads to all of them. Clean code always looks like it was written by someone who cares. There is nothing obvious that you can do to make it better. All of those things were thought about by the code’s author, and if you try to imagine improvements, you’re led back to where you are, sitting in appreciation of the code someone left for you—code left by someone who cares deeply about the craft.Michael Feathers, author of Working Effectively with Legacy Code
My takeaways from all those descriptions:
- Code should be elegant and efficient
- Clean code does one thing well
- Clean code is simple and direct
- Clean code reads like well-written prose
- Clean code can be read, and enhanced by a developer other than its original author
- Clean code always looks like it was written by someone who cares
- There is nothing obvious that you can do to make it better
Why should you apply Clean Code?
As stated earlier, code which was implemented without clean code principles in mind, consumes significantly more time in understanding it. Also code is regularly touched to apply changes, refactorings or enhancements, which is remarkably harder when having none of the best practices and/or guidelines in place. Applying clean code early on can reduce code maintenance time notably, because not only is the time reduced to familiarize yourself with the structure, code and/or logic but also the code is already prepared to be enhanced by having code extracted into methods, by using interfaces and by having reduced dependencies.
Also speaking from practical Experience why you should apply Clean Code best practices. As a writer of dirty and also clean code I can say it is in most cases just more convenient and easier to write dirty code which does its job but looks terrible, reads terribly and is not prepared for future changes and/or refactorings. The clean version of the code takes longer to implement and needs a bit more brainpower but the huge benefits are the improved readability and that further changes, improvements, adaptions and refactorings will be much easier to perform.
Reading through dirty code is like reading a article in a newspaper while you are distracted by something else. You read line by line and after you are done with the paragraph you have no clue what you just read and therefore you’ll have to read it again. This costs time and nerves. Clean code at the opposite is (or should be) like reading a good book in a comfy environment. You read through a page or a whole chapter and you can easily recite what you have read.
What are Code Smells?
So lets consider the unlikely event that your code is not following Clean Code standards. Then you might encounter Code Smells. Code Smells are the symptoms your code suffers from because the code does to many things bad, is not simple and direct, reads like a badly written eBook, can not be touched by anyone else than the writer and the writer probably did not care too much.
Some of those typical symptoms are:
- Long Methods: If a method is too long, it can be hard to understand and maintain. Break it down into smaller, more manageable methods that each perform a single task.
- Duplicate Code: Repeated blocks of code are a sign of redundancy. Extract the common code into a separate function or class to eliminate duplication.
- Large Classes: Classes that have too many responsibilities violate the Single Responsibility Principle. Refactor them into smaller, more focused classes that each have a single responsibility.
- Magic Numbers: Hard-coded numbers make code less readable and maintainable. Replace them with named constants or variables to improve clarity.
- Long parameter list: Pretty much self explanatory. A method with lets say 10 parameters should raise some questions. Try to replace some of them with one object which contains parameters that are related.
- Divergent Change: Adding a new type to a class and then having to adapt many methods in this class because of that change can be a sign of a not well thought through program structure. Split up the behavior of the class.
- Shotgun surgery: Similar to divergent change but now a single change forces you to adapt many other classes aswell. Try to move those related methods into one single class.
- Primitive obsession: Overusage of primitive types (e.g. string) as fields instead of grouping them in their own class.
For more information about code smells I would recommend to visit refactoring.guru which is an awesome page for content about Refactoring and Design Patterns.
Long Methods:
The following example actually shows several code smells besides that it is a long method. But it is shown pretty well that you lose the overview if the method gets too long.
public void UpdateQuality()
{
for (var i = 0; i < Items.Count; i++)
{
if (Items[i].Name != "Aged Brie" && Items[i].Name != "Backstage passes to a TAFKAL80ETC concert")
{
if (Items[i].Quality > 0)
{
if (Items[i].Name != "Sulfuras, Hand of Ragnaros")
{
Items[i].Quality = Items[i].Quality - 1;
}
}
}
else
{
if (Items[i].Quality < 50)
{
Items[i].Quality = Items[i].Quality + 1;
if (Items[i].Name == "Backstage passes to a TAFKAL80ETC concert")
{
if (Items[i].SellIn < 11)
{
if (Items[i].Quality < 50)
{
Items[i].Quality = Items[i].Quality + 1;
}
}
if (Items[i].SellIn < 6)
{
if (Items[i].Quality < 50)
{
Items[i].Quality = Items[i].Quality + 1;
}
}
}
}
}
if (Items[i].Name != "Sulfuras, Hand of Ragnaros")
{
Items[i].SellIn = Items[i].SellIn - 1;
}
if (Items[i].SellIn < 0)
{
if (Items[i].Name != "Aged Brie")
{
if (Items[i].Name != "Backstage passes to a TAFKAL80ETC concert")
{
if (Items[i].Quality > 0)
{
if (Items[i].Name != "Sulfuras, Hand of Ragnaros")
{
Items[i].Quality = Items[i].Quality - 1;
}
}
}
else
{
Items[i].Quality = Items[i].Quality - Items[i].Quality;
}
}
else
{
if (Items[i].Quality < 50)
{
Items[i].Quality = Items[i].Quality + 1;
}
}
}
}
}
Check out the Gilded Rose Kata to dive into some refactoring excercises.
Duplicate Code:
Its just an example but I have seen such a case several times. Code to transform or calculate something is copied to several places and bulks up the solution and decreases maintainability.
public class Cat
{
public ICollection<string> Toys { get; set; }
public string GatherStrings()
=> string.Join(',', Toys);
}
public class Dog
{
public ICollection<string> Toys { get; set; }
public string CompileToys()
=> string.Join(',', Toys);
}
Found the code snippet at code-maze.com
Large Classes:
As soon as I see classes with 200 lines of code or more, the first thing that happens is some sort of workout for my eyes in form of a rolling movement.
public class Game
{
List<string> players = new List<string>();
int[] places = new int[6];
int[] purses = new int[6];
bool[] inPenaltyBox = new bool[6];
LinkedList<string> popQuestions = new LinkedList<string>();
LinkedList<string> scienceQuestions = new LinkedList<string>();
LinkedList<string> sportsQuestions = new LinkedList<string>();
LinkedList<string> rockQuestions = new LinkedList<string>();
int currentPlayer = 0;
bool isGettingOutOfPenaltyBox;
public Game()
{
for (int i = 0; i < 50; i++)
{
popQuestions.AddLast("Pop Question " + i);
scienceQuestions.AddLast(("Science Question " + i));
sportsQuestions.AddLast(("Sports Question " + i));
rockQuestions.AddLast(createRockQuestion(i));
}
}
public String createRockQuestion(int index)
{
return "Rock Question " + index;
}
public bool isPlayable()
{
return (howManyPlayers() >= 2);
}
public bool add(String playerName)
{
players.Add(playerName);
places[howManyPlayers()] = 0;
purses[howManyPlayers()] = 0;
inPenaltyBox[howManyPlayers()] = false;
Console.WriteLine(playerName + " was added");
Console.WriteLine("They are player number " + players.Count);
return true;
}
public int howManyPlayers()
{
return players.Count;
}
public void roll(int roll)
{
Console.WriteLine(players[currentPlayer] + " is the current player");
Console.WriteLine("They have rolled a " + roll);
if (inPenaltyBox[currentPlayer])
{
if (roll % 2 != 0)
{
isGettingOutOfPenaltyBox = true;
Console.WriteLine(players[currentPlayer] + " is getting out of the penalty box");
places[currentPlayer] = places[currentPlayer] + roll;
if (places[currentPlayer] > 11) places[currentPlayer] = places[currentPlayer] - 12;
Console.WriteLine(players[currentPlayer]
+ "'s new location is "
+ places[currentPlayer]);
Console.WriteLine("The category is " + currentCategory());
askQuestion();
}
else
{
Console.WriteLine(players[currentPlayer] + " is not getting out of the penalty box");
isGettingOutOfPenaltyBox = false;
}
}
else
{
places[currentPlayer] = places[currentPlayer] + roll;
if (places[currentPlayer] > 11) places[currentPlayer] = places[currentPlayer] - 12;
Console.WriteLine(players[currentPlayer]
+ "'s new location is "
+ places[currentPlayer]);
Console.WriteLine("The category is " + currentCategory());
askQuestion();
}
}
private void askQuestion()
{
if (currentCategory() == "Pop")
{
Console.WriteLine(popQuestions.First());
popQuestions.RemoveFirst();
}
if (currentCategory() == "Science")
{
Console.WriteLine(scienceQuestions.First());
scienceQuestions.RemoveFirst();
}
if (currentCategory() == "Sports")
{
Console.WriteLine(sportsQuestions.First());
sportsQuestions.RemoveFirst();
}
if (currentCategory() == "Rock")
{
Console.WriteLine(rockQuestions.First());
rockQuestions.RemoveFirst();
}
}
private String currentCategory()
{
if (places[currentPlayer] == 0) return "Pop";
if (places[currentPlayer] == 4) return "Pop";
if (places[currentPlayer] == 8) return "Pop";
if (places[currentPlayer] == 1) return "Science";
if (places[currentPlayer] == 5) return "Science";
if (places[currentPlayer] == 9) return "Science";
if (places[currentPlayer] == 2) return "Sports";
if (places[currentPlayer] == 6) return "Sports";
if (places[currentPlayer] == 10) return "Sports";
return "Rock";
}
public bool wasCorrectlyAnswered()
{
if (inPenaltyBox[currentPlayer])
{
if (isGettingOutOfPenaltyBox)
{
Console.WriteLine("Answer was correct!!!!");
purses[currentPlayer]++;
Console.WriteLine(players[currentPlayer]
+ " now has "
+ purses[currentPlayer]
+ " Gold Coins.");
bool winner = didPlayerWin();
currentPlayer++;
if (currentPlayer == players.Count) currentPlayer = 0;
return winner;
}
else
{
currentPlayer++;
if (currentPlayer == players.Count) currentPlayer = 0;
return true;
}
}
else
{
Console.WriteLine("Answer was corrent!!!!");
purses[currentPlayer]++;
Console.WriteLine(players[currentPlayer]
+ " now has "
+ purses[currentPlayer]
+ " Gold Coins.");
bool winner = didPlayerWin();
currentPlayer++;
if (currentPlayer == players.Count) currentPlayer = 0;
return winner;
}
}
public bool wrongAnswer()
{
Console.WriteLine("Question was incorrectly answered");
Console.WriteLine(players[currentPlayer] + " was sent to the penalty box");
inPenaltyBox[currentPlayer] = true;
currentPlayer++;
if (currentPlayer == players.Count) currentPlayer = 0;
return true;
}
private bool didPlayerWin()
{
return !(purses[currentPlayer] == 6);
}
}
Again if you want a repository to excercise your clean code skills, then check out the trivia repository by emilybache.
Magic Numbers:
Especially with numbers, if they are hardcoded in your program (Magic Numbers) you can only assume but never be 100% sure what their actual purpose is.
using System;
class Example
{
static void Main()
{
int numberOfSeconds = 7200;
ConvertToHours(numberOfSeconds);
}
static void ConvertToHours(int seconds)
{
int hours = seconds / 3600; // Using the magic number directly
Console.WriteLine($"The equivalent hours are: {hours} hours");
}
}
Long parameter list:
Why not just pass a for example “Person“ or “Address“-Object which contains those single parameters?
static void DoSomething(string firstName, string middleName, string lastName,
string postalCode, string address, string houseNumber,
int age, ...)
{
//something happens in here
}
Shotgun surgery:
In every method there is a check if amount is less than or equals 500. If this changes now from 500 to lets say 600 then you have to adapt that in every method.
public class Account
{
private String type;
private String accountNumber;
private int amount;
public Account(String type,String accountNumber,int amount)
{
this.amount=amount;
this.type=type;
this.accountNumber=accountNumber;
}
public void debit(int debit) throws Exception
{
if(amount <= 500)
{
throw new Exception("Mininum balance shuold be over 500");
}
amount = amount-debit;
System.out.println("Now amount is" + amount);
}
public void transfer(Account from,Account to,int cerditAmount) throws Exception
{
if(from.amount <= 500)
{
throw new Exception("Mininum balance shuold be over 500");
}
to.amount = amount+cerditAmount;
}
public void sendWarningMessage()
{
if(amount <= 500)
{
System.out.println("amount should be over 500");
}
}
}
Some more information about Shotgun surgery: The Shotgun Surgery Code Smell – DZone
Primitive obsession:
Instead of using many fields with primitive datatypes you can similar to the “long parameter list”-solution introduce objects which contain those fields.
public class Person
{
public Person(string id, string firstName, string lastName, string address, string postcode, string city, string country)
{
// initialisation logic
}
public string Id { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public string Address { get; set; }
public string PostCode { get; set; }
public string City { get; set; }
public string Country { get; set; }
public void ChangeAddress(string address, string postcode, string city, string country)
{
// change address logic
}
}
How can you apply Clean Code?
First off you have to know what the possible problems are that could occur in your code. The code smells chapter should have given you a rough overview already and by visiting pages like refactoring.guru you can dive deeper into the topic.
After knowing what to look for, you got to develop the awareness of always keeping one eye on the code identifying those code smells and when finding them, putting the effort in to find a solution and apply it.
Usually Clean Code is applied by following guidelines and best practices which typically contain suggestions or practices about for example Naming conventions, Code organization, Refactorings, Comments, Code complexity or code duplication.
Helpful in preventing code smells from the get go are some well known and established principles you should basically always follow when writing or reviewing code.
Principles
- DRY (Don’t Repeat Yourself): This principle advocates for eliminating redundancy in code by extracting common functionality into reusable components or functions. It helps in reducing the chances of errors, improving maintainability, and enhancing readability.
- Rule of Three: This rule suggests that when you find yourself writing similar code for the third time, it’s time to refactor and extract that code into a reusable abstraction. It helps in avoiding duplication and ensures that your codebase remains maintainable and scalable.
- KISS (Keep It Simple, Stupid): The KISS principle emphasizes simplicity in design and implementation. It encourages developers to favor simplicity over complexity, as simpler solutions are easier to understand, maintain, and debug.
- Boy Scout Rule: This rule encourages developers to leave the code cleaner than they found it. Every time you touch a piece of code, make a small improvement, whether it’s fixing a bug, refactoring, or improving documentation. Over time, these small improvements add up, leading to a cleaner and more maintainable codebase.
- Root Cause Analysis: Do not fix problems only superficial, always look for the root cause. Otherwise, it will get you again.
- SOLID:
- Single Responsibility Principle (SRP): Each class or module should have only one reason to change, i.e., it should have only one responsibility.
- Open/Closed Principle (OCP): Software entities should be open for extension but closed for modification. It promotes the use of abstraction and polymorphism to achieve flexibility and maintainability.
- Liskov Substitution Principle (LSP): Subtypes should be substitutable for their base types without affecting the correctness of the program. It ensures that inheritance is used correctly to avoid unexpected behavior.
- Interface Segregation Principle (ISP): Clients should not be forced to depend on interfaces they don’t use. It advocates for breaking down interfaces into smaller, more specific ones to avoid unnecessary dependencies.
- Dependency Inversion Principle (DIP): High-level modules should not depend on low-level modules; both should depend on abstractions. It promotes loose coupling and easier maintainability by decoupling modules from concrete implementations.
Why do I write about Clean Code?
When you want to fix a bug, implement new stuff, refactor something or review code but have to spent several hours on trying to understand what the code in front of you does (even if it is written by yourself) and again several hours on finding a solution how to fix the issues because the code follows no transparent structure, then you have to inevitably deal with the topic Clean Code. And in my case it stuck to me and I try to point out flaws in code whenever I find them. Especially the Boy Scout Rule is very dear to me, because if everyone in your company would follow it then code would slow but steady increase in quality and we would not need those “lets clean our code base“-Task forces you might know or heard about.
In conclusion, writing clean code is not just a good practice; it’s essential for building maintainable, scalable, and efficient software systems. By following best practices, principles, and techniques such as those outlined above, developers can elevate their coding skills and contribute to creating high-quality software that stands the test of time.