“You can solve every problem with another level of indirection” they say. But don’t be fooled! Full quote goes something like: “You can solve every problem with another level of indirection, except for the problem of too many levels of indirection”. And keep that in mind when writing your code.
I’m currently working on refactoring quite important piece of code in big, complex project. Fun thing to do, I enjoy it tremendously. But I’m new to the project and I’m lost in the code. Thankfully I got some sequence diagrams and sequence diagram along with long explanation what this part of system does and why. Then I looked at the code, trying to debug it few times to know where I am and what I’m working with.
What I saw there looked nothing like the diagrams I got. After few hours (days?) I could find some relation between code and description I received but it was way to hard. Even though the system is complex and functionality of this part is not exactly piece of cake, the whole thing had so much code and so many different layers that it is sill nearly impossible for me to tell where am I and what the code does in this place.
Interfaces are cool. Extracting functionality to small, well defined classes is what I like. But once you start jumping between five or six different classes to complete one small action you start feeling that something is definitely not right. At the moment I have opened something like 20 or 30 different classes to go through the workflow, and this is just preparation for actual calculations happening in different place (thankfully this is the part I’m not going to touch this time).
It may feel better to extract class from one piece of code. But I’ve noticed that the same type,
Report in this case, is represented by four or five different classes with slightly different naming and functionality. But it is still exactly the same report in exactly the same system! Why not just settle for one, well defined object? Need to get something from database – jump between three or four objects before you will get to actual query.
Flatten your abstractions. Extract classes where needed, but don’t hide everything behind interfaces when not needed. Those are your domain objects, there’s no harm in your main object knowing about exact implementation of one of sub objects, those two are tightly coupled anyway. Sometimes we need to get our hands dirty in all those pesky details. That’s OK, it needs to be done. Extract methods to make it easier to understand. Make code short and concise. But fight the urge to extract every single detail into new class hidden behind interface because it needs to be testable. That’s not how tests are supposed to look most of the time.
So when to abstract things into interface, different class, different layer altogether? I’m not sure myself. Right now I’m just following my guts, making mistakes in fixing them in future when I see that something is not right. One day, I believe, I will master this to create perfect code, but not today and not tomorrow. But I’m getting there, you can be sure!