Hacker News new | ask | show | jobs
by Nursie 2189 days ago
When I was young and fresh, I wanted to cater for all sorts of future possibilities at every turn. But what if things change this way? What if things change that way? I came to realise that when things change, unless the design change is trivial (allow use of database Y as well as Z, etc) then you probably haven't anticipated the way in which it is going to change anyway. Better to have clean, straightforward code than layers and layers of abstraction and indirection, to the point where it's hard to tell what's actually going on. You'll probably find that when change does come, your abstractions are an annoyance and a straight-jacket.

A junior engineer recently presented me with some completed work. There was a data schema change on one interface and an API version change on another. He had created a version of the microservice he was working on which could be configured to work on either the old or new version of each of these, with feature flags, switching functionality and maintaining the old and new model hierarchies for both. He viewed this as an achievement. While technically it was, the result was code bloat and unnecessary complexity. The API upgrade and schema change were happening at the same time, and would never be rolled back, his customisability was a net negative.

Code is a liability. Be sparse. Build with some flexibility but overall just build what is required and when the future comes, change with it. Don't build to requirements you can imagine, because the ones you don't will kick your ass.

3 comments

Less is more. The simpler your code is, the easier it usually is to change to meet new requirements.

If you need to push every variable through 15 layers of interfaces and factories and generators, you'll have a bad time.

> The API upgrade and schema change were happening at the same time

Atomically? What if your DDL times out?

What you describe is, in my experience, extremely standard process for rolling out breaking changes (you do of course remove the switch and old version support after everything is rolled out).

> Atomically? What if your DDL times out?

Effectively yes, during a defined outage period (don't ask, this is finance), on any error anywhere in the platform during rollout, the entire platform would be reset to its prior state.

> What you describe is, in my experience, extremely standard process for rolling out breaking changes

But entirely unnecessary here. There was no requirement for a single version of the microservice to be able to address multiple versions of either dependency. It was a net negative to have that support, added significantly to the software complexity and the raw LoC.

But abstractions done right, can really help with refactoring.

But this is hard to do. I once made a major feature change - where I feared the consequences and bugtracking afterwards - but it worked like a charme, because the complex abstractions I made in the beginning, really helped, so it was done in a couple of days and not weeks or months, like I thought. I was really proud of my older me, that day.

Only, like you said, when you have layer of layer of abstractions, the problems rise. When you really have to dig in to understand what is going on. Or when the abstractions are simply not helpful for the new change, which happens way too often, too.

"Code is a liability. Be sparse. Build with some flexibility but overall just build what is required and when the future comes, change with it"

So yes, I very much agree to that.

I definitely agree with that, and there's an art to getting abstractions right :)