Ok, so here's my go:
- 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.
- Use LaTeX3.
- Use LaTeX3.
- (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.)
- 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.
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
or a command
\arc should only be defined inside of the
diagram environment and
\option only while parsing the optional argument of
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.
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).
- If you load other packages or classes, always specify the minimal release date with the second optional argument.
- 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.
- Never rely on internal macros of the LaTeX kernel. Though it is more stable than most packages, changes can occur.
- 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).
- If you need to patch a macro from another package, make sure to act minimally invasive. The package
etoolbox offers commands
pretocmd for that (maybe LaTeX3 has something also for this).
- 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.
- Never do
-\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.
- 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.
- 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.
- 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.
- 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.
- Close your groups and your ifs. There are few errors harder to find than a forgotten
\endgroup. Familiarise yourself with code formatting conventions which make it easy to spot all structures are complete.
- 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.
- 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.
- 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.
- Use local assignments wherever possible. In general, you want your macros to be reentrant.
- 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).
- 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
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
- Be careful with
\edef, as non-expandable unprotected code is likely to break inside it.
\write and its derivatives like
\addtocontens are in the same category. LaTeX offers a protection mechanism with
\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)
- If you wish to "just group something", it's more robust to use
\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
\endgroup exclusively, you are more likely to get an error message in case of structural errors.
- 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.
- Use LaTeX3.
- Use LaTeX3.
- 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).