A Quick Guide to Clean Code
Clean Code - Image Credits: DALL-E
As programmers, we know that the messes we make in the code will come back and haunt us. And yet, we make messes in order to deliver fast and meet deadlines. On the contrary, the only way to go fast is to keep the code clean.
The Art of Clean Code
To master the art of clean code, a sense of "cleanliness" should be acquired. The following set of steps will help the programmer to gain a "code-sense" and look at a messy code and see options and variations to make it cleaner.
Meaningful Names
There are some simple rules that can be followed for naming variables, functions, classes and packages.
Use Intention-Revealing Names
Choose a name for a variable, function or class that tells why it exists, what it does and how it is used. See the following example.
int d; // elapsed time in days
This variable does not reveal anything about its purpose. Instead use a more meaningful name like follows.
int elapsedTimeInDays;
Notice how it reveals its intent and makes it easy to understand and change the code.
Avoid Disinformation
Consider an example of a group of accounts.
accountList;
This does not necessarily mean that the data structure used is a list. So, it can be disinformative. Instead use the following.
accounts;
Use Pronouncable Names
It makes easier to read when the names are pronounceable.
private Date genymdhms;
This variable contains a generation timestamp in year, month, date, hour, minute, second format. It would be easier if it was like below.
private Date generationTimestamp;
Class Names
Classes and objects should have noun or noun phrase names.
Customer, Account, AddressParser
Method Names
Methods should have verb or ver phrase names
getName(); deletePage();
Use Solution or Problem Domain Names
Since the people that read your code are also programmers it is better to give solution domain (Computer Science) names than giving problem domain names. When there is no approprate solution domain names available, you can use problem domain names. The code that is more related to the problem domain should have names from the problem domain.
Functions
Keep the functions shorter. Optimally, it should not be more than 3 or 4 lines. Use method extraction and refactoring to keep the functions shorter. Also, it is better if the indent level of a function is not greater than 2.
Do One Thing
Functions should do one thing. they should do it well. An easy way to check whether a function is doing more than one thing is if you can extract another function from it with a name that is not merely a restatement of its implementation. Make sure the statements within the function are at the same level of abstraction. Consider the following code snippet of a ReST controller.
CustomerService.getCustomer(customerId);
ItemRepository.getItem(itemId); // Wrong
Only the service layer should be accessed from a controller.
Switch Statements
It is hard to make a switch statement small and also hard to make it do one thing. So, if it is unavoidable to use a switch statement, make sure it is in a very low level of abstraction. Use polymorphism to achieve this.
public Money calculatePay(Employee e)
throws InvalidEmployeeType {
switch (e.type) {
case COMMISSIONED:
return calculateCommissionedPay(e);
case HOURLY:
return calculateHourlyPay(e);
case SALARIED:
return calculateSalariedPay(e);
default:
throw new InvalidEmployeeType(e.type);
}
}
Above example violates both Single Responsibility Principle and Open/Closed Principle. This can be avoided by moving the switch statement to an Abstract Factory which will create instances of various derivatives of Employee.
Function Arguments
More than three arguments for a function needs a special justification. The readers of your code would have to interpret those arguments each time they saw it. It is even harder for testing since a lot of test cases will be needed to cover various combinations of the arguments. It is ideal if functions contain no arguments at all. When a function needs more than two or three arguments, in most of the cases those arguments can be wrapped into one object.
Circle makeCircle(double x, double y, double radius);
Circle makeCircle(Point center, double radius);
In this example x and y arguments are coordinates of a point. So they deserve to be an object of their own.
Flag Arguments
Flag arguments are boolean arguments that are passed into functions that change the behavior of the function based on that argument. This violates the Single Responsibility Principle. So, avoid flag arguments as much as possible.
Have No Side Effects
Side effects happen when your function promises to do one thing but also does some other hidden things.
public class UserValidator {
private Cryptographer cryptographer;
public boolean checkPassword(String userName, String password) {
User user = UserGateway.findByName(userName);
if (user != User.NULL) {
String codedPhrase = user.getPhraseEncodedByPassword();
String phrase = cryptographer.decrypt(codedPhrase, password);
if ("Valid Password".equals(phrase)) {
Session.initialize();
return true;
}
}
return false;
}
}
The side effect here is Session.initialize(). This creates a temporal coupling where checkPassword() can only be called when it is safe to initialize a session. This violates the "Do one thing" mentioned earlier. In an unavoidable case, at least mention the coupling in the method name.
Prefer Exception to Returning Error Codes
Returning error codes from a command function can lead to deeply nested structures. If exceptions are used instead of returning error codes, the error processing can be separated from the happy path. Also, it is better to extract bodies of try and catch blocks to separate functions.
public void delete(Page page) {
try {
deletePageAndAllReferences(page);
}
catch (Exception e) {
logError(e);
}
}
Don't Repeat Yourself
Duplication bloats the code. Many principles and practices have been introduced to control or eliminate it. Codd's database normal forms, Object Oriented Programming and Structured Programming are among them.
Comments
Do not add comments for the sake of explaining a messy code. The better way is to clean the code and explain yourself in the code.
// Check to see if the employee is eligible for full benefits
if ((employee.flags & HOURLY_FLAG) &&
(employee.age > 65))
Above comments can be easily summed up in the code as follows.
if (employee.isEligibleForFullBenefits())
Sometimes corporate coding standards require some copyright and law related comments. This is unavoidable. Some IDEs collapse these comments automatically.
These are some of the major things I came across in Clean Code. Hope you learned something.
"Master programmers think of systems as stories to be told rather than programs to be written." - Robert C. Martin
References
-
R. C. Martin, Clean code a handbook of agile software craftsmanship.