Привет! Меня зовут Константин Нежберт, я технический писатель команды Deckhouse в компании «Флант». В апреле я выступал на международной ИТ-конференции «Стачка» с докладом о проверке документации. Я рассказал, как мы с командой технических писателей проверяем документацию, зачем она нужна и какие есть подходы. Также подробно разобрал интеграцию инструмента для проверки.
Это текстовый вариант доклада, а ссылку на видео с выступлением можно найти в конце материала.

Зачем проверять документацию и как это делать
Проверка документации является важным этапом при работе с ней по следующим причинам:

Опечатки инженеров. Документация хранится вместе с кодом на GitHub, и её обычно пишут сами инженеры по мере добавления новых фич. Поскольку инженеры не всегда являются специалистами в работе с текстом, в документации могут встречаться ошибки и опечатки. Поэтому текст необходимо быстро обновлять.
Неверное использование терминов. В документации часто встречаются специфический DevOps-сленг и термины из области ИТ, которые могут быть понятны специалистам, но не всегда уместны в официальных текстах.
Смешивание латинских и кириллических букв. Иногда в русских словах случайно используются похожие по форме латинские буквы (например, латинская «C» вместо русской «С»), что может приводить к путанице и снижать качество текста.
Перечисленные выше проблемы можно устранить, и я выделил три следующих способа.
Можно мотивировать инженеров придерживаться единого стиля и внимательно относиться к тому, что они пишут. Такой подход помогает снизить количество ошибок, но реализовать его на практике довольно сложно. Если постоянно напоминать каждому сотруднику о необходимости исправлять тексты, это может вызвать раздражение и недовольство в командах.
Также можно нанять корректора, который будет отслеживать все изменения в документации и проверять их. Это действительно помогает повысить качество текста и обеспечивает единый стиль, ведь все вопросы, связанные с оформлением, будут сосредоточены в руках одного человека. Но и у этого подхода есть недостатки: корректор — тоже человек, он может что-то упустить из виду из-за усталости или по другим причинам.
Но мы выбрали третий вариант — автоматизировали проверку документации. Такой подход позволяет существенно снизить количество ошибок и обеспечить единообразие стиля, поскольку все проверки выполняются автоматически и не зависят от человеческого фактора. Однако у этого решения тоже есть свой минус — на его внедрение и настройку потребуются время и усилия.
Какой инструмент выбрали для проверки документации
Сначала мы рассмотрели готовые инструменты для автоматической проверки документации. Их главные плюсы — минимальные усилия на внедрение, наличие сообщества и поддержки разработчиков, к которым можно обратиться за помощью. Но такие решения часто слишком специфичны и не всегда подходят под конкретный кейс и особенности документации.
Например, в одном из проектов использовался словарь неправильных написаний слов — словарь опечаток, по которому проверялись варианты ошибок. Однако мне это показалось сомнительным, ведь составить такой словарь корректно практически невозможно, так как вариантов ошибок может быть очень большое количество, а ещё могут появляться новые, что не всегда возможно отследить.

Потом мы обратили внимание на инструменты, на базе которых можно построить собственную систему проверки. Один из таких — LanguageTool. Это инструмент для проверки орфографии, пунктуации, повторов слов и других ошибок. Он позволяет глубоко анализировать текст, но требует разработки собственного решения для интеграции. Кроме того, LanguageTool достаточно тяжёлый — для работы нужен либо платный сервис с подпиской, либо собственный сервер, который нужно развёртывать и поддерживать в инфраструктуре. По этим причинам мы отложили его внедрение до лучших времён.
И тогда к нам пришла идея использовать Hunspell — консольную утилиту в виде одного бинарного файла, которая широко применяется в таких продуктах, как LibreOffice, Firefox, Chrome и многие другие.
Почему мы выбрали именно Hunspell? Потому что это быстрый, лёгкий и простой инструмент, для которого не нужно запускать дополнительные сервисы. Он доступен в репозиториях большинства Linux-дистрибутивов, а мы, работая с Open Source и Kubernetes, используем Linux повсеместно.
Также Hunspell поддерживает большое количество языков благодаря стандартным словарям, которые входят в его состав. Кроме того, мы придерживаемся принципа использования Open Source-решений в наших продуктах — и этот инструмент идеально вписался в нашу философию.
Как мы проводили первые тесты Hunspell
Начнём с исходных данных. В нашем случае это сайты наших продуктов. Например, сайт утилиты werf хранится в репозитории на GitHub и представляет собой статически сгенерированный сайт на базе Jekyll.
Jekyll — инструмент, который берет Markdown-файлы и собирает из них статические HTML-страницы, которые затем можно разместить на сервере. Для описания различной логики при генерации страниц на них используется язык разметки Liquid.
В репозитории каждого сайта лежат следующие файлы: шаблоны страниц с контентом, страницы, из которых генерируются конечные файлы, и дополнительные элементы Jekyll, позволяющие переиспользовать контент с одной страницы на другую с помощью специальной разметки. Всё это автоматически собирается с помощью GitHub Actions: мы вносим изменения, оформляем pull request, после чего процесс сборки и деплоя запускается автоматически, и сайт становится доступен нашим клиентам.
Первым делом мы запустили всё «как есть». Взяли страницу нашего самоучителя по Kubernetes и прогнали по ней Hunspell, не внося предварительных изменений:
---
title: Самоучитель по Kubernetes
permalink: /guides.html
layout: plain
breadcrumbs: none
---
{% asset overview.css %}
{% asset guides.css %}
<h1 class="docs__title">Самоучитель по Kubernetes</h1>
<p>Самоучитель ориентирован на разработчиков, которые хотят научиться работать с Kubernetes и доставлять в него код своих приложений. Также эти материалы будут полезны DevOps-инженерам, которые хотят эффективнее решать задачи по CI/CD в K8s и познакомиться с werf на практике.</p>
<p>Самоучитель — это и пошаговые практические инструкции, и необходимая теория. Он разбит на несколько разделов: от базового уровня до более продвинутых фич. В руководствах учтена специфика языков/фреймворков и приложены примеры исходного кода приложения и инфраструктуры (IaC).</p>
<p>Выберите наиболее близкую вам технологию:</p>
{% include common/guides-landing-tiles.html %}
Страница содержит метаданные: title, ссылку для генерации, шаблон. В теле страницы видны элементы Liquid (asset, include), HTML-разметка (h1, p и т. д.) и текст. Мы запустили Hunspell с русским словарём на этом файле и получили список непонятных слов — например, framework, фича и другие:

Также туда попали все английские слова, что натолкнуло нас на идею добавить английский словарь. С двумя словарями результат улучшился, но в основном оставались термины вроде framework, Kubernetes, werf, DevOps:

Далее мы взяли страницу, написанную только на Markdown, и запустили Hunspell на ней:
Конфигурация для Redis, которая поможет с этим:
```shell
maxmemory 500mb # Если данные начнут занимать 500 Мб...
maxmemory-policy allkeys-lru # ...Redis удалит редко используемые ключи.
```
А для Sidekiq это может быть [Sidekiq worker killer](https://github.com/klaxit/sidekiq-worker-killer):
```shell
require 'sidekiq/worker_killer'
Sidekiq.configure_server do |config|
config.server_middleware do |chain|
# Корректно завершить Sidekiq при достижении им потребления в 500 Мб.
chain.add Sidekiq::WorkerKiller, max_rss: 500
end
end
```
Понятное дело, что во всех этих случаях `limits.memory` должен быть выше, чем пороги срабатывания вышеуказанных механизмов.
Оказалось, что он проверяет весь текст без исключений, включая блоки кода, например shell-скрипты, хотя их проверять не нужно:

Также он разбивает слова по апострофу — у нас есть особенность: некоторые термины пишутся на английском с русским окончанием через апостроф (например, job’ы). Hunspell считает суффикс неправильным, что частично верно, но с точки зрения контекста — приемлемо.
Третий тип файлов — YAML, где хранится определённая информация. Например, страница со ссылками на публикации о наших продуктах генерируется из YAML-файла с ссылками и краткими описаниями:
...
- title: "Вышла werf 2.0: новый движок развёртывания Nelm и 300+ релизов за четыре года"
habr_url: "https://habr.com/ru/companies/flan..."
img: "/assets/images/publications/ru_160524.png"
created: 2024-05-16
comment: |
<p>Четыре года мы развивали и улучшали werf 1.2, но теперь наконец‑то выпустили стабильную werf 2.0. Причина простая — последовательно накопилось множество улучшений (300+ релизов!), а кроме того, мы доработали новый движок развёртывания Nelm, и в werf 2.0 это единственный движок. Старый движок удалён. Nelm обратно совместим с Helm 3, поэтому никаких особых изменений в чартах не потребуется — они будут развёртываться так же, как и раньше.</p>
Hunspell проверяет каждую строку YAML, но нам нужно, чтобы он анализировал только комментарии и текстовые поля.
Минусы, которые мы обнаружили: необходимо исключать блоки кода из проверки, так как там много ненужных слов. Либо нужно писать «обёртку», чтобы проверять только комментарии в коде. В YAML тоже следует проверять не все поля, а только определённые.
Как мы реализовывали проверку орфографии
Далее нам пришла новая идея: сгенерировать сайт в HTML в том виде, в каком он должен быть доступен всем, и проверить уже готовый HTML. Для этого мы взяли исходники, собрали их с помощью Jekyll и получили каталог с множеством HTML-файлов, многие из которых повторяются. Затем очистили HTML от лишнего — убрали блоки кода и элементы, которые не нужны для проверки. Далее запустили Hunspell в режиме HTML, используя ключ, который позволяет работать именно с HTML-контентом.
Для реализации мы сделали следующее:

Во-первых, процесс должен был запускаться в CI/CD. Во-вторых, предусматривался ручной запуск на локальной машине для проверки документации во время её написания. В обоих случаях запускается сборка Docker-контейнера с сайтом.
Далее берётся контейнер со спелл-чекером, то есть с Hunspell и всем необходимым — словарями и подготовленными скриптами для очистки. После этого мы забираем собранный контент из контейнера с Jekyll и передаём его в контейнер с Hunspell для проверки, выводя результат.
Сборка контейнера с сайтом и генерация контента
Контейнер с сайтом собирается из Git-репозитория с исходниками, где хранятся тексты для русской и английской версий. Jekyll генерирует внутри два каталога — ru и en:

Сборка контейнера со спелл-чекером, словарями и всем необходимым
Контейнер с Hunspell — это базовый образ Ubuntu, в который устанавливаются Hunspell, Python, библиотека Beautiful Soup для обработки HTML, а также необходимые словари и скрипты для очистки:

Передача контента из контейнера сайта в контейнер спелл-чекера
Передача контента между контейнерами происходит просто — путём копирования файлов:

Запуск проверки документации в контейнере
Запуск проверки документации осуществляется двумя bash-скриптами:
#!/bin/bash
set -e
arg_site_lang="${1:?ERROR: Site language \'en\' or \'ru\' should be specified as the first argument.}"
script=$(cat <<EOF
cd /spelling/$arg_site_lang && \
./container_spell_check.sh $arg_site_lang
EOF
)
werf run spell-checker --env='test' --dev --docker-options="--entrypoint=bash" -- -c "$script"
Первый определяет язык и запускает контейнер с Hunspell. Внутри контейнера запускается второй скрипт, который выполняет следующие действия:
1. Перекодирует словари в UTF-8:
cp /usr/share/hunspell/en_US.aff /usr/share/hunspell/en_US.aff.orig
cp /usr/share/hunspell/en_US.dic /usr/share/hunspell/en_US.dic.orig
iconv --from ISO8859-1 /usr/share/hunspell/en_US.aff.orig > /usr/share/hunspell/en_US.aff
iconv --from ISO8859-1 /usr/share/hunspell/en_US.dic.orig > /usr/share/hunspell/en_US.dic
rm /usr/share/hunspell/en_US.aff.orig
rm /usr/share/hunspell/en_US.dic.orig
sed -i 's/SET ISO8859-1/SET UTF-8/' /usr/share/hunspell/en_US.aff
Почему это необходимо — до сих пор неясно, так как локально всё работает корректно, а в контейнере без перекодировки — нет.
2. Проверяет на игнорирование файла. Мы создали файл исключений с перечнем файлов, которые не нужно проверять из-за большого количества «мусора».
3. С помощью Python-скрипта, который проходит по всем страницам, чистит их от блоков кода:
python3 clear_html_from_code.py $file
#!/usr/bin/python3
# -*- coding: utf-8 -*-
from bs4 import BeautifulSoup
import sys
if len (sys.argv) > 1:
html = open(sys.argv[1]).read()
root = BeautifulSoup(html, 'html.parser')
if root.find('code'):
for code in root.select('code'):
code.decompose()
snippetcuts = root.find_all("div", {"class": "snippetcut"})
for snippetcut in snippetcuts:
snippetcut.decompose()
code_wrappers = root.find_all("div", {"class": "viewer__wrap"})
for wrapper in code_wrappers:
wrapper.decompose()
print(root)
4. Удаляет закомментированные участки. Некоторые элементы на страницах неизменно приводили к падению Hunspell с ошибками. Для них мы добавили HTML-комментарии, оборачивающие эти элементы. Перед проверкой эти участки вырезаются с помощью sed — утилиты Linux для работы с текстом:
<!-- spell-check-ignore -->
<img src="/assets/images/cncf-logo-small.svg" alt="">
<!-- end-spell-check-ignore -->
sed '/<!-- spell-check-ignore -->/,/<!-- end-spell-check-ignore -->/d'
5. Переводит HTML в plain text. Мы пришли к выводу, что лучше сначала конвертировать HTML в текст, а затем запускать Hunspell на полученном тексте:
html2text -utf8
6. Запускает Hunspell. Проверка проводится с использованием русских и английских словарей в зависимости от контента:
hunspell -d $language -l
Все эти шаги повторяются для каждого файла внутри контента.
Результаты
Hunspell выводит список слов с опечатками — тех, которые не найдены в словаре и считаются неправильными. Для каждого файла формируется такой список. Если ошибки обнаружены, мы выводим результат и возвращаем код возврата 1, чтобы GitHub Actions завершился с ошибкой и задача (Job) на GitHub была отмечена красным крестиком. Это сигнализирует, что что-то пошло не так. Если ошибок нет, возвращается код 0, а GitHub показывает зелёную галочку — всё в порядке.
В какие моменты происходит запуск? Проверка запускается при каждом pull request на GitHub — то есть при внесении изменений в документацию. В списке задач можно увидеть статус: зелёная галочка или красный крестик. Также проверка выполняется при мерже в ветку main. Хотя запуск при мерже необязателен, мы решили делать это дважды — для надёжности.
Ниже примеры результатов, которые есть на GitHub. Успешно завершённые проверки отображаются зелёными — spellcheck.ru и spellcheck.en прошли без ошибок:

Красным отмечена упавшая русская проверка, где в файле index.html Hunspell обнаружил термин, отсутствующий в словаре:

После запуска мы добавили несколько важных улучшений. Первое — это генерация кастомного словаря. Многие наши термины отсутствовали в стандартных словарях, поэтому мы сформировали собственный. Для этого создали отдельную команду, которая собирает все найденные слова, сортирует их, подсчитывает количество и генерирует словарь для Hunspell. Этот словарь — обычный .dic
-файл, совместимый с такими программами, как GoldenDict.
Также добавили команды для работы с кастомным словарём — как локально, так и в CI/CD:
site:generate-special-dictionary
— генерирует словарь специальных терминов из набора слов в wordlist'е;site:get-words-with-typos
— собирает все слова с опечатками в два файла — для русской и английской версий сайта:site:get-words-with-typos:en
— подкоманда для английской версии;site:get-words-with-typos:ru
— подкоманда для русской версии;
site:run-spell-check
— запускает проверку русской и английской версий сайта:site:run-spell-check:en
— только для английской версии. Позволяет выбрать конкретный файл для проверки, если указать параметр--
./path/to/file
;site:run-spell-check:ru
— только для русской версии. Позволяет выбрать конкретный файл для проверки, если указать параметр-- ./path/to/file
;
site:sort-custom-dict
— сортирует wordlist перед отправкой изменений в Git;site:view-plain-text-of-target-html
— отображает очищенное содержимое целевого файла;site:view-plain-text-of-target-html:en
— только для английской версии;site:view-plain-text-of-target-html:ru
— только для русской версии.
Выводы
Внедрение этой системы дало нам следующие преимущества:
Мы сразу проверили всю документацию и нашли множество пропущенных опечаток — реально много.
Теперь ошибки пресекаются на стадии разработки, что позволяет оперативно их исправлять.
Работа корректора упростилась: если проверка показывает зелёный статус, значит, явных ошибок нет и можно сосредоточиться на содержании.
Видео с выступлением (~22 минуты):
P. S.
Читайте также в нашем блоге: