6 ms·
You mean... iterative development? It is simple, you break up whatever improvement work you want to get done into small enough pieces so that these pieces can
by onetimeuse92304 3y ago
You mean... iterative development?
It is simple, you break up whatever improvement work you want to get done into small enough pieces so that these pieces can be included to use up any capacity margin during your development cycle.
You always want to have some spare capacity to fill with improvement work. This way you can manage unplanned work by temporarily reducing your improvement work rather than overrunning your project.
The important part is that any improvement needs to be broken up into small enough pieces that each can be shipped separately. You don't want half done, unshipped work to be a continuing feature of your development process putting an overhead on everything you do.
As to refactoring, best refactoring is usually done in small increments. I look at the codebase, I see something I do not like, I pick one thing I can fix here and now. Lather, rinse, repeat.
Some of the worst mistakes I have seen that really put the existence of a project in question was bright developers getting their managers to agree on a huge step change and that change never getting done. This usually is some sort of rewrite even if devs try to hide the rewrite. Usually they say "let's create a brand new service where we will keep everything clean and then we will slowly migrate the functionality from the old to the new". And it rarely works.
Because of these experiences, I have pretty much banned rewrites at all projects I work with. I now understand that a rewrite carries with it a huge amount of various risks and that people are biased to overestimate the costs of the ugly stuff they see in the old application and underestimate the risks of things they are not aware of when deciding to do the rewrite.
- Kinrany 3y ago> Usually they say "let's create a brand new service where we will keep everything clean and then we will slowly migrate the functionality from the old to the new". The "create a new service" part or the "slowly migrate" part? The latter sounds like it would be iterative in exactly the same way
- The_Colonel 3y ago> The latter sounds like it would be iterative in exactly the same way I think you're comparing different things. The alternative to "create a new service" is "refactor existing service iteratively". The alternative to "slowly migrate" is ... nothing. You don't need to do this if you refactor the existing service (incrementally). If the service has state (like DB), then this "slowly migrate" usually becomes crazy complex.
- berkes 3y ago> best refactoring is usually done in small increments. Yes. And contrary to many people, I'm fine with the (eternal?) limbo that we then end up with: parts of the codebase using the new stuff, parts still using the old stuff. I actually prefer that. Just add proper documentation, if the language/framework supports it: add deprecation notices and move on. Through e.g. "refactor on touch" we can move the old code to the new if we are there anyway, changing other stuff. Sometimes I'll find that after a while, the old code is used so rarely that now's the time to just rip it out entirely - a small refactoring. Sometimes I'll find that the important pieces - important business logic, critical performance path, often touched code, exposed parts etc - can be changed to use the better/more-performant/more-testable/safer code. I think just leaving the last 10% is preferable over postponing the entire refactor untill we can also move the last 10%. It's part of what I tend to call "Continuous Incremental Refactoring".
- petalmind 3y ago> add deprecation notices one thing that I think needs to be more common is that you should not write simply "this method is deprecated". You must always say "...is deprecated *in favour of* FOO".
- berkes 3y agoyes! And it's one of the first things I'll add to the lib/framework/tools/utils if it doesn't exist: a mechanism to mark code deprecated. It should require a description, the item that should be called instead, and optionally a URL and optionally a time/version after which it will be considered an error instead of a warning. The deprecation should be configurable (through ENV vars, e.g. DEPRECATIONS=fail|warn) so that I can run a test-suite or some local dev env and have it blow up when it hits a deprecation. Useful for when working on them and to raise awareness. Such a method/macro/function/annotation isn't that hard in most languages: just a proxy calling the deprecated method with the arguments passed in and then formatting a message and logging or raising that.
- quietbritishjim 3y ago> And contrary to many people, I'm fine with the (eternal?) limbo that we then end up with: parts of the codebase using the new stuff, parts still using the old stuff. That is fine when, as you described it, there are only two types of code: one still using old technique/technology A and the other using newer version B. Where it becomes a problem is when you haven't finished the transition and realise you need to start moving on to C, or you leave and the new person doesn't understand the difference (and probably also has their own desired replacement). Before you know it, you have three or even more styles of code in play at once, with diminishing ability to keep moving to the latest version. This is sometimes called the lava layer antipattern [1] [1] https://mikehadlow.blogspot.com/2014/12/the-lava-layer-anti-pattern.html?m=1 https://mikehadlow.blogspot.com/2014/12/the-lava-layer-anti-...
- fl0ki 3y agoI've pulled off several large, successful rewrites so I'm going to respectfully disagree, even though I agree I have seen exactly the failed rewrites that would motivate someone to ban rewrites. The crux of my argument is that some projects really are costly enough to maintain that a rewrite is an overall lower cost (pricing in risk), while others are fixable in-place and a rewrite is an unnecessary cost, and it's rarely obvious which is which for all of the same reasons that project planning and cost estimation are infamously difficult. > The important part is that any improvement needs to be broken up into small enough pieces that each can be shipped separately. You don't want half done, unshipped work to be a continuing feature of your development process putting an overhead on everything you do. This is an ideal but it should not be a requirement. If you "need" this for any improvement, you're going to miss out on a lot of the biggest possible improvements because they have exactly the far-reaching impacts that make them harder to pull off but also worth much more when you do merge them. If you reject any such refactorings, you'll only improve in small local ways and never large global ones. Best case, you can try to get the best of both worlds by supporting both old & new interfaces to the same improved implementation, slowly migrating old edges to new ones. That's also just an ideal, and there are many ways it can prove impractical, e.g. subtle divergence between old and new types which is useful for the new implementation but makes it harder to interoperate with the old one. The fact is, sometimes a design has a big enough problem that a large change will pay off, and sometimes an implementation can be bad enough that this change cannot safely be made within the existing implementation. Usually when I've seen those, it's because the regression testing was so inadequate that you can't make changes with confidence, and yet the code isn't factored in a way to introduce the testing without the refactoring itself facing risk of regression, etc. and it's a total deadlock that destroys a project. Many, many real world projects end up in this state. Often it's because leadership prioritized deadlines more than quality, and promised to "fix it once it's launched", but then nobody was confident changing something that sorta kinda worked. If you don't invest in regression testing from the start, it'll always be too risky to add later. At that point, you may as well build a new project factored for safe maintainability including its own regression testing that will pay off forever, including testing that it does not regress on any use cases you can reproduce from the old project. You have to do something like this to break the deadlock, so it may as well fix other deficiencies as well. I have saved several mission-critical FAANG projects this way, and even my managers agreed that it was a huge success despite the general resistance to rewriting large projects. It even takes less time than people will assume, because once you factor the new project for confident maintenance without regressions, you become far more productive working on it than anyone could ever be on the old project. You get there sooner than you expect to, and it pays off more than anyone can imagine because they're so used to the problems of the old project. I'd also like to add that while a bad project can limp along for many years, it faces a different kind of problem good managers should fear. Only a very capable engineer can maintain such a project with a low defect rate, but they do it with great stress and frustration on their end, because truly poor maintainability hurts even the best engineers. The better the engineer, the more they feel the deficiencies of the project, and the more likely they are to want to leave. An engineer like that MAY be able to pull off a rewrite, but if you ban it, they're more likely to leave than play along.
- juliogreff 3y ago> I now understand that a rewrite carries with it a huge amount of various risks and that people are biased to overestimate the costs of the ugly stuff they see in the old application and underestimate the risks of things they are not aware of when deciding to do the rewrite. I see this a lot with large scale refactorings, where people usually start with the easiest case and work their way up in the complexity ladder, and by the time they reach the hairy stuff it's basically time for another refactor to account for what's been missed. I started to look at any proof of concept or refactoring proposal that doesn't take into account the complicated bits, the corner cases, with a lot of suspicion.
- fl0ki 3y agoExactly, this is perfectly put and something I've hit several times myself. I have learned to do the exact opposite: sprint to structure the project just enough to begin to tackle the hairy stuff as soon as possible, so that any changes required for the hairy stuff are being made without a ton of other code and tests to update along the way. Recent example: the architecture that worked well to generalize validations & mutations for individual records of different types had to change drastically to support relational cascades between multiple records of different types, but it was only a day of refactoring to do it early, instead of a month if it had been left to later.
- keriati1 3y agoFor projects where the estimated rewrite duration exceeds three months, we have employed an iterative approach to refactoring for several years. This methodology has yielded pretty good results. We also utilize a series of Bash scripts designed to monitor the refactoring process. These scripts collect data regarding the utilization of both the old and new "state" within the codebase. The collected data is then dumped in Grafana, providing us with a clear overview of our progress.
- Kinrany 3y agoOh, the idea of tracking the state of the refactoring process with small scripts is very cool. Obvious in retrospective too. These scripts would be useful even if they're only like 90% correct.
- cpeterso 3y agoAn example: “Are We ESMified Yet?” is a Mozilla dashboard tracking an incremental Firefox code migration (1.5 years and counting) from a Mozilla-specific "JSM" JavaScript module system to the standard ECMAScript Module "ESM" system. Current ESMification: 96.69%. “Are We X Yet?” is a Mozilla meme for dashboards like this. https://spidermonkey.dev/areweesmifiedyet/ https://spidermonkey.dev/areweesmifiedyet/
- zogrodea 3y agoI saw the phrase “are we X yet” used in the Rust community (is Rust ready for games or whatever) but never realised the phrase’s origin with Mozilla. Thank you for the little piece of history.
- cpeterso 3y agoAFAIK, https://arewefastyet.com/ https://arewefastyet.com/ (AWFY) was the first, registered in 2010. “Are We Meta Yet?” http://www.arewemetayet.com/ http://www.arewemetayet.com/ is an incomplete and outdated list of some of these dashboards. Some domains expired and are now squatted.
- 3y ago
- BurningFrog 3y ago> "let's create a brand new service where we will keep everything clean This is maybe the core delusion here. Somehow people think that the next project they start from scratch will have clean well factored code, even though empirically all their previous projects have not.
- steveBK123 3y agoEvery single time. The code base is what it is because of the business requirements, management, priorities and timescales demanded. Any new system will end up in the same place very quickly. Sometimes I've even seen management in on the delusion and promising "this time is different" to the devs.
- Jaygles 3y agoThe first version evolves over time to new requirements, which introduces cruft as you can never prepare your code to be extended to every new requirement. Subsequent versions/rewrites benefit from that history. Using the existing version as a complete spec, and the history of evolutions as a clue to how to set up extendability, gives re-writes a distinct advantage in producing better code.