Monday, June 6, 2016

Thinking about abstractions

Begin by coding abstractions for the concepts of your application before you write the application itself.

When writing a Object Oriented program, your unit of abstraction is an object. This is important, although fairly straightforward. On every paradigm, what rules it is its unit of abstraction: on procedural languages, the abstraction is a procedure; on functional languages, the main abstraction are functions; And, if I may say, on LISP-style languages, the main abstraction are lists.

Abstraction are very important to computer development. A computer is made of such, otherwise, it would be impossible to deal with all the complexity that it englobes. It's something very particular to the field of Software Development. A civil engineer thinks of bricks and mortar. A doctor does think about cells, organs, and other basic stuff that make up our body. (of course, a quark is the most non-abstract way for thinking about anything, and if a doctor thinks of organs, he's not thinking of cells individually -- still, organs abstract over very little when thinking about a living organism).

A computer is simply a machine that runs instructions written on a memory one-by-one, so it's formed by three basic components, the memory which is a "string" of bytes, a program counter that points to a location at this memory, and the CPU which executes the instruction at the location where the program counter is pointing to. Given this, we could program anything ever, but the complexity would be unmanageable.

So we come up with abstractions. Instead of memory locations, we have variables, pointers maybe or references. Instead of a program counter, we have lines on a text editor.

Back to OOP, as soon as you can realize a abstraction is part of your program, you should create a class for it. No abstraction is smaller than a class in OOP; A bunch of methods loose within some other class aren't. However, you can build abstractions from other abstractions, so objects can be made of other objects.

An example: let's abstract over a INI file:

class IniFile
    public IniFile(string fileName)
        // ... open a stream, and read the file if it exits
    public string Get(string keyName)
        // returns a value based on the key or null
    public string Put(string keyName, string value)
        // Store a key/value pair
    public void Save()
        // Write the file back to the disk
Now, this is a simple example; We can build a better abstraction than that using other language features, but it doesn't matter. The main benefit of the abstraction has been leveraged: clients of this code don't have to think about how the IniFile works:
class Configurator
{     private const string _properties = @".\config.ini";       public Configuration GetConfiguration()     {         var ini = new IniFile(_properties);         var url = ini.Get("URL");               return new Configuration { Url = url; }     } }

Let me rephrase all that:
The main benefit of using proper abstractions is that you can focus your thoughts. When coding the abstraction itself, you do not need to consider the rest of the application, only the abstraction (the IniFile above cares not if it's part of a Instant Messaging app, a game, or a scientific experiment). When coding the clients of the abstraction, you do not need to care how it works internally -- abstractions are opaque -- so you can focus on whatever the client needs to do.
If for some reason your clients have to look inside your abstraction, violating it's principle, we have a leak abstraction and it should be reviewed.

When designing your abstractions an important factor to consider is coupling. Whenever an abstraction depends (uses!) another, it's coupled to it. So when the depended abstraction changes, the depending abstraction also changes. So it's important to reduce your dependency graph on your abstractions. The most common abstractions should depend on nothing but frameworks libs, which are your building blocks.

(however, coupling is, ultimately, inevitable; our responsibility is to make our couplings loose instead of tight)

Finally, abstractions can backfire. Abstract too much and your program gets difficult to understand. Also, you may create a lot of couplings that, loose or not, make your program difficult to evolve and maintain.