The Importance of Good Interfaces

by Szymon LipiƄski

A Short Introduction To A Good Interface

When you are using a class, a function, or a library, you have use its interface. Interface is the set of public knobs and switches which you can use. Usually the interface for most of the libraries, functions, and classes has one common feature: it irritates programmers.

The Main Pain Points When Using an Interface

The Great Interface

On the other hand, what can be the main good points for making a great interface?

In this post I will concentrate only on the incompatibility problem, which can be avoided very often, just because you think about it a little bit earlier.

Thinking and Experience Isn’t Overrated

I know, there is the great quote repeated endlessly by the programmers who haven’t read the book:

premature optimization is the root of all evil (or at least most of it) in programming

However if you would read that book you would find some wider context. You can also use wikiquotes if you wish.

The real problem is that programmers have spent far too much time worrying about efficiency in the wrong places and at the wrong times; premature optimization is the root of all evil (or at least most of it) in programming.
We should forget about small efficiencies, say about 97% of the time: premature optimization is the root of all evil. Yet we should not pass up our opportunities in that critical 3%.

An Example

So let’s imagine quite a simple example. There is a requirement to process data for rectangles. We have lots of ways to implement it, for now let’s assume that we need a class for this rectangle.

So the basic interface should be something that would give us data like:

width
height

There will be some additional requirements added later, as usually. Usually when you are quite happy with your interface, there will come some production guy and will destroy everything.

Simple Java Implementation

Let’s talk about Java for w while, before going to Python. In Java the situation is simple, the basic class can look like this:

public class Rectangle {
    public int width;
    public int height;

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

Rectangle r = new Rectangle(10, 20);

So we have two public fields. Those Java programmers who just prematurely optimized that code in their minds could notice that this class is bad. But you remember that the premature optimization is bad, don’t you? OK, jokes aside.

So why is this class not good enough? Well, because sometimes you need to change the internal representation of those fields, or make something lazy (so calculate something when it is needed), or cache something. And you want to do all the things without changing the interface.

Use C# Properties

What should I do if I want to leave those fields as ints and store them internally as strings? Unfortunately Java doesn’t have properties. Property is a nice mechanism of saying, that if you access this field, in fact you call my internal function.

In C# the properties can look like this:


class Rectangle
{
    private string width_;
    private string height_;
    
    public Rectangle(int width, int height)
    {
        this.width_ = width;
        this.height_ = height;
    }

    public int width
    {
        get { return width_.ToString(); }
        set { width_ = Int32.Parse(value); }
    }

    public int height
    {
        get { return height_.ToString(); }
        set { height_ = Int32.Parse(value); }
    }
}

Rectangle r = new Rectangle(10, 20);

And now I can read the width with:

r.width;

but I can also set it with:

r.width = 20;

And this doesn’t change the interface, I still see it as two public variables and there is some logic in the class, which is fine, because it is hidden and I don’t care about the hidden things.

Properties in Java

In Java there is another mechanism, that’s quite ugly, but this is the only thing I know which gives us the possibility of changing the implementation without changing the interface: getters and setters.

The good Java practice is to write the getters and the setters from the very beginning, so the Rectangle class will look like this:

public class Rectangle {
    private int width;
    private int height;

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

    public int getWidth() {
        return this.width;
    }

    public int getHeight() {
        return this.height;
    }
}

This means that that the public interface is as simple as:


constructor:

  (int, int)

getters:

  getWidth()  -> int
  getHeight() -> int


As you can notice, the interface is not that bad:

The First Change

So when you have this kind of an interface and you are happy with that, someone comes and changes the requirements. Now this class must also calculate the area. The area of a rectangle is simply width * height, so the obvious way to implement it would be:

public class Rectangle {
    private int width;
    private int height;

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

    public int getWidth() {
        return this.width;
    }

    public int getHeight() {
        return this.height;
    }

    public int getArea() {
        return width * height;
    }
}

This looks fine, unless we have more detailed requirements:

What if the class is mutable (so the object can change after creating)? In this case nothing. The area function still works. Maybe you noticed that there is no field named area. The private fields should not influence the public interface. The interface is public and those who use it, shouldn’t know the private implementation.

The Immutable Class

What if the class is immutable? Then we can calculate the area in the constructor, because the immutability means that the object cannot change after creating.

public class Rectangle {
    private int width;
    private int height;
    private int area;

    public Rectangle(int width, int height){
        this.width = width;
        this.height = height;
        this.area = width * height;
    }

    public int getWidth() {
        return this.width;
    }

    public int getHeight() {
        return this.height;
    }

    public int getArea() {
        return this.area;
    }
}

Expensive Calculations

What if calculating the area would be something more expensive, so we want this to be lazy? This means that we want to calculate it only when the getArea() function is called. This way we should change it to something like:

public class Rectangle {
    private int width;
    private int height;
    private Integer area;

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

    public int getWidth() {
        return this.width;
    }

    public int getHeight() {
        return this.height;
    }

    public int getArea() {
        if (this.area == null) {
            this.area = this.width * this.height;
        }
        return this.area;
    }
}

As you can notice, I have changed the type of the area field from int to Integer. The problems is that I want to have an uninitialized variable. The type int is just a normal 32 bit number while the type Integer is an object containing a 32 bit number and this can also be null, which simply means that there is no value set in this variable.

In the getArea() function I check if the area field is not initialized. If it is initialized, I just return the calculated area. If it is not initialized, then I calculate the area, store it in the field, and return. The next time someone calls this function on this object, it will just return the number without calculating anything.

I hope you have already noticed that all the versions of the getArea() function were not changing the interface. The laziness is a very nice optimization add-on, and the code using this class will not break if it will be lazy or not.

Mutable Expensive Calculations

The mutable version is harder, as lots of things can change. Basically I will add setters and in each setter I need to clear the area object, so it will be recalculated when the dimensions changed, of course if they changed.

public class Rectangle {
    private int width;
    private int height;
    private Integer area;

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

    public int getWidth() {
        return this.width;
    }

    public int getHeight() {
        return this.height;
    }

    public int getArea() {
        if (this.area == null) {
            this.area = this.width * this.height;
        }
        return this.area;
    }

    public void setWidth(int width) {
        if (this.width != width) {
            this.width = width;
            this.area = null;
        }
    }

    public void setHeight(int height) {
        if (this.height != height) {
            this.height = height;
            this.area = null;
        }    }

}

This change also doesn’t break the code which uses this class. There are just two new functions.

Another Change

So the final requirement: just ensure that the width and the height are not smaller than 0. For the mutable class it will be:

public class Rectangle {
    private int width;
    private int height;
    private Integer area;
    
    private void validateWidth(int width) {
    	if (width < 0) {
    		throw new IllegalArgumentException(
                    "Width cannot be smaller than 0.");
    	}
    }
    
    private void validateHeight(int height) {
    	if (height < 0) {
    		throw new IllegalArgumentException(
                    "Height cannot be smaller than 0.");
    	}
    }

    public Rectangle(int width, int height){
    	this.setWidth(width);
    	this.setHeight(height);
    }

    public int getWidth() {
        return this.width;
    }

    public int getHeight() {
        return this.height;
    }

    public int getArea() {
        if (this.area == null) {
            this.area = this.width * this.height;
        }
        return this.area;
    }

    public void setWidth(int width) {
    	validateWidth(width);
        if (this.width != width) {
            this.width = width;
            this.area = null;
        }
    }

    public void setHeight(int height) {
    	validateHeight(height);
        if (this.height != height) {
            this.height = height;
            this.area = null;
        }    }

}

One more time the interface didn’t change, but the class behaviour changed. This is what we get when we have a good interface.