I work on a code base which was started by a small team. Over time, more features were added, and the team has grown significantly. Everyone has added functionality here and there. There is a rather central struct which is used to keep track of intermediate results as it is processed through the system. It started out with just a handful of fields, but now it has a mind-boggling number of extra fields for special cases in added features. The fields are not encapsulated at this point, various functions change the state of that object.
The sheer size of this struct prevents me from reason about it and the associated functions completely. There are lots of things where function f()
sets fields a
to some value, and function g()
takes field a
and sets field b
if flag c
is enabled. I can understand these things locally, but I lack a detailed big-picture view, which I would need for this. So when I need to make changes, I try to find some spot to put in another thing without having to change anything else. This only leads to an incremental build-up of even more complexity.
From what I read [1] the code should rather be in a state where there is less coupling and more cohesion, clearly defined components, encapsulated fields and enforced invariants. My problem is that I don't see how one could get there. The code has grown into a complexity that I cannot fully understand. And I have the impression that my coworkers feel the same. I fear that nobody really understands this complexity, but that the current state was a group effort of local changes. Due to this nature, the code feels rather brittle and cannot be tested by the construction. So whatever one changes might break something without realizing it at first.
This qualifies for Feather's definition of legacy code [2]: code without tests. And one cannot simply introduce tests into a code base which has evolved from a monolithic prototype. Rather one has to break out little pieces, make them testable and continue doing that. This will be a significant effort, and it might still break some things as one goes. But I feel that I don't really know where to start, because there is just so much coupling and so little structure. Also it is still possible to add more features by tacking on even more complexity and ignoring everything else. Yet I know that this problem will only get worse with time going forward.
I find myself in the situation where I think that reducing the complexity is necessary to stay productive in the future. But there is just so much complexity accumulated already, that I don't know where to start. Also there are different perceptions to the urgency of this problem. When I add new features I try to build them of small pieces and integrate them into the main code, but often I find myself bogged down by the surrounding complexity. For every single new feature I can just tack it on and be done within a few hours, or start to refactor everything which will take months. So the decision seems to be made to just tack it on for now.
Whenever such a decision is made, I voice my concerns but then follow the decision. Is there anything more I can do than just point out the looming problems and wait for the team as a whole to decide on an effort to reduce complexity?
- Martin, R. C. Clean Architecture: A Craftsman’s Guide to Software Structure and Design. (2017).
- Feathers, M. Working Effectively with Legacy Code. (2004).