Hacker News new | ask | show | jobs
by Edd314159 1407 days ago
I would argue that a lot of the time, people do not inherit a "bad" codebase. They inherit a codebase that successfully made enough of the right quality-vs-speed tradeoffs to survive long enough to be inherited by someone other than its original author.

It's easy to spend a day with a codebase (that others spent years writing) and call it "bad". I'd argue it even feels pretty good to take that stance of superiority. But you're viewing it with literally zero of the context of the time in which it was written. You see none of the constraints, none of the pressures, none of the alternatives presented in the moment.

Particularly for a young or small company, if you're "inheriting" a codebase it's because it's existed and been in operation for a while. Yes, it may still be bad. But I would advise taking time to consider whether it's actually, within the lens of yesterday (or 2 years ago)... good?

12 comments

Well said.

I recently inherited a code base which was developed by some devs-for-hire while the company started building up the team to take over (including me).

I met the last remaining dev-for-hire for the handover and got the impression that the company paid very very close attention to how many hours they billed and which meetings they were allowed to attend, and 0 attention to the code they produced as long as it worked.

Thankfully, the code was actually working well enough - and the thing that I appreciated most of all was the fact that at lots and lots of places in the code, there were frank comments like

    // This is completely undefined as of yet. I'm just implementing this how I think it should work, you can probably delete this and start over if it no longer makes sense.
So incredibly helpful. No Chesterton's fencing around, just clarity about the parts of the code everyone knew was just around to do its job, then get rid of.
> No Chesterton's fencing around

That’s an interesting way to put it. I think that might be a good rule of thumb to help future maintenance: Avoid unintentionally erecting Chesterton’s fences.

> Avoid unintentionally erecting Chesterton's fences

This is a great rule of thumb. I often find I unintentionally do that all the time and then I have to figure out why I did something I did. Trying to avoid that with good comments, documentation, etc. earlier in my career would have been a great practice.

I find I often think something is so common-sensical that I would never forget and then come back a while later and have completely forgotten.

The opposite of Chesterton's fence is "traffic cones" in my lexicon.

As in, "put some traffic cones around that hack" meaning make sure there is a comment or appropriate error message or something that will make it clear to posterity that this is a known temporary sketchy situation, not an accidentally load bearing wall"

See, this is the sort of behavior I would wish companies would test for when hiring engineers.

Another (in-house, salaried) contributor to the project I took over added a error message (in a very specific error handler for exactly one REST endpoint), and I quote

    raise Exception("could not load data because of error")
FWIW I got this way thanks to a stint in the sysadmin/support/operations world at the start of my career. I wonder if trying to give juniors similar experiences might help jumpstart the process of getting this kind of intuition.

When you operate software, you realize all the little things a developer can do to make the operator's life much easier or harder. I always thought this was the "true meaning of DevOps" -- thinking of how to make it easy to operate the software at dev/design time, as if it were an old school shrinkwrapped application like Windows 95... but instead it got turned into writing terraform or something haha.

Well, funny thing is, that developer was also the operator.

Because if he's the only one that understands the error message, he's the only one that can fix the problem.

That's how you get credit with the suits if something breaks.

No one ever notices a stable application. A programmer fixing a problem that no one understands? That must be a very smart person indeed.

I'm being needlessly cynical at this point. This was just one person at one company. Overall, I do think that given the right incentives, what you describe can happen. But it does not someone in leadership to care. And that's a hard sell to me.

> to survive long enough to be inherited by someone other than its original author.

survival is far too low of a bar. We want delighted customers, low churn on employees, low minimum bar of required IQ to understand it (easier hiring, less stress), inexpensive modifications + improvements.

Survival is only sufficient for cash strapped startups on borrowed time&money because if survival fails, then the future no longer matters.

But as soon as the future seems probable there is _much much_ better aims than just surviving.

>survival is far too low of a bar

Correct. I sometimes invite coworkers to partake in a thought experiment where they anthropomorphize the company (and/or codebase) they work at imagining, if it were a person, what state of health would it be in? This usually engenders a chuckle or two of some sort as they picture someone whom, though they are surviving, is otherwise in seriously poor health and does well to illustrate to them that if they were this individual they would likely be striving to significantly improve their quality of life and not just merely maintain status-quo of still having a pulse being the measure of success.

>> soon as the future seems probable

The next target then is acquisition or an IPO which is then followed up by quarterly earnings. It’s always survival first unfortunately

Yes, I agree survival is the bottom layer on the Corporate maslow's but what I'm pointing out is that a pure focus on survival in the ways that are common in tech companies these days (largely informed by cargo culting SV startups) actually will negate the possibility of future survival. Customer churn is more expensive than doing a feature well, no velocity on roadmap is death but also rapidly inevitable if pains aren't taken to ensure the code is "good code"
> I would argue that a lot of the time, people do not inherit a "bad" codebase.

I think that's underselling the problem. A bad codebase is a codebase that isn't what it should be. Just because there are reasons, even legitimate reasons, as to why the codebase is what it is, that doesn't mean it isn't bad. Constraints can be bad and bad constraints make a bad codebase.

In my opinion, thorough documentation is a minimum. If a codebase doesn't have documentation, then it is automatically bad.

Software/programming/whatever you want to call it is about three things: (1) instructing a computer, (2) communicating between humans, (3) encoding knowledge of a domain. If a codebase fails at any one of those three, then it is a bad codebase. Too often, people only view a codebase in the lens of (1), in that if for the most part it does the thing it's supposed to, then that's enough.

> They inherit a codebase that successfully made enough of the right quality-vs-speed tradeoffs

Or was lucky enough that all the choices they made did not blow up yet.

I totally understand the value of technical debt. But I have also seen in the wild cases where people thought the codebase was great simply because they hadn't run into cases where its rotten core would be exposed.

Yet.

Yeah that’s my current place. When I got there management didn’t even realize how bad the code was or that spending 70% of dev power on big fixes wasn’t acceptable.
I'm going through something similar. I recently scheduled a call with my engineering VP and highlighted the ratio of branches prepended with "hotfix" vs. "feature." It was a simple way to get their attention. With that perspective it's becomes fairly obvious there are some problems here that should be identified and addressed.
Sounds like it worked. My current place has basically done everything a guy could ask for (when given a nightmare codebase) so I no longer believe it’s always a systemic issue. My employers arent engineers and weren’t aware how poor their codebase was.

For any mid engineers who run away from spaghetti, I’ve learned identifying and solving that spaghetti problem can be a huge leap forward in your career towards management/leadership roles provided you find the right company and are able to solve the spaghetti issues.

> But I would advise taking time to consider whether it's actually, within the lens of yesterday (or 2 years ago)... good?

Other than to soothe egos, this almost never matters, because the person inheriting someone else’s code doesn’t live 2 years ago and can’t be blamed for not having the prior context (establishing that context is the job of the original author and is why documentation matters).

If you find yourself in the thankless position of having to reverse engineer intent and especially if you have an unsympathetic manager ready to lecture you about how you should magically read the mind of the original author, start looking for a new job and hack/slash away as much of the old code as possible to do the job that you’re asked to do.

> It's easy to spend a day with a codebase (that others spent years writing) and call it "bad"

Well put! When I was younger I would call codebases bad after a couple days of working with them. Now, it takes more like a couple months to fully form my opinion, although it's less about "Is this codebase good or bad?" and more like "What refactoring opportunities do we have to make this more maintainable and which ones make sense (from a bang-for-the-buck / prioritization perspective)?"

Well, you can have a successful product with a better or worse code-base. Of course, if the product is successful, the code has to be good in some sense. But that doesn't mean that there aren't parts of it that are in bad shape.

For example, I've worked on a huge C# product that has been developed in various epochs by up to 100 people at a time. When I was working on it, it was around 10 years old. Certain parts of it were in good shape, others had stuff like a 5k+ line OnPaint() method for a table cell that was doing data processing in place and had stuff like "if ((value == -2) && (type == someTableType) && (userOptions.SomeFlag == SomeEnum) && (someOtherColumn.Value == "something")) { value = paintCheckBox(enabled) }" and such.

Of course, that code was doing what it was supposed to (at least in general), and it had gotten to this state during a really bad delay where bug fixes/features had to be churned out to reach an important release date - so in some sense it was "good". But in the sense of being possible to change without a major re-write (of that particular area I mean) or without introducing 3 bugs for obscure combinations of features, it was really bad code.

You are providing an excellent example of the fact that in a business context, good code means “successfully meeting business objectives at acceptable cost”

But there is another context in which we might evaluate code, something like, “how far the code is from ideal, where ideal is the state the code would end up in if we could push a magic button to refactor the entire codebase, for free, to perform identical functionality and reduce both operational risk and the cost of future development”

Or, more succinctly: how clean is the codebase? Clean code has a cost, and sometimes it isn’t worth paying. That one off bash script that runs once every few months perhaps isn’t worth tidying up. I think the author is really asking about clean codebases.

Legacy codebases that are “bad” usually have years of built-up domain knowledge and things to handle “gotchas” and corner cases that somebody new may not know about until they’ve spent a significant amount of time with.

I’ve seen it time and time again, with myself, colleagues above and below my grade, and devs I’m mentoring.

Chestertons Fence is a very important concept to have deeply engrained in your mind.

I completely agree. It's easy to pick apart legacy code and say what you would do differently, but it's starting from a place where we have learned a lot of knowledge about the product we're trying to build and the architecture we actually need. When you start writing code, that is when you have the least knowledge about what your target product is and the least knowledge about how best to solve it. Both of those things are only discovered over time, with multiple iterations, by which point your original design may be ill-equipped to handle the new world you find yourself in.

Of course, with experience, you get better at designing solutions to add some "optionality" in case things change, but it's a trade-off that you can't always make, nor should you always make.

In my experience it usually is actually bad. The bad parts are most often not tradeoffs, but rather over-engineered complexity that could have been simple and improved development speed at the time too. Usually the issue is developers thinking of a bunch of things they might want to add in the future and implementing abstractions to make adding those things easy. But, of course, 1 out of 10 of those things ever actually end up getting built, and another 9 that were never planned for do, so the whole thing is a disaster since it was designed for X, but ends up being Y. This is entirely avoidable and can't be described as the traditional speed for ugliness tradeoff.
> But you're viewing it with literally zero of the context of the time in which it was written. You see none of the constraints, none of the pressures, none of the alternatives presented in the moment.

While this is true and compassionate, I think bad code is still a bad code. It'd be dishonest to call a bad code a good code.

I agree though that from time to time circumstances simply wouldn't allow developers to take the time to write good code and/or address tech debts. This problem I feel would need to be catered at management level. If companies were to attract great talents, they would need to maintain certain standard of code quality and empower their developers to do clean ups/refactors every once in a while.

Good points. Further, a codebase which has survived for a significant period of time is probably one which has created (and is creating) a lot of value for people. If it didn't do anything valuable or useful, it would already have been discarded.