Keeping a codebase organized always requires some changes: functions, files, structures, everything someday needs a new name or place. How do we make a change without breaking anything?
Open-source codebases come with several advantages but some specific challenges. Different from a service that simply provides a more strict API, like a REST API, for example, others can call almost anything in your code. That said, you must be cautious even while renaming the simplest function.
Although all this can sound painful, it is not. Keeping your codebase organized is worth it!
Make the change in phases
Simply renaming the function is not a good idea: any solution depending on it will suddenly break. You need to warn that action will be needed and, after enough time, really implement the new solution.
To define what and when needs to be changed, we can use Semantic Versioning.
Semantic Versioning
Semantic Versioning gives meaning to the numbers of a software version, aligning expectations of what can happen in each new release.
The following table summarizes what would be the major, minor, and patch version number of a software in its 2.4.3 release, in addition to what we could expect from the upcoming 2.4.4, 2.5.0, or 3.0.0 releases.
Major (2.4.3) | Minor (2.4.3) | Patch (2.4.3) |
Changes that do break backward compatibility | New features that do not break backward compatibility | Bug fixes only |
Updating to version 3.0.0 requires attention, as the current solution can break | Updating to version 2.5.0 means that everything continues to work but with some new functionality | Updating to version 2.4.4 means some bugs were fixed |
Practical Example
Keeping both the function rename example and our 2.4.3 version in mind, let’s see how we could plan that out. Let’s split the process into some steps and define their release versions.
Version 2.5.0: Implement new code and change the old code without breaking it
- Implement the new code. It doesn’t matter if it is a renamed function or a brand-new method, the new code needs to be there so people can do the switch;
- Change the old code. In case it is just a simple switch, the old code needs to become a wrapper of the new function with a warning about the (now) wrong usage;
function new_name( $parameter ) {
// Do something
}
function old_name( $parameter ) {
_deprecated_function( __FUNCTION__, '2.5.0', 'new_name' );
new_name( $parameter );
}
Note that calling old_name()
still works but a warning was added there saying that starting from version 2.5.0 new_name()
is the function that should be called.
Version 3.0.0: Remove the old code
After warning for long enough, you can remove the old code. Attention: this removal is expected to happen in a major version.
You do not need to remove the code in the immediate following version, though. Depending on your user base and your release calendar you can wait for any major version, like 4.0.0 or 5.0.0, for example.
A project that just releases a major version per year can have a slightly different approach from a project that releases a major version per month.
Move deprecated code into separate files
One of the intentions behind these changes is to keep your codebase clean and organized. With that in mind, keeping these functions in your main files can make you uncomfortable as things can actually look messier than before.
Some development teams move these pieces of code to specific files, named deprecated.php
, for example. In addition to a readability increase, this also helps when you need to identify which code can be removed while launching 3.0.0.
The more you communicate, the better
In the example above we called the _deprecated_function()
WordPress function but that will only have an effect if the WP_DEBUG
constant is set to true.
Unfortunately, quite often these warnings are useless: in many cases, people are not paying any attention to logs. You also need to warn your users in an accessible and human-readable way.
Inform your users about important changes in all ways possible: logs warnings, items in your changelog, blog posts, and even social media if needed.
Changelog
The best place to communicate changes in your software is in its changelog. In WordPress, this type of log is kept in a specific section of the readme.txt file and is displayed on both the plugin page and in a modal inside the Dashboard.
Using Akismet as an example, this is the link to see the next version’s details.
Clicking on the link will open a modal with the changelog:
WordPress gets that information from the == Changelog ==
section of the readme.txt plugin’s file, hosted in WP.org official repository:
Usually, changelogs are divided into sections, separating what was added, changed, fixed, removed, and deprecated. An example of this division can be seen in both ElasticPress’s release notes and its changelog.
WordPress Functions
WordPress has some functions that help to inform the usage of deprecated code. Here is the list:
_deprecated_function( $function, $version, $replacement = '' )
This function is in the example we used before. A simple way to mark a function as deprecated.
apply_filters_deprecated( $hook_name, $args, $version, $replacement = '', $message = '' )
This and the next are wrappers for _deprecated_hook()
, which is not part of this list, as it should not be called directly. As the name says, this function applies a filter but also informs it is deprecated and, if available, provides its proper replacement.
do_action_deprecated( $hook_name, $args, $version, $replacement = '', $message = '' )
Similar to the last one but applied to an action.
_doing_it_wrong( $function, $message, $version )
Although not necessarily related to deprecated code, I’m adding _doing_it_wrong()
to the list as it can be super useful when the change requires more attention than a simple name change.
_deprecated_argument( $function, $version, $message = '' )
As the name implies, this is for a change in an argument.
Although always related to an argument, there are a couple of different scenarios where this function can be used:
- A change in the expected value of an argument like
'blacklist_keys'
that is now'disallowed_keys'
; - An argument that does not exist anymore;
- An index of an array argument. Let’s say before
$args['cat']
was expected but now it is$args['taxonomy']
.
_deprecated_file( $file, $version, $replacement = '', $message = '' )
Marks an entire file as deprecated. Usage examples are renamed files or files that are not needed anymore. The wp-includes/class-json.php
file calls that function, as since WP 5.3.0 the native PHP extension is required.
_deprecated_constructor( $class, $version, $parent_class = '' )
This is related to object constructors. Years ago, constructors methods in PHP had their class name but since PHP 5.4 the recommended way is using the magic __construct()
method. Class name constructors are deprecated since PHP 7.0 and should not be used anymore.
Obsolete vs. deprecated
Each language has its own specificity. For Brazilian Portuguese, for example, we don’t have a specific word for “deprecated”. People quite often wrongly translate that as “depreciated.” So, we use obsolete and deprecated interchangeably.
In English, those two words refer to different states of the code: deprecated code is still functional and present, obsolete code usually doesn’t work anymore, and/or was removed from the codebase.
Conclusion
Keeping your codebase clean and organized is essential. To make the needed changes and keep retro compatibility you need to plan the changes in phases.
Semantic Versioning can help you to plan which changes are shipped in which versions, so your users are not caught off guard.
Communicating changes through your changelogs also brings more visibility to actions your users will need to take. Specific sections listing those changes help even more.
We also saw some WordPress functions that can help to inform changes in several different scenarios.
Lastly, we saw how these terms can vary in different languages and the difference between deprecated and obsolete.