When to Abstract?
First published at Tuesday, 17 May 2016
Warning: This blog post is more then 7 years old – read and use with care.
When to Abstract?
One of the most difficult challenges in a developers life is finding the "right" abstraction, or at least the best one given the current circumstances. The core problem is that abstraction is a bet on the future development of the software and we know that future is volatile. The circumstances will change, so will the view on the best abstraction change.
But there is another dimension which influences this decision: What kind of software are you developing? When it comes to decisions about Object Oriented Design we separate between three different types of projects:
It is just used by you and your co-workers. You are working in a small team. When you are changing an API you can adapt all code using this API easily – especially if you are using a Monorepo approach.
There is a common entry point for your library, an interface which everybody is using. This could be everything from a popular Open Source library to a company internal library used in multiple teams or projects. Changing the API is already a no-go because it was develop to fulfill the use-case of the library. You cannot see who is using this API and what code will break. The only chance is a new major release for each change to this API.
There are generic software solutions out there which will be adapted by agencies and developers like Wordpress, Magento or similar products. Users of those products will use any API in the source code of those products to change the behaviour of the system. You cannot change any API without breaking someones application.
So how does this influence the decision about abstracting your code? In the first case (Internal Project) you can delay abstracting code pretty far while in the last case (Adaptable Product) you should get it right from the very beginning – since you shouldn't change anything afterwards.
But, in an Adaptable Product and in Libraries your use case is usually well defined, thus you know what the interface looks and you can think about it, analyze it and come up with a sane abstraction. One could even say: You define the use case with your API, thus your abstraction is fine by definition. Your library and product will be used in other use cases as well, which you did not consider when writing the software, but it usually less of an issue to adapt to that later.
Authors of those libraries and software products are often well known and talk about their code in public. Also this is the code you, as a developer, will see when learning and trying to improve.
But especially in the area of Internal Projects the circumstances change often and fast. You will have no idea what the next requirement of the project stakeholders will be. So, let's be blatant: Do not abstract! Do not use interfaces or abstract classes.
OK, sometimes you still should use them. But at least wait until the requirements get obvious. I, by now, suggest a process like the following:
Implement the current requirement in a concrete class
Adapt the concrete class to the ever changing requirements
If a different implementation for the same use case is required create an abstraction, but not earlier
Example: If you are supposed to integrate with some newsletter provider just implement the code in a concrete class. When starting, I assure you, not all final requirements are provided. There will be some additional requirements even before the first release of this small part of your application. If you came up with an abstraction first you'll have to adapt the abstraction and the concrete class. Also both will be more complicated since you already thought about making this somehow generic. Usually with requirements which will not show up because the other stakeholders will think about different things then you do.
Only when the second and third newsletter provider shows up and you are supposed to connect them with the same classes create an abstraction. Since we are a lot later in the project you are now better aware of all requirements and have a better understanding for the actual use-cases and differences between the endpoints.
Trying to follow "best practices" from library development in other projects will lead to wrong abstractions and increased workload. Do not abstract before you are sure about the actual requirements and before there is a need to do so. Concrete classes without interfaces are fine in Internal Projects, while they are not in Libraries or Adaptable Products.