В данной статье будет рассмотрен мой личный опыт постановки на поток документирования и анализа PHP проекта, который был разработан порядка 10-ти лет назад и не претерпевал с тех пор никаких существенных изменений.
Статья может быть полезна специалистам, которые занимаются управлением проектами или решают схожие задачи представленные в данной статье.
Рассматриваются подходы, практики и инструменты, которые были использованы мной и моей командой в ходе анализа и документирования целевого проекта.
Введение
Для начала хотелось бы рассказать о том, что это за проект, который потребовалось задокументировать и проанализировать, какова цель, какие вообще есть задачи и почему это потребовалось сделать сейчас, а не в далёком будущем.
Проект, который попал в область моих задач называется "Selena". Точно не знаю с чем связано данное название, однако по умолчанию будем считать, что это связано с древнегреческой мифологией.
Определение слова "Селена", позаимствованное из Википедии
Селе́на (др.-греч. Σελήνη; от др.-греч. σελήνη — «луна», далее от σέλας — «свет, сияние») — одна из богинь в древнегреческой мифологии, известная также под именем Ме́не (др.-греч. Μήνη, от др.-греч. μήνη — «луна»). Богиня Луны, в поздней античности отождествлявшаяся с Артемидой и Гекатой.
Проект представляет собой веб-приложение уровня предприятия. Используется оно сотрудниками предприятия и работает во внутренней его сети, тем самым ограничивая доступ "не посвящённым".
С помощью веб-приложения сотрудники получают различные возможности по взаимодействию с базой данных, конфигурированию и настраиванию станков, взаимодействию с ними, генерации отчётов, получении рабочих уведомлений и многие другие. В общем, веб-приложение приносит пользу одному предприятию.
Само по себе веб-приложение представляет собой монолитное приложение написанное на PHP 5.4, без каких-либо популярных на сегодняшний день архитектурных подходов, с игнорированием SOLID принципов, без пакетного менеджера (инъекция зависимостей происходит напрямую) и со множеством проблем в проектировании базы данных (одной из которых является отсутствие явных связей между таблицами через foreign key, т.к. используемые в проекте технологии и подходы не обновлялись с начала разработки проекта) . В общем, есть причины отнести данный проект к типу legacy.
Основная цель анализа и документирования проекта заключается в том, чтобы сформировать базу технической документации проекта, расширить его возможности в области командного образования и как таковой его дальнейшей поддержки. До того, как моя команда взялась за решение задач, связанное с достижением поставленных целей, никакой технической документации проект не имел. За 10 лет разработки сменилось множество разработчиков, которые не оставили после себя документы, которые бы подробно описывали что там в системе происходит, какой функционал в ней есть, какие бизнес-процессы и бизнес-функции присутствуют, и так далее. Как только моя команда приступила к анализу проекта, выяснилось, что в нём присутствуют лишь комментарии к PHP-коду, которые не давали никакой ясности в том, что происходит. Пришлось проводить ещё и документирование PHP-кода, что позволило нам также создать пространство для тестирования такого популярного на сегодняшний день инструмента, как ChatGPT в области анализа программного кода.
Теперь рассмотрим задачи, которые были передо мной и моей командой поставлены:
Задокументировать исходный код проекта в виде развёрнутых комментариев к PHP-коду, с ведением учёта задокументированных файлов. Под документированием в данном пункте понимается комментирование исходного кода.
Сформировать единую точку (хранилище), в которой бы хранилась вся техническая документация проекта, все концептуальные модели, описание бизнес-процессов, бизнес-функций, архитектуры системы и многое другое.
Описать все API веб-приложения и вынести их описание в отдельную информационную базу.
Теперь приступим к последовательному рассмотрению процесса решения поставленных задач.
Немного о семантическом версировании
Как только мы взялись за проект, сразу же встал вопрос - а какую версию веб-приложения мы будем анализировать и документировать?
Ситуация осложнялась ещё и тем, что помимо нашего участия в проекте присутствовал разработчик, который активно вносил изменения в проект и переписывал систему на более новую версию языка - PHP 7.
Спойлер
Разработчик до сих пор занимается переписыванием проекта на PHP 7 с PHP 5.4, однако нам удалось с ним наладить контакт и синхронизироваться в процессе работы, а потому моя команда поработала с разными версиями PHP, что благоприятно повлияло на их опыт в код-ревью.
Проект разрабатывается порядка 10-ти лет, а репозиторий для него на GitHub был создан 3 года назад, и, что естественно, ни о каких соглашениях о коммитах и семантическом версировании речи и не шло, т.к. это бы повлияло на создание документации, которой не было. Я принял решение закрепиться за конкретной версией по временному принципу - задокументированные исходники кода отстают по своей "актуальности" от боевого проекта всего на 5 недель (в долгосрочной перспективе это не такой большой промежуток).
Как только моя команда создала копию проекта в GitHub, мы сразу же начали использовать соглашение о коммитах, которое посчитали полезным, не смотря на то, что проект уже очень давно разрабатывался без каких-либо соглашений (никогда не поздно начать).
Нет комментариев? Проставим!
После того, как мы скопировали исходники проекта и выделили для его документирования отдельный репозиторий, моя команда незамедлительно приступила к комментированию исходного кода проекта.
Файлов в проекте оказалось очень много (что не удивительно), и нужно было каким-то образом вести учёт уже задокументированных и описанных файлов. Здесь сделаю небольшой спойлер (без этого никак) и скажу, что для ведения технической документации мы выбрали инструмент Wiki.js, а потому для начала комментировали PHP-код, изучали бизнес-процессы, строили схемы по архитектуре приложения, а затем переносили это всё в отдельные страницы Wiki.js. К слову, получилось лучше чем я предполагал.
Для учёта файлов, которые уже были задокументированы было принято решение их отмечать определёнными PHP комментариями в самом начале файла. Выглядело это следующим образом:
<?php
// В многострочном комментарии находится "метка" задокументированного файла
/* --------------------- */
/* Documented in Wiki.js */
/* --------------------- */
// Дальше идёт PHP код файла
Чтобы каждый раз не просматривать все файлы на наличие данной метки и поставить на "поток" процесс комментирования файлов исходного кода проекта была создана утилита одним из разработчиков в моей команды.
Скрипт выглядел следующим образом (он достаточно прост для понимания):
import os
import pandas as pd
# Получение всех файлов в определённой директории
def getfiles(dir = os.getcwd()):
filelist = []
# Перебор всех файлов и добавление наиболее интересных
# в результирующий массив (интересны только PHP и JS файлы)
for dirpath, dirnames, filenames in os.walk(dir):
for filename in filenames:
if filename.count('.php') or filename.count('.js'):
filelist.append(os.path.join(dirpath, filename))
return filelist
# Функция парсинга файлов
def parcer():
# Получение пути к директории с файлами проекта
way_to_file = input(str('Введите путь до файла(если путь окажется некорректным программа вернёт пустой файл): \n'))
if way_to_file == '':
# По умолчанию директорией с исходными файлами считается та, где данный скрипт запущен
mass = getfiles()
else:
mass = getfiles(way_to_file)
# Массив ещё не задокументированных файлов
output = []
# Поиск во всех файлах исходного кода "метки"
for i in mass:
cheq = ''
# Чтение данных из файла
with open(i, "r", encoding="latin-1") as file:
cheq = file.read()
# Проверка содержания "метки" в файле
# Если метка есть - то файл задокументирован и в выходной массив он не добавляется
# Если метки нет - то файл ещё не задокументирован и он добавляется в выходной массив
if cheq.count('/* --------------------- */\n'
'/* Documented in Wiki.js */\n'
'/* --------------------- */\n') == 0:
output.append(i)
return output
# Получение результата работы функции "парсинг файлов"
data = parcer()
while True:
# Выбор типа сохранения файлов
save_as = input(str("Сохранить как:\n"
"1. excel фaйл\n"
"2. .txt файл\n"))
# Сохранение результата работы утилиты в Excel-таблицу
if save_as == '1':
df = pd.DataFrame(data=data)
df.to_excel('./result.xlsx')
break
elif save_as == '2':
# Сохранение результата работы утилиты в текстовый файл
with open('result.txt', 'w') as file:
for i in data:
file.write(i + '\n')
break
else:
print("Введите 1 или 2")
Если рассматривать общий поток комментирования, то он представлен на рисунке 1 в виде схемы.
Таким образом моя команда смогла задокументировать большое число файлов исходного кода проекта.
Куда складывать техническую документацию? Wiki.js!
После первых успехов в понимании работы веб-приложения, наступил момент определения дальнейшего вектора развития наших действий, ведь до этого момента мы ещё не определились с единой точкой, в которую бы складывали все результаты нашего анализа системы и технической документации.
Конечно, можно вести техническую документацию и в обычном Word или даже текстовом документе с соответствующими соглашениями об оформлении и структуре. Однако читать линейный документ не доставляет большого удовольствия. Тем более когда страниц в нём порядка 500 или даже 1000. Мне хотелось найти инструмент, который бы предоставлял гибкие возможности в плане структурирования контента и даже в некотором смысле позволял бы имитировать файловую структуру исходного кода (грубо говоря).
Были мысли использовать docuWiki, мы даже начали её использовать в первую неделю работы над проектом, однако быстро поняли, что его функциональных возможностей недостаточно для удовлетворения всех наших требований.
Нам нужен был инструмент, с помощью которого мы могли бы организованно хранить разные страницы, причём изолированно друг от друга. Инструмент docuWiki не предоставлял такие возможности. Как и имел не очень хороший дизайн (кто работал с docuWiki поймёт о чём я).
Полный список требований к инструменту для содержания технической документации по проекту был следующий:
Инструмент должен иметь хороший дизайн, чтобы было приятно им пользоваться
и не было желания разбить экран монитора.Инструмент должен предоставлять широкие возможности по организации контента
Инструмент должен предоставлять возможности сделать бэкап всех ресурсов и контента
Инструмент должен предоставлять различные способы редактирования контента и поддерживать язык разметки Markdown
Желательно, чтобы инструмент можно было развернуть в контейнере с простым сценарием развёртывания
Всем требованиям, которые были предъявлены к искомому инструменту, удовлетворяла платформа Wiki.js.
Данный инструмент имеет хороший дизайн (см. рис. 2), предоставляет широкие возможности по организации контента, позволяет сделать бэкап (с возможностью синхронизацией с GitHub), а также поддерживает разметку Markdown и разворачивается через простые инструкции в Docker Compose.
Предприятие, для которого работает веб-приложение, предоставило нам два шлюза:
Для развёртывания своих сервисов и платформы Wiki.js (серверный шлюз)
Для работы с развёрнутыми сервисами и платформой Wiki.js (клиентский шлюз, на котором установлена ОС Windows 10 и с помощью которой мы взаимодействовали с тестовой версией проекта и развёрнутой Wiki.js)
На серверном шлюзе было развёрнуто приложение Wiki.js, по достаточно простому сценарию развёртывания, который включает в себя использование инструмента Docker.
Следующие шаблонные инструкции были использованы нами для развёртывания Wiki.js с помощью Docker Compose:
version: "3"
services:
db:
image: postgres:11-alpine
environment:
POSTGRES_DB: wiki
POSTGRES_PASSWORD: wikijsrocks
POSTGRES_USER: wikijs
logging:
driver: "none"
restart: unless-stopped
volumes:
- db-data:/var/lib/postgresql/data
wiki:
image: ghcr.io/requarks/wiki:2
depends_on:
- db
environment:
DB_TYPE: postgres
DB_HOST: db
DB_PORT: 5432
DB_USER: wikijs
DB_PASS: wikijsrocks
DB_NAME: wiki
restart: unless-stopped
ports:
- "80:3000"
volumes:
db-data:
В шаблонном коде всё достаточно просто. Есть сервис db, который представляет собой базу данных (в данном случае - postgresql), и также есть сервис wiki, который, собственно, и является нашим приложением Wiki.js (мы используем вторую версию данного приложения). После запуска данных инструкций происходит развёртывание контейнера с базой данных и приложением Wiki.js (конечно, все переменные окружения должны быть добавлены в данные шаблонные инструкции).
После внедрения инструмента Wiki.js в наш рабочий поток несколько изменился. Теперь после того, как были задокументированы исходные файлы проекта, составлены необходимые схемы и концептуальные модели нужно было всё вносить в Wiki.js.
Более того, предоставленные шлюзы ограничивали нам доступ к GitHub (политика безопасности предприятия), а потому не было возможности напрямую синхронизироваться с репозиториями. Однако, была возможность подключиться к серверному шлюзу по SFTP, и загружать туда под правами root файлы бэкапа (Wiki.js поддерживает множество функций для бэкапа, SFTP и Git это лишь одни из них, полный их список можно найти тут).
Для периодического бэкапа была придумана схема, по которой после внесения каких-либо результатов в Wiki.js необходимо было выгружать бэкап сначала локально (на серверный шлюз), а затем с помощью FileZilla загружать этот бэкап на свою локальную машину и переносить всё в репозиторий на GitHub.
Полная схема бэкапа представлена на рисунке 3.
В общем-то, такой способ сохранения информации о содержимом Wiki.js используется нами до сих пор. Он достаточно прост и эффективен, а также позволяет гибко распоряжаться содержимым бэкапа (например, можно его загружать не на GitHub, а на любую другую платформу). Моя команда осуществляет данную процедуру раз в день и этого достаточно чтобы сохранить результаты работы и в любой момент времени перенести их на любое другое устройство где можно развернуть Wiki.js (для этого есть механизм импорта бэкапа).
А в поток работ по документированию и анализу кода проекта добавляется новые узлы - добавление результатов анализа и документирования в Wiki.js, а также создание бэкапа Wiki.js (см. рис. 4).
На рисунке 5 представлено веб-приложение с технической документацией целевого проекта (результаты плодотворной работы моей команды).
Куда пропали контроллеры для API? Они есть везде!
Так уж сложилось, что ни один из популярных подходов при проектировании рассматриваемого веб-приложения не был использован. Ни MVC, ни CSR, ни даже SOLID принципы не посчитали нужным уделить внимание данному проекту.
К сожалению, многообразие обработчиков POST и GET запросов в почти каждом файле исходного кода (даже там, где это не уместно) может вызвать смущение даже у начинающего архитектора. В данном проекте огромное пространство для изучения ошибок проектирования, чем я и занимался какое-то время.
Сейчас уже очень популяризированы такие подходы как MVC, CSR, MVVM, DI и многие другие. На момент разработки данного проекта они были не настолько популярны, да и не из каждого угла можно было услышать "пиши MVC, делай по SOLID, а архитектурный стиль используй микросервисный". Так сложилось, ничего не поделаешь. Однако, в наши задачи входит исключительно анализ, а значит меньше критики, больше дела.
Такая подводка была не случайна. Дело в том, что когда документируют API каких-либо сервисов часто взаимодействуют с каким-то одним слоем системы.
Например, есть архитектурный подход Controller Service Repository, где приложение делится на разные слои: контроллеры, сервисы и репозитории соответственно. Слой контроллеров обрабатывает запросы пользователя, слой сервисов реализует бизнес-логику, а слой репозиториев - работает с базой данных.
Так вот, описание API серверных приложений осуществляется на слое контроллеров, это можно взять как правило, даже если вы не используете именно CSR всё равно в хорошей архитектуре (а я говорю про хорошую архитектуру) слой контроллеров (адаптеры связи с внешним миром, если угодно) выделяется отдельно и именно с ним предстоит проделать работу при документировании API.
В данном проекте можно найти части работы контроллеров. Но, только лишь части. Слой контроллеров явно не был выделен, а потому одному члену моей команды пришлось собирать всю работу контроллеров по частям, что было сделано достаточно успешно.
После получения первых результатов в описании API контроллеров появились вопросы о том, какую методику выбрать для их документирования.
Было понятно, что мы будем использовать Swagger для описания API веб-приложения, поскольку он очень удобен для решения данной задачи.
Мне пришла в голову интересная идея - создать шаблон для документирования API сервисов на базе Node.js приложения, в котором реализована возможность генерации документации Swagger по комментариям.
Относительно данного инструмента (шаблона документирования) я уже делал статью, и предлагаю заинтересованному читателю с ней ознакомится для большего понимания статьи текущей.
Было принято решение создать по шаблону отдельное приложение и начать описывать API веб-приложения в комментариях.
К слову, для описания контроллеров не нужен был даже JavaScript код, т.к. платформа использована Node.js читатель мог подумать, что шаблон требует знаний JavaScript. Однако там достаточно базового понимания работы npm и совсем азов JavaScript - знание как комментировать код.
Пример описания контроллеров API с помощью используемого нами инструментария следующий:
/**
* Авторизация пользователя
* @route POST /index.php
* @group Авторизация (пользователь) - Функции для авторизации пользователя
* @param {string} username.query.required Входные данные
* @param {string} password.query.required входные данные
* @returns {RequestRedirectDto.model} 200 - перенаправление на страницу /
* @returns {RequestRedirectDto.model} 400 - перенаправление на страницу inc/templates/middle/login/loginfailed.html
*/
/**
* Запрос списка записей для конкретного материала
* @route GET /xxx_editmat.php
* @group Материалы - функции работы с материалами
* @param {number} matid.query.required идентификатор материала
* @returns {RequestRedirectDto.model} 200 - перенаправление на страницу admin/editmat/editmat
* @returns {RequestRedirectDto.model} 400 - перенаправление на страницу admin/editmat/editmat
*/
/**
* Запрос операции редактирования записи
* @route PUT /xxx_editmat.php
* @group Материалы - функции работы с материалами
* @param {number} matid.query.required идентификатор материала
* @param {number} edit_mat.query.required идентификатор редактирования записи
* @param {number} mat_art.query.required артикул записи
* @param {string} mat_name.query.required название материала
* @param {number} mat_price.query.required цена материала материала
* @returns {RequestRedirectDto.model} 200 - перенаправление на страницу admin/editmat/editmat
* @returns {RequestRedirectDto.model} 400 - перенаправление на страницу admin/editmat/editmat
*/
В комментариях идёт описание API по стандарту Open API, с использованием комментариев в стиле JSDoc.
На серверном шлюзе было также развёрнуто серверное приложение используемого шаблона, что позволяет на клиентском шлюзе видеть описание API через Swagger UI (см. рис. 6).
Ну, а с серверным приложением, которое генерирует документацию, отправлять изменения в репозиторий на GitHub ещё проще - не нужно загружать с серверного шлюза какие-либо файлы через FileZilla на локальную машину, все действия с генератором происходят на локальной машине разработчика-исследователя.
Однако, все результаты с локальной машины теперь необходимо загружать на серверный шлюз, чтобы на клиентском шлюзе были продемонстрированы все результаты. Весь этот процесс представлен на рисунке 7.
Что ж, в наш поток документирования и анализа добавляется последний элемент - документирование API веб-приложения (см. рис. 8).
Этот код мне непонятен. Нужна нейронка!
В ходе анализа исходников кода проекта встречались ситуации, когда было не сразу понятно как какой-то кусок кода работает, а порой и вообще было не понятно долгое время, в следствии чего общий процесс работы затянулся.
Мы решили поэкспериментировать и использовать популярную на сегодняшний день нейронную сеть ChatGPT для решения задач анализа исходного кода 10-ти летней давности и были приятно удивлены её результатами.
Она отлично справилась с пояснениями к исходному коду на PHP, она достаточно точно определяет зачем и какие функции в PHP были использованы (имеется ввиду стандартные функции языка), и выдвигает верные предположения.
Например, я выдал задачу ChatGPT прокомментировать определённый участок кода с подробными комментариями. Вот часть кода, который нужно было прокомментировать:
globalize($_REQUEST, array('url' => STR, 'userid' => INT));
if ($userid != $bbuserinfo['userid'])
{
eval(print_standard_error('logout_missing_userid'));
}
$prefix_length = strlen(COOKIE_PREFIX);
foreach ($_COOKIE AS $key => $val)
{
$index = strpos($key, COOKIE_PREFIX);
if ($index == 0 AND $index !== false)
{
$key = substr($key, $prefix_length);
if (trim($key) == '')
{
continue;
}
vbsetcookie($key, '', 1);
}
}
А вот результат работы ChatGPT:
"Данный код начинается с вызова функции globalize(), которая принимает два аргумента: массив данных из запроса ($_REQUEST) и массив, содержащий пары ключ-значение, где каждый ключ указывает на переменную, которую нужно объявить глобальной, а значения определяют тип данных для каждой переменной.
Далее происходит проверка условия, где $userid (полученный из глобального массива $_REQUEST) должен равняться значению $bbuserinfo['userid']. Если они не равны, вызывается функция print_standard_error(), в которую передается строка-код ошибки 'logout_missing_userid', которая должна отобразиться пользователю.
Затем происходит очистка всех cookies, начинающихся с префикса COOKIE_PREFIX. Для этого в цикле проходится массив $_COOKIE и для каждого элемента проверяется начинается ли его имя с COOKIE_PREFIX. Если да, то происходит удаление этой cookie с помощью функции vbsetcookie().
Общий смысл кода заключается в том, чтобы разлогинить пользователя, очистить все cookies и произвести некоторые другие действия, связанные с очисткой сессии текущего пользователя."
И действительно, если более детально рассмотреть код, то ChatGPT будет прав - это все инструкции используемые для осуществления выхода пользователя из системы:
// Код ...
if ($_REQUEST['do'] == 'logout')
{
globalize($_REQUEST, array('url' => STR, 'userid' => INT));
if ($userid != $bbuserinfo['userid'])
{
eval(print_standard_error('logout_missing_userid'));
}
// clear all cookies beginning with COOKIE_PREFIX
$prefix_length = strlen(COOKIE_PREFIX);
foreach ($_COOKIE AS $key => $val)
{
$index = strpos($key, COOKIE_PREFIX);
if ($index == 0 AND $index !== false)
{
$key = substr($key, $prefix_length);
if (trim($key) == '')
{
continue;
}
vbsetcookie($key, '', 1);
}
}
// Код ...
И это лишь один из множества примеров его использования.
ChatGPT мы использовали для ускорения работы по задачам анализа и документирования кода. С его помощью нам удалось в сравнительно короткие сроки для такой задачи всё проанализировать и задокументировать.
Вполне возможно, что в скором времени ChatGPT и другие продвинутые нейронные сети будут обладать возможностями для работы с разработчиками в командах, и будут формироваться целые коллективы разработчиков, где смешаны люди и нейронные сети. Однако, уже сейчас нейронные сети используются повсеместно как инструменты для облегчения рутинного труда людей.
Заключение
В статье был рассмотрен мой опыт формирования потока для документирования и анализа проекта 10-ти летней давности, который систематизировал работу моей команды, а внедрение ChatGPT в рабочие процессы позволил им ускорить выполнение задач анализа.
Был пройден путь от комментирования исходного кода до документирования API веб-приложения, которые располагаются по приложению где только можно, нарушая принципы SOLID и озадачивая разработчика-исследователя тем, как собрать по частям работу контроллеров.
На протяжении всего пути менялся и поток анализа и документирования, в который постоянно что-то добавлялось. В своей статье я описал все переходы, которые претерпел данный поток по ходу работы.
Были решены все поставленные задачи и достигнуты цели. Для формирования единой точки сбора технической документации был использован инструмент Wiki.js, а для документирования API - Swagger UI.
В статье я также показал, что разработать систему можно не только с помощью языков программирования, но и с помощью различных концептуальных моделей, схем и абстракций, которые представляются на естественных языках. Порой системы с главным участником в лице человека намного сложнее, чем программы.