35

I have posted several questions ultimately involving various aspects of robustness, that have arisen in the context of other questions in risk of clouding the main point of the question in each case. I thought it would be appropriate to have a centralised discussion here about what needs to be taken into account to achieve robustness and what sort of things one can do early on to minimise the pain of upgrading one's own code later on.

There was a recent discussion about backward compatibility here where some mixed emotions were being expressed about whether or not a certain author was doing the right thing by his users when abandoning backward compatibility, so it's clearly an important topic. It would be good to discuss this topic without opinions of any one author's application of what is considered best practice and limiting ourselves to discussion of the principles themselves.

I'm in the middle of writing a package myself as a fledgling package writer and various issues have arisen basically concerning robustness of code. Does it access globals appropriately, can it be used in any context, or what are reasonable expectations on where it can be used, without breaking?

None of the other suggested questions, that came up when I typed this one in, are focussed on this particular question. I keep coming up with little questions that I want to ask relating to this topic that would get buried as comment questions and would spread the discussion all over the place as separate questions. Hopefully this is considered a worthy meta discussion.

Answers could either be about general principles or particular examples of use, such as: \pgfmathsetlength can set a variable prefixed with \global, whereas \pgfmathaddtolength can not. Is it best practice in the former case, as you must in the latter case, making the value global with

\pgfmathsetlength{\testvar}{0}\global\testvar\testvar

just in case this changes in future? I suppose whatever the argument, that consistent practice has to be an important guide.

17
  • 3
    this isn't about how best to write a package, but how to release it. announce a preliminary version on relevant forums -- texhax and comp.text.tex come to mind -- and invite potential users to try it out and provide feedback. don't release version 1.0 until you've done that; start with version 0.1. and when you are ready to release version 1.0, post it to ctan. Commented Oct 30, 2013 at 12:30
  • 1
    @percusse So, with all the members here, with all the cumulative experience they have, I should none the less just plough on and repeat the same mistakes everyone else has made, find out for myself and reinvent the wheel? Surely we can have a worthwhile discussion here about traps for young players? You keep coming back to my example, which is only that, an example. It's not the point of my question. We could see the last point in your last comment as getting the answer ball rolling. And, it's a completely general point. In any context, don't use \global in a call to a macro. Commented Oct 30, 2013 at 22:02
  • 4
    I would suggest limiting the discussion to certain aspects such as use of dimen or length registers for starters. then ask more about other details. people here are delighted to discuss TeX internals for specific cases.
    – percusse
    Commented Oct 31, 2013 at 10:13
  • 6
    I don't know if this is the type of thing you're looking for, but I suggest that when you start writing a new package you use the commands in etoolbox and have some kind of prefix in your command naming scheme to help prevent clashes with other packages. Commented Nov 5, 2013 at 16:29
  • 3
    Why limit discussion to one aspect? There's a laundry list of basic practices that could be discussed: using a package-specific prefix like ld@ for internal command names; patching macros rather than replacing them outright; using latex constructs rather than the corresponding TeX primitives; avoiding catcode changes. And what about \usepackage vs. \RequirePackage, demanding a minimum version for an included package (when necessary), etc., etc.
    – alexis
    Commented Nov 8, 2013 at 7:58

2 Answers 2

24

Ok, so here's my go:

  1. Use LaTeX3. Call texdoc expl3 and texdoc interface3 and read that. This is the first attempt I've seen which tries to take care of interface design for LaTeX on a systematic, wholistic and complete scope.
  2. Use LaTeX3.
  3. Use LaTeX3.
  4. (Note that I don't know by heart what LaTeX3 offers in detail, mainly because I'm not the robustness-loving type, but more of a bare-metal enthusiast. Hence, it is entirely possible that every single item of advice below can be replaced simply by "Use LaTeX3". After reading the docs you will know better than I.)
  5. Don't choose too common names for user-level macros of your package. TeX has a global name space. Even if commands like \newcommand help to avoid conflicts, you may make it impossible for users to use your package together with another one. If feasible, use the name of your package as a name component of all user-level macros.
  6. If a macro can only be sensibly used in the argument of another macro (or inside an environment), define it locally.

    For instance, if you define an environment like

    \begin{diagram}
      \line...
      \circle...
      \arc...
    \end{diagram}
    

    or a command \mycommand[\option{foo}{...}\option{bar}{...}]{...} then \line, \circle, \arc should only be defined inside of the diagram environment and \option only while parsing the optional argument of \mycommand.

    Some packages still give a global definition which will generate an error message outside the correct context, but if the macro names are very generic (like in my example), it's probably better to leave them completely alone to avoid conflicts with other packages.

  7. Make sure to avoid name conflicts for your internal macros (with the kernel or other packages) at all costs. Always use a name component unique to your package! (quite probably LaTeX3 will take care of this).

  8. If you load other packages or classes, always specify the minimal release date with the second optional argument.
  9. Never rely on internal macros of another package or class. Specifying a release date does not prevent a later version to be loaded, and backwards compatibility never applies to internal macros.
  10. Never rely on internal macros of the LaTeX kernel. Though it is more stable than most packages, changes can occur.
  11. If you absolutely need an internal macro from elsewhere, it might be better to duplicate the code (but that is highly context dependent of course).
  12. If you need to patch a macro from another package, make sure to act minimally invasive. The package etoolbox offers commands \patchcmd, \apptocmd, pretocmd for that (maybe LaTeX3 has something also for this).
  13. If you need to patch a macro from another package, make sure your patch will do the right thing. The commands I mentioned above give feedback whether the patch could be applied; use it! In extreme cases I sometimes copy the command to be patched verbatim to my own package (under another name of course) to check beforhand with \ifx whether the definition is still the same.
  14. Never do \global\somemacro, \immediate\somemacro, \protected\somemacro, \long\somemacro, \unless\somemacro, \the\somemacro, -\somemacro or other things which are extremely dependent on the inner structure of some other macro. \global belongs in front of an assignment; if you need it, at least make a patch.
  15. Don't reinvent the wheel. Packages like etoolbox (probably the LaTeX3 package bundle has a lot more of that type) offer tools to deal with common situations like numeric comparisons which can be much more versatile and robust than a self-defined ad hoc macro.
  16. Take care about the type of the data you are processing. The possibilities of creating fragile code are endless, for example, your macro might behave differently whether a numeric argument is given as a sequence of digits, the name of a counter register, a macro containing some number, or a numeric expression. It might be best to just surround a numeric argument with \numexpr#1\relax just in case.
  17. Deal gracefully with user errors. Again, too many possibilities to list them all here. Example: If you expect an argument in a certain form (for instance, a cs name or a length), there are certain ways to test for conforming input. Again, the package etoolbox offers useful tools.
  18. If you are constructing cs names with \csname, be extra careful. With \ifcsname you can check whether a control sequence you're about to define already exists. Be extra careful not to (inadvertently) overwrite something. If you want to maintain a list of, say, letters, and #1 contains the name of a letter, then by defining \csname let#1\endcsname you can wreck the whole system if #1 should be empty by some user error. Also, make sure that the components going inside a \csname construct expand to a sequence of admissible characters at all.
  19. Close your groups and your ifs. There are few errors harder to find than a forgotten \fi or \endgroup. Familiarise yourself with code formatting conventions which make it easy to spot all structures are complete.
  20. Always, always, always give the right number of arguments to macros. The one error which is hardest to find is a macro which was called with too few arguments gobbling up some completely random token from the input stream and inserting it in a totally unexpected place.
  21. Make sure to wrap a \loop in a group. You don't want any macro you call in a loop to contain a loop which is not grouped. It's not a nice sight.
  22. Don't use fancy delimited argument tricks. Delimited arguments are nice for parsing some strange syntax, but don't use them to design a cool macro interface. It's too easy for this kind of thing to break under user errors and very hard to recover gracefully from. Just think about all the things always going wrong with key-value parsing which is of this "cool user interface" type. Stay with the standard LaTeX way of passing arguments.
  23. Use local assignments wherever possible. In general, you want your macros to be reentrant.
  24. If you write a macro which will produce text that can cause a page break, remember that code is executed in the page header and footer. You'd be surprised of the strange effecs which can occur when a list or section heading should be put in the header ot footer as they do some global assignments (which the authors undoubtedly thought to be safe).
  25. Make sure numeric and dimension/glue expressions are correctly terminated. After \hskip for instance, TeX will go expanding stuff until it is absolutely sure a glue expression is complete. This includes a dimension and a possibly following plus and/or minus component. I wrote a macro once which would gobble the word plus if it was accidentally following after it - quite hard to spot! Also, macros that "just" cause adjacent stuff to be expanded prematurely can cause all kinds of inexplicable behaviour. Terminate such expressions by an explicit space or even \relax.
  26. Be careful with \edef, as non-expandable unprotected code is likely to break inside it. \write and its derivatives like \typeout or \addtocontens are in the same category. LaTeX offers a protection mechanism with \DeclareRobustCommand, \protect, \protected@edef, \protected@write and so on, but still non-expandable things should be handeled with great care in expanding contexts (also beware of unexpandable stuff getting into contexts where TeX is expanding stuff away looking for input, for instance when \ifnum is looking for a number). With e-TeX, you can define a macro with \protected\def so it won't expand in \edef or other contexts where TeX is building an expanded token list. It will be expanded, however, in all places where things are "totally" expanded, for instance when looking for a number. See the e-TeX manual (texdoc etex) for more information. (thanks to @cgnieder for the hint)
  27. If you wish to "just group something", it's more robust to use \begingroup...\endgroup because \bgroup...\egroup (mostly equivalent with {...}) are used for a lot of other purposes (for instance box delimiters) and especially {...} can be mixed up with argument delimiters and whatever, hiding structural errors. If you use \begingroup...\endgroup exclusively, you are more likely to get an error message in case of structural errors.
  28. Always read through the log file during testing and don't ignore any error messages or warnings. If you get a message \end occurred inside a group or \end occurred before \ifx was complete or whatever, that is a sign the structure of your code is seriously broken and will cause trouble to others.
  29. Use LaTeX3.
  30. Use LaTeX3.
  31. Use LaTeX3. (for good measure)

Note that I don't use LaTeX3 myself, but I know from observing the projects progress that robustness and clean interface design are a central consideration of this project, and those packages which are already published are sufficiently completed and sophisticated that they offer a far superior foundation for developing robust packages than any other TeX-based development environment.

All the other advices (apart from the first and last three :) stem from my daily TeX/LaTeX2 programming experience and are probably applicable to any kind of TeX programming (though LaTeX3 might make some of them obsolete by providing more specific tools for clean interface design).

16
  • 1
    I like item #27 a lot. :) Commented Dec 7, 2013 at 11:03
  • @PauloCereda Beware, I'm still adding items :-) Commented Dec 7, 2013 at 11:22
  • 1
    @alexis These are advices. You can decide item by item what to consider :-) Concerning LaTeX3, as I see it there are three possible points of view. If you care about robustness above everything else, then use LaTeX3 no matter what, as achieving robustness is one main goal of the project. You won't achieve the same level of package robustness without substantially re-inventing it. If robustness is one consideration among others, but you can grok LaTeX3, then use LaTeX3, because you get robustness as a bonus. If you (like me) can't get yourself to use LaTeX3, the other 24 advices are for you :-) Commented Dec 7, 2013 at 14:22
  • 1
    @alexis I totally agree. Otherwise one item would have been sufficient in my answer :-) As I don't use LaTeX3 myself, naturally I run into robustness issues all the time :-) Commented Dec 7, 2013 at 15:06
  • 1
    @StephanLehmke The purpose of this thread has always been a general one. I really appreciate your answer as it is about exactly what I was meaning in the first place. I have asked, do ask and will continue to ask more specific questions about problems as they arise in my programming in separate threads. This thread was an attempt to solicit more general advice from the experienced members of the forum about approaches to programming in LaTeX in order to avoid problems in the first place. It seemed like a good idea and the answers so far are interesting. Commented Dec 10, 2013 at 6:39
13

Here's a laundry list of basic practices that are generally recommended. They all follow from the general principle for managing any complex software project: Take steps to keep the components as independent as possible, in their design ("loose coupling") and in their implementation.

  1. Protect the TeX namespace. Since TeX has only a single persistent namespace (global), packages should be very careful about the macro names they use.

    a. I've seen packages that unconditionally define \a, \b, \c, etc. as "shortcuts", with no concern that they might be stepping on another package's shortcuts. This kind of thing should never be done by default. It should be done (if at all) by non-default package option, by explicit command, or inside an environment provided by the package.

    b. Use a package-specific prefix like ld@ for internal command names.

  2. Avoid catcode changes. They are particularly prone to triggering incompatibilities during the subsequent loading of other packages, which are mysterious and frustrating to end-users (even if all too familiar). Catcodes are really a low-level part of the namespace, since @/11 is a different symbol than @/12, for example.

  3. Patch macros rather than replacing them outright. There are many reasons to modify the \footnotetext macro, for example. There are numerous good solutions that make patching easy and (relatively) safe.

  4. Use the file-loading commands provided for package authors, instead of the corresponding userland version. E.g., use \RequirePackage instead of \usepackage. (Not sure what the advantages are, tbh, but it allows for a cleaner ecosystem down the road.)

  5. Demand a minimum version for an included package (when necessary).

  6. Use LaTeX constructs rather than the corresponding TeX primitives. This is also good practice in userland. (Unless, of course, the LaTeX construct lacks needed features that the original has: E.g., \def is much more powerful and flexible than \newcommand.)

6
  • Do you have an opinion about the idea of enclosing macros in a group as good practice in general? It seems when I first raised this question here it was misunderstood as a programming problem but not so. The comment has been made elsewhere that this is good practice in general. Is that really so? Is that widely agreed? Commented Dec 10, 2013 at 6:42
  • If by "enclosing macros in a group" you mean having a command that defines another macro with local scope in an environment or in an internal brace group, yes I think this is a good idea when appropriate. It's mentioned in suggestion 1a (it's also on @stefan's list). The downside is that such macros are much harder to patch/redefine (in fact latex seems to use this technique for this very purpose), so the package should provide good customization options.
    – alexis
    Commented Dec 10, 2013 at 8:46
  • Or maybe I misunderstood your comment as well? Because I'd call this a "programming problem" too (as I would the current question).
    – alexis
    Commented Dec 10, 2013 at 8:50
  • Yes, the issue came up in the context of a problem. But, then a general comment was made about using groups in macros. That is, one should always begin one's macros with \begingroup and end them with \endgroup. Is that generally agreed upon as robust practice? It's certainly not a hard principle to apply. Until two days ago, I didn't do that. There are of course individual cases where outer braces are an essential part of the expanded macro, but that is not the point being made here. Commented Dec 11, 2013 at 1:51
  • It's good housekeeping to avoid leaving behind definitions after your macro is done. But braces are pointless if your macro doesn't define anything, and when the purpose of the macro is to define something longer-lived, surrounding braces would be a mistake: they'd force you to make the definition \global, which in my opinion is worse since in general you want to be as local as possible. Not using \global allows the caller of the macro to restrict its effects by enclosing it in braces.
    – alexis
    Commented Dec 13, 2013 at 19:39

You must log in to answer this question.

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