But yes, I can definitely imagine cases where trying to apply this method leads to a chicken-and-egg problem.
I don't program in Rails, but, in the languages that I do/have I don't recall a situation where it was _likely_ that a function or API that replaces something deprecated in an older version, was already available in that older version.
The only case I can think of, that happens regularly, is that something would be deprecated and marked as such, with a replacement available at the point of deprecation.
That deprecated usage should be replaced before it is removed; and if we're talking about skipping multiple major versions over a long period, the replacement likely didn't exist in the older version, so this method still wouldn't work.
the answer to that problem in this approach is to do multiple step upgrades, rather than skipping
serious frameworks/languages do not remove methods in the same version that introduces a replacement, that's the point of having a deprecation mechanism in the first place
That's what I said
> That deprecated usage (of the thing being deprecated) should be replaced (in your code, with the thing that replaced it, in the language/library) before it (the deprecated thing in the language/library) is removed (from the language/library);
The example in OP was that they were upgrading major versions after-the-fact, so they've missed the transition period (say, 2 major versions, after which deprecations are removed).
In my experience it has often not been beneficial to try and upgrade through multiple versions after the fact, when instead a big-bang update provides opportunities to improve the overall structure and quality of the code, because those things that were deprecated were done for a reason, and the thing being upgraded may have improved significantly in structure, usage, performance, etc in that time.
It's amazing that exist people whose personal experience alone isn't enough to think that way. But well, just to be clear, no all libraries do not agree on any way of managing updates.
If you go from version 4 (say), to version 7 (say,several years later), something could have been deprecated in 4/5 and removed entirely in 6/7, you've missed the transition period between the thing being deprecated and it being removed entirely.
There's no way you're going to use the method in OP to "fix" all of the problems you'll encounter going from 4 all the way to 7 without just going to 7 and fixing everything that's broken.
As you do this more and more often the dependency trees will become more clear until eventually you can generally skip the "sit down and try to do X" step, or at least come close.
An example is, you had code that worked on a local server but now you want to run it in the cloud, "natively". You sit down to start doing the conversion, but discover that you had some files you wrote to disk, and to be "cloud native" they ought to live in S3. You stop the big-bang "native cloud" conversion. You work on abstracting away what you do with the files into some sort of interface (whatever your local language calls it) which backs to something that implements that interface on a local file system. (In this particular case, there's a good chance someone has already written this abstraction layer and you just need to go pick it up, but it's not out of the question to write it yourself either; often the price of the two options are comparable.) At all points in this process you can ship the results, because even if it's ugly behind the scenes due to a half-done conversion, and you can, say, read files but not write them through the new interface, it's still shippable. And you can ship it when the only implementation of the interface is to the file system. But later on, you can implement something that backs to S3 and slip it in to the interface, and even though it's likely you'll still find a thing here and a thing there that need to be tweaked, it's far less disruptive than if you had stuck with the initial "make a big PR to convert to cloud native all in one go".
It is also more work. Although perhaps less "more work" than you might anticipate. A lot of the process of discovering what surprisingly deep dependencies on file system behaviors you had and untangling all of them is really the same in both cases. The saved effort of writing an explicit interface and jumping straight to the new thing can be easily lost in the additional effort of trying to juggle the intermediate phases (which can get quite complex) and feature flags and the bugs you introduce in the process. Even if you don't have a "Mikado" philosophy, you may well still find the process I outline in the previous paragraph is the one you adopt anyhow! In which case, why not do it independently and isolated, without a big bang?
I haven't thought "Mikado" explicitly in my refactoring plans, but really, the only "big bang" rewrite I'm willing to undertake is when my back is forced against the wall and I simply have to switch languages. Though there can still be some value in trying to straddle some services across in the meantime, even that is itself often a rather large rewrite on both sides. Otherwise, there is almost always some sort of path to doing your rewrite incrementally, one step at a time, without ever stopping main development.
If you look at the graph of "value obtained over time", it's really a no brainer to adopt this approach, too. Generally these incremental refactorings also have intermediate value they deliver. Shipping that value now means you start benefiting from it before the big bang cutover. It also means you can stop at any time and just pocket the gains you've made, with no risk of having all this work go to nought. The big bang rewrite needs to be a desperate last-ditch effort, not the first thing you reach for.
Honestly, they generally aren't even as fun as developers anticipate; they may be more fun than an incremental refactor in the first month, but the eternal grind of discovering some corner case that the old code base handled and you completely whiffed on and now you need a major rearchitecting gets to be not fun in the third month and every month subsequently. Over the course of a full project I think the incremental refactors are actually more fun.