Michael Mentele

software

Second and Third Order Considerations

Recently, I was working on an out of memory error that occurs whenever a given page is loaded. My assumption was that too much was being loaded and instantiated in memory while the request was being built. No twist there–that assumption was correct. But fixing it wasn’t as easy as simply having a more precisely scoped query that loaded less into memory and serialized less data.

Well, actually it was, but modifying the existing code was a nightmare. Why? Because the existing code was trying to be generic enough that it could handle any request from the client React application and know how to query the backend. The problem was, it was too generic too know how to do this intelligently and the code was sphaghetti.

A prior engineer had done something clever. They had essentially built their own version of GraphQL. I don’t believe Graphene-django was out at this point. It was a good solution at the time, and limited the coupling of the client requests to the backend.

However, I don’t think it is a good long term solution for a few reasons.

One, the code wasn’t clean. Reading through it, it seemed a wonder to me that it hadn’t broken more often. It was not modular, it was sphaghetti of the worst kind. The benefit of the doubt has to be given, I don’t know under what constraints the software was written and that it had worked so well and for so long was a testement to the code.

Two, it was bound to run into issues. The code was too generic and not granular enough to allow for well formed queries. The code tended to grossly overfetch, serializing thousands of models, when for example, all it needed was a count.

This has been a lesson to me. Looking through this code. A reminder to consider second order and third order effecs of my decisions. More concretely:

  • write methods at the same level of abstraction
  • spend some extra time to refactor my code so that the first draft doesn’t go into produciton and become permenently opaque
  • imagine the performance issues that could arise and while I don’t want to solve a bottleneck that doesn’t exist, I do want to write my code in such a way that it can be easily modified to solve one that arises

So what did I do? I weighed my options, usually I want to fix the issue to stop the pain then refactor but in this case before fixing the problem I stopped and refactored the code. Why? It was too likely that a patched in fix would break something else. Better to refactor it so I could make my one change in an independent way, then try to shotgun my fix across a sphaghetti mess – that’s how sphaghetti messes get created.