Surface Area
The problem is that any change now requires consulting two or more "separate" code bases using different styles, conventions, frameworks, and perhaps even languages, paradigms, or platforms.
The surface area that must be explored is larger, and necessarily includes the dark, and dank area inhabited by the decaying mud ball™.
Worse some changes require work in both the old and new world. This is seriously limiting. Either you must:
- envision two distinct solutions, and a clean mapping between them.
- or implement a single common solution which suffers the worst constraints of both the new and old world with none of their unique benefits.
In neither case are you being setup for success. Just more misery...
Clean mappings are Hard as anyone who has had to massage data from one database to another well knows. Which is made worse by the fact that the best solutions in the new and old world will probably have incompatible data models.
The single common solution means that not only are you writing bad code, you are writing code fundamentally worse than what was present in the old world. To top it off, if you ever sever the link to the old world, you are already stuck with this tragic implementation. If by a miracle the implementation is actually mildly okay or actually good code then you have a maverick genius and the fates have smiled upon you - just don't rely on it to be true for the next Code Change™.
Coupling
This is why the Coupling is so bad. Its not that you have a shared database, or shared APIs. Its that you have two different paradigms of implementation duking it out without a good boundary between the two keeping the peace.
If you do happen to have a decent boundary between the two, its hard to maintain. Largely because the two code bases co-habitat quite closely: sharing common data, data-structures, and are attempting to solve the same problem.
You can envisage them as two large stars slowly eating each other as their close proximity allows their gravity to ripe at the other one.
Anti-Corruption Layer
An anti corruption layer is a good start. Its like a microservice (even if its just a dll) that stands as a guardian of the integrity of shared data structures. Usually it protects a database, but you could have similar guardians responsible for shared communication streams. Their goal is to ensure that neither program puts the data-structures into a bad state due to bad code, asynchronous behaviours, or luck.
Tight Modules
Start enforcing module boundaries. It might be ugly on the inside, but a tight module is also a replaceable module. No code from the new or old world is to directly access the implementation (aside from the "entry" class).
Module Level Tests
Also advantageously, a tight module is much easier to test, and tests improve the quality of life for you when dealing with legacy code, even if its written in the new world.