Relocatable costs and wrapping an awkward dependency
Look beyond the size of the cost and examine its character.
When deciding not to use a dependency, the choice is often based on its biggest problem, such as poor documentation, slow performance, few users, or a hard-to-use interface.
At first, these issues might seem similar, but they differ. Some problems consume resources repeatedly, while others require a one-time effort.
A limit on flexibility affects every step. When a library does not meet design needs, its problems appear in every chart and screen, regardless of system changes. This cost remains with the project.
Poor documentation is a hurdle cleared once. One person dives into the source, deciphers its secrets, and packages that understanding into an interface for the team. This cost can be moved and managed.
So the real question is not which library has the shortest list of flaws, but which flaws can be boxed in and kept from spreading.
Containment is equally important in the decision-making process.
Choosing a more complex tool does not show careful thinking. It just passes your ability to understand code to a team that might lack it.
This choice is justified only if the complexity can be contained. Selection and boundary definition must happen together, not selecting first and mitigating later.
Ideally, product code imports an internal interface, with the awkward dependency hidden behind it. Only one person needs to understand the dependency, regardless of team growth.
This tests effective containment. If a second engineer must read the vendor’s source to deliver a feature, the dependency was not properly wrapped; only an extra file was created.
The wrapper should be simple to explain and learn.
This step is often missed, and skipping it weakens the entire effort.
An undocumented internal abstraction over an undocumented dependency is more problematic than the dependency alone. It results in two opaque components, one lacking community support, issue tracking, or searchable resources. Knowledge is concentrated instead of the cost being contained.
Enablement is not an extra cost; it is integral to the wrapper. This deliverable should include: a workbench for rendering and manipulating components (such as a minimal application or environment where engineers can interact with and test the abstractions directly), visual regression coverage to detect changes as diffs instead of bug reports, and sufficient documentation for independent use. Documentation should cover the wrapper's responsibilities and limitations, example integrations, and usage patterns for both common and advanced scenarios. Together, the workbench and documentation reduce onboarding time and help the team use the abstraction effectively.
Allocate resources for enablement during selection. This is a recurring cost, including financial considerations if visual testing is part of CI. Skipping this step risks making you the only person able to deliver charts.
Where it stops working
Wrapping addresses the cost of understanding the dependency, but does not eliminate the cost of understanding the wrapper itself.
The cost of understanding the wrapper is high and often appears later as misuse. This includes bypassing the abstraction, using it incorrectly, or creating alternative solutions due to unclear functionality. Adding another layer does not fix this; it is an adoption issue requiring adoption-focused solutions.
In reality, you exchange a distributed comprehension cost for a concentrated one plus ongoing maintenance. This is often a beneficial trade but not without cost.
When not to
If the problematic attribute recurs, this approach does not apply, and the better-documented tool should be chosen. If the code will not be maintained by others, boundaries are unnecessary. If enablement cannot be ensured, select the more user-friendly library, as an uncontained difficult dependency is the worst outcome.
Decision Checklist:
- Is the problematic attribute (such as limited flexibility or recurring performance issues) present in every use of the dependency?
- Will the codebase be maintained by multiple people, or just one engineer?
- Can you create clear boundaries and a simple internal interface around the dependency?
- Do you have the resources to provide adequate documentation and enablement for future users?
- Is the cost of using this dependency concentrated and manageable, or does it recur throughout the system?
- Will visual testing or other ongoing costs be supported by your budget and workflows?
If most answers favor containment (one-time, manageable issues, shared codebase, support for enablement), wrapping and containing the dependency may work. If not, consider a more maintainable tool.
Before rejecting a dependency for its worst attribute, determine whether the cost is a one-time, contained expense or a recurring one. If it can be contained, containment is essential and should guide the decision.
A note, not a case study. No employer, no project. The pattern and where its seams go.
The shape of the cost, not the size
Every dependency you reject, you reject on its worst attribute. Poor documentation. A performance ceiling. A thin ecosystem. An awkward API.
Those feel like the same kind of objection and they are not. Some costs recur at every call site forever. Others get paid once and never again.
A flexibility ceiling is the first kind. If the library cannot render what design asked for, that fight happens on every screen, and no architecture makes it stop.
Bad documentation is the second kind. Somebody reads the source, works out how it behaves, and encodes that in an interface other people use instead. Relocatable.
So the selection question is not which library has fewer problems. It is which problems stay where they land.
Containment is the other half of the decision
Picking the harder tool is not rigor. On its own it is moving your tolerance for reading source onto a team that never agreed to it.
It works only when the difficulty has somewhere to go, which makes the selection and the boundary one decision rather than a choice plus a later mitigation. Product code imports something you own, the awkward dependency sits behind it, and the number of people who need to understand it is one.
The test: if a second engineer has to read the vendor's source to ship a feature, you did not wrap it. You added a file.
The wrapper has to be teachable
An undocumented abstraction over an undocumented dependency is worse than the dependency alone. Two things nobody understands, one of which has no community and no search results. That is concentrating the knowledge, not containing the cost.
So the enablement is not overhead attached to the wrapper. It is the wrapper: a workbench where components render and can be manipulated, visual regression coverage so an upstream change surfaces as a diff instead of a bug report, and enough written down that nobody has to ask you.
Budget it at selection time, including the money if visual testing runs in CI. A plan without it is a plan to become the only person who can ship.
Where it stops working
Wrapping solves the cost of understanding the dependency. It does nothing about the cost of understanding the wrapper.
That arrives later, usually as misuse: people reaching past the abstraction, or building their own because yours did not obviously do the thing. Another layer does not fix it. That is an adoption problem, and it wants adoption answers.
You have traded a distributed comprehension cost for a concentrated one plus a maintenance obligation. Usually a good trade. Never a free one.
When not to
If the awkward attribute is the recurring kind, take the better-documented tool. If nobody else will touch the code, the boundary is ceremony. And if you cannot commit to the enablement work, choose the comfortable library, because an uncontained hard dependency is the worst outcome available.