寻求帮助以提高 expl3 中编写的一些代码的质量

寻求帮助以提高 expl3 中编写的一些代码的质量

我开始用 expl3 编写新包。由于与通常的 LaTeX2e 相比,学习资源和示例较少,我对我的代码质量(格式、可读性、约定等)真的不太了解。因此,我想用一个具体的例子来征求您的建议。

以下是我的包的当前版本crefthe。我确信存在更好的实现,但这是我目前能做到的最好的。无论其目的是什么,您能否指出代码中存在的问题(例如命名约定、格式和可读性、用户和编程级宏的错误使用等)并提出一些可能的改进建议?我很感激任何建议。

\NeedsTeXFormat{LaTeX2e}[2020-10-01]
\RequirePackage{l3keys2e}
\ProvidesExplPackage
  {crefthe}
  {2022/02/02} {}
  {Cross referencing with proper definite articles}

\keys_define:nn {crefthe}
  {
    unknown .code:n =
      {
        \PassOptionsToPackage{\CurrentOption}{cleveref}
      }
  }
\ProcessKeysOptions{crefthe}

\RequirePackage{cleveref}

\tl_gset:Nn \g_crefthe_prepf_tl {}
\tl_gset:Nn \g_crefthe_prep_tl {}
\tl_gset:Nn \g_crefthe_sep_tl {}
\cs_new:Nn \crefthe_case:n {#1}
\cs_new:Nn \crefthe_nochange:n {#1}

\cs_new:Nn \crefthe_atbegindocument:n {#1}
\cs_new:Nn \crefthe_atbegindocument_if_needed:n
  {
    \cs_if_eq:NNTF \@onlypreamble \@notprerr
      {
        \cs_gset_eq:NN \crefthe_atbegindocument:n \crefthe_nochange:n
      }
      {
        \cs_gset_eq:NN \crefthe_atbegindocument:n \AtBeginDocument
      }
    \crefthe_atbegindocument:n {#1}
  }

\NewDocumentCommand{\crefthe}{st-t+O{}m}
  {
    \IfBooleanTF{ #2 }
      {
        \tl_gset:Nn \g_crefthe_prepf_tl { #4 }
      }
      {
        \IfBooleanTF{ #3 }
          {
            \tl_gset:Nn \g_crefthe_prep_tl { #4 }
          }
          {
            \str_case_e:nn { \crefthe_prep_mode:nn }
              {
                { - } { \tl_gset:Nn \g_crefthe_prepf_tl { #4 } }
                { + } { \tl_gset:Nn \g_crefthe_prep_tl  { #4 } }
              }
          }
      }
    \IfBooleanTF{ #1 } { \cref*{#5} } { \cref{#5} }
    \tl_gset:Nn \g_crefthe_prep_tl {}
  }

\NewDocumentCommand{\Crefthe}{st-t+O{}m}
  {
    \cs_gset_eq:NN \crefthe_case:n \text_titlecase:n
    \IfBooleanTF{ #2 }
      {
        \tl_gset:Nn \g_crefthe_prepf_tl { #4 }
      }
      {
        \IfBooleanTF{ #3 }
          {
            \tl_gset:Nn \g_crefthe_prep_tl { #4 }
          }
          {
            \str_case_e:nn { \crefthe_prep_mode:nn }
              {
                { - } { \tl_gset:Nn \g_crefthe_prepf_tl { #4 } }
                { + } { \tl_gset:Nn \g_crefthe_prep_tl  { #4 } }
              }
          }
      }
    \IfBooleanTF{ #1 } { \Cref*{#5} } { \Cref{#5} }
    \tl_gset:Nn \g_crefthe_prep_tl {}
  }

\NewDocumentCommand{\crefthename}{mO{}mO{}m}
  {
    \str_if_eq:eeTF {#2} {}
      {
        \crefname{#1}{#3}{#5}
        \cs_set:cn {cref_#1_format:nnn} {##2#3~##1##3}
        \cs_set:cn {cref_#1_format_first:nnn} {##2#5~##1##3}
      }
      {
        \crefname{#1}{\crefthemark{#2} \g_crefthe_sep_tl #3}{\crefthemark{#4} \g_crefthe_sep_tl #5}
        \cs_set:cn {cref_#1_format:nnn} {\crefthemark{#2} \g_crefthe_sep_tl ##2#3~##1##3}
        \cs_set:cn {cref_#1_format_first:nnn} {\crefthemark{#4} \g_crefthe_sep_tl ##2#5~##1##3}
      }
    \crefthe_atbegindocument_if_needed:n
      {
        \cs_set_eq:cc {cref@#1@format} {cref_#1_format:nnn}
        \cs_set_eq:cc {cref@#1@format@first} {cref_#1_format_first:nnn}
      }
  }

\NewDocumentCommand{\Crefthename}{mO{}mO{}m}
  {
    \str_if_eq:eeTF {#2} {}
      {
        \Crefname{#1}{#3}{#5}
        \cs_set:cn {Cref_#1_format:nnn} {##2#3~##1##3}
        \cs_set:cn {Cref_#1_format_first:nnn} {##2#5~##1##3}
      }
      {
        \Crefname{#1}{\crefthemark{#2} \g_crefthe_sep_tl #3}{\crefthemark{#4} \g_crefthe_sep_tl #5}
        \cs_set:cn {Cref_#1_format:nnn} {\crefthemark{#2} \g_crefthe_sep_tl ##2#3~##1##3}
        \cs_set:cn {Cref_#1_format_first:nnn} {\crefthemark{#4} \g_crefthe_sep_tl ##2#5~##1##3}
      }
    \crefthe_atbegindocument_if_needed:n
      {
        \cs_set_eq:cc {Cref@#1@format} {Cref_#1_format:nnn}
        \cs_set_eq:cc {Cref@#1@format@first} {Cref_#1_format_first:nnn}
      }
  }

\NewDocumentCommand{\crefthemark}{m}{
    \crefthe_contraction:nn { \crefthe_case:n \g_crefthe_prep_tl }
      {
        \crefthe_contraction:nn { \crefthe_case:n \g_crefthe_prepf_tl }{#1}
      }
    \tl_gset:Nn \g_crefthe_prepf_tl {}
    \tl_gset:Nx \g_crefthe_prep_tl { \text_lowercase:n \g_crefthe_prep_tl }
    \str_case_e:nnF {\str_tail:n {#1}}
      {
        {'} { \tl_gset:Nn \g_crefthe_sep_tl {} }
      }
      { \tl_gset:Nn \g_crefthe_sep_tl {~} }
    \cs_gset_eq:NN \crefthe_case:n \crefthe_nochange:n
    % \cs_gset_eq:NN \crefthe_case:n \text_lowercase:n
}

\cs_set:Npn \crefthe_prep_mode:nn {
    \str_case_e:nn { \languagename }
    {
      {french}        { + }
      {italian}       { + }
      {spanish}       { - }
      {portuguese}    { + }
      {brazilian}     { + }
    }
}

\cs_set:Npn \crefthe_contraction:nn #1#2
  {
    \str_if_eq:eeTF {#1} {}
      {#2}
      {
        \str_case_e:nn { \languagename }
          {
            {french}
              {
                \str_case_e:nnF {#1~\text_lowercase:n{#2}}
                  {
                    {à~le}      {au}
                    {à~les}     {aux}
                    {de~le}     {du}
                    {de~les}    {des}
                    {À~le}      {Au}
                    {À~les}     {Aux}
                    {De~le}     {Du}
                    {De~les}    {Des}
                  }
                  {#1~\text_lowercase:n{#2}}
              }
            {italian}
              {
                \str_case_e:nnF {#1~\text_lowercase:n{#2}}
                  {
                    {a~il}      {al}
                    {a~lo}      {allo}
                    {a~l'}      {all'}
                    {a~la}      {alla}
                    {di~il}     {del}
                    {di~lo}     {dello}
                    {di~l'}     {dell'}
                    {di~la}     {della}
                    {da~il}     {dal}
                    {da~lo}     {dallo}
                    {da~l'}     {dall'}
                    {da~la}     {dalla}
                    {in~il}     {nel}
                    {in~lo}     {nello}
                    {in~l'}     {nell'}
                    {in~la}     {nella}
                    {su~il}     {sul}
                    {su~lo}     {sullo}
                    {su~l'}     {sull'}
                    {su~la}     {sulla}
                    {a~i}       {ai}
                    {a~gli}     {agli}
                    {a~le}      {alle}
                    {di~i}      {dei}
                    {di~gli}    {degli}
                    {di~le}     {delle}
                    {da~i}      {dai}
                    {da~gli}    {dagli}
                    {da~le}     {dalle}
                    {in~i}      {nei}
                    {in~gli}    {negli}
                    {in~le}     {nelle}
                    {su~i}      {sui}
                    {su~gli}    {sugli}
                    {su~le}     {sulle}
                    {A~il}      {Al}
                    {A~lo}      {Allo}
                    {A~l'}      {All'}
                    {A~la}      {Alla}
                    {Di~il}     {Del}
                    {Di~lo}     {Dello}
                    {Di~l'}     {Dell'}
                    {Di~la}     {Della}
                    {Da~il}     {Dal}
                    {Da~lo}     {Dallo}
                    {Da~l'}     {Dall'}
                    {Da~la}     {Dalla}
                    {In~il}     {Nel}
                    {In~lo}     {Nello}
                    {In~l'}     {Nell'}
                    {In~la}     {Nella}
                    {Su~il}     {Sul}
                    {Su~lo}     {Sullo}
                    {Su~l'}     {Sull'}
                    {Su~la}     {Sulla}
                    {A~i}       {Ai}
                    {A~gli}     {Agli}
                    {A~le}      {Alle}
                    {Di~i}      {Dei}
                    {Di~gli}    {Degli}
                    {Di~le}     {Delle}
                    {Da~i}      {Dai}
                    {Da~gli}    {Dagli}
                    {Da~le}     {Dalle}
                    {In~i}      {Nei}
                    {In~gli}    {Negli}
                    {In~le}     {Nelle}
                    {Su~i}      {Sui}
                    {Su~gli}    {Sugli}
                    {Su~le}     {Sulle}
                  }
                  {#1~\text_lowercase:n{#2}}
              }
            {portuguese}
              {
                \str_case_e:nnF {#1~\text_lowercase:n{#2}}
                  {
                    {a~o}       {ao}
                    {a~a}       {à}
                    {a~os}      {aos}
                    {a~as}      {às}
                    {de~o}      {do}
                    {de~a}      {da}
                    {de~os}     {dos}
                    {de~as}     {das}
                    {em~o}      {no}
                    {em~a}      {na}
                    {em~os}     {nos}
                    {em~as}     {nas}
                    {A~o}       {Ao}
                    {A~a}       {À}
                    {A~os}      {Aos}
                    {A~as}      {Às}
                    {De~o}      {Do}
                    {De~a}      {Da}
                    {De~os}     {Dos}
                    {De~as}     {Das}
                    {Em~o}      {No}
                    {Em~a}      {Na}
                    {Em~os}     {Nos}
                    {Em~as}     {Nas}
                  }
                  {#1~\text_lowercase:n{#2}}
              }
            {brazilian}
              {
                \str_case_e:nnF {#1~\text_lowercase:n{#2}}
                  {
                    {a~o}       {ao}
                    {a~a}       {à}
                    {a~os}      {aos}
                    {a~as}      {às}
                    {de~o}      {do}
                    {de~a}      {da}
                    {de~os}     {dos}
                    {de~as}     {das}
                    {em~o}      {no}
                    {em~a}      {na}
                    {em~os}     {nos}
                    {em~as}     {nas}
                    {A~o}       {Ao}
                    {A~a}       {À}
                    {A~os}      {Aos}
                    {A~as}      {Às}
                    {De~o}      {Do}
                    {De~a}      {Da}
                    {De~os}     {Dos}
                    {De~as}     {Das}
                    {Em~o}      {No}
                    {Em~a}      {Na}
                    {Em~os}     {Nos}
                    {Em~as}     {Nas}
                  }
                  {#1~\text_lowercase:n{#2}}
              }
            {spanish}
              {
                \str_case_e:nnF {#1~\text_lowercase:n{#2}}
                  {
                    {a~el}      {al}
                    {de~el}     {del}
                    {A~el}      {Al}
                    {De~el}     {Del}
                  }
                  {#1~\text_lowercase:n{#2}}
              }
          }
      }
  }

答案1

TeX-sx 的格式不太适合复习,也很难制作一般的答案。在这些限制条件下工作:

  • 一般来说,不要过度优化代码。除非你编写了非常紧密的循环,每页使用 100 次,否则你可以使用“更高级别”的抽象。

  • 阅读l3style-guide有关间距代码,ETC。:虽然这不是很重要,但我们对内核文件有一个“固定”的样式,如果软件包使用相同的样式,那么会更容易一些。

  • 避免使用括号括住 N 类型参数。例如,您不需要在后面的命令名称\NewDocumentCommand或后面的布尔标记周围使用括号。\IfBooleanTF

  • 变量应该在使用前声明,例如\tl_gset:Nn \g_crefthe_prepf_tl {}应该在前面加上\tl_new:N \g_crefthe_prepf_tl。大多数情况下,在创建函数时也应该做类似的事情,所以\cs_new:Npn在顶层使用 或其他任何形式,\cs_set:Npn如果要在作用域内调整函数的含义,则使用 或类似形式。

  • 只有记录的材料才应被赋予公开名称:虽然您想要记录的内容不是 100% 清楚,但我认为例如\g_crefthe_prepf_tl应该是内部的\g__crefthe_prepf_tl

  • 仅在需要时使用全局范围:同样,我不是 100%确定,但我怀疑\g_crefthe_prepf_tlETC。可能是本地的。

  • 跟踪命令的扩展状态。如果您有一个使用任何不可扩展代码,它本身不可扩展,应该是protected。例如,\crefthe_atbegindocument_if_needed:n使用\cs_gset_eq:NN,它是不可扩展的,因此\crefthe_atbegindocument_if_needed:n应该使用来定义\cs_new_protected:Nn(或者\cs_new_protected:Npn- 这里的选择在某种程度上是个人偏好)。

  • 正确指定函数的参数。例如,\crefthe_prep_mode:nn函数不带参数,但有:nn签名。

  • 不要依赖实现的副作用,除非存在重大的性能问题,否则请使用最高级别的扩展控制。您有\str_case_e:nn { \crefthe_prep_mode:nn },它滥用了您已定义\crefthe_prep_mode:nn并且它实际上是一个字符串的事实。例如,最好有\str_case:Vn \l__crefthe_prep_mode_str字符串创建的位置,就像您已经为所做的那样\crefthe_prep_mode:nn。请注意,我使用了-type 扩展,因为它比这里的或类型V更清晰、更可控。另一个例子是,您依赖于将扩展参数的事实:我赞成将其作为-type 传递。eo\tl_gset:Nx \g_crefthe_prep_tl { \text_lowercase:n \g_crefthe_prep_tl }\text_lowercase:nV

  • 同样,使用正确的工具来完成工作:\str_if_eq:eeTF {#2} {}几乎肯定会更好,例如\tl_if_blank:nTF {#2}(或者如果你真的需要该扩展,请创建变体并使用\tl_if_blank:eTF)。

  • 将数据存储在函数之外。\crefthe_contraction:nn您已将所有语言数据放入函数内。这会使用户或您以后难以进行更改。更好的方法是创建数据结构,以便将信息存储在外部,然后您可以动态找到它,例如 \tl_if_exist:cTF { g__crefthe_lang_ \languagename _tl },可能带有一个或多个辅助程序,以避免重复生成名称。(例如,您\str_case_e:nnF {#1~\text_lowercase:n{#2}}为每种语言都生成一个,而我会预先生成一次,然后将整个内容传递给辅助程序。)

  • 当有其他方法可用时,请保持警惕\cs_(g)set_eq:NN。例如\cs_gset_eq:NN \crefthe_case:n \text_titlecase:n,如果您需要跟踪代码,您将永远不会“看到” \text_titlecase:n。另一方面,如果您有,\cs_gset:Npn \crefthe_case:n ##1 { \text_titlecase:n {##1} }您会发现调试要容易得多。在非常紧密的循环之外,一次扩展的性能损失通常不是问题。

  • 注意不要过度使用ltcmd来创建复杂的接口。签名几乎总是类似于somsO{}m。例如,您有\NewDocumentCommand{\Crefthe}{st-t+O{}m},它使用非标准标记(不是星号)来改变行为。

  • 编写公共代码级接口并使用它们来实现文档命令。文档命令代码中应该有最少的“编程”,足以根据签名的可选部分划分不同的路径。对于任何实质性的新expl3代码,应该有一个程序员 API,提供代码级方法来获取功能但不使用文档命令(例如在文档级别使用可选参数的地方使用单独的函数)。

  • 尽可能遵守 LaTeX2e 惯例;我真的不知道你在做什么\crefthe_atbegindocument_if_needed:n,但我立即想到你正试图颠覆声明必须在序言中的想法,而声明的材料只能在文档正文中使用

相关内容