Traditionally Drupal was developed in two parallel branches. Take the example of Drupal 7 and 8. There was the supported stable branch of Drupal 7 that received bugfixes only and there was a development branch of Drupal 8 where developers went free to change and improve things the way they have seen best. As the development of Drupal 8 was nearing release, contributed module and theme authors were asked to take a look and release new versions as well in preparation. While the new branch was built with great care, it was not proven yet in various production environments. It also often took quite some time for contributed modules and themes to update.
Traditional Drupal code vs. module compatibility
Drupal 9 gets built in Drupal 8
With Drupal 8 however, we adopted a key change in our process. Instead of working on Drupal 9 in its own confined space, we work on it within Drupal 8. What does that mean? Drupal 8 allows new backwards compatible changes and additions as well as provides ways to mark outdated functionality as deprecated. This means that Drupal 9 is built step by step in Drupal 8 and so large parts of it are already running in production environments.
There are various good reasons for this. We get our improvements into user's hands much faster this way and also get validation of the new additions and changes faster. We can improve the version people actually use while creating the next version. It hopefully also means that modules and themes get updated with the new better APIs. This way the upgrade effort is spread out from one big upgrade to several smaller ones for site owners and contributed project developers alike. Ultimately this will lead to an easy and relatively small upgrade path to Drupal 9, because Drupal 9 will mainly be updating dependencies and cleaning up our codebase by removing deprecated APIs.
Keeping module/theme/custom code up to date
So theoretically contributed modules and themes and custom code could follow this update process:
Contributed module that keeps up with core updates
This sounds good in theory, however it is worth thinking about a bit. As shown, the simplest case is the module keeps releasing versions of its own with new features and bugfixes (those are the gaps in numbers between versions shown), while it also updates regularly to new core APIs in the same stream. This is not a problem at all if it is about your custom code and you have full control over the site being deployed to. However it could lead to problems for users of modules and themes.
If you closely follow new core APIs, that also means that you keep updating the required core version number for your code. While Drupal 8.7 may be backwards compatible to Drupal 8.6, once you start using the new things from Drupal 8.7 you are not compatible with lower version numbers anymore. Keeping Drupal 8's one year security support for minor versions in mind, that also means that you would force people using your updated code to update from a Drupal core version that is otherwise security supported just to receive bugfixes as well for the module.
I would say that is fine as long as you only drop support for core versions that are not supported anymore (out of the one year support period), because sites should not be running those anyway. Any security fixes will only be released for the supported versions.
Alternatively a contributed module or theme may consider opening new branches for each core compatibility change. That puts a lot of burden on the maintainer, to keep fixing 8.x-4.x, 8.x-5.x, etc. bugs as well and I would say likely would die off the same way core support gets removed.
Why not do it all at once?
So why not do it the same way contributed module and theme developers always did? All at once!
That is exactly what we are trying to avoid. While Drupal 9 will be mostly the same as Drupal 8 (no new paradigms of development, etc.), the extent of deprecated code will not be trivial to process all at once. We only get the changes proven and Drupal 9 battle tested if developers do keep their code updated. That is why we keep publishing change notices, marking deprecated code and build tooling to support the process. If you are to do everything at once after Drupal 8.8 is released at the end of 2019 you will have quite a limited time to update before Drupal 9 is released 6 months later. For simpler modules and themes this may not be an issue, but for code with some complexity, it is best to start assessing now.
How to figure out what work am I up to?
1. If your code has automated tests, great! Not just great for the tests themselves, but because you can use deprecation testing to find issues. This will not exactly prove that your code is ready for Drupal 9 but if it is not, that will be revealed. Once you fix the issues found, you will be a few steps closer.
2. An additional tool even if you have automated tests, but especially if you don't is static code analysis. Matt Glaman implemented Drupal integration for PHPStan to do deprecation testing effectively. The good thing about this is that it relies on phpdoc annotations, which core consistently uses (as opposed to the tests which rely on trigger_error() that is missing still in many places). This is great for your custom code especially.
For example, Dries recently ran Matt's tools on his site's custom code and fixed his issues:
— Dries Buytaert (@Dries) February 3, 2019
3. Finally we have a third path under discussion where your live site would record use of deprecated code. This could find some more deprecated code use on top of what is found with tests and static analysis. A tool like this could empower site builders to asses Drupal 9 readiness as well while the prior two methods are entirely developer focused.
Working with core and contributed project maintainers
Drupal core has various issues to properly mark deprecated code now and then clean up our technical debt once Drupal 9 opens (eg. remove deprecated modules). Please search the issue queue and submit issues when existing ones are not found. The api.drupal.org listing is a good place to start to help clean up our own use of deprecated code.
Some of the methods above will get you deprecated code use in a contributed modules or themes you don't maintain. In this case it is best to coordinate with the maintainer. Open an issue if there is not one already to remove deprecated code use and discuss next steps with the maintainer. Submitting a report from the phpstan tool or test failures you found would go a long way to help the maintainer asses the extent of work needed. Depending on how many modules does the maintainer own and the extent of work required, they may or may not prefer to keep it up to date every 6 months. Offering your help updating use of deprecated code is the best way to get closer to the outcome you are looking for.
Further opportunities to learn and participate
Comments are welcome on this post. I am also happy to see you on the Drupal 9 meetings every other week in the #d9readiness channel and the Drupal deprecations office hours held by Lee Rowlands and me in the same channel every Thursday. Exact dates and times of meetings are on the Drupal core calendar. If you can make it to DrupalCon Seattle, it would be lovely to meet you at one of the daily Drupal 9 office hours or the "Drupal 9 is coming: Getting your code ready" session.