LaTeXの`\section`マクロをテンプレートメソッドパターンで作り直す

## 概要

いわゆるGoFのデザインパターン本に出てくる「テンプレートメソッドパターン」を使って、LaTeXの `\section` マクロを作り直してみます。

ポイント：

- `\section` を、より小さいマクロの組み合わせとして定義する。
- カスタマイズするとき、必要なマクロだけを上書きするだけで済む。
- TeX言語では継承は使えないので、かわりにマクロの上書きを利用する。

オブジェクト指向言語じゃないのに「テンプレート**メソッド**パターン」とはおかしなことかもしれません。
しかし実装方法が、継承によるメソッドオーバーライドなのか、それともマクロ定義の上書きなのかという違いだけであり、考え方はテンプレートメソッドパターンそのものです。

（注：あるマクロを小さいマクロに分解するだけでは「テンプレートメソッドパターン」とは言いません。挙動を上書きできるように分解してこその「テンプレートメソッドパターン」です。）

なおTeXおよびLaTeXでは「マクロ」とは言わず、「コントロール・シーケンス」または「制御綴」と呼ぶのが正しいそうです。
しかしこの記事では、一般的な呼び名として普及している「マクロ」という用語を使います。


## `\section` を定義する

`\section` をステップ・バイ・ステップで定義してみます。


### Step 1. とても原始的な\Sectionマクロ

まずは、次のようなとても原始的な `\Section` マクロを考えます。
（`\section` ではなく `\Section` にします。）


```latex
%% セクションタイトル用のマクロ
\newcommand\Section[1]{%      % #1: タイトル
  \vspace{2\Cvs}%             % タイトル上部に2行分のマージン（余白）
  \refstepcounter{section}%   % セクション用のカウンタを増やす
  {%
    \setlength{\parindent}{0pt}% インデント幅をゼロに
    \headfont\Large%          % フォントを大きめのゴシック体に変更
    \thesection%              % セクション番号を表示
    \hspace{1zw}%             % 全角1文字分の空白
    #1%                       % 引数（タイトル）を表示
    \par\nobreak%             % 改行、かつ直後での改ページを防ぐ
  }%
  \vspace{1\Cvs}%             % タイトル下部に1行分のマージン（余白）
}
```

使い方は `\section` と同じです（ただし`*`などを除く）。

```latex
\Section{セクションタイトルのサンプル}
```


### Step 2. 上部と下部のマージンをマクロ化

次に、タイトルの上部と下部にマージン（余白）を入れている部分を、独立したマクロにします。

```latex
%% セクションタイトル用のマクロ
\newcommand\Section[1]{%      % #1: タイトル
  \Section@topmargin%         % !!!!!
  \refstepcounter{section}%
  {%
    \setlength{\parindent}{0pt}
    \headfont\Large%
    \thesection%
    \hspace{1zw}%
    #1%
    \par\nobreak%
  }%
  \Section@bottommargin%      % !!!!!
}

%% セクションタイトル上部のマージン（余白）
\newcommand{\Section@topmargin}{\vspace{2\Cvs}}    % 2行分

%% セクションタイトル下部のマージン（余白）
\newcommand{\Section@bottommargin}{\vspace{1\Cvs}} % 1行分
```

これで、マージンだけをカスタマイズできるようになりました。
たとえば次のようにマクロを上書きすれば、セクションごとに改ページできます。

```latex
%% マージンを入れるかわりに、改ページする
\renewcommand{\Section@topmargin}{%
  \ifnum\c@section=0%   % 最初のセクションなら
    \vspace{2\Cvs}%     % マージン（余白）を入れる
  \else%                % そうでないなら
    \clearpage%         % 改ページする
  \fi%
}
```

従来の `\section` 定義では、これをするだけでも `\section` の定義をコピーして上書きしなければいけませんでした。
`\Section` マクロを分解しておけば、全体を上書きすることなく（つまり他の部分に影響を与えることなく）、必要な箇所だけを変更できます。


### Step 3. セクションタイトル部をマクロ化

続いて、セクションの番号とタイトルを表示する部分をマクロ化します。
ついでにフォント指定もマクロ化します。

```latex
%% セクションタイトル用のマクロ
\newcommand\Section[1]{%      % #1: タイトル
  \Section@topmargin%
  \refstepcounter{section}%
  {%
    \setlength{\parindent}{0pt}
    \Section@font%                    % !!!!!
    \Section@title{\thesection}{#1}%  % !!!!!
    \par\nobreak%
  }%
  \Section@bottommargin%
}

%% セクションタイトル上部のマージン（余白）
\newcommand{\Section@topmargin}{\vspace{2\Cvs}}

%% セクションタイトル下部のマージン（余白）
\newcommand{\Section@bottommargin}{\vspace{1\Cvs}}

%% セクションタイトルのフォント
\newcommand{\Section@font}{%
  \headfont\Large%            % フォントを大きめのゴシック体に変更
}

%% セクションタイトル
\newcommand{\Section@title}[2]{%  #1: 番号, #2: タイトル
  #1%                         % セクション番号を表示
  \hspace{1zw}%               % 全角1文字分の空白
  #2%                         % タイトルを表示
}
```

ここで、`\Section@title` の引数に（タイトルだけでなく）セクション番号も渡されていることに注意してください。
このおかげで、「セクション番号は `\thesection` で参照する」というLaTeX知識を持っていなくても `\Section@title` を上書きできます。


### Step 4. タイトルデザインをカスタマイズ

ここまでできたら、タイトルデザインをカスタマイズしてみましょう。

```latex
%% 設定ファイルの値によってセクションデザインを変更
\def\@tempa{grayback}
\ifx\config@section@decoration\@tempa
  \renewcommand{\Section@title}[2]{%  #1: 番号, #2: タイトル
    \textcolor{lightgray}{%   % タイトルの背景を薄いグレーに
      \rlap{\rule[-0.6zh]{\textwidth}{2zh}}% 本文幅
    }%
    \textcolor{black}{%       % 章番号の背景色を黒に
      \rlap{\rule[-0.6zh]{3.5zw}{2zh}}% 全角3.5文字分の幅
    }%
    \makebox[3.5zw][c]{\textcolor{white}{#1}}% 白抜き文字で番号を表示
    \hspace{0.9zw}%           % 空白
    #2%                       % タイトルを表示
  }
\fi
```

こんなデザインになります。

<img width="633" alt=" sectiondesign.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/224266/1dcc37ff-208c-22bc-988b-806884c3af3e.png">


このようなデザインの変更をするのに、必要な箇所のマクロだけを上書きすればいいのが分かります。

繰り返しますが、従来の `\section` の定義ではデザインを変更したければ `\section` 定義をコピペして変更、上下のマージン（余白）を変更するだけだとしても `\section` 定義をコピペして変更する必要がありました。

これに対し、テンプレートメソッドパターンを使えば `\Section` マクロをより小さいマクロの集まりとして定義できます。
そのおかげで、必要な箇所のマクロだけを上書きすればいいので、`\Section` 全体を上書きする必要はありません。


### 説明しなかったこと

今回のサンプルコードでは、次のようなことは説明を省略しました。

- `\Section*` のように `*` をつけたときは番号をつけない
- 番号がつく場合とつかない場合でデザインやマージン（余白）を切り替える
- `\addcontentsline{toc}{見出し種類}{タイトル}` を使って目次を出力
- `\hyperref` の機能を使ってセクションへのアンカーを設定
- `\@hangfrom{章番号\hspace{1zw}}{タイトル}` を使って、長いタイトルが2行になるときの対策
- 各種ペナルティの設定（←今回は実装しなかった）

実際のスタイルファイル（後述する`sty/starter-heading.sty`）ではこれらを考慮したので、ずっと複雑であり、マクロもより細かく分かれています。
しかし「テンプレートメソッドパターン」という考え方は変わりません。


### 実際のコード

ここで説明した方法は、「Re:VIEW Starter」というドキュメント生成ツールの中で使っています。

- Re:VIEW Starter<br>
  https://kauplan.org/reviewstarter/

興味ある人はここにアクセスして、プロジェクトを作成し、zipファイルを解凍してください。
その中の `sty/starter-heading.sty` に実装コードが書かれてあります。



## 従来の `\section` の問題点

従来の `\section` には、カスタマイズしにくいこと以外にも問題があります。


### 問題点：「番号はつけないけど目次には出す」ができない

従来の `\section` では、`\section*` のように `*` をつけると番号をつけなくなります。
それだけでなく、目次にも出さなくなります。

つまり、`*` が2つの機能を持っています。

- 番号をつけない
- 目次に出さない

しかし、番号はつけないけど目次には出したいことは珍しくありません。
たとえば「まえがき」や「あとがき」では、チャプターやセクションには番号をつけません。
そこで `\section*` を使うのですが、そうすると目次にも出なくなります。

（LaTeXでは `\frontmatter` と `\mainmatter` と `\backmatter` を使うと、`\chapter` だけは「まえがき」や「あとがき」のチャプターに番号がつきません。しかし `\section` や `\subsection` ではそのような手当はされていません。）

これは `\section` の仕様が間違っています。
本当なら「番号をつける・つけない」と「目次に出す・出さない」を個別に指定できるべきでした。


### 解決策：第1引数が空文字列なら目次に出さない

ところで `\section` には、タイトルとは別に「目次用のタイトル」を指定できます。

```latex
\section[目次用タイトル]{セクションタイトル}
```

通常は目次用タイトルを指定しないので、そのときは `セクションタイトル` がそのまま目次用タイトルとして使われます。

この引数を利用すれば、**目次用タイトルに空文字列を指定したら目次に出さない**という仕様にできます。
使い方は次のようになります。

```latex
\Section{AAA}         % 目次に「AAA」が出る
\Section[BBB]{AAA}    % 目次に「BBB」が出る
\Section[]{AAA}       % 目次に出ない!!!!!!!!!!
```

実装はこんな感じになるでしょう。

```latex
%% `\relax` は、他の言語でいうところのNULLみたいなもの。
%% ここでは第1引数 `#1` のデフォルト値を `\relax` (NULL) に指定している。
\newcommand{\Section}[2][\relax]{%
  \def\@tempa{#1}%
  \def\@tempb{\relax}%
  \ifx\@tempa\@tempb%      % 第1引数が指定されてなければ
    #2を目次として使う
  \else\ifx\@tempa\empty%  % 第1引数が空文字列なら
    目次に出さない
  \else%                   % 第1引数が空文字列でなければ
    #1を目次として使う
  \fi\fi%
  ...
}
```

要は、こういうことです。

- 第1引数 `#1` が未指定なら、第2引数 `#2` を目次に出す。
- 第1引数 `#1` が空文字列なら、目次に出さない。
- 第1引数 `#1` がそれ以外なら、第1引数を目次に出す。

これは `*` の指定とは独立して行えます。
そのため、「番号をつける・つけない」と「目次に出す・出さない」を別個に指定できます。
前述の `sty/starter-heading.sty` ではこの方法を採用しています。


### 問題点：一部を変更したいだけなのに全体のコピーが必要

`\section` や `\subsection` は実際にどのような定義になっているでしょうか。
クラスファイル `jsbook.cls` から該当箇所を引用します。

<!--
```latex
\newcommand\section{\@startsection {section}{1}{\z@}%
                                   {-3.5ex \@plus -1ex \@minus -.2ex}%
                                   {2.3ex \@plus.2ex}%
                                   {\normalfont\Large\bfseries}}
\newcommand\subsection{\@startsection{subsection}{2}{\z@}%
                                     {-3.25ex\@plus -1ex \@minus -.2ex}%
                                     {1.5ex \@plus .2ex}%
                                     {\normalfont\large\bfseries}}
\newcommand\subsubsection{\@startsection{subsubsection}{3}{\z@}%
                                     {-3.25ex\@plus -1ex \@minus -.2ex}%
                                     {1.5ex \@plus .2ex}%
                                     {\normalfont\normalsize\bfseries}}
\newcommand\paragraph{\@startsection{paragraph}{4}{\z@}%
                                    {3.25ex \@plus1ex \@minus.2ex}%
                                    {-1em}%
                                    {\normalfont\normalsize\bfseries}}
\newcommand\subparagraph{\@startsection{subparagraph}{5}{\parindent}%
                                       {3.25ex \@plus1ex \@minus .2ex}%
                                       {-1em}%
                                      {\normalfont\normalsize\bfseries}}
```
-->

```latex
  \newcommand{\section}{%
    \if@slide\clearpage\fi
    \@startsection{section}{1}{\z@}%
    {\Cvs \@plus.5\Cdp \@minus.2\Cdp}% 前アキ
    {.5\Cvs \@plus.3\Cdp}% 後アキ
    {\normalfont\Large\headfont\raggedright}}

  \newcommand{\subsection}{\@startsection{subsection}{2}{\z@}%
    {\Cvs \@plus.5\Cdp \@minus.2\Cdp}% 前アキ
    {.5\Cvs \@plus.3\Cdp}% 後アキ
    {\normalfont\large\headfont}}

  \newcommand{\subsubsection}{\@startsection{subsubsection}{3}{\z@}%
    {\Cvs \@plus.5\Cdp \@minus.2\Cdp}%
    {\if@slide .5\Cvs \@plus.3\Cdp \else \z@ \fi}%
    {\normalfont\normalsize\headfont}}

  \newcommand{\paragraph}{\@startsection{paragraph}{4}{\z@}%
    {0.5\Cvs \@plus.5\Cdp \@minus.2\Cdp}%
    {\if@slide .5\Cvs \@plus.3\Cdp \else -1zw\fi}% 改行せず 1zw のアキ
    {\normalfont\normalsize\headfont\jsParagraphMark}}

  \newcommand{\subparagraph}{\@startsection{subparagraph}{5}{\z@}%
    {\z@}{\if@slide .5\Cvs \@plus.3\Cdp \else -1zw\fi}%
    {\normalfont\normalsize\headfont}}
```

こうして見ると、`\section` から `\subparagraph` までが `\@startsection` という1つのマクロに共通化されていることが分かります（これは `book.cls` でも同様です）。
おかげでそれぞれの定義はわずか数行で済んでいます。
おそらくですが、**数行しかないんだから `\section` を変えたいときはまるごとコピーして変更すればいい**という設計思想なのでしょう。

しかしこれはプログラミング的には悪手です。
たとえばマージンの高さを変更したい場合であっても、全体をコピーして上書きする必要があるので、もしオリジナルのほうがフォントを変更した場合、その変更はコピーには反映されません。
オリジナルの定義が一切変わらないという保証がない限り、このようは方法は推奨されません。
（今のところ、`book.cls` や `jsbook.cls` では変更がされてないから問題になってないのでしょう。）


### 問題点：個別にカスタマイズしたいのにコードを共通化している

先ほど説明したように、クラスファイル `book.cls` や `jsbook.cls` では見出し定義の `\section` から `\subparagraph` までを1つのマクロ（`\@startsection`）で共通化しています。
そのせいで、`\@startsection` を変更するとそれが（`\chapter` を除く）すべての見出しレベルに影響します。

すべての見出しレベルを同じようなデザインにしたいならこの方法でもいいのですが、実際には「`\section` と `\subdsection` と `\subsubsection` はデザインを大きく変えたい」という要望が多いです。
その場合、1つのマクロで共通化していることはマイナス要素でしかありません。

個別に変更やカスタマイズが必要なものを下手に共通化すると、変更やカスタマイズはとてもしにくくなります。
`\@startsection` はまさにその例です。

（なお `\@seccntformat` というマクロをうま〜く再定義すると、番号のデザインは変更できます。うま〜く定義すればね。）


### 問題点：性質が大きく異る `\section` と `\paragraph` を同じマクロで定義している

`\@startsection` は、`\chapter` を除いた次の見出し定義で使われています。

- `\section`
- `\subsection`
- `\subsubsection`
- `\paragraph`
- `\subparagraph`

しかし上3つはいいとして、下2つ（`\paragraph` と `\subparagraph`）は見出しとしての性質が大きく違います。
この2つには番号はつかないし、目次にも出しません。
見出しの一種とはいえ、`\paragraph` や `\subparagraph` を `\section` などと同じように定義する必要はありません。

また `\@startsection` は次のような奇妙な実装になっています。

- 第4引数がマイナスかどうかでインデントの挙動を変える。
- 第5引数がマイナスかどうかでタイトル後の改行する・しないを変える。

なぜこんなことになっているのか、マクロ定義を読んだときは意味が分からなかったのですが、どうやら `\@startsection` が `\paragraph` と `\subparagraph` をサポートするためにこんな奇妙な仕様になっているようです（あくまで推測です）。

どう考えても、`\@startsection` マクロが `\paragraph` の定義までサポートするのはやめて、`\paragraph` と `\subparagraph` は個別に定義したほうがシンプルになったことでしょう。
性質の異なるものを共通化したせいで、共通化部分が複雑になった失敗例です。
（どちらかというと、`\chapter` を `\@startsection` で定義できるようにすべきでした。）

前述の `sty/starter-heading.sty` では、`\paragraph` と `\subparagraph` は `\section` とは関係なく定義しているので、定義がとても簡潔になりました。


### 問題点：ページ先頭でもセクションタイトルの上部にマージンが入る

`jsbook.cls` を使っていると、たとえページ先頭であってもセクションタイトルの上にマージン（余白）が入ります（`book.cls` なら入りません）。

`jsbook.cls` の中身を見ると、次のような箇所があります。

```latex
1134:      \addpenalty\@secpenalty
1135:      \ifdim \@tempskipa >\z@
1136:        \if@slide\else
1137:          \null
1138:          \vspace*{-\baselineskip}%
1139:        \fi
1140:        \vskip\@tempskipa
1141:      \fi
```

1140行目では `\vskip` を使っているので、一見するとページ先頭ならマージンは入らないように見えます。
しかし1137行目と1138行目のせいで、ページ先頭のはずが余計な文字が入るせいでページ先頭とみなされなくなってしまうのです。

（`jsbook.cls` がなぜこんな余計な仕様になっているかは分かりませんでした。
また1136行目に `\if@slide` があるので、`\@slidetrue` を使えば回避できますが、`\if@slide` が使われているのはここだけではないので、他の箇所に影響が出ます。）

この問題を回避するために、たとえば `\section` 定義を次のようにしてみましょう。

```latex
  \renewcommand{\section}{%
    \@startsection{section}{1}{\z@}%
    {0pt}%                     % 上マージンをなくす
    {.5\Cvs \@plus.3\Cdp}%     % 下マージンは残す
    {%
      \vskip \Cvs \@plus.5\Cdp \@minus.2\Cdp\relax%  上マージンを入れる
      \normalfont\Large\headfont\raggedright%
    }%
  }
```

一見するとこれでうまくいきそうですが、実は `hyperref` パッケージを読み込むと、やはりページ先頭であっても上マージンが入ります（`hyperref` を使わなければこんな余計なマージンは入りません）。

これは `hyperref` が `\refspepcouter` を上書きしているせいです。
`\refspepcouter` はチャプターやセクションの番号を増やすマクロですが、`hyperref` はこれを上書きして、番号を増やすだけでなくハイパーリンク用のアンカーもつけてしまいます。
このアンカーはもちろん目に見えませんが、LaTeXではテキスト要素とみなされてしまうので、ページ先頭であってもページ先頭だとはみなされなくなり、`\vspace` によるマージンが入ってしまいます。

前述の `sty/starter-heading.sty` ではこの問題に対処するために、上書きされた `\refstepcounter` を使わず、上書きされていない `\refstepcounter` を使いました。
またハイパーリンク用のアンカーは `hyperref` パッケージのマクロを使って明示的につけました。
これでようやく、ページ先頭では余計なマージンが入らないようになりました。
（もっと簡単な方法としては、`\refstepcounter` を呼び出す前に上マージンを入れる方法もあります。今回はマクロ定義の都合でできませんでした。）


### 問題点：いろんなパッケージが `\section` の実装に依存している

先ほど説明したように、`hyperref` パッケージはハイパーリンクを実現するために、`\section` の内部で使われている `\refstepcounter` マクロを上書きします。
つまり `hyperref` パッケージは `\section` の実装に強く依存しており、密結合になっています。

そのせいで、独自に `\section` マクロを定義すると `hyperref` パッケージの目次機能は機能しません（当然です）。
そのため、独自に `\section` マクロを定義するときは自力で `hyperref` の機能を呼び出して同じような機能を実現する必要があります。

これはうまい解決策はなさそうです。
ただし `\section` マクロが部分的にカスタマイズしやすくなっていれば、密結合の強度を弱めることはできたはずです。


## まとめ

LaTeXの `\section` マクロを、テンプレートメソッドパターンを使って実現する方法を紹介しました。
この方法を使うと、必要な箇所のマクロだけを上書きすればいいので、`\section` のカスタマイズ性が格段に向上します。

また `\section` の仕様そのものにも問題があることを説明しました。
特に「番号をつける・つけない」と「目次に出す・出さない」を個別に指定できないのは、仕様の欠陥です。

プログラミングの観点でみると、LaTeXはTeX言語だけでなく、クラスファイルやスタイルファイルの定義にも問題が多いと感じます。
いくらTeXやLaTeXが古いからとはいえ、プログラミングの定石から外れたコード（マクロ定義）が多すぎるように見えます。
`\section` の定義はそれが顕著で、コードを共通化したいという気持ちは分かるものの、やり方がよくなくて、「タイトル上下のマージンの高さを変えたい」「フォントの大きさを変更したい」という程度の変更ですら「`\section` 定義の全体をコピーして必要な箇所を書き換える」ことが必要です（Open-Closed Pricipleに反してますよね）。
また `\section` と `\paragraph` の定義は共通化する必要がないのに共通化してるし、逆に共通化できるはずの `\section` と `\chapter` はしていないという、とてもちぐはぐな設計です。

LaTeXのパワーユーザーで『[リファクタリング](https://www.amazon.co.jp/dp/427405019X)』を読んだ人って、実は少ない（というかほぼいない？）のではないでしょうか。
せめて『[リーダブルコード](https://www.amazon.co.jp/dp/4873115655)』ぐらいは読んで、もうちょっとコード品質を気にかけてほしいです[^1]。
そうすれば「LaTeXに詳しくないと読めないコード」が減って、LaTeXに詳しくなくてもカスタマイズができるのに、と思います。

逆に言えば、クラスファイルやスタイルファイルの品質（LaTeX的な意味ではなくプログラミング的な意味での品質）を向上すれば、LaTeXはもっと使いやすくなるはずだと期待しています。
TeX言語の仕様は変えられないけど、クラスファイルやスタイルファイルは変えられるので。

[^1]: もし読んでいてあのコード品質なら、読んでないのと同じ。


## おまけ

余計なお世話でしょうけど、jlreq.clsが[200行を超えるようなマクロ定義](https://github.com/abenori/jlreq/blob/master/jlreq.cls#L2127)を平気でしてるの、やめたほうがいいと思いますよ？（プログラミング的な意味で）