Let's talk
engineering

The tables existed, the columns existed, and not one route served them

Planning the rebuild of the mobile app, I surveyed the existing screens against the live API to work out what could be built immediately. The parent’s side came out at thirty-four of thirty-seven screens servable that day. The child’s side came out at zero of eighteen.

All seventy-five endpoints on that server were parent-facing. There was no device authentication, no way for a paired tablet to read the lessons assigned to it, and no route anywhere that records a quiz attempt — which means that taking a quiz did not work anywhere in the product, including in the parent’s own web portal.

The tables were there. The device table even carried a column for a hashed token, sitting empty, waiting for the authentication scheme that had never been written. The schema described a complete product. About forty per cent of that product had no server behind it.

Why the schema was not evidence

The schema had been designed up front, in one pass, from the full feature list. That is a reasonable way to work and it produces exactly this hazard: a database that describes the finished product from the first day, and an API that describes whatever has been built so far, with nothing anywhere comparing the two.

Reading the schema, the feature exists. Reading the plan, the feature exists — it was in the document. Reading the route list, you see seventy-five endpoints, which is a lot of endpoints and reads like coverage.

What finds it is neither of those lists. It is walking the product’s surfaces one at a time and, for each control on each screen, naming the endpoint it calls. Sixteen endpoints turned out to be missing before the child’s app could start at all. That audit also turned up gaps the route list alone would never have shown: the review queue returns exactly three kinds of item, quiz attempts are not among them, and no code anywhere reads an attempt after it is written.

Audit by surface, not by inventory. An inventory of what you have cannot tell you what is missing. Only walking the thing a person actually does can.

The tell was written in a test

The best single indicator of the problem was sitting in the test suite, in a comment, in plain English.

A test module needed task instances in a state where they had been assigned to a child but not yet submitted. It could not create them through the API, so it inserted rows directly into the table, with a comment explaining that the API cannot produce the state this module’s tests need.

That comment is a defect report and it had been read by several people as a note about test convenience.

The cause was an endpoint doing double duty. The route that creates a task instance also submitted it, in one step. That had been a deliberate, documented shortcut: at the time it was written there was no child app to submit anything, and the review queue needed something in it to develop against, so one route did both jobs.

The consequence, once a real parent used it, is that assigning a task did not assign it. It went straight into the assigning parent’s own review queue, already submitted, and the child’s list of jobs could never contain anything at all — the state it lists does not exist in the database.

Instances are now created in an open state, with the old combined behaviour available behind an explicit flag for the seeding case it was written for.

When a test cannot construct a state through the API, that state is either impossible or unreachable, and both are bugs. A test that reaches around the API to set up its world is telling you something about the API, not about the test. The comment explaining why is the diagnosis, and somebody wrote it down and moved on. I have done that myself more than once.

Shortcuts outlive their justification

The shortcut was correctly reasoned when it was taken and correctly documented. It still caused the defect, because the condition that justified it — no client existed to call the separate route — stopped being true and nothing was watching for that.

A deliberate shortcut needs a trigger, not a note. “This does double duty because the child app does not exist yet” is a note. What it needed was a failing test, or a comment attached to the thing whose arrival invalidates it, or a line in the task that builds the child app saying “split this route first”.

The version I now use: when you take a shortcut, write down the event that ends it, and put the note where that event will happen. A note in the file you are shortcutting will be read by people maintaining the shortcut. It will not be read by the person who makes it obsolete.

The plan was wrong more often than the code

A related pattern from the same project, recorded because it was expensive.

More than once, work was planned against a design document rather than against the source, and the document was wrong. A screen design showed section-by-section editing of a lesson; lessons are a single text column with no sections table anywhere, so building it would have meant a schema migration nobody had asked for. A design used two of the three bundled typefaces, so every reading surface was specified in the wrong one. In both cases the document was the older artefact and had been treated as the authority.

There is a simple ordering here that I would state as a rule. The schema is a plan. The document is a plan. Only the running code and the live route list are facts, and where they disagree with a plan, the plan is out of date.

Checking that costs half an hour before a piece of work and saves days inside it. In this case it turned a rebuild that would have started on the child’s app and stalled immediately into one that built sixteen missing endpoints first.

Working on something like this?

We build this kind of software, and we staff the teams that do.

Get in touch