The philosophy of LaTeX3 is a strict separation of user interface and implementation. That is, why you should declare a code level function \johannes_all_caps:nn (implementation), which takes the letterspacing factor and the text as arguments and let the document level function \allcaps only handle the star (user interface).
As already mentioned, expl3 has the concept of document level functions (DLFs) and code level functions (CLFs). Roughly speaking, DLFs are intended to be used by the author typing the manuscript, whereas CLFs are meant to be used by the macro designer. The DLF calls the CLF(s) after preparing the user arguments, e.g. branching between CLFs.
In your case the DLF is \allcaps. Following the above guidelines, \allcaps has to call a CLF, e.g. \johannes_all_caps:nn. You use the star to branch between the starred and the unstarred version of \textls, thus I would define two CLFs \johannes_all_caps_kern:nn and \johannes_all_caps_nokern:nn. The resulting code would look like
\documentclass{article}
\usepackage{xparse,fontspec,microtype}
\setmainfont{Minion Pro}
\ExplSyntaxOn
\cs_new_protected:Npn \johannes_all_caps_kern:nn #1#2
{
\tl_set:Nx \l_tmpa_tl { \text_uppercase:n { #2 } }
\group_begin:
\addfontfeature{Scale=0.9}
\textls [ #1 ] { \l_tmpa_tl }
\group_end:
}
\cs_new_protected:Npn \johannes_all_caps_nokern:nn #1#2
{
\tl_set:Nx \l_tmpa_tl { \text_uppercase:n { #2 } }
\group_begin:
\addfontfeature{Scale=0.9}
\textls * [ #1 ] { \l_tmpa_tl }
\group_end:
}
\NewDocumentCommand \allcaps { s O{200} m }
{
\IfBooleanTF { #1 }
{ \johannes_all_caps_kern:nn { #2 } { #3 } }
{ \johannes_all_caps_nokern:nn { #2 } { #3 } }
}
\ExplSyntaxOff
\begin{document}
\end{document}
You might think: “Two CLFs with essentially the same code? This is highly redundant! Let us handle the star argument in the CLF.”
This is exactly what expl3 is after. A clean separation of user interface and macro implementation. User arguments should never be handled on the code level (that is why xparse defines all these really handy functions \If…TF).
Parsing optional arguments and branching is tedious and requires revising every function taking part in such a DLF-CLF chain. Therefore, expl3 encourages the usage of key=value interfaces. It makes setting optional arguments intuitive for the user, as you can give the arguments descriptive names and there is no rigid order of the arguments to obey. In the example below I added the scaling factor of \addfontfeature as an additional key to emphasise the extensibility (Thanks to Joseph Wright for this brilliant suggestion).
I this case, I would design \allcaps to accept an optional argument (the key=value list, by default empty) and the mandatory string. The initial values of the key=value list are chosen to resemble the same behaviour as above, i.e. unstarred \textls with a letterspacing factor of 200.
Inside \allcaps the keys are set within a group, because we do not want to have this specific choice of parameters leaking out into other parts of the document, especially not into other DLFs calling \johannes_all_caps:n.
\documentclass{article}
\usepackage{xparse,fontspec,microtype}
\setmainfont{Minion Pro}
\ExplSyntaxOn
\keys_define:nn { johannes_all_caps }
{
kern .bool_set:N = \l__johannes_all_caps_kern_bool,
kern .initial:n = { true },
ls .int_set:N = \l__johannes_all_caps_ls_int,
ls .initial:n = { 200 },
scale .fp_set:N = \l__johannes_all_caps_scale_fp,
scale .initial:n = { 0.9 }
}
\cs_new_protected:Npn \johannes_all_caps:n #1
{
\tl_set:Nx \l_tmpa_tl { \text_uppercase:n { #1 } }
\addfontfeature{Scale=\fp_use:N \l__johannes_all_caps_scale_fp}
\bool_if:NTF \l__johannes_all_caps_kern_bool
{ \textls * [ \int_use:N \l__johannes_all_caps_ls_int ] { \l_tmpa_tl } }
{ \textls [ \int_use:N \l__johannes_all_caps_ls_int ] { \l_tmpa_tl } }
}
\NewDocumentCommand \allcaps { O{} m }
{
\group_begin:
\keys_set:nn { johannes_all_caps } { #1 }
\johannes_all_caps:n { #2 }
\group_end:
}
\ExplSyntaxOff
\begin{document}
abc \allcaps{abc} abc
abc \allcaps[kern=false]{abc} abc
abc \allcaps[ls=500]{abc} abc
abc \allcaps[ls=500,scale=2.0,kern=false]{abc} abc
\end{document}
For your \allcaps example a starred/unstarred version with one optional argument is perfectly reasonable (in my opinion). For commands with several optional arguments a key=value interface is definitely the preferred solution.
\johannes_all_caps:nnwhich takes the letterspacing factor and the text as arguments and let the document level function\allcapsonly handle the star. 2) I don't think that a class should load a font. You might want to make it a package instead. 3) I guess you also want to\LoadClassWithOptions. Otherwise the options in\documentclass[options]{foo}are just discarded. – Henri Menke Jan 04 '16 at 09:34expl3guidelines. – Henri Menke Jan 04 '16 at 09:43\johannes_all_capsfunction to handle the star? – Johannes Jan 04 '16 at 10:09explclasses' that is probably not the best long-term idea (we really want to separate design and functionality more clearly). I'd place the code in a package which then isn't tied to the design of your document: for example, the scaling should be an additional argument or probably better a keyval set globally. – Joseph Wright Jan 04 '16 at 10:20