A long time ago, I had to think about architecture trade-offs and design decisions when building widely adopted frameworks. Surely you’d tell me that one just shouldn’t do such trade-offs, but in a corporate world that may or may not be an option.
At that time, it appeared to me that there are three types of bad design decisions (also known as work-arounds), and they mostly differ in their cost, and how that cost scales. So far, in every discussion I was told that it’s trivial. Still, it’s usually not observed.
Trivial: Person Days
The first type of bad design decision is truly trivial: It is a purely internal decision and can be removed more or less at any time if you just choose to. If it’s purely internal, it can be refactored without any impact on adoption (or adopting teams) once one has the time or budget to do it. To get that, these work-arounds must not affect any interfaces, and it should not impact the user observable behaviour. An example could be to deliver a caching component later to improve performance.
That’s a work-around as smart as it can get in terms of dealing with project constraints: It’s a way to buy time, and it’s paid back in Person Days.
Unfortunately, these instances are rare.
Showstopper: A “tax”
The worst type of bad design decisions is also pretty trivial – but not from the technical point of view. It’s often an over-simplification in order to match project constraints, but it impacts interfaces – often interfaces where one doesn’t have control over adoption. So, for the sake of compatibility, one will have to maintain both the “real” interface once it’s there and the old work-around interface for a long time. For all future development, this is imposing a “tax”: Everything becomes more expensive by a percentage – 10% in simple cases, 100% if things really get sour. The obvious example is all kinds of bad APIs, a case I’ve met had to do with a broken locking mechanism. Rumour has it that the original Netscape browser was inspecting the IP address of the machine it was running on in order to adjust rendering logic to certain intranet applications.
Needless to say, this should be avoided at all cost – especially, one should avoid to pile these work-arounds on top of each other, because the cost multiplies.
The middle ground: Encapsulation and Deprecation – FTE
A variant of the “tax” is to simply write the entire component as “one-off”: If time is tight, it may be an option to satisfy the urgent stakeholder with his own component or component version and make a deal that there will be no significant further development (i.e. an agreement of deprecation). Any kind of customer-specific component version that exposes certain APIs that differ from the “standard” fits this category if it is well isolated. Consuming special APIs usually puts the providing component into the “percentage” category.
Then, one has to dedicate resources to the maintenance of that special component version, but one is free to do it “right” for the rest of the world. The “cost” can then be some percentage or multiple of FTEs: 0.3FTE, 3FTE or some such through the life span of that stakeholder relationship.
No, it’s not rocket science. Like in the middle ages when there were three degrees of torture, there are three degrees of “cost” for design trade-offs when building frameworks:
- “Simple”: Keep it “inside” what you have control, don’t expose the work-around to the outside. Then the cost is some number of PDs.
- “Controlled”: Keep it limited to APIs that are provided, and keep it limited to ~one stakeholder. Then the cost is some number of FTSs.
- “Dangerous”: Double maintenance within the same component – everything may happen. Then the cost is a “tax”, a percentage of all future development and maintenance.