I’ve moved this from Stack Overflow and this was the suggested place to ask. This has been marked as a possible duplicate of a question about the merits of composition over inheritance. I’m not asking that question, I’m looking for strategies to manage development given the code we have.
The context of the question is a set of applications written in C++ with Qt. We work in a pseudo agile manner, i.e. we have 2 week sprints but don’t have frequent releases.
They are all variations of a theme but each modifies the one before it.
This is not an ideal scenario but what I have to work with.
So application A uses Alib. application B uses Blib and Alib; most of the classes in Blib are descendents of those in Alib. Similarly application C uses Alib Blib and Clib, which subclasses from Blib primarily but also occasionally Alib.
We use CVS and it is a proper pain in the neck to branch our builds as the MSVC projects never merge correctly.
What currently happens is a developer working on Alib can cause unforeseen issues in both Blib and Clib unless we adopt some strategy for review or rules on development.
We are under a lot of time pressure and don’t currently have the time to reorganise the code, so I am looking for things we can do within the current framework.
If we were using git it would be easier to branch the code for each dev but again we’re not currently there just yet (it is in the pipeline).
I’m thinking along the lines of requiring all new developments which alter (as opposed to add) functionality in ALib to subclass that behaviour.
Other options I’ve read about include “fail often” i.e. being brash about committing/changing the stuff in Alib and make sure that Blib and Clib are review often enough to keep up with the changes.
Similar to this is frequent code reviewing (one developer (me) has good knowledge of all three libs so should be able to spot potential downstream effects early), which begs the question pre or post commit.
Edit: Thanks for the broad range of solutions, I wish I could mark a couple of these as answers, but have gone with John W’us answer. He’s articulated my dilemma quite well in the third point – trying to reduce repeating ourselves (i.e. don’t violate DRY) while having some isolation for each lib. Ultimately a code refactor is in order, but more (some!) unit tests should highlight broken code. One issue we’ve had is that the developer in Alib would see their changes affecting Blib and Clib and go and change those in ways the developers of Blib and Clib didn’t exactly appreciate. So a rule about keeping code closed (extend but don’t modify) is also very pertinent.
Three options for you
Follow the Open/Closed Principle
From a code and process management perspective, getting a teamwide agreement to adhere to the Open/Closed Principle should help resolve the problem.
If you are able to get everyone to follow this principle pretty strictly then it may be all that you need to do.
Continuous integration builds with automated unit tests
Develop an automated continuous build process in which ALib, BLib, and CLib are built whenever any of them change.
Develop a suite of automated unit tests that execute as part of the build. If any of the tests fail, the build should fail.
Publicly shame anyone who breaks the build..
Use interfaces instead
If you cannot help yourself and ALib needs to be able to be modified all the time, then perhaps you should move away from implementation inheritance and stick strictly with interface inheritance, as follows:
- Develop a new lib, call it
ILib. This library should contain zero implementation code and should only contain interface definitions.
- Remove the inheritance relationship between ALib, BLib, and CLib
- Add an interface relationship between ALib->ILib, BLib->ILib, and CLib->ILib
- Freeze ILib
That way, any implementation changes in ALib will have zero impact on BLib and CLib.
The drawback is that you may end up duplicating a lot of code, i.e. violate DRY.
You’re unlikely to find a silver bullet I’m afraid. You have all the symptoms of the fragile base class problem.
In the short term, I would focus as much effort as possible on getting a CI build up and running and getting test coverage as wide as possible. That way you at least mitigate the problem if not eliminate it.
One structural idea you could investigate is to try and have application A not use Alib directly but subclasses in A’lib. It may not be OOP pure but, if the functionality you need for app A isn’t in fact needed for B and C, you can put it in A’lib and mitigate some of the fragility.
Good luck. I’ve been in your shoes and it wasn’t the most pleasant experience.
a developer working on Alib can cause unforeseen issues in both Blib and Clib
That sounds you have no or too few automated tests for your Alib, and too few for your other libs as well. If a developer changes something in Alib which he expects to be upward compatible, the tests should make sure the existing functionality does not get broken. And if the change is intentional incompatible, only the expected set of tests should break.
So that is what you should work on – automate a big portion of your manual reviewing process by using tests.