4 ms·
I've come to the same conclusion. Infrastructure and libraries breaking backward compatibility should not happen, it is not a sign of technical quality. It is a
by jnxx 5y ago
I've come to the same conclusion. Infrastructure and libraries breaking backward compatibility should not happen, it is not a sign of technical quality. It is almost always possible to provide wrapper interfaces which contain the old behavior and function signature as a special case. And if that does not work, it is better to release a different library.
There is also one interesting insight I had when looking at error codes and exceptions: Normally, backwards compatibility is broken when you take things away from an interface - functions from a library, values from an enumeration type, and so on (Rich Hickey has made a brilliant talk about this). However, the general thing is that backward-compatible change must never narrow pre-conditions, nor widen post-conditions.
This means that it is not OK to just add extra error codes to the return values of a function interface, or to add new classes of exceptions. This breaks backward compatibility.
You can of course add enumeration values, optional arguments, keyword arguments and such to function call arguments, and these are great ways to make interface changes backwards-compatible.
Another insight is that backward-incompatible changes tend to cascade up larger dependency graphs. The broader and deeper the dependency graphs becomes, the more probable it is that a backward-compatible change (e.g. requiring a newer dependency) becomes itself a breaking change up the dependency chain. The philosophy in this age seems to be that of coourse the other library authors should "just fix their stuff", they get the responsibility of upgrading to the new version of the dependency. I do not agree to this at all - I think if something changes in a component, and this breaks the software, the responsibility is always within that component, and nowhere else. If some change in the component causes breakage, this is a breaking change, by definition. And this is also true if it is an upgrade from Qt4 to Qt5, or OpenCV, or imageio-py2 to imageio-py3 or such.
One could increment the mayor version number but this does not fix the problem. What fixes the problem is to not break stuff in the first place.
I am quite convinced that as in the future, ever more programming will be done in libraries and components, there will be ecosystems which do not accept that kind of breakage. Linux is already a good example, and it is wildly successful.
- asdfasgasdgasdg 5y agoI think widening post conditions may have to be possible in some cases. In my experience, programs should only depend on three possible error states: ok, eagain, and everything else. I guess it's fine for library authors to promise more, but I'd only do so in the case where I think that calling code can respond with more specificity to other conditions, which I think is pretty rare.
- jnxx 5y agoMight in some cases be OK, but it is also how you get "general protection fault" and rampant uncaught exceptions in some earlier versions of Windows.
- asdfasgasdgasdg 5y agoIn the protobuf library, there are some protections against cases like this. For example, they add extra enum values that are explicitly labeled as "do not use" to prevent switch statements from assuming that an enum is complete and future-proof. https://github.com/protocolbuffers/protobuf/blob/master/src/google/protobuf/compiler/cpp/cpp_enum.cc#L131-L134 https://github.com/protocolbuffers/protobuf/blob/master/src/...
- nawgz 5y ago> And if that does not work, it is better to release a different library Is that really a solution though? You're just going to end up with an unsupported old version and a supported yet breaking new version which probably doesn't even have upgrade guidance... Not sure I see it. I think libraries should work hard as you say to not break backwards compat, but I also think the library author has the right to change some function signatures when they change major behaviors too
- jnxx 5y agoYou can just fold the old interface with the old semantic into a special case of the new interface, and provide a wrapper function with the old signature. This avoids code duplication. > I also think the library author has the right to change some function signatures when they change major behaviors too When the the semantics change, it is already a backwards-incompatible change anyway, so it is cleaner to provide an extra function name for that new version. There might be extreme cases where it is not possible or desirable to keep backwards compatibility, such as hardware bugs in Intel CPUs speculative execution, or APIs which turn out to be so thoroughly botched that they cannot be made safe. But these are exceptions. One thing is to compose from backwards-compatible components makes it much much smother and easier to upgrade to new versions in a system which has many components. Paradoxically, it makes upgrading easier, and this is what library authors ultimately really want from users. While breaking compatibility does not only leads to a rat-tail of consequential breakage and forced upgrades which leads to more breakage, and makes everyone hesitant to do even small upgrades, ensuring backwards-compatibility leads to people upgrading without fear that they are interrupted for weeks and months.