Recently, I had to dig into the topic of code deprecation in JavaScript. I feel like this topic gets less coverage even though it may a play key role in certain projects, especially when working in bigger teams or dealing with external APIs.
In JavaScript-land, I don’t know of any true industry standards for deprecating JavaScript. It could be different per any team, library or vendor.
That’s why my goal here is to sum up my findings and thoughts on this topic, alongside some good practices when it’s time to mark a JavaScript method obsolete.
What does “deprecation” actually mean?
First, let’s start by clarifying that the deprecation is just a status applied to a software feature. It indicates that this feature should be avoided, typically because it has been superseded.
Deprecation may also indicate that the feature will be removed in the future. Features are deprecated—rather than immediately removed—in order to provide backward compatibility, and give programmers who have used the feature time to bring their code into compliance with the new standard.
Additionally, a deprecated feature suggests that there won’t be any further development from this point onward. It shouldn’t work any different than it did in a previous version (unless documentation explicitly states something else). So, generally, it should be the same as it was when the deprecation action happened.
It may or may not work in the latest version—no guarantees!
However, since there aren’t any true industry standards that are strictly followed inJavaScript-land, this could be slightly different per team, library or vendor.
When to deprecate code and when to delete it?
It’s important to note that a deprecated software feature or method is still a part of the software! Consider the “deprecated” label as just a status of the code. Whether the software feature will actually be removed in the future depends on what that particular software team decides.
In my opinion, large teams or projects relying on external APIs or libraries ought to deprecate first, then remove later (after a reasonable time, however you define that). At the very least, give at least one major version bump before actually removing the deprecated code so users have a chance to adjust to the change.
You might want to look at Semantic Versioning, a simple set of rules and requirements that dictate how version numbers are assigned and incremented. Given a version number MAJOR.MINOR.PATCH
, increment the MAJOR
version when you make incompatible API changes, MINOR
version when you add functionality in a backwards-compatible manner, and PATCH
version when you make backwards-compatible bug fixes.
If your software is rapidly changing and evolving and you are deprecating a feature, try to communicate with your project manager if this feature is expected to be resurrected later. If you choose to deprecate, instead of delete, it might be a lot easier for you to revert should you need to.
For smaller teams or projects with internal methods and APIs, go ahead and remove first rather than deprecate. Sometimes it just doesn’t make sense to waste time and deprecation only increases the complexity just for the sake of following best practices.
How to mark a method obsolete
Here are five good practices I have found the most useful:
- Add a
@deprecated
JSDoc flag. - Mention the version that the method was deprecated.
- Figure out a timeframe for when this method will be deleted, including which version it will take place. Otherwise, based on my experience, it stays forever 🙂
- Use comments liberally to explain the implementation for the benefit of other developers or your future self. This is extremely useful if your use-case is writing a library that others use as a dependency for their work.
- Add a console warning message that indicates that the function is deprecated.
Here’s a more practical example where I use all five practices:
/**
* A magic method that multiples digits.
*
* @deprecated [#1] since version 2.3 [#2].
* [#3] Will be deleted in version 3.0.
* [#4] In case you need similar behavior, implement it on you own,
* preferably in vanilla JavaScript
* or use the multiplyTheSameNumber method instead,
* if the same number needs to be multiplied multiple times, like so:
* multiplyDigits([5, 5, 5]) === multiplyTheSameNumber(5, 3)
*
* @param {array} _digits - digits to multiply
*/
function multiplyDigits(_digits) {
console.warn("Calling a depricated method!"); // [#5]
// ....
}
To avoid repetition in the console warnings or in case you plan to deprecate multiple methods and you have their replacements, it might be more convenient to use a helper:
/**
* Creating a deprecated / obsolete behavior for methods in a library.
* [Credits]{@link: https://stackoverflow.com/q/21726472/1333836}
*
* @param {function} replacementFunction
* @param {string} oldFnName
* @param {string} newFnName
* @return {function}
*/
const Oboslete = function(replacementFunction, oldFnName, newFnName) {
const wrapper = function() {
console.warn("WARNING! Obsolete function called. Function '" + oldFnName + "' has been deprecated, please use the new '" + newFnName + "' function instead!");
replacementFunction.apply(this, arguments);
}
wrapper.prototype = replacementFunction.prototype;
return wrapper;
}
Wrapping up
I’d suggest getting your team on the same page and inherit deprecation practices that make the most sense for your project or use case, whether it’s adopting the practices we’ve covered here or others.
Note that there are certain times when deletion makes more sense than deprecation. Sometimes, investing efforts to deprecate something simply aren’t worth it. Again, it’s totally up to you and what makes the most sense for your project.
Do you know other good practices when marking a method obsolete in JavaScript? Let me know in the comments!
Credits
The ideas I shared here were inspired by comments I found on Software Engineering Stack Exchange and on StackOverflow.
Might be nice to make sure the console warning is only issued once if the deprecated function is called multiple times; otherwise it clutters up the console uselessly.
Well, you shouldn’t rely on a deprecated function anyway, so I would say the annoyance from warning cluttering up the console can motivate you to deal with the problem source. Besides, you can set the filter to hide warnings if you have to (in chrome at least).
Useful article but might be nice to have an illustration of how to actually use the helper you’ve created as it’s not explained (oh, also you made a typo in the name of the helper, should be Obsolete).
Just one side note when version is 0.x.x there is no need to deprecation because that version don’t have stable API yet, so the method can just be removed.
Some of this depends on who your users are.
If you have an internal library used only by your own developers, then a short normal -> deprecated -> deleted cycle is fine. If you have a library that is in use in many places then deprecate all you want, but don’t delete.
Instead you publish a new library, with a new name. The new library explicitly declares that it doesn’t support the old library’s function X, and that syntax has changed for the use of Y and Z.
New library should also have a guide on code migration.
The last publication of the old library gives pointers to the new one, and to the incompatibility readme.
I’ve gotten burned by this frequently with Apple. Old program doesn’t run on new version of the OS because they have removed functions from standard libraries.
It can be useful if a library has some kind of optional logging feature. At one level it just records if a library was used, (as opposed to just being loaded.) At a more detailed level it logs what functions were called. This can be installed as a sleeper function if the library or it’s parent calling program are network aware. On the first of the month it sends a single UDP packet to the developer’s server, ‘Do you want logs?” The server responds either “no go to sleep for X days/months/years/forever” or “yes, log X% of calls matching regex {expression}
This gives you a way to see if people are still using the library despite it being deprecated.
Shouldn’t the wrapper function return the result of
replacementFunction.apply
?