Author: Brandon Pearman

The views expressed here are mine alone and do not reflect the view of my employer.

Adding extra layers of abstraction to your system often solves many problems, mainly coupling. The downside of abstraction is that it can lead to unclear code if it is overused. I know this because I love abstraction and would usually be advising you to abstract further, but I have also created some overly-complex systems due to it.

A lot of overengineering has been justified in the name of flexibility. But more often than not, excessive layers of abstraction and indirection get in the way. Look at the design of software that really empowers the people who handle it; you will usually see something simple. Simple is not easy.
"A lot of overengineering has been justified in the name of flexibility. But more often than not, excessive layers of abstraction and indirection get in the way. Look at the design of software that really empowers the people who handle it; you will usually see something simple."

-Domain Driven Design By Eric Evans

To understand how abstraction can increase complexity, let’s look at the pseudo-code below:

Abstraction complexity example

This example is just to illustrate the point, and is not an example of correct code. To give a real world example would be too complex and difficult to understand.

function getBooks()
{
    return bookService.getBooks();
}

The above function tells you that it is getting books, but not from where or how. It lacks a lot of context which means that a dev will have to dive in deeper to understand what it actually does. This is not necessarily a problem if digging deeper is clear and simple, and we should abstract a lot of the details to stay in line with SOLID principles. But, in order to maintain some degree of simplicity, there is a fine balance to keep in mind when abstracting. The design may be a little too complex if a dev has to spend 10 minutes digging through a big map of abstraction layers and inheritance hierarchies to find how it all works.

Let’s contrast the above code block with the below:

[Route("GET", "api/books")]
function getBooks()
{
    using(var dbConnection = getDbConnection("localhost"))
    {
        var books = dbConnection.query("select * from books").execute();
        books = filterOutInvalidBooks(books);
        return {statusCode:200, body: books };
    }
}

The above code is just to illustrate the point and is not a suggestion for actual code. As a contrived example it is easy for any dev to know:

Many experienced developers would argue that heavy abstraction is crucial for reusability, while others argue for very little abstraction for the sake of readability. Whichever side you're on you have to realize that abstraction does increase complexity.

Abstraction is a double edged sword which gives you reusability but also adds complexity. The trick is to find useful abstractions. I strive to abstract code where I need the advantages of more maintainable and testable code, while keeping the simplicity where possible with clear verbose code. There is no definite answer on exactly how much abstraction is too much, it is something which comes with a deep understanding of abstractions.

"If you don't have a good abstraction, then don't use any abstract."

-Eric Evans

When concepts are not clear trying to abstract them cause massive complexity and confusion. For there scenarios Eric Evans suggests avoiding abstractions and to improve clarity.

Check out these links for more info:

My design and architecture repo