Howto:Implementing backward compatibility in Nasal
This article is a stub. You can help the wiki by expanding it. |
Objective
Let's assume an old API got phased out, and a new one is made available - you'd like your script to run using either version. This is where it makes sense to introduce a "compatibility layer" that checks whether the old API is still available or if only the new one is available. This can be accomplished easily using just a handful of lines of Nasal code.
Background
Backwards compatibility is a constant problem for anyone developing software. FlightGear development is moving quickly forwards at the moment, and breaking compatibility is something that we all try to avoid - but sometimes moving forwards is more important for the core code because there are always things that cannot be kept compatible[1]
nasal methods that start with a underscore _ means it should be treated as private. So not meant to be called outside the namespace/class. In other words if aircraft depend on it, be prepared to change stuff if its gets removed/changed.[2]
As a general rule, all underscore prefixed Nasal methods are ‘private’ APIs and should be considered implementation details. Not to say there aren’t some which could be made public, but if it starts with an underscore, we might change or remove it and assume nothing will break.[3]
Noone breaks backward compatibility needlessly, but it's also not sacrosanct and yes, it can happen. When a developer is discovering a roadblock on the way forward caused by how things were done up to now, there's always two choices: Either not to go forward because the pain caused is too large or move forward, discover the breakage this causes and fix everything on the FG repository over time (and often this gets discussed here). Which actually is one of the important arguments for licensing GPL and including any work with the FG project rather than licensing more restrictive and keeping under your control - work on the FG repositories is considered the responsibility of the FG developers and if we break it, we fix it. Work on external repositories is the responsibility of the respective maintainers and even if we break it, they're expected to fix it themselves. That's the price-tag that comes with having complete control over your own repository and having a different license. [4]
- any* underscore prefixed pieces are NOT party of the supported compatibility API. There have to be parts of the API we keep compatible as much as possible, but there also have to be interfaces inside which the developers can adjust the implementation. Since nasal lacks a protection model, we can only do this by naming convention, not anything strict. So, as several people said, compatibility is hard, but important, and we expend plenty of development effort on it. But we can’t try to keep compatibility on APIs we have explicitly declared as private, otherwise what is the point of making that declaration at all?[5]
Proof of Concept
var haveAPI = func(symbol, type='func') {
var handle = globals[symbol];
if (handle == nil or typeof(handle) != type) return [0, "not found"]; # API not available
return [1, "found"]; # API found
}
var APIList = ['settimer', _setlisten', 'setIdiot', 'print'];
foreach(var api, APIList) {
print("API name: ", api, " Status: ", haveAPI(api)[1] );
}
At this point, it would also be possible to add unit tests for each API and actually execute those one by one.
References
References
|