日本語は非常に難しいナリ〜というお話

この記事は [TIS Advent Calendar 2020](https://qiita.com/advent-calendar/2020/tis) の 23 日目です。

世の中は猫も杓子もやれ継続的インテグレーション、継続的デリバリーという状況でして、みなさんもそんな流行りに乗っかって、今日も継続的ギョームに日々勤しんでおられることでしょう。大変お疲れ様でございます。
もう少しで継続的年末年始ホリデーですので、残る力を最後の血の一滴まで振り絞り、張り切っていきましょう。

さて、継続的インテグレーションや継続的デリバリーといったプラクティスは価値を素早く高い品質で継続的に提供するためにあるわけなのですが、これらの適用はどうしてもソフトウェアプロダクトが先行しており、忘れられがちなのがドキュメントです。

ドキュメントは一つの成果物であるとともに、人に何かを伝えるためにあります。
そうであれば当然「伝わりやすさ」を品質として定義できるはずです。
一方で、ドキュメントは自然言語で書かれるが故に統一的な品質の定義が難しいため、一貫しない品質定義によって振り回される状況も容易に発生します。

## ドキュメントレビューの地獄

ここでいくつか、ドキュメントレビューの例を挙げます。

### レビュー指摘・修正のやりとりの方が文章が多い

本来はわずかな修正で終わるところ、なぜか成果物たるドキュメントよりもレビュー指摘表周辺への記述の方が長くなってしまうケースです。

1. レビュアーがレビュー指摘表に「nページ目の3段落目のこの文章の末尾に句点がついていません〜〜〜ちゃんと見直そうよ〜〜〜」と長大な文章で指摘する
2. レビュイーが「。」を一文字付与する修正を行う。
3. 最後にレビュイーがレビュアーに「大変申し訳ございません。〜の理由で見落としてしまっておりました。お手数ですが再度レビューを〜〜」とレビューの再依頼を行う

一文字の指摘修正のために100倍以上の文字を書く。これがレバレッジです。知らんけど。

### 日本語とは本来…？という哲学が始まる

統一された基準がない中でレビューが行われることにより、レビューイがレビュアーに「お前はそうやって指摘するけどそういうお前の指摘は本当に正しいの？」と疑いを持ちはじめるケースです。

場合によっては互いに譲れなくなり、それぞれで「正しい日本語とはなんなのか」、「歴史的に"かな"はどうやって発展したのか」等という探求のGoToトラベルが始まります。知らんけど。

### 品質評価での分析

上記のようなミスと指摘が繰り返されると、レビュー指摘数が上昇していきます。
一般にレビュー指摘数は品質を測る指標の一つであり[^1]、この指標が高い値を示すとステークホルダーは「成果物の品質は本当に大丈夫なのか…？？？」と不安を抱きます。そのような不安は解消せねばなりません。
句点をちょっと忘れたことで、レビュー指摘の内容を分析し、分析した結果から改善方針を見出し、報告し、実践する必要があるかもしれません。

私たちは次は絶対に句点を忘れないという強い意思のもと、「句点があるかどうかを徹底的に確認するマン」を誕生させ、迫り来る2021年に立ち向かうのでした。

[^1]: 要出典

## 改善するためには

何がまずいのかというと、以下の2つではないかと思います。

- 品質基準が定義されていない
- フィードバックが遅い

基準が定義されていないレビューは混乱を来します。遅いフィードバックは余計な手戻りを招きます。

こういう話は自動化すれば良い。ソースコードにlintをかけるように、ドキュメントにもlintをかけるべきです。「レビュー」という場で問題を検出することを期待するのではなく、レビューの場には問題のない状態で臨むべきです。

## 具体的な改善策

というわけで、我々のプロジェクトでは[textlint](https://github.com/textlint/textlint)をCIに設定しています。

textlintは自然言語に対して適用できるlinterです。pluggableな構造になっており、自分たちのコンテキストにあうルールを組み込むことができます。
自動的にルールが適用されるということは、ある種の「最低限の品質」を定義し担保できるということです。これで、句点がないばかりに謎の手間暇をかける必要がなくなります。

<!--
textlintはネイティブでMarkdownとテキスト形式に対応していますが、ルールはASTに対して適用されます。つまり、ASTに変換するプラグインさえあれば他の形式にも対応できます。例えばLaTeXにも対応できます。

- [textlint-plugin-latex2e](https://github.com/textlint/textlint-plugin-latex2e)

Microsoft WordやExcelには対応していないのか、と残念がる向きはあると思います。
ファイルフォーマットに縛りがあるくらいであれば、[pandoc](https://pandoc.org/)とかでなんとかできると良いですね。
-->

## プロセスへの組み込み

「ドキュメントを修正したら、手動でtextlintをかけましょう」だと、いつのまにか誰もやらなくなります。このため、チェックはCIに組み込むことが多いです。

textlint用のコンテナイメージを作っておき、リポジトリにPUSHされるタイミングでtextlintを実行します。
ぼくたちは今、GitLabで開発を進めているので、[GitLab Container Registry](https://docs.gitlab.com/ee/user/packages/container_registry/)にコンテナイメージを登録しておき、`.gitlab-ci.yml`にジョブを定義しています。

```dockerfile
FROM node:12-alpine3.12

COPY package.json package-lock.json ./
RUN npm install

ENTRYPOINT ["textlint"]
CMD ["-h"]
```

```yaml
textlint:
  stage: lint
  image:
    name: ${CI_REGISTRY}/${CI_PROJECT_NAMESPACE}/${CI_PROJECT_NAME}/lint-cli:${LINT_IMAGE_VERSION}
  script:
    - '**/*.md'
  only:
    changes:
      - "**/*.md"
      - .gitlab-ci.yml
      - .textlintrc
      - prh.yml
```

これにより、Markdownが変更されたらtextlintのチェックが走り、問題があればCIが落ちます。
修正されるまでマージできないという状態を作ります。

## ルール

textlint は非常に便利ですが、自由度の高いプラガブルな構造ゆえにどういうルールを使うのかが悩みどころです。そこで、現在ぼくたちが使っているルールをいくつかご紹介します。

### [textlint-rule-preset-ja-technical-writing](https://github.com/textlint-ja/textlint-rule-preset-ja-technical-writing)

いきなりルールではないものを紹介しますが、技術文書に対するtextlintの「ルール集」です。
適用されるルールは[ルール一覧](https://github.com/textlint-ja/textlint-rule-preset-ja-technical-writing#%E3%83%AB%E3%83%BC%E3%83%AB%E4%B8%80%E8%A6%A7)を参照ください。

例えば句点を統一する[textlint-rule-ja-no-mixed-period](https://github.com/textlint-ja/textlint-rule-ja-no-mixed-period)、二重否定を防止する[textlint-rule-no-double-negative-ja](https://github.com/textlint-ja/textlint-rule-no-double-negative-ja)などなど、たくさんのルールが入っています。最初のとっかかりとしては非常に良いと思います。

我々が頻繁に遭遇するエラーは、1文の長さを100文字以内[^2]とする[textlint-rule-sentence-length](https://github.com/textlint-rule/textlint-rule-sentence-length)によるもの、
「することができます」「処理を行う」といった冗長表現を「できます」「処理する」といった簡潔な表現へ変える [textlint-rule-ja-no-redundant-expression](https://github.com/textlint-ja/textlint-rule-ja-no-redundant-expression)によるものでしょうか。

[^2]: 文字数は設定により変更可能です

文字数チェックは議論の多いところですが、読みづらい文章というのは経験的に以下の特徴を持ちます。

- 係り受けの関係が複雑で、どの形容詞や副詞が何を修飾しているのかがわからない
- 多くの「言いたいこと」が接続詞によって接続され、何が言いたいのかがわからない

文字数を強制的に制限することにより、上記のような問題が間接的に改善されます。

### [textlint-filter-rule-comments](https://github.com/textlint/textlint-filter-rule-comments)

そうはいっても、「ここだけは例外的にtextlintエラーを許容したい」という要望がありえます。

textlint-filter-rule-comments は、`<!-- textlint-disable -->`および
`<!-- textlint-enable -->`で囲まれた範囲をtextlintで無視できるというフィルタです。

特定ルールだけを無視するということも可能です。

```markdown
<!-- textlint-disable ruleA,ruleB -->

Ignore RuleA and RuleB

<!-- textlint-enable ruleA -->

Still ignore RuleB
```

### [textlint-filter-rule-allowlist](https://github.com/textlint/textlint-filter-rule-allowlist)

こちらは、特定の語句をtextlintのチェック対象から外すというフィルタです。

例えば、前述のプリセットに同梱されている[textlint-rule-max-kanji-continuous-len](https://github.com/textlint-ja/textlint-rule-max-kanji-continuous-len)は、漢字6文字以上の連続をエラーとする極めて強力（暴力的）なルールです。

一方で、日本で生きていく上では、[「基礎控除申告書兼給与所得者の配偶者控除等申告書兼所得金額調整控除申告書」](https://www.nta.go.jp/taxes/tetsuzuki/shinsei/annai/gensen/annai/1648_73.htm)のような単語との共生を余儀なくされます。

こういった凶悪な単語を、textlint-filter-rule-allowlistでは例外として許可できます。[正規表現](https://github.com/textlint/textlint-filter-rule-allowlist#regexp-like-string)も書けるので便利です。

```json
{
  "filters": {
    "allowlist": {
      "allow": [
        "基礎控除申告書兼給与所得者",
        "配偶者控除等申告書兼所得金額調整控除申告書"
      ]
    }
  },
  (snip)
```

### [textlint-rule-preset-ja-spacing](https://github.com/textlint-ja/textlint-rule-preset-ja-spacing)

スペース周りのルールをまとめたプリセットです。

英単語と日本語との間にスペースを入れる人と入れない人がいます ([LaTeX](https://ja.wikipedia.org/wiki/LaTeX)で論文を書くときは、スペースを入れろと指導されることが多いですね)。
何も手を打たないと不統一になってしまうので、[textlint-rule-ja-space-between-half-and-full-width](https://github.com/textlint-ja/textlint-rule-preset-ja-spacing)で統一しています。

### [textlint-rule-spellcheck-tech-word](https://github.com/azu/textlint-rule-spellcheck-tech-word)

技術用語の表記揺れチェックをかけることができます。

- `Webkit` -> `WebKit`
- `git` -> `Git`
- `browserify` -> `Browserify`

最低限の表記揺れ修正に効果を発揮します。

### [textlint-rule-prh](https://github.com/textlint-rule/textlint-rule-prh)

表記揺れルールを自分たちで作りたい、カスタマイズしたいという場合はprhを使います。
「ユーザ」ではなく「ユーザー」と表記したい、「jquery」ではなく「jQuery」と表記したいというケースですね。

いくつか我々のプロジェクトで使っているルールを抜粋します。これでイメージが伝わるでしょうか。

```yaml
rules:
  - pattern: /ユーザ([^ー])/
    expected: ユーザー$1
  - pattern: /サーバ([^ー])/
    expected: サーバー$1
  - pattern: /Prod環境/i
    expected: Production環境
```

GitHub上には[WEB+DB Press のサンプル?](https://github.com/prh/rules/blob/711e00793d9d69eeda04d68a796b6d9afb6d5748/media/WEB%2BDB_PRESS.yml)もあり、記述例として参考になると思います。

### [textlint-rule-no-dead-link-fork](https://github.com/textlint-rule/textlint-rule-no-dead-link-fork)

Markdown から、他のMarkdownあるいは外部URLにリンクを貼ることがありますが、そのリンク誤り(dead-link)を検出してくれるルールです。これ最近導入したのですが非常に良い。

プロジェクトの運営の中で、ファイル名や場所の変更は頻繁に発生します。また、外部サイトのURLがいつの間にか変更されているケースもあります。
こういったときのリンクエラーの検知は非常に難しい問題だったのですが、このルールを導入することで解消しました。

我々はこんなルールで運用しています。

```json
    "no-dead-link": {
      "ignore": [
        "http://localhost**",
        "https://docs.aws.amazon.com/**"
      ],
      "ignoreRedirects": true,
      "retry": 3,
      "userAgent": "textlint-rule-no-dead-link/1.0",
      "concurrency": 8
```

## まとめ

ドキュメントは成果物の一つです。素早いフィードバックを行う状態を構築することで、地獄の釜に蓋をしておくことができます。

ちなみにこのエントリは一度も textlint をかけていません。
だからこんなにわかりにくいんだ。だからこんなに冗長なんだ。さっさと textlint をかけろ。