Live data from Hacker News

Garbage collect your technical debt (2021)

ieeexplore.ieee.org

61–70 of 92 posts

Re: Garbage collect your technical debt (2021)

#61
post #48

Earlier quoted context omitted.

"I'm sorry, I didn't understand much of that. Are you saying you can commit to finishing the feature on a shorter timeframe than you originally asked for? Would it help if we forgo writing tests?"

Ouch, right in the feels. I’ve come to realise that there’s no valid business case for dealing with tech debt early, nor adding tests to an existing project (bar some special circumstances / legacy change, critical outage, etc) It’s like a lot of things have to be aligned for “good” development practices to reap benefits, most shops are much less organised, and a bit of chaos and early/quick iterative shipping will a…

I'd argue that there's definitely a business case for frequent and early refactoring - it just needs to be refactoring worth doing in the first place. IMHE a most code gets touched rarely, and some parts get touched all the time, so refactoring needs to be strategic if it's going to have any utility.

This also argues for ongoing refactoring - addressing pain points as they arise, while they are fresh in people's minds, rather than suffering through them until Stockholm Syndrome sets in, people can no longer see the forest for the trees, and much of the velocity refactoring would provide is lost because the subsequent work is already done.

But the biggest issue I've seen with not handling tech debt on an ongoing basis is that there's never a good time to start. So if it's not built into the development cadence, then resistance builds for doing it at all. Product starts pointing fingers at "slow" engineering, who point back at the "breakneck" demand for feature work, and negotiations start for unrealistic (for both sides) halts in feature work - neither sufficient to resolve the problems nor short enough to avoid hurting the business.

Then product breathes a sigh of relief - the tech debt is "resolved" and will never need to be addressed again, engineering returns to wading through a codebase that is only marginally less swampy than it was before the cursory refactoring sprint, and the downward spiral (and finger pointing) resumes.

At least that's how it always seems to happen around me :)

As far as tests, IMHO refactoring without them (with BDD style tests being greatly preferable) is fraught with peril. But unless the team is bought in on BDD tests and using them to guide development, I agree they are a time sink to write them early. However, writing them later (and around code that might have been touched by multiple hands) rather than maintaining them is flavor of pain - like the refactoring, it's harder to be sure they won't miss things and you'll break something.

Re: Garbage collect your technical debt (2021)

#63

Earlier quoted context omitted.

Yes, but every other feature will take ten times as long as over the next three years we will lose 90% of our best developers.

"I'm confused. When I greenfielded this app, several thousand commits ago, I took half the time you've already spent on this feature. You said you were a senior engineer!!" True story. Twice.

what was your response?

Re: Garbage collect your technical debt (2021)

#64
Wow, what a weird feeling. The authors of this article seem like super smart and experienced guys, but the article itself is introduced with incorrect statements in the very first two sentences and slides downhill from there.

"There is a kind of design distortion that happens when a team chooses to build iteratively instead of looking at all of the requirements at once. Ward Cunningham coined the term technical debt to describe those design distortions."

1) There isn't an option to build software by "looking at all of the requirements at once." The requirements of any (sufficiently complex) project will emerge over the course of development, regardless of whether it's built in an iterative style, or with a much larger investment in planning and design up front. We often work iteratively because we acknowledge this particular aspect of reality and it helps everyone when we work closer to reality, rather than fighting it.

2) Technical Debt does not describe design distortions that arise due to "iterative" development, it describes design problems that arise in every project.

Quantizing the choices for dealing with tech debt into 4 buckets doesn't feel right either. Changing the design of a running system happens along a continuum and the need and benefit vary widely over the course of any given software lifecycle and surrounding (business or other) environment.

Maybe I'm just grumpy this morning, and I think reasonable folks could disagree, but the metaphor at the center of the thesis (tech debt ≅ soon-to-be-deallocated-memory) doesn't hold up for me at all.

Tech debt is sometimes left in place, intentionally or not, for many years. Sometimes we chip away at it, sometimes we stop the world and push on it. Sometimes it's bad enough to throw the whole system away and start over. It's like debt for businesses. It can be valuable to take on some debt if it lets you stay alive long enough to pay it back.

Finally, I'm struggling with this article because tech debt is already a metaphor, that includes information about how, and when to take it on and pay it down.

It's not helpful to layer another, unrelated metaphor on top of it.

Re: Garbage collect your technical debt (2021)

#65
post #29

I do opportunistic collection - when I am working on a feature, and spot opportunities for a refactoring/cleanup in code that is more or less directly related to the code I'm touching, I will keep making small incremental changes and keep testing them until my feature is implemented and the cleanup is done as well. I also ensure to not make a breaking change while doing this e.g. no change to the user facing api sign…

I have done this in the past and it has worked well. On some teams though, I have been met with: “Why did you do this refactor with the feature? Can you pull the feature into another PR, then we’ll leave the refactor in the original PR to be merged at another time? (read: never)”

I'd definitely agree with the putting the refactor into a separate PR and then the new functionality in another.

Aside from the obvious that people are more likely to be willing to review your change when it's small, it makes it much easier reason about both when they are separate.

There will be a lot of noise generated from the refactor that will drown out the new feature, but self-contained in its own PR it's a lot easier to understand the new feature and spot mistakes.

A simple refactor can likewise be likely skimmed over quickly, looking for the patterns of how it was done and assuming that most of the changes were similar and mostly just looking out for the differences.

As for accepting the feature PR but not the refactor PR, that suggests that the feature doesn't actually rely on the refactor. In that case, it's even clearer that they should be separated.

Personally, I'd always even create a new ticket for the refactor so that there's some justification for the work, and maybe you can say that this ticket blocks the one for the actual work. Maybe that's just me though, because I like to make sure that every non-trivial commit is tied to an issue in the bug/issue tracking software. This means that every bit of code is a simple "git blame" away from a justification of why it was changed.

Re: Garbage collect your technical debt (2021)

#66
post #6

It's not always easy to find the right words to describe why a feature pause to refactor is needed. This quote was a succinct statement that avoids analogy: "Postponing a small cleanup can transform it into a big cleanup because, over time, code builds up around the problem, and it too must be refactored."

This follows the "debt" analogy pretty well - it's effectively the interest you have to pay on your technical debt

Re: Garbage collect your technical debt (2021)

#67

Earlier quoted context omitted.

A couple times I've put a "if (now() > xxxxxx) fail()` in tests for this reason.

I think this is the absolute worst way of dealing with the issue, and I truly hope everyone who sees this in a code-review has the sense to reject it. By all means, have such a check as a compile-time error, but not as something that gets shipped to customers. The code you have now works, even if as a developer you'd like to refactor it. However, what if you ship this software to a paying customer, and then your comp…

> By all means, have such a check as a compile-time error, but not as something that gets shipped to customers.

Tests don't get shipped to customers.

Re: Garbage collect your technical debt (2021)

#68

Wow, what a weird feeling. The authors of this article seem like super smart and experienced guys, but the article itself is introduced with incorrect statements in the very first two sentences and slides downhill from there. "There is a kind of design distortion that happens when a team chooses to build iteratively instead of looking at all of the requirements at once. Ward Cunningham coined the term technical debt…

Ward Cunningham's original idea of tech debt (see [1], a beauty of concision at just 300 words) is that iterative development distorts your code because you start writing code before you know the requirements, but even so, it's better than waterfall. "The traditional waterfall development cycle has endeavored to avoid programming catastrophy by working out a program in detail before programming begins. We ... [instead use] the alternative, incremental growth ..."

Today, the term "tech debt" includes sloppy code, shortcuts, novice code -- really any kind of bad code. The original conception of tech debt is more limited and tied to waterfall vs iterative process choices. [2]

[1] Ward Cunningham, The WyCash Portfolio Management System, OOPSLA 1992. https://c2.com/doc/oopsla92.html

[2] George Fairbanks, Ur-Technical Debt, IEEE Software 2020. https://ieeexplore.ieee.org/document/9121630

Re: Garbage collect your technical debt (2021)

#69
post #6

It's not always easy to find the right words to describe why a feature pause to refactor is needed. This quote was a succinct statement that avoids analogy: "Postponing a small cleanup can transform it into a big cleanup because, over time, code builds up around the problem, and it too must be refactored."

In my experience you need to do ‘the big refactor’ when a codebase has to handle something big that was not part of the original design such that you find yourself having to break the simplicity, elegance, completeness, coherence, etc of the existing system by tacking on some lopsided or alternate route or structure. What you really want in place of doing the ‘tack-on’ is a new simple, elegant, coherent, etc system that can handle both the old requirements and the new. In other words, you want to do the big refactor when you have new requirements that really should have been known at the time the system was designed such that you would have done things differently to accommodate them along with all of the requirements that _were_known at the time. This is easier to do the more monolithic and strongly-typed the application is.

Naturally then, you do want to know as many requirements up front as possible, which is the basic point of the article. Even though it’s not always possible, it’s still the best path to try.

All of this ‘screw design lets just roll up our sleeves and start coding’ is a great way to end up with spaghetti code and technical debt.

The key is to do as much requirements gathering as you can up front because your initial design will address only the requirements you know about, and the initial design constraints future updates.

Re: Garbage collect your technical debt (2021)

#70

Earlier quoted context omitted.

"Always leave the code you're editing a little better than you found it" - Robert C. Martin (Uncle Bob) There's no point in refactoring the whole thing. Maybe add a longer comment explaining the logic you had to decipher when you encountered the code. Rename a few variables from foo, bar and baz tom something more descriptive etc.

> There's no point in refactoring the whole thing Deep architectural/design flaws in a codebase can't always be addressed using a series of small independent changes.

> Deep architectural/design flaws in a codebase can't always be addressed using a series of small independent changes.

Not sure that's true. At the extreme end, you introduce a replacement with a better architecture and run it side by side with the old one, incrementally switching over dependents. Of course, that may take more overall work, but maybe the incrementality is sometimes worth it.

Post reply on HN