In my last post, I lamented that adopting new software technology should be easy but often isn’t. This is the case whether the software is new to us, new to the application we’re developing or even new in the set of features that will be leveraged. I focused on two common issues that plague many of these solutions that are difficult to adopt: documentation and breaking changes. Let’s focus on breaking changes first.
Small breaking changes can also be deliberate though. Think of a developer who comes across some code and, rightly or wrongly, believes the current approach is inefficient or even flat-out wrong. Fixing broken code shouldn’t affect anybody, but when you change the API, you’re not truly fixing something that’s broken, you’re actually changing something that is functional, it’s just not functioning in the way that you might prefer. A conscientious developer will not change it without providing a way through for the consumers – you and me.
What about large, wholesale breaking changes? Well, sometimes the root cause is the same as with smaller breaking changes. But usually there’s more at play. Large, wholesale breaking changes involve a decision to abandon the former way of doing things, and in so doing, they usually are choosing to abandon us to some extent. If they recognize one or more serious flaws in their previous design, they may rightfully conclude that the easiest way (for them) to address it is to make a lot of wholesale changes toward a proper design.
So does that mean we never introduce breaking changes? Of course not, but there should always be a clear way through and I would say a breaking change should never be introduced when the desire for the change is first detected.
For the example of the argument order in the JS event handler. The developer comes across this and feels that the argument order is wrong. What should she do? First, leave the original API alone. Don’ t break things like this without some notice given. Introduce a new function with the desired signature and move the implementation code there. Update the API documentation to mark the original as deprecated (most languages have formal support for this in some form) including at which version it was deprecated, indicate the replacement function to use and provide a target version which will be the earliest version at which point it will be removed. Ensure that whatever release documentation is generated includes at least a brief note regarding the deprecation. Finally, schedule the removal of the deprecation using whatever issue tracking is used for the project.
Granted, that’s a lot of work for what seems like a small change. But as developers of libraries or APIs that other people use, we should all commit to one another that we will show some consideration to what it’s like to be on the other side of that change. I respect projects that follow these standards, speak highly of them and ultimately encourage their use to others. Projects that don’t ultimately fall out of my favour and I will cease from using them moving forward.
What about the larger breaking changes we see? Well, the same approach can and should be taken, it just needs to be applied every step of the way. It may involve a lot of refactoring and moving of code around, but if you introduce a completely new way of doing something, all existing functionality should remain functional for a reasonable period of time, say at least one major release cycle. At the same time, a migration path should be identified.
This brings us to documentation. I’ll first go into a bit more detail about documentation as it relates to breaking changes. Then I’ll review general documentation standards that should be followed to ensure easy adoption.
Breaking changes should always be highly visible in release documentation – whether they’re coming in the future because something new has been introduced or because the time has come to finally drop the old approach after the notice period has expired – use emphasis, colour, dancing baby video…. just kidding on that last one, but you see what I mean. Just make it plain. If the release documentation has a summary view, some kind of visual indication should clearly indicate that a release contains one or more breaking changes there as well – agree to some standard for your project.
For the larger changes, assuming you have followed the recommended approach and not introduced the breaking changes right away but have introduced the new design, you’ve already helped the community by allowing them to upgrade and not break their application. But you want to also help them migrate to your new design. An upgrade path should be identified and fully documented. The upgrade path should be tested using the documentation as the guide and defects should be logged and fixed as part of the release. After all, you want to make sure the upgrade path works.
So what about the other documentation issues that lead to difficulty in adoption? Well, those are mostly documentation quality issues that require self-discipline, project oversight and proper change tracking.
First, any inline documentation, like JavaDoc or JSDoc should be properly maintained while the changes are being made. The best time to maintain that documentation is right then. Updating it later can result in quality issues ranging from minor oversights to completely wrong information.
It should not have to be said, but all changes including brand new functionality should be tracked in some kind of issue management system (like Github Issues or Bitbucket JIRA). Developers should not make any change without it being against something tracked and it should correspond to the details described therein. This will form the basis for a documentation review that should be included as part of the release cycle. I would actually say that this type of documentation probably should be written toward the end of the release cycle.
Why wait? I can think of a few reasons. First, is the change going to end up in the release? It may get delayed or pushed out. And when that happens, it may end up changing further since the motivation for the delay likely means more change in that area is coming. And if we generally dislike writing documentation, we definitely dislike rewriting documentation. Related to that is that features will often impact on one another. Change in one means change in another. Deferring one will mean the impacted change on the other is now missing or different. Another revision of the documentation would be required. But probably the most important reason I feel it should be done late in the release cycle is directly tied to documentation quality.
With a final phase focused on documentation, development timelines are now in the rearview mirror, the release is feature complete and the developers that are either writing the documentation or at least contributing to it significantly can give this effort their near-exclusive focus. They can do so in a deliberate way, addressing their changes one-by-one ensuring not one is missed. They can take the time to revisit the code, read the inline documentation and review test cases without the distraction of new development or reviewing defect reports or the like. They may even look at the inline documentation and see some things that should be tweaked and can do so at that time.
We’re fairly proud of how we keep our community in mind when adding new functionality into Obsidian and the quality of the documentation we produce. I believe we’ve had two breaking changes in the entire product’s history, one was in the installer and the other was in a dependent library, not the product itself. Even when our Legacy API was replaced by the Embedded API, we took it upon ourselves to continue to support the Legacy API right through the 4.x stream of releases until it will finally be dropped for 5.0.
What about you? Have you found some practices that help mitigate these types of issues? Leave a comment or drop us line, we’d love to hear from you.