Hacker News new | ask | show | jobs
by kazinator 3689 days ago
In the TXR language interpreter, I have a -C option which takes a numeric argument: it means, simulate the old behaviors of version N. If you don't specify -C, you get the latest behavior.

Throughout the code, old behaviors are emulated, subject to tests which look similar to this:

   if (opt_compat && opt_compat < 130) {
     /* simulate 130 and older behavior */
   } else {
     /* just the new behavior please: -C was not specified,
        or is at 130 or more. */
   }
  
I think that tying specific old behavior to a proliferation of specific options is a bad idea. It does provide more flexibility (give me some old behavior in one specific regard, but everything new otherwise), but that flexibility is not all that useful, given its level of "debt".

The purpose of compatibility is to help out the users who are impacted by an incompatible change; it gives them a quick and dirty workaround to be up and running in spite of the upgrade to the newest. They can enjoy some security fix or whatever, without having to rewrite their code now.

However, they should put in a plan to fix their code and then stop relying on -C.

If users are given individual options, that then encourages a behavior whereby they use new features with emerging releases, yet are perpetually relying on some compatibility behaviors. This leads to ironies: like being on version 150, and starting to a feature that was introduced in 145 and changed incompatibly in 147 and 148---yet at the same time relying on a version 70 behavior emulation of some other feature. Hey we don't care that this new thing was broken recently twice before being settled down; we never used it before! But we forever want this other thing to work like it did in version 70, because we did use it in version 70. It's like using C++14 move semantics and lambdas, but crying that GCC took away your writable string literals and -fpcc-struct-return (static buffer for structure passing).

It's very easy to hunt down the opt_compat uses in the source code just by looking for that identifier, and the version numbers are right there. If I decide that no emulation older than 120 will be supported in new releases going forward, I just grep out all the compat switch sites, and remove anything that provided 119 or older compatibility. The debt is quite minimal, and provides quite a bit of value.

1 comments

Is there any way you could explain that again? I don't quite get what you are doing.
Without knowing which part you don't get, there is a risk I just repeat everything in a scrambled order!

The highlights are:

We have software (a programming language and its library) that is versioned in a simple, linear way: it goes from version N, to N+1, to N+2 and so on.

Users who are using version K now depend on some features. Suppose the behavior in version K+1 changes some of the features. The users will be rightfully unhappy; they upgrade to K+1 and things work differently, breaking their code.

To anticipate this, we can have a command line switch or environment variable whereby users can request "please emulate version K". Then version K+1 (and K+2, K+3 ...) will restore those behaviors which were altered starting in K+1.

This does not disable purely new features that don't break existing behaviors. For instance, if a two-argument function can now take an optional third argument, such that a two-argument call behaves exactly the same way as before, that won't be subject to emulation. A whole new function that didn't exist in version K is not going to disappear under K emulation.

This isn't a perfect strategy. Things can go wrong. But it's fairly decent.

It's similar to the stuff you see in a raw "mysqldump" output, PHP extensions or in Microsoft's C stdlib headers. Shitloads of stuff hidden between #ifdef VER > xxx.

Pretty easy to deal with, tbh. And it's flexible as hell.

You can flame MS for a LOT of things, but not for ignoring backwards compatibility. You can take most age-old VC6 projects, import them in a modern VS version, and BUILD them and it will WORK.

Not so much in the Linux space. A statically compiled binary from Win95 may very well run on a Win7 machine (e.g. EarthSiege 2)... good luck trying to get a Linux binary from the same era running on a similarly fresh Linux kernel.

the user can optionally specify which version of behaviour they want. This is named the `opt_compat` value in the code. All through the code there are checks against the `opt_compat` value to decide which version of which old/current behaviour to use.
And the opt_compat has C integer/boolean semantics in this case, so the test

  if (opt_compat) ...
tests whether the option has a nonzero value (has been specified).

And so

  if (opt_compat && opt_compat <= 130)
means "user has requested compatibility, with a value of 130 or less".

By the way -C 0, which would look as a Booealn false, as if -C were not specified, is not allowed. If the user specifies -C N such that N is lower than the oldest version that we emulate, the implementation terminates with an error message like "sorry, compatibility with versions less than 70 is not supported by version 140".