Clean Code Session 3: Classes, Objects, Errors & Testing


We’re back on the topic of clean code. In the previous articles we expressed our motivation towards writing clean code, as well as gave the first portion of practical advice considering naming, methods, comments and formatting. This time, inside our final clean code session, we are going to have a look over how to make our classes and objects tight and clean, as well as how to purify our code during error handling and test writing.

Fine Classes

We have always been taught – classes are the blueprints for the objects we want to make use of, so it is vital they are finely structured, reader-friendly and precisely declared. First of all, the role of the class (respectively the real-world object, data structure, service or whatever it represents) must be reflected in its name – all naming conventions we discussed so fare are applicable here. Furthermore, a class must, as said for methods here, have a single responsibility and be of small size. Making a class do many things obscures its structure and intent, as well as provides poor integrity. Once again, the same rule as for methods applies here – if a class does many things (and this reflects in it having many instance variables), it has to be split into several sub-classes. In this direction of thinking, what would you prefer to have at home – a cupboard with three drawers each of which has tools of a certain type properly aligned inside, or a big toolbox with fifty tools just thrown inside randomly, making you rustle for ten minutes every time you want to find the hammer? It’s the same with classes – when we say they must be small, we don’t exactly mean small length, but single responsibility.

Considering class structure, it is recommended that we always declare the public static constants at the top, then all class variables, then all instance variables (which are, naturally, private, but can be protected as well – this is useful for your unit tests to have access to the instance variables if necessary) and all public methods, each of them followed by the private methods it calls.

So, that’s about classes, now let’s move on to the…

Well-structured objects

In the previous paragraph I said that the instance variables of a class are naturally private, but this does not apply in 100% of the cases. So, it is important that, for us to have well-structured objects, we need to be able to clearly make the difference between objects and data structures.

Firstly, a data structure is an object represented by a class with public instance variables and no meaningful (or even no) methods. You can imagine data structures as the perfect constructs for mapping a table and its columns from a relational database into our code – that’s where their name actually comes from. The simplest form of data structure is the following:

public class Person {
    public String name;
    public String address;
}

It is called a DTO (data transfer object) and does not have any methods at all – the instance variables holding the data are directly accessible. A more recognizable form of a data structure, on the other hand, is the so-called bean:

public class Person {
    public String name;
    public String address;
    public Person (String name, String address) {
        this.name = name;
        this.address = address:
    }

    public String getName() {
        return name;
    }

    public String setName(String name) {
        this.name = name;
    }

    public String getAddress() {
        return address;
    }

    public String setAddress(String address) {
        this.address = address;
    }

Beans have private instance variables and are supplied with getter and setter methods for these data fields.

Furthermore, objects, from the OO perspective (not the pure programming perspective, since data structures are also objects in the sense of Java) represent things from the real world, have instance variables holding the properties of these things and possess meaningful methods which do certain actions and perform different mechanisms for processing the data held by their properties. Roughly said, in contrast to data structures, objects have real behavior.

What is important to make our objects (in the OO sense) clean is that we encapsulate their data not just by making the instance variables private and provide getters and setters, but achieve encapsulation through hiding their internal mechanisms using abstraction and exposing external methods for managing the data (properties) of these objects. Let’s have a look at the following example – we have a simple data structure like this:

public class Point {
    public double x;
    public double y;
}

We want to turn it to a real object in the OO sense. What we could do to achieve at least minimal abstraction and proper encapsulation is create an interface called Point which requires that our implementation class will provide separate getters for the coordinates, but a single setter method so that the coordinates are always required to be set together. What this means is that, inside the setter, we can have e.g. a given logic for checking the arguments, we can have some number transformations and so on. That’s why the mentioned bean structure is some kind of a mixture between a real object with pure OO behavior and a data structure. Here is how the described Point interface and PointImpl class would look like:

public class PointImpl extends Point {
    private double x;
    private double y;

    double getX() {
        return x;
    }

    double getY() {
        return y;
    }

    //Minimal level of abstraction - coordinates
    //are expected to be set together
    void setCoords(double x, double y) {
        this.x = x;
        this.y = y;
    }
}

It can be said that the PointImpl class hides its data behind abstraction and exposes only methods for operating with this data. This is encapsulation done the right way – through abstraction! Once again, don’t think private instance variables and getters/setters give you real encapsulation – we explained it – this is a bean, a data structure with minimal OO logic inside it.

If we go a little bit deeper on that topic, abstraction can be improved even by being wiser when choosing the name of a given method inside the interface. Let’s have a look at this one:

public interface Vehicle {
    double getFuelTankCapacityInLiters();
}

As you can see, the name of the method that the interface defines somehow hints that, inside its implementation, there may be a logic for getting the exact size of the fuel tank of the vehicle, there may be some mechanisms for transformation of units from gallons into liters, etc. In other words, the name of the method reveals what may stay behind the implementation, making the class internals possibly exposed. However, if we name the method like this…

public interface Vehicle {
    double getPercentOfFuelRemaining();
}

…it is much better, right? You just know you can get the percent of fuel remaining. What happens behind the scenes remains hidden – as it ought to be. So, if a time comes when you are sure your abstraction mechanisms are correct, always think twice – there is always a way to make it better, thus purifying encapsulation.

Code tests

I know – we all feel lazy to write unit tests, but they are really important – they provide the health and freshness of our project. By having a unit test written for every entity (e.g. public method) throughout your code, you guarantee that everyone, after having done modifications to the method, can run the test suite and assert that the method’s behavior has not changed (output data or actions performed are the same as expected). Keeping this in mind, having dirty tests, however, is even worse than having no tests at all. The reason is simple – if you write sloppy tests, with time, as code base accumulates, they will stop functioning and won’t be able pass. You will, consequently, begin to not run them at all. With time, the test suite could become so unmanageable that you will be forced to drop it – the result is that, with no tests at all, you may become fearsome of adding new features to the product. This is the process of “software rotting” (or decaying) and you know – when rotting is complete, the product is dead – it is neither capable of extension with new features, nor is it maintainable anymore. So, always write tests following all rules mentioned so far – test code is equally important to production code. Never write dirty tests or useless tests – just cover all cases and directions in which your method can go (don’t forget the exception cases as well) and you will never have a test suite management problem or a feature decaying.

In general, when writing tests…

  • Each test should have only one assert statement – remember the principle of one and only one responsibility for methods and classes? The same applies here.
  • All tests must be as readable and simplified as possible – don’t do overdesign. Just test what you need to, what is testable.
  • Pay attention to the proper formatting, as well as keep your eyes open for unwanted duplication.
  • Be generous with hardware resources when it comes to tests – production code must be as efficient as possible, but, since tests would not be publically available, why not giving them more power so they can run quickly? And, in this flow of thoughts…
  • Make your tests run fast, because, if they don’t, you won’t have the patience to wait for them and this can be the beginning of the rotting scenario described above.
  • Make your tests be runnable on every environment, any time, no matter if it comes to development, test, production environment or your local machine – you must be able to run a test suite while in the train as well.
  • Make your tests independent from one another – if you don’t, a single failing test may trigger a whole chain of failures, making it harder for you to find the root cause of the problem.
  • Always make tests self-validating – strive to require minimal eye movement on site of the reader.

Some final mash-up

Here are several pieces of advice that I could not find place in any of the groups we already described, but are good to follow. So, to make clean code even cleaner:

  • Always set up your environment so that builds and test suites can be run with a single command-line statement or a single push of a button in the IDE. Otherwise, as already said, you may wreck your nerves and become highly impatient.
  • When writing methods, once again, pay attention to the boundary conditions – corner cases are tricky, so you need to always think twice before you say a method is complete.
  • Always remember to put configurable data at the highest levels of abstraction. Configuration is applicable for the whole project, right? So, it needs to be placed in the highest levels of hierarchy.
  • Never override or turn off the built-in safety features of the programming language you are using. I know, this could make the whole process of testing and so on easier for you, but you may forget to turn these features on again before deploying the code to production. The consequences can be catastrophic, I believe that’s pretty clear.
  • Never leave dead code – if a method is never called, delete it. If a class is not instantiated – remove it. Dead code brings worse readability and clutter, so it is to be removed.
  • A well-known advice, but just for recap – don’t use magic numbers, create constants for them. In the same time, however, don’t use constants for well-known values such as PI. Especially for PI, always make use of Math.PI or its equivalent for the language you are using, because it is way more precise than what you can assign manually. Furthermore, one wrong digit in your assignment will make your calculations wrong.
  • Don’t use negative conditionals – always be positive! if(isActive()) is much more readable and understandable than if(!isNotActive()).

 

That’s it for now, folks! In a total of three articles we summarized how we can make our code clean, concise, well-structured and beautiful. Once again, please pay attention to your code – it is what makes your programs work. To your application the code entities are the same as the cells to your body – structural, building parts. So, never leave code cleaning for tomorrow, do it now!

 


If you find yourself looking for extended information considering clean code, it is recommended that you have a deeper look over the following book, which has served as one of the main motivational sources for summing up the information given above: Clean Code: A Handbook of Agile Software Craftsmanship, Robert C. Martin, ISBN-13: 978-0132350884.

Alex

Leave a comment

Your email address will not be published. Required fields are marked *