Мой опыт создания технической документации
СтатьиВ первых строках данного поста хочется выразить благодарность всей нашей команде разработки, которая стойко и мужественно переносит все тяготы и трудности пионеров новых подходов. Всех люблю =)
Признаюсь честно — я очень люблю документацию. Когда-то очень давно, когда моей стезёй и основным стеком разработки был WordPress, то особой документации в практике не было. Максимум — краткий брифинг того, что должно получиться (до технического задания там ещё далеко), либо просто рисунок сайта.
С тех далёких пор минуло много лет, проекты стали больше, а требования сложнее. Лишь только WordPress нет-нет, да выплывет.
Да, на больших и сложных проектах документация — это база, необходимый must have. Была пара проектов, на которых кода за два года накидали очень много, но что где и как работает особо не документировали.
Для моей большой любви к подробному описанию процесса есть несколько причин.
Первая, самая большая и главная — упрощение планирования. Документ описывает конечный результат, осталось только декомпозировать его на задачи, определить очерёдность исполнения и передать в работу. Не могу сказать, что мы получаем конечное количество задач, приводящих нас к успеху — по пути всегда что-то вылезает, тут есть нюансы, конечно.
Во-вторых, она существенно облегчает координацию между всеми участниками проекта. Самый распространённый случай — это взаимодействие backend и frontend разработки. Мы имеем единую точку зрения на то, какими принципами руководствуется вся система и как между собой общаются сервисы. Не будем забывать и про QA — тестировать описанный функционал, имея перед глазами описанные входящие параметры и ожидаемый результат, сильно проще.
Во-третьих, хорошо составленная техническая документация помогает быстрее влиться новым разработчикам, попавшим на проект. Она снимает большую часть вопросов относительно того, как это всё вообще работает и взаимодействует друг с другом, что где и для чего лежит. И это, в свою очередь, позволяет сэкономить время и ресурсы, которые могли бы быть потрачены на исправление ошибок.
Ещё один большой и важный момент — часто на длинных дистанциях какие-то вопросы и моменты относительно реализации функционала теряется. Причём даже не на уровне «не пойму, что тут написано», а «не помню, как это работает точно». У систем, особенно больших, может быть множество взаимосвязей, триггеров событий и прочего, что вспомнить спустя год-два сложно (но можно). Документация помогает этот процесс ускорить.
Поиск нового
Первое, что приходит на ум, когда мы говорим про документирование взаимодействий frontend / backend — это Swagger, конечно. Рассказывать про данный инструмент смысла не вижу, т.к. по факту он является стандартом.
Признаюсь честно — мы очень стараемся, но полноценно он так у нас и не прижился.
Затем, Swagger — это инструмент для документирования API. Но ведь большой проект это не только набор методов, который он предоставляет для других. Это огромный мир процессов, которые происходят в том числе для того, чтобы на запрос в API вернуть ответ.
Но не взаимодействием через API сервисы едины, есть ещё фоновые задачи, например. Поэтому, одним лишь Swagger’ом для описания полноценной документации не обойтись. И на основании всего вышесказанного сложился ряд ожиданий того, какие задачи должен решать технический документ:
- описывать всю систему в целостности, а не только лишь API
- упростить процесс планирования, разработки и тестирования
- быть легко редактируемой и расширяемой
- возможность отслеживать изменения, вносимые по ходу работы
- быть понятной не только разработчикам
В итоге, примерно год поисков, проб и ошибок привёл к подходу, про который я хочу вам рассказать.
Технический документ
Самый главный вопрос — где хранить документацию — решился сам собой. Изначально мы использовали разные онлайн-сервисы типа Confluence, но потом после нескольких раз фатальных несохранений статей у меня жутко подгорело, я перешёл сначала на предварительное написание документации в markdown. А потом подумал, что можно убрать слабое звено из уравнения и перенёс хранение в gitlab. И по итогу это оказалось жизнеспособным решением.
Фиксация изменений – это то, что у git, как говориться в крови. Если нужно при изменении функционала чётко указать, что конкретно было изменено в документации, достаточно указать ссылку на коммит.
Кроме того, работа с репозиториями — это база для разработчика, поэтому вопрос как внести изменения не стоит.
Чаще всего для описания функции достаточно markdown-файла. Но если нужна иллюстрация, то и её можно вполне себе встроить. Да и таблицу набросать если необходимо.
Поделюсь ещё одним лайфхаком – если в документ необходимо встроить схему систем или архитектур, то можно для начала расчертить её в сервисе drawio, затем сохранить как svg (это важно) и подключить в документ картинкой. Нам это даст пару плюсов:
- векторная схема приложения
- этот вектор можно редактировать — благодаря GigaIDE узнал, что можно редактировать прямо там, без необходимости выгружать измененное изображение
С хранением и редактированием разобрались, перейдём теперь к структуре. Как театр начинается с вешалки, так и проект начинается с README-файла. Хотя, объективности ради, в документации все файлы read me.
Задача README-файла – дать основные вводные по проекту. Здесь обязательно перечислены:
- приводится общая терминология и определения проекта – важно, чтобы при обсуждении деталей все участники находились в одном понятийном поле
- архитектура приложения: все сервисы / микросервисы / модули, принципы и правила взаимодействия
- ссылки на дополнительные документы, будь то внешняя документация или дизайн в Figma
- описания всех контуров проекта и прочая важная техническая информация
В настоящее время мы не указываем языки и фреймворки, которые используются на проекте, т.к. основная задача документации — это описание принципов и структуры, а не деталей реализации. Один и тот же сервис может быть написан сначала на одном стеке, а потом, в процессе использования быть переписан на другой (например, более производительный).
В зависимости от проекта его структура может быть поделена на две большие части:
- internal — здесь хранится вся внутренняя документация по системе
- external — внешние сервисы и описание их работы
У вас может быть возникнуть справедливый вопрос – а зачем описывать внешние сервисы, типа API VK или что-то такого открытого? Там же всё уже описано, кроме того, её постоянно актуализируют (по крайней мере мы все надеемся на это).
Справедливое замечание, соглашусь, однако есть пара контраргументов:
- открытая документация есть не для всего
- иногда для проекта необходимо выделить только ряд необходимых интеграций, чтобы не заставлять разработчиков блуждать по её дебрям (привет, документация по OK API)
Описывать содержимое раздела внешних сервисов (external) не вижу смысла, т.к. он является урезанной копией внутренних модулей (internal). Кроме того, если смысла в подробном описании внешних интеграций нет, то она и не создаётся.
Перейдем к разделу внутренних сервисов, где вся мякотка.
Главный файл раздела всегда содержит в себе ссылки на входящие в состав модули и их краткое описание. Кроме того, данный документ определяет названия всех глобальных переменных, которые применимы ко всем модулям.
Например, для общения между собой backend-сервисы подписывают все запросы специальным ключом, значение которого указывается в переменной CONNECTION-KEY.
Это вносит единообразие в репозитории и позволяет не запутаться и не забыть важных параметров при развёртывании проекта на стенде.
Далее, для каждого из внутренних модулей (или микросервисов) системы есть свой отдельный раздел, в котором описываются по возможности все его функциональные особенности. Каждая из них описывается отдельно и обычно представляет собой комплекс документов:
- Миграции
- API
- Типы данных
- Процессы или сценарии
Сразу оговорюсь, что в некоторых случаях список может быть и короче, а где-то и больше — всё по ситуации, но примерная структура такая.
И ещё один маленький нюанс – мы также описываем и клиентские приложения (в нашем случае веб-приложения на React), однако его содержимое чуть отличается от всех прочих разделов. Но об этом ниже.
Теперь поговорим о том, что же храниться внутри.
Миграции
Начнём с миграций. В данном документе описываются названия и структура таблиц, а также пояснения к столбцам — за какой параметр каждый отвечает. Например, типичная достаточно таблица пользователей:
На первый взгляд, это может показаться избыточным. Однако, спустя год-два очень приятно зайти и уточнить, за какой конкретно параметр отвечает столбец. Кроме того, по мере развития проекта какие-то из столбцов могут устаревать и / или переноситься в другие таблицы, и все эти изменения так же можно фиксировать и отмечать deprecated-столбцы.
API
Наш аналог Swagger, только чуть проще. С другой стороны, он подробнее.
Каждый запрос обязательно имеет:
- указание метода и роут, по которому он может быть доступен
- список всех обязательных и необязательных параметров и их значения по-умолчанию
- описание производимых проверок и действий, а также ссылки на сценарии (если необходимо)
- возвращаемый результат
Здесь каждый может найти что-то для себя:
- тот, кто метод вызывает, знает, что ему нужно послать и в каком виде оно вернётся после выполнения
- тот, кто метод разрабатывает, имеет готовое описание задачи и ожидаемый результат
Надеюсь, вы заметили, что возвращаемые объекты имеют ссылки — это ссылки на типы данных. Про них дальше и поговорим.
Типы данных
Собственно, это описания объектов, которые передаются между сервисами. Кроме того, данный раздел единственный, который присутствует не только на уровне конкретной функциональности, но и конкретного сервиса, и даже на глобальном уровне.
Изначально объекты данных описывались с помощью json, но затем мы плавно переехали на использование Typescript-подобного синтаксиса. Например, объект пользователя из вышеупомянутого запроса их списка:
Да, можно возразить, что TypeScript — не самый лучший вариант. Но при этом он даёт наглядность в описании возвращаемых данных и позволяет комбинировать типы / интерфейсы между собой, что даёт отличную гибкость в описании возвращаемых данных.
Как это работает на практике.
Например, у нас существует определённая договорённость о том, что все сервисы всегда (разве что за исключением 500 ошибок, когда плохо серверу) должны возвращать объект конкретного типа.
В описаниях методов API в основном описываются положительные варианты выполнения запросов, т.к. если происходит ошибка, то её нужно обработать и жить дальше. В примере запроса списка пользователей указывается, что мы возвращаем массив объектов TUser в объекте пагинации, который в свою очередь, наряду с TResponse, является глобальным типом:
Теперь нам осталось просуммировать всю имеющуюся информацию – в итоге, в случае успешного ответа, мы получим вот такой объект:
Итого – наглядно и расширяемо. А больше нам ничего и не надо.
Процессы или сценарии
Целый огромный мир, описывающий работу модуля. Каждый сценарий подробно описывает завершённую последовательность действий и обязательно указывает возвращаемый результат.
Если в процессе выполнения задачи есть переиспользуемые участки, то они так же выносятся в отдельный сценарии и на них обязательно указывается ссылка.
Более того, здесь мы указываем название класса, в котором данный функционал реализован. Что нам это даёт:
- Если при выполнении задачи разработчику нужно обратиться к сценарию, то он может быстро найти класс, а не начать пилить свою реализацию с нуля (привет, DRY)
- Наглядно показывает, какие сценарии были уже написаны, а какие нет (если только разработчик не забыл внести его название после того)
Клиентское приложение
Отдельный раздел документации посвящён клиентскому приложению. Здесь вы не найдёте типизаций, миграций (что логично) или описания того, на каких библиотеках реализован тот или иной функционал.
Здесь для frontend-разработчиков суммируются ссылки на полезные для них ресурсы: объекты и методы, которые используются при работе конкретной фичи.
Как использовать
Планирование работ начинается обычно задолго до того, как будет сделан первый git push. Но вместо того, чтобы подробно описывать в карточке задачи то, что необходимо сделать, мы указываем ссылку на техническую документацию.
Если в процессе работы что-то изменилось, то всегда к задаче можно приложить ссылки на коммиты, в которых были произведены изменения. Равно как и если необходимо изменить логику конкретных функций, главное — для каждой задачи делать свой отдельный коммит, чтобы можно было сконцентрировать внимание на этом.
Итог
Стоит сказать, что описанный способ может вызывать определённые сомнения или даже противоречия. Равно как и нельзя его назвать идеальным и ультимативным способом ведения документов.
Однако, как вариант, который будет удобен как backend-разработчикам, так и frontend, QA, аналитикам и менеджменту показывает себя очень и очень неплохо. А возможность использовать ссылки между файлами (в том числе на заголовки) позволяет создать качественную перелинковку.
В заключение хочется отметить, что техническая документация — это неотъемлемая часть проектов. Она играет ключевую роль в обеспечении стабильности и эффективности работы команд. Поэтому важно уделять внимание созданию и поддержанию технической документации на всех этапах развития проекта.