7

The third major curse of computer programming is backward compatibility. You have to fix bugs which will unavoidably be found. Some syntax is not really a good idea, and has to be modified to be less bad. These modifications are evil. Either you fix the bug and the behavior of a command changes, or you keep the bug to ensure backward compatibility. The first possibility can have dramatic consequences, e.g. when you use latex to manage your hotel and that \removeClient is modified so instead of removing a patient from the database, it sends a robot to actually remove him from the room, and you tex an old file. The second one leaves you with a \removeClient macro along with a \deleteClient macro, and you never know which one does what you want. This problem is especially acute in latex, where you are always hacking around suspicious behaviors to get what \expandafter\noexpand\noexpand\noexpand you want : a bug correction can wreck your document at the worse time.

I noticed that several packages deal with this kind of problem by providing a compatibility command. For example, pgfplots has \pgfplotsset{compat=1.7}, siunitx has a version-1-compatibility option, mhchem is invoked with \usepackage[version=3]{mhchem}. Other strategies include designing your package correctly from the beginning and, in the rare cases where a drastic change is needed, well, you can afford both \pgfdeclarearrow and \pgfarrowsdeclare. Alternatively, not caring at all about the problem is an option.

From the point of view of an user, the first solution is extremely convenient, as you don't have to find old versions of the packages and fight to get it to work. My question is, how does this work from the point of view of the latex package author ?

Is it better to have conditionals inside your main code, or is it better to have some compatibility files, loaded when compatibility is sought, that contain the compatible definitions of the relevant macros, which supersede the regular ones ? Or are there other solutions ? And how much difficult it is to maintain a latex code base where you want to take such measures ?

All in all, what are best practices when dealing with backward compatibility in latex ?

2
  • 3
    Isn't the only answer 'it depends'? That is, it depends on the function of your package, who is using it, how they are using it, whether other packages are using it (and, if so, whether you maintain them or somebody else), the nature of the bug etc. If, say, the bug is that \removeClient sends a robot to execute the guest rather than deleting an entry from the database, then maintaining backward compatibility would seem less than desirable.
    – cfr
    Commented Dec 7, 2014 at 22:04
  • I agree that the decision of breaking backwards compatibility or not often depends on the specific modification. However, when you do, it is often good to provide the users with the possibility to switch back to the old behavior, at least in widely used codes. How to do so correctly in latex is my main question. Commented Dec 11, 2014 at 22:13

2 Answers 2

2

In the case of my stackengine package (and every developer's case will vary), in version 1.0, I stored default stacking gaps in lengths, rather than in macros. Since lengths are fixed in machine units at the time of their definition, this had the unintended behavior that if I changed font size (e.g., went to \Large, for example) after setting the stacking gap, the gap was still expressed in the former font size units (which matters if the gap was a scalable length like 1ex).

I changed over to defining it in terms of macros, so that the actual length could be set from the stored macro, just prior to building the stack, thus in the current font size.

But the syntax to define those gaps could no longer remain, for example, \Sstackgap=1ex, which is a length assignment, but instead changed to \setstackgap{S}{1ex}, which invoked a macro assignment.

For backward compatibility, I provided an [oldsyntax] package option. In my case, the change in code was small enough that I included both code blocks in the main code, with the use of a conditional, as such:

% PROCESS PACKAGE OPTIONS
\newif\ifstackengine@oldsyntax
\newif\ifstackengine@usestackEOL
\DeclareOption{oldsyntax}{\stackengine@oldsyntaxtrue}
\DeclareOption{usestackEOL}{\stackengine@usestackEOLtrue}
\ProcessOptions\relax
\ifstackengine@oldsyntax%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%%%% THIS VERSION 1.0 SYNTAX WAS PHASED OUT %%%%%%%%%%%%%%%%%%%
%%%%% (but can invoke with \usepackage[oldsyntax]{stackengine} )
\newlength\Sstackgap\newlength\Lstackgap
\newcommand\stackgap{\if S\stacktype\the\Sstackgap\else\the\Lstackgap\fi}
\global\setlength{\Sstackgap}{3pt}
\global\setlength{\Lstackgap}{\baselineskip}
\else%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%%%% AND REPLACED WITH THE FOLLOWING %%%%%%%%%%%%%%%%%%%%%%%%%%
% Again, thanks to Prof. Gregorio for his lucid explanation that helped
% improve this package.  See:
% http://tex.stackexchange.com/questions/123443/
% defining-a-length-that-scales-with-fontsize-changes/123470#123470
\newcommand{\setstackgap}[2]{\@namedef{#1stackgap}{#2}}
\newcommand\stackgap{\@nameuse{\if S\stacktype S\else L\fi stackgap}\relax}
\setstackgap{S}{3pt}% SHORTSTACKING GAP BETWEEN ITEMS (SAME AS \shortstack)
\setstackgap{L}{\baselineskip}% LONGSTACKING GAP BETWEEN ITEMS (def. \baselineskip)
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
\fi
2

Since the question was posed, the landscape has evolved, and the LaTeX kernel now provides a mechanism to offer 'frozen' versions of packages. This is particularly useful if making breaking changes. For example, for siunitx v3 I have

\providecommand\DeclareRelease[3]{}
\providecommand\DeclareCurrentRelease[2]{}
\DeclareRelease{2}{2010-05-23}{siunitx-v2.sty}
\DeclareRelease{v2}{2010-05-23}{siunitx-v2.sty}
\DeclareCurrentRelease{}{2021-05-17}

This means if the user requests v2

\usepackage{siunitx}[=v2]

then they get exactly that: a file called siunitx-v2.sty is loaded, and this is the frozen last release of the v2 series.

It is then up to the developer to decide how much granularity to add. Again, looking at siunitx, I am using semantic versioning for v3, meaning that there will be no breaking changes without a major version step. As such, I do not believe I will need to offer rollback to v3.1, v3.2, etc., as the changes are always strictly additive.

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .