Live data from Hacker News

Move Fast and Fix Things

githubengineering.com

71–80 of 95 posts

Re: Move Fast and Fix Things

#71
post #29

> Finally, we removed the old implementation — which frankly is the most gratifying part of this whole process. On average, I get much more satisfaction from removing code than I do from adding new code. Admittedly, on occasion I'm very satisfied with new code, but on average, it's the removing that wins my heart.

I've long been dreaming of adding the following tagline to my resume: "Fixing bugs by removing code since 2006"

Re: Move Fast and Fix Things

#72
post #23

I'll highlight something I've learned in both succeeding and failing at this metric: When rewriting something, you should generally strive for a drop-in replacement that does the same thing, in some cases, even matching bug-for-bug, or, as in the article, taking a very close look at the new vs. the old bugs. It's tempting to throw away the old thing and write a brand new bright shiny thing with a new API and a new da…

You can break this down even more. As we speak, I'm "replacing" old code by just writing a wrapper around it with the new API it should have. Then I'll rewrite it without the wrapper, bug-for-bug. And then I'll actually fix the bugs.

There's a simpler method than this that provides even more surety, used by e.g. LibreSSL:

1. Start writing your new implementation (or heavily refactoring your old implementation, whichever), but in parallel, for each legacy function you remove, write an equivalent "legacy wrapper" function that implements the old API (and ABI; you have to return the same structs and all) in terms of the new API.

2. As you develop the new code, continue to run the old code's tests. (This shouldn't require any work; as far as the tests can tell, the codebase containing all of {the new code, what's left of the old code, and the legacy wrapper} presents exactly the same ABI as the old codebase.) The old tests should still all pass, at every step.

3. Once you're finished developing the new code, and all the old code's tests are passing, rewrite the tests in terms of the new API.

4. Split off all the legacy-wrapper code into a new, second library project; give it the new "core" library as a dependency. Copy all the old tests—from a commit before you rewrote them—into this project, too. This wrapper library can now be consumed in place of the original legacy library. Keeping this wrapper library up-to-date acts to ensure that your new code remains ABI-compatible; the old tests are now regression-tests on whether a change to the new "core" library breaks the legacy-ABI-wrapper library.

5. Document and release your new core as a separate, new library, and encourage devs to adopt it in place of the legacy library; release the legacy-wrapper (with its new-core dependency) as the next major version of the old library.

When all-or-nearly-all downstream devs have transitioned from the legacy wrapper to the new core, you can stop supporting/updating the legacy wrapper and stop worrying about your updates breaking it. You're free!

In LibreSSL, if you're wondering, the "new core" from above is called libtls, and the "legacy wrapper" from above is called libssl—which is, of course, the same linker name as OpenSSL's library, with a new major version.

Re: Move Fast and Fix Things

#73
post #23

I'll highlight something I've learned in both succeeding and failing at this metric: When rewriting something, you should generally strive for a drop-in replacement that does the same thing, in some cases, even matching bug-for-bug, or, as in the article, taking a very close look at the new vs. the old bugs. It's tempting to throw away the old thing and write a brand new bright shiny thing with a new API and a new da…

You can break this down even more. As we speak, I'm "replacing" old code by just writing a wrapper around it with the new API it should have. Then I'll rewrite it without the wrapper, bug-for-bug. And then I'll actually fix the bugs.

Yup, this is how I do it too, generally there's an implementation that needs an API and a new implementation.

I've generally found that do the smallest possible thing that results in an improvement to be the best way forward.

Re: Move Fast and Fix Things

#74
post #23

I'll highlight something I've learned in both succeeding and failing at this metric: When rewriting something, you should generally strive for a drop-in replacement that does the same thing, in some cases, even matching bug-for-bug, or, as in the article, taking a very close look at the new vs. the old bugs. It's tempting to throw away the old thing and write a brand new bright shiny thing with a new API and a new da…

There's always more use cases for the existing code than you think there are.

There's always more corner cases handled by the existing code than you think there are.

There's always more bug fixes in the existing code than you think there are.

Combine all of those, and writing a replacement is always much harder than you expect it to be.

Re: Move Fast and Fix Things

#75
post #62

Does anyone know what an "O(n) issue" is? I can think of a few possible meanings in the usage here, but I've never heard it before and they all seem wrong.

"O(n) issue" is generic for some asymptotic optimization issue. It doesn't literally mean it was running in O(n) and there was a better O(n log n) solution. It could be O(n^2) and there was an O(n) solution or O(n) when there was an O(log n) solution or O(n!) when there was an O(n^2) solution. It just means there was a poor algorithm choice / design decision for some part of the code when a more efficient option is available.

Re: Move Fast and Fix Things

#77

When running with Scientist enabled, doesn't that mean you will add both the runtime of the old/new implementation instead of just one implementation? I could see this begin ok in most cases where speed is not a concern, but I wonder what we can do if we do care about speed?

The article mentions running both code paths in parallel, so it becomes a matter of whether they have enough capacity for an extra cost for a (rather specific) path of their code. For this specifically I would imagine the cost is negligible in the big picture, considering regular activity far outnumbers pull/merge requests.

Re: Move Fast and Fix Things

#78

This is tangential, but given the increasing functionality and maturity of libgit2, I wonder if it would yet be feasible to replace the Git command-line program with a new one based on libgit2, and written to be as portable as libgit2. Then there would be just one Git implementation, across the command line, GUIs, and web-based services like GitHub. Also, the new CLI could run natively on Windows, without MSYS.

At GitLab we're very grateful for all the work that has been done on libgit2 ​by GitHub and others, and we plan to move to it completely.

Re: Move Fast and Fix Things

#79
post #39

Earlier quoted context omitted.

>but it's also true that over a long enough timescale (100 years, to trigger a reductio ad absurdum) there is a very high risk that not replacing or rewriting that code will sink your technology and possibly your organization. Could you elaborate on what basis you claim this as a truth?

It's largely conjecture, admittedly. But there are very few pieces of software that last 40 years. Most companies don't plan to be out of business in the next 40 years. And many tech companies have gone out of business in even the last ten years because they weren't agile enough to adapt quickly to new technological changes.

While I agree that in the abstract, technical debt can catch up to you, I'm just not sure that its impact is necessarily such that it cannot be contained or mitigated.

I mean UNIX is still around for 30 odd years. It hasn't sunk. It carries tremendous technical debt, in terms of bad design, in terms of implementation bugs that need to be carried forward, etc.

My sense is that the more organizations that depend on a piece of tech, the more chance there is that it is going to age well, warts and all.

Re: Move Fast and Fix Things

#80
post #52

Earlier quoted context omitted.

One strategy that I've seen work for this kind of deep architectural change is to write the new system, then write a shim that provides a compatibility layer (however hacky and ugly) to emulate the old system. This lets you test the new system without then having to also test everything that the system interacts with. And then , start replacing usages of the shim with direct interaction with the newer prettier system…

I believe you are describing http://www.martinfowler.com/bliki/StranglerApplication.html and particularly http://www.martinfowler.com/bliki/AssetCapture.html

Yup! Loved that article when I first saw it on HN - I felt it described well a lot of upgrade projects undertaken at my then-employer.
Post reply on HN