Some tips to improve your code quality

Nazim Benbourahla
6 min readJan 31, 2018

Each software engineer should be obsessed with the quality of their code and should always seek to improve it.

This is because quality code:
- is more maintainable.
- lets you add and update features more easily.
- makes the app more stable.
- makes the app more testable

In this article I will share some of my personal guidelines. This is not meant to be an exhaustive list of quality tips, so don’t hesitate to share yours in the comments section.

Single Responsability Principle

This principle states that every module of your code base must have only one reason to change.

Imagine you have the following Employee class

public class Employee {
private final String firstName;
private final String lastName;
private final String phoneNumber;

public Employee(String firstName, String lastName, String phoneNumber) {
this.firstName = firstName;
this.lastName = lastName;
this.phoneNumber = phoneNumber;
}

public Pay calculatePay() {

}

public TimeOff calculateEmployeeTimeOff() {

}
}

There is an issue with this class, can you spot it ?

This class has many reasons to change and many responsibilities.

  • Employee creation.
  • Pay and time off calculation.

This class must be simplified and updated to have only one responsibility and only one reason to change.

Your Employee class must just handle Employee creation, the pay and time off calculation must be handled in dedicated classes.

Don’t have strong dependencies with libraries

Strong dependencies are a common issue in code base. This happen when changing a dependency leads to changing a lot of modules in your code base.

To illustrate, imagine you need a libraries to download images (for example Picasso or Glide for Android).

If you directly use these libraries in your code, you will have a big dependency between them and your code. Because when this library is no longer maintained and you have to use another one then each module using the library will be impacted. That is a huge effort.

The most common solution is to create a wrapper class that encapsulate this dependency.

If we continue with our image loader library dependency, you could create ImageLoader class that provides an interface to use the library. This pattern provides the following benefits:

  • If we need to change the library, we will only change it in one place (ImageLoader class)
  • We can easily test our ImageLoader class.
  • We can provide more context comments and explain how to use our ImageLoader API and hide some unused API dependencies.

Open / Closed principle

This principle says that “software entities like classes, modules, functions should be open for extension but closed for modification”, which means that an entity doesn’t have to change each time requirement changes.

There is a famous example to illustrate this principle. Imagine, you have this Rectangle class.

public class Rectangle {
private final float width;
private final float height;

public Rectangle(final float width, final float height) {
this.width = width;
this.height = height;
}
}

And another class to calculate the area of the rectangle

public class AreaCalculator {
public double calculateArea(final Rectangle rectangle) {
return rectangle.getWidth() * rectangle.getHeight();
}
}

Imagine now, that the requirement changes and you need also to calculate the area of a circle. You will need to change the code of the AreaCalculator class to respond to this new requirement.

public class AreaCalculator {
public double calculateArea(final Object shape) {
double areaValue = 0.0;
if (area instanceof Rectangle) {
final Rectangle rectangle = (Rectangle) shape;
areaValue = rectangle.getWidth() * rectangle.getHeight();
} else if (area instanceof Circle) {
final Circle circle = (Circle) shape;
areaValue = circle.getRadius() * circle.getRadius() * Math.PI;
}
return areaValue;
}
}

Do you spot the issue you will face in the previous code?

If you add new shapes, the calculateArea method will add new conditions. This code will become a big bloc with many conditions and will not be easy to maintain and modify.

Here is a better approach that respects the open / closed principe:

public abstract class Shape {
public abstract double area();
}

First, we will have an abstract class that can calculate the area of a shape. Then our Rectangle and Circle class will extend this abstract class.

public class Circle extends Shape {
private final float radius;

public Circle(float radius) {
this.radius = radius;
}

public float getRadius() {
return radius;
}

@Override
public double area() {
return radius * radius * Math.PI;;
}
}

And to finish our areaCalculator class will become more and more simple:

public class AreaCalculator {
public double calculateArea(final Shape shape) {
return shape.area();
}
}

You can notice now that creating a new shape doesn’t require updating the AreaCalculator class. So, when you are writing code, think about what happens when the requirements change. Is my code simple to update / refactor and make it match the new requirements.

Choose wisely class, methods and variables names

When you are writing code, you will have to name your classes, packages, functions, variables … There is some tips to make naming easier. Don’t hesitate to rename them if you find better one.

  • A name should tell you why this component (class, variable, function …) exists. Avoid names that don’t mean anything. If you see the declaration below in a code, you will probably ask yourself many questions: what does this variable do? Can I update it? What role does this variable play?. You will be forced to inspect the rest of the code to understand the intent of this variable.
private int d;
  • A name should be explicit and and shouldn’t hide a behaviour to the user of the API. For example, in the code bellow, an API user will call the “isExistingUser” to check if the user exists in our database. But he will never spot the side effect that logs an existing user. So, the API user will maybe call loginUser method after calling isExistingUser method. The method loginUser will be called twice. So never hide the behavior of a method. Choose explicit names and document your code., choose an explicit name and well comment your code.
public boolean isExistingUser(@NonNull final String userName, @NonNull final String password) {
if (!userName.isEmpty() || !password.isEmpty())
return false;
final User user = userRepository.findUserByName(userName);
if (user != null) {
userRepository.loginUser(userName, password);
return true;
}
return false;
}

Comment your code

A well placed comment is a great improvment in a code base, it saves time and makes the code review easier..

Comments explain all the behaviour of a code, exceptions that can be raised and it is essential when you design any API.

The idea is mainly to comment all exposed (public) modules in order to build a behavioral documentation for your library users.

Test, test and test

Testing is one of the most essential part to improve code quality. They help catch regressions as early as possible. Tests are very important, it saves you a lot of debuging time.

To start, you can give some mandatory rules for unit test:

  • Test all public / exposed methods.
  • Test all code that contains business logic.
  • Test all edge and limit cases.

With these minimal rules, you will have a good base for testing your code base. After that you can improve your coverage each time and adding new rules to the list before.

Pull Requests should not be taken lightly

A Pull Request is a strong tool to improve code quality and they shouldn’t be taken lightly. A well done review, will help you to spot all issues that we mentioned before in this article.

Spending quality time reviewing a PR is always a good investment and will help reduce tech debt from crippling the code base.

When you review a pull request don’t hesitate to checkout the code base, and make the review directly inside your IDE. This will help to easily spot dependencies between classes, the hierarchies of the code and will help you spot warnings or errors.

Conclusion

We (better) should follow some rules. They are not there to bother you but to :

  • Raise your code base quality code.
  • Prevent you from loosing time trying to understand the code base.
  • Reduce time needed to improve / update your code base.
  • Reduce time needed to debug issues.

Don’t hesitate to share some of your personal or team tips / rules.

--

--