Hacker Read top | best | new | newcomments | leaders | about | bookmarklet login

Your list is interesting because of how it overlaps with yet reframes mine (from http://akkartik.name/about):

A. Backwards compatibility considerations. Early mistakes in the design of an interface are often perpetuated indefinitely. Supporting them takes code. Projects that add many new features also accumulate many missteps. Over time the weight of these past adaptations starts to prevent future adaptation.

B. Churn in personnel. If a project lasts long enough early contributors eventually leave and are replaced by new ones. The new ones have holes in their knowledge of the codebase, all the different facilities provided, the reasons why design decisions were made just so. Peter Naur pointed out back in 1985 (http://akkartik.name/naur.pdf) the odd fact that that no matter how much documentation we write, we can't seem to help newcomers understand our programs without talking to the original authors. In-person interactive conversations tend to be a precious resource; there's only so many of them newcomers can have before they need to start contributing to a project, and there's only so much bandwidth the old hands have to review changes for unnecessary complexity or over-engineering. Personnel churn is a lossy process; every generation of programmers on a project tends to know less about it, and to be less in control of it.

C. Vestigial features. Even after accounting for compatibility considerations, projects past a certain age often have features that can be removed. However, such simplification rarely happens because of the risk of regressions. We forget precisely why we did what we did, and that forces us to choose between reintroducing regressions or continuing to cargo-cult old solutions long after they've become unnecessary.

---

I can't really rebut anything you say. I'm going to keep it on my radar as I go about my project. It's currently pre-network, unsecured and inaccessible. And will always be manually configured, for reasons described in the link. But it's supported, for what that's worth.



sort by: page size:

There's also the opposite problem: older devs will use outdated features and have best practices in place that are now considered antipatterns. This exacerbates the existing difficulties to modernize legacy code bases and creates incentives to not do so. New devs are forced to learn C++ from 20 years ago which is much worse than modern C++.

Old large codebases are mostly maintained by people who weren’t around when the code was originally coming into existence. They don’t know the implicit design assumptions and decisions, or even the history of requirements. One thing you’ll find in nearly any software project of any age is a lack of good documentation of those things, so as you lose the community folklore, people will start making myopic changes, cargo-culting, violating future-looking design principles, and so forth. Pretty soon you just have a pile of incoherent features and making systematic improvements is hard because the code is no longer systematic.

Legacy? Tons of code to change, and users more interested in new features? No tests? Developer conceit about their own abilities to "do it right"? All the usual reasons any project has old code that exhibits bad practices by current standards.

I had the misfortune to work on (and re-write) a legacy system in a previous job and I constantly alternated between admiration and horror.

In some places the web GUI was precisely designed exceptionally well, items placed together precisely, all designed expertly around the specific domain of concern.

On the other hand, there were race conditions, corrupted data, sql injection, mountains of source files that re-implemented the same things, IE6-7 era compatibility issues, etc.

Then there was the mysterious parts of the program which I never understood, like the auto-email capabilities whose scripts could never be located, mysterious mirror servers that I would figure out existed by looking at the ip addresses of odd requests, little bits of domain logic which I would accidentally break and have to cobble back together.

In a lot of ways, many of the problems just stemmed from age, it was an early 90's application in an early-2010's world. There definitely were some not ideal software development practices that contributed to difficulties as well. But I still had a sense that the previous developer had crafted this beautiful, unique, intricately complex and inter-dependent little world.

Sometimes I was sad I had to brush all of that aside even when my changes made things more robust, reliable and compatible with the modern world.


I have never seen any programming project back-peddling to its previous design in recognition that it was better than the current one. Even though, unlike in many professions, we have the means (via source-control).

The hope is that as time passes, the generation that had first-hand experience with the old thing will die off and the new generation will re-discover things unjustly forgotten.


To me, legacy code has cost. Great cost.

Certainly, but it's the kind of cost that comes with having a lot of users, so it falls into the "nice problem to have" basket. If you've got no users, you have a lot more freedom to throw things away.

I understand where you're coming from, because I also do mostly-sole-maintenance on a large codebase with all the issues that come with a 20 year legacy. One strategy that can work in such a situation where you can't maintain perfect backwards-compatibility forever is to boil the frogs slowly - gradually deprecate and remove features over a longish time frame, because a single changed or missing feature is easier to adapt to for the userbase than a whole slew at once (many people probably don't even use the feature and won't notice).


Sometimes they can be well architected to a fault!

I worked on a periphery of a big mainframe system that was designed and implemented before I was born. I think release 1 was in 1975. It still runs really well and is hard to displace because operationally, even though the people who understood it fully are retired or dead, they left behind strong materials so a moderately intelligent person can run everything.

The “other” systems, old .Net, Java, etc that support the interfaces to the old app are hot garbage, and tend to steal the budget dollars that are needed to migrate off the 1970s stuff. My team had to fix one of these… argh.


It makes that history harder to read. The same piece of logic can move across files and modules and functions over the years. While it's possible to go past each of them and trace that code past those moves, it takes time and effort to do so.

I work on some software of which the oldest parts of the source code date back to about 2009. Over the years, some very smart (some of them dangerously smart and woefully inexperienced, and clearly - not at all their fault - not properly mentored or supervised) people worked on it and left.

What we have now is frequently a mystery. Simple changes are difficult, difficult changes verge on the impossible. Every new feature requires reverse-engineering of the existing code. Sometimes literally 95% of the time is spent reverse-engineering the existing code (no exaggeration - we measured it); changes can take literally 20 times as long as they should while we work out what the existing code does (and also, often not quite the same, what it's meant to do, which is sometimes simply impossible to ever know).

Pieces are gradually being documented as we work out what they do, but layers of cruft from years gone by from people with deadlines to meet and no chance of understanding the existing code sit like landmines and sometimes like unbreakable bonds that can never be undone. In our estimates, every time we have to rely on existing functionality that should be rock solid reliable and completely understood yet that we have not yet had to fully reverse-engineer, we mark it "high risk, add a month". The time I found that someone had rewritten several pieces of the Qt libraries (without documenting what, or why) was devastating; it took away one of the cornerstones I'd been relying on, the one marked "at least I know I can trust the Qt libraries".

It doesn't matter how smart we are, how skilled a coder we are, how genius our algorithms are; if we write something that can't be understood by the next person to read it, and isn't properly documented somewhere in some way that our fifth replacement can find easily five years later - if we write something of which even the purpose, let alone the implementation, will take someone weeks to reverse engineer - we're writing legacy code on day one and, while we may be skilled programmers, we're truly Godawful software engineers.


I’m not sure that article really aged well. Code rots pretty quickly nowadays. Convoluted old code no one understands starts to create wicked bugs as future teams wedge in new features. Processors get overloaded. Memory leaks appear. Libraries evolve. Design trends change.

The stereotype isn't that the code was bad -- it's around the UI. (There are also stereotypes around modern UI being too minimalistic. Obviously these things are not so simple.)

There's also the idea NOT that it was bad when it was written 30 years ago, but that 30 years of patches by people who didn't write the original code mean it's no longer clean code.


I think it's not like a natural law. But several things help with it. Changing maintainers and committers for example. When the original ideas are lost, so is the structure of the code.

Also the quality of the test suite makes a huge difference in combination with the willingness of developers to refactor. No testsuite automatically means no refactoring. With a testsuite the question remains whether the organization penalizes or encourages larger code changes (sometimes with old code bases managers demand pinhole-surgery only).

And then there is this funny thing that perfectly acceptable code bases age without a change to their code. Idiomatic C++ code from the 90ies is from our perspective not clean, even if the person who wrote it was a dedicated and smart programmer following the best practices that they could get a hold on.

With functional code bases you might see similar patterns. A Haskell person of today may look at an older common lisp code base with similar reservations as a java programmer to the visual basic 3 app.

I just sat down and wondered how a tech stack potentially used by a startup could age. In the future i think we will see much more dependency problems. When you get to maintain a Django/nodejs/ruby on rails application, you always take pypi/rubygems servers for granted (or your local mirror on artifactory). Think about the time in 40 years when the dependencies are not available. Or the small languages we sometimes see and still are able to find tutorials for, how will it feel to take over a Lua codebase in 40 years? I hope that enough docs stick around, but already when I browse the web on Smalltalk stuff most links are broken because actually information can disappear from the web.

Or database technology. How will that NoSQL db appear to a maintainer in 30 years?

So while today we are unhappily having to maintain software that was written without version control, it may be that future generations will have it even worse because they dont even have a monolithic code base in front of them but something with dependnecies they cannot install anew anymore.


I agree. Similarly, keeping around tech with several decades of built-up cruft can also hurt your cause when you are trying to build an elegant, concise, and predictable system. :) You can keep putting a new coat of paint on 1950 Ferrari, even regularly changing out its parts, but it will never match something designed from the ground up with modern tech. The web browser, by nature of its spontaneous birth, ill-thought-out design, and insistence on backwards compatibility places many bent nails in the foundation. Adding on new layers to an old thing almost inevitably results in leaky abstraction.

I have projects that are 10 years old. The code isn't unusable, but I know I can do better. Sometimes adapting the old code just makes it ugly, hard to maintain, and error prone. So when it makes sense, I usually start fresh.


It's funny, my experience is that the same handful of technical problems keep arising regardless of the age of a code base. After a while, it becomes rarer to find yourself saying something like, "well, I haven't written one of these before."

There are a lot of "Maybe"s in your second paragraph.

I'd say, yes, there are a lot of lessons buried in old code, but often, there is an equal amount of baggage we are too fearful to abandon because of backward compatibility and "bug became feature"-situations.

And even then, it's not you who is abandoning that, which makes the attitude even more baffling to me.


The legacy systems you refer to are used because they satisfy the business needs.

But many have awkward, confusing UI are brittle in face of required changes and the documentation out of sync with the code which often has confusing and contradictory comments.

The management is always screaming for the latest fix, change to be made ASAP. So the programmers do their best in adverse conditions. Any attempt to do a proper job is a career limiting move.


Code doesn't rust or corrode or accumulate bugs as it gets older. Obviously things can change around it, i.e. Dependencies and that can lead to some mandatory changes but really there are plenty of software that is old. Much older than 20 years and it's "ready" i.e. Has All the features it needs and it's still being built and run on modern computers and it works fantastically. My own software goes back +15 years now and over the years I've ported it from 32bit builds to 64bit builds and from Qt4 to Qt5. Other than that very few new features or changes in the past years since it has all the functionality that is needed. And it works great. What's wrong with that?

Changing code (or UI or UX or anything really) just for the sake of changing it is just nonsense busywork to me.


If a system has been continuously adjusted over time (like ours) we still work from first principles. Even though we have institutional knowledge in the form of people who have worked here for 40 years, it’s often impossible to know the reasoning behind why a change was made. Most ageing code bases contain redundant logic due to some situation in the past which no longer occurs, or a constraint imposed by a dependency which will now be removed.

For example, what was once a file based batch process meant that other processes had to wait and split their processes accordingly. Replace that with an event based system and everything can run contiously and a lot of the restrictions disappear.


Can you elaborate on this? I'm a new developer and would be interested in this perspective. If they have a new, energized staff that is shipping software on time and have old, untouched software that still works and communicates with the new software, what is the problem with that?

Looks like many agree with you, so I'm really curious about what problems arise long-term from decisions like that.

I mean...our legacy code is in BASIC...but if they told me I needed to maintain the BASIC programs, I would probably quit.

next

Legal | privacy