The Importance of Good Interfaces

Author: Szymon Lipiński
Published at: 2017-01-18

A Short Introduction To A Good Interface

When you are using some class, function, 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 Pains 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

Yea, I know, there is the great quote repeated endlessly by the programmers who cannot read:

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

If you can read, take that book, and check what is written a couple of pages later. Or use wikiquotes if you wish.

This is not about not optimizing the things.
This is about knowing when to optimize to get the biggest impact on the overall efficiency.

The 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 mind could notice that this class is bad. Hey, remember, that’s just premature optimization, don’t be fool :) 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 change 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 the 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? 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 it is not a number.

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, and 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.

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.

Summary

My idea was to write why I think the Python’s requests library interface is bad. However going deeper, and deeper into the examples it was going longer, and longer. So, that’s all for now: an example of good practice in interface changes. About Python will be next time.

The comments are disabled. If you want to write something to me, you can use e.g. Twitter.