Back to Blog

The Invisible Contract

First in a three-part series on cleaning up legacy code. A bug that compiled clean and broke nothing until a page loaded in a browser, because the data structure at its centre had no type and no owner. On implicit contracts, and why the compiler can't save you from one.

The bug compiled clean. It passed review. It shipped. And it did nothing wrong until a page loaded in a browser, at which point a value that was supposed to be there simply wasn't, and a feature quietly broke for every user at once.

Nobody had made a mistake you could point at. That's what made it the good kind of horrible. The data structure at the centre of it had no owner, no type, no shape anyone had written down, and so nothing anywhere could tell you it was wrong until the moment it was too late to catch cheaply.

The shape that nobody owned

Here's the pattern. A chunk of important state lived as a nested dictionary, string keys going three deep, built up in C# on the server. Then it got serialized to JSON and handed across to the browser, where JavaScript consumed it as a plain object and reached into those same keys by hand.

At no point in that chain did any type own the shape. Not on the C# side, where it was dictionaries all the way down instead of a class. Not across the wire, where JSON is structurally anything. Not on the JS side, which will happily read a key that isn't there and hand you back undefined without a word of complaint. The structure was real, and load-bearing, and everyone depended on it, and it existed nowhere as an actual declared thing. It was a contract that every part of the system had agreed to and none of them had written down.

Why the compiler couldn't save you

The specific cruelty of an implicit contract is that it disarms the one tool that's supposed to catch this class of bug. Rename a key in the C# dictionary and nothing breaks. The compiler sees a string, and the string is still a valid string, so it says nothing. The build is green. Everyone moves on.

The break has just been deferred to runtime, and moved to a different language. The browser goes looking for the old key, finds nothing, and the failure surfaces there, far from the change that caused it, with no stack trace pointing home. Two sides of the system silently disagreed about what was in the blob, and there was no layer anywhere whose job was to notice. You don't get a compile error. You get a support ticket.

The fix is just making the shape say its own name

The repair isn't clever, and that's the point. You give the shape a type. A real class or record on the C# side that declares, explicitly, these are the fields and these are their types. The dictionary becomes an object that owns its own structure.

The moment you do, the invisible contract becomes a visible one, and everything that was deferred to runtime moves back to compile time where it's cheap. Rename a field now and the compiler lights up every place that touched it. The shape can be shared, documented, and trusted, and the two ends of the system are no longer guessing at each other across a JSON blob. All you really did was force the implicit thing to declare itself out loud, which is the whole theme of cleaning up legacy code: drag what the system silently assumes into a place where it has to be stated, and checked.

Share this article

Want to Work Together?

Let's discuss how I can help with your project.

Get in Touch