Пример Написания Функциональных Требований К Системе Enterprise

Недавно мой друг-программист рассказал мне, что он не читает требования, а вместо этого приглашает аналитика на чашку чая, они садятся вместе, и аналитик рассказывает ему, что нужно реализовать.

Мой друг умный человек и хороший программист, и причина, по которой он получает знания о требованиях таким способом, не в том, что ему лень читать документацию, а в том, что даже прочитав ее, он не до конца поймет, что такое нужно сделать.

В этой статье я хочу рассказать вам, как можно написать требования к программному продукту так, чтобы программисты не только использовали требования, но и участвовали в их написании; На основе собственного опыта я хочу показать, как можно описать требования так, чтобы этих описаний было достаточно для реализации системы.

Целью нашей разработки было создание с нуля системы учета для одной из крупных российских компаний.

Система была призвана заменить действующую, написанную в конце 90-х.

В результате была реализована платформа и один из бизнес-модулей.

Реализованная часть включала около 120 объектов, 180 таблиц и около 30 печатных форм.

Хочу оговориться, что описанный ниже подход не является универсальным для написания любого программного обеспечения.

Подходит для систем уровня предприятия, построенных на основе объектно-ориентированного подхода: бухгалтерских, CRM, ERP-систем, систем документооборота и т.д. Вся документация к нашему программному продукту состояла из следующих разделов:

  • общая часть • Список терминов и определений.

    • Описание бизнес-ролей

  • Требования • Бизнес-требования
    • Распространенные сценарии
    • Случаи использования
    • Алгоритмы и проверки
    • Системные Требования • Нефункциональные требования • Требования к интеграции • Требования к пользовательскому интерфейсу
  • Выполнение
  • Тестирование
  • Путеводители
  • Контроль
общая часть состоял всего из двух разделов: списка терминов и их определений и описания бизнес-ролей пользователей.

Любая документация системы, включая, например, тестовые примеры, основывалась на приведенных здесь определениях.

Бизнес-требования описал, что нужно бизнес-пользователям.

Например, им вообще не нужен системный объект «Пользователь», но им нужна возможность изменить стоимость товара в счете и распечатать его.

Бизнес-требования состояли из общих сценариев, вариантов использования и описаний алгоритмов обработки данных.

Подробнее о разработке этих типов требований вы можете узнать из книги Карла Вигерса и Джой Битти.

Разработка требований к программному обеспечению .

Системные Требования описаны свойства и методы всех объектов системы.

Нефункциональные требования в этой статье мы не будем касаться этого.

Я могу только порекомендовать вам отличную книгу.

Архитектура корпоративных решений Пол Дайсон, Эндрю Лонгшоу.

Требования к интеграции описал низкоуровневый интерфейс взаимодействия новой системы с несколькими другими системами компании.

Мы не будем рассматривать их здесь.

Требования к пользовательскому интерфейсу – отдельная большая тема, возможно, для другой статьи.

Также здесь я не буду касаться других разделов документации, касающихся внедрения, тестирования, руководств и управления.

Давайте подробнее разберемся, что такое список терминов и зачем он нужен.



Список терминов и определений

Очень часто при обсуждении функционала системы разговор заходит в тупик.

Еще хуже, если стороны расходятся во мнениях, думая, что все согласовано, но в результате у них разное понимание того, что нужно делать.

Не в последнюю очередь это связано с тем, что изначально участники проекта не смогли договориться о том, что означают те или иные термины.

Бывает, что даже самые простые слова вызывают проблемы: что такое пользователь, чем отличается группа от роли, кто такой клиент. Поэтому, в отличие от описания бизнес-ролей, термины необходимо определять как можно точнее.

Поясню это на примере термина Пользователь .

Википедия дает такое определение: Пользователь — человек или организация, использующая операционную систему для выполнения определенной функции.

Но нас это не устроило по нескольким причинам.

Во-первых, в систему может войти только человек, а не организация.

Во-вторых, для нашей системы настоящее время глагола «использует» некорректно — система хранит данные о неактивных или удаленных пользователях, т.е.

о тех, кто пользовался системой ранее, но не может в данный момент. Наконец, у нас есть данные о потенциальных пользователях.

Например, мы регистрируем сотрудника компании-клиента, который может (или не может) получить доступ к системе в будущем.

Наше определение: Пользователь - лицо, которое имеет, имело или может иметь доступ к системе для осуществления операций.

Теперь программист, прочитав определение, сразу поймет, почему свойство Авторизоваться в объекте User является необязательным.

Термины связаны друг с другом.

В термине Пользователь употребляется «операция», поэтому приведу его определение: Операция — совокупность действий, составляющих содержание одного акта предпринимательской деятельности.

Операция должна соответствовать требованиям ACID (атомарность, согласованность, изоляция, долговечность).

Набор операций одного модуля представляет собой клиент-серверный интерфейс этого модуля.

Как видите, это определение очень важно для всей системы — оно не только связывает пользователя и его бизнес-действия с тем, что должно быть реализовано, но и накладывает требования к тому, КАК система должна быть реализована (это КАК было определено ранее при разработка архитектуры) — бизнес-действия внутри операции должны быть внутри транзакции.

Работа над перечнем терминов продолжается.

Мы сохранили его полноту, т. е.

постарались сделать так, чтобы в документации не было термина, не определенного в этом списке.

Кроме того, были случаи, когда мы меняли условия.

Например, по прошествии нескольких месяцев с момента начала написания требований мы решили заменить Контрагента на Компанию.

Причина была проста: оказалось, что слово «контрагент» ни в речи, ни в разговоре никто не умел использовать.

А раз так, то его следовало заменить чем-то более гармоничным.

Нередко возникали случаи, когда приходилось прервать обсуждение и углубиться в требования, чтобы понять, соответствует ли обсуждаемый функционал существующим определениям.

И чтобы сохранить согласованность требований, нам в итоге пришлось либо менять реализацию, либо корректировать описания терминов.

В результате у нас в списке оказалось около 200 определений бизнеса и системы, которые мы использовали не только во всей документации, в том числе, например, в техническом проекте, разрабатываемом программистами, но и в разговоре, при устном обсуждении функционала системы.

Вторая часть, на которой основывалась вся документация, — описание бизнес-ролей.



Описание бизнес-ролей

Все знают, что пользователи используют систему.

Но даже в небольшой системе у них разные права и/или роли.

Вероятно, самое простое разделение — это администратор и обычный пользователь.

В большой системе ролей может быть несколько десятков, и аналитику необходимо заранее подумать об этом и указать роли при описании распространенных сценариев (см.

ниже) и в заголовках вариантов использования.

Список бизнес-ролей используется для реализации групп пользователей и ролей и назначения им функциональных прав; тестировщикам необходимо тестировать сценарии под требуемыми ролями.

Нам не пришлось придумывать бизнес-роли для пользователей, поскольку в компании были созданы отделы, роли и функции.

Ролевые описания даны на качественном уровне на основе анализа основных функций сотрудников.

Окончательное присвоение конкретных прав ролям произошло ближе к концу разработки, когда набор функциональных прав стал стабильным.

Несколько примеров:

Пример написания функциональных требований к системе Enterprise



Уровни требований

Одной из важных концепций, которые мы использовали при разработке требований, было их разделение на слои.

Алистер Коберн в книге Современные методы описания функциональных требований к системам различает 5 уровней.

Мы использовали 4 — три уровня бизнес-требований плюс системные требования: Бизнес-требования

  1. Общие сценарии (эквивалент очень белого уровня Коберна)
  2. Сценарии использования (соответствует синему цвету)
  3. Алгоритмы и проверки (скорее чёрные)
4. Системные требования (прямого аналога нет, скорее черный) Кроме того, нашими требованиями было дерево (с циклами).

Те.

общие сценарии уточнялись вариантами использования, которые, в свою очередь, имели ссылки на проверки и алгоритмы.

Поскольку мы использовали вики, физическая реализация такой структуры не составила проблем.

Варианты использования, алгоритмы и тесты использовали объекты, их свойства и методы, описанные на уровне системы.

Данная методология позволила нам, с одной стороны, описать текущий сценарий настолько подробно, насколько это необходимо на данном уровне, выведя детализацию на нижний уровень.

С другой стороны, находясь на любом уровне, можно подняться выше, чтобы понять контекст его реализации.

Этому способствовал и функционал вики: скрипты и алгоритмы писались на отдельных страницах, а вики позволяла видеть, какие страницы ссылаются на текущую.

Если алгоритм использовался в нескольких сценариях, то он обязательно размещался на отдельной странице.

Программисты обычно реализовывали такие фрагменты как отдельные методы.

На картинке ниже показана часть нашей иерархии (о содержании речь пойдет дальше).



Пример написания функциональных требований к системе Enterprise

Важно отметить, что хотя на системном уровне описывались все без исключения объекты системы, скрипты писались не для всех случаев поведения пользователя.

Ведь многие объекты были, по сути, каталогами, и требования к ним более-менее очевидны и схожи.

Таким образом мы сэкономили время аналитика.

Интересный вопрос — кому в команде проекта какой уровень нужен.

Будущие пользователи смогут читать общие сценарии.

Но сценарии использования для них уже сложные, поэтому аналитик обычно обсуждает сценарии с пользователями, а не дает им изучить их самостоятельно.

Программистам обычно нужны алгоритмы, проверки и системные требования.

Определенно можно уважать программиста, который читает варианты использования.

Тестировщикам (как и аналитикам) нужны требования всех уровней, поскольку им приходится проверять систему на всех уровнях.

Использование вики позволило всем членам проектной команды параллельно работать над требованиями.

Замечу, что в один и тот же момент разные части требований находились в разных состояниях: от находящихся в работе до уже реализованных.



Бизнес-требования



Распространенные сценарии

Корневая страница нашего дерева требований состояла из общих сценариев, каждый из которых описывал один из 24 бизнес-процессов, которые необходимо реализовать в данном модуле.

Сценарии на странице располагались в той последовательности, в которой они выполнялись в компании: от создания объекта с реализуемым товаром до передачи его клиенту.

Некоторые конкретные или вспомогательные скрипты были помещены в конце в отдельный раздел.

Общий сценарий — это последовательность действий пользователя и системы для достижения конкретной цели.

Описания общих сценариев были гораздо менее формальными, чем варианты использования, поскольку они не предназначались для реализации.

Основная цель общего сценария — обобщить варианты использования, подняться над системой и увидеть, что в конечном итоге хочет сделать пользователь, и как система ему в этом помогает. Хотелось бы отметить, что общие сценарии содержали и шаги, которые пользователь осуществлял вне системы, поскольку необходимо было отразить его работу в целом, со всеми этапами, необходимыми для достижения бизнес-цели.

На этом уровне хорошо видна роль системы в работе сотрудника компании; понятно, какая часть этой работы автоматизирована, а какая нет. Именно здесь стало понятно, что определенная последовательность действий, которую мы предлагали пользователю выполнить в системе, избыточна, и что некоторые шаги можно сократить.

Некоторые другие цели общих сценариев:

  • систематизация знаний о работе пользователей и системы
  • координация бизнес-процессов с будущими пользователями
  • основа для понимания того, что требования выполнены и ничего не упущено
  • точка входа при поиске нужного скрипта или алгоритма
Вот пример одного распространенного сценария:

Пример написания функциональных требований к системе Enterprise

Как видите, автоматизирована только половина шагов, да и те описаны максимально кратко.

Также с первого шага понятно, что перевод задания на печать вручную в статус «В работе» в принципе излишен; можно упростить работу пользователя и автоматически перевести задание в этот статус при печати.

Ссылка «Задание на печать», указывающая на описание объекта в системных требованиях, избыточна, так как никому не нужно переходить на нее из общего скрипта.

Но ссылка «пакетная печать документов на груз» важна — она ведет к варианту использования, формально описывающему действия пользователя и системы.

Наши варианты использования имели следующий формат:

  • Заголовок со следующими полями: • статус (В процессе | Готово к рассмотрению | Согласовано) • пользователи (согласно описанию бизнес-ролей) • цель • предварительные условия • гарантированный результат • успешный результат • ссылка на описание пользовательского интерфейса (разработано дизайнером интерфейса) • ссылка на скрипт тестирования (заполняется тестировщиками)
  • Основной сценарий
  • Расширения скриптов


Случаи использования

Вариант использования содержал пронумерованные шаги, которые в 99% случаев явно начинались со слов Пользователь или Система .

Нумерация важна, поскольку она позволяет вопросам и комментариям ссылаться на желаемый момент. Каждый шаг обычно представляет собой простое предложение в настоящем времени.

Проверки и алгоритмы были выведены на новый уровень и часто на отдельных страницах, чтобы облегчить понимание сценария, а также возможность повторного использования.

Вот вариант использования, который упоминается в общем сценарии выше.



Пример написания функциональных требований к системе Enterprise

Часто аналитики рисуют пользовательский интерфейс и пишут на его основе скрипты, объясняя это тем, что он более наглядный.

Доля правды в этом есть, но мы заняли позицию, что интерфейс — дело дизайнера интерфейса.

Сначала аналитик описывает, что должно произойти, а затем дизайнер интерфейса рисует эскиз веб-страницы или диалога.

В то же время случилось так, что сценарий пришлось изменить.

В этом нет ничего плохого, ведь наша цель — спроектировать все части системы так, чтобы пользователю было удобно.

При этом каждый член проектной команды, будь то аналитик или дизайнер интерфейсов, обладая специфическими знаниями и внося свой вклад в общее дело, влияет на работу других членов проектной команды.

Только вместе, объединив усилия, мы сможем получить отличный результат.

Алгоритмы и проверки

Интересная проблема возникла при написании алгоритмов.

Аналитик постарался описать их как можно полнее, т.е.

включить все возможные проверки и разветвления.

Однако полученные тексты оказывались плохо читаемыми, и, как правило, некоторые детали все же упускались (вероятно, из-за отсутствия компилятора — ).

Поэтому аналитик должен описать алгоритм настолько полно, насколько это важно с точки зрения бизнес-логики; программист сам должен предусмотреть мелкие проверки в коде.

Например, рассмотрим простой алгоритм ниже.



Пример написания функциональных требований к системе Enterprise

В алгоритме предусмотрена только одна проверка, но очевидно, что при написании кода метода программист должен реализовать проверки входных параметров; выдать исключение, если текущий пользователь не определен и т. д. Программист также может совместить этот алгоритм с алгоритмами перехода в другие статусы и написать единственный закрытый метод. На уровне API останутся те же операции, но они будут вызывать один метод с параметрами.

Выбор лучшей реализации алгоритмов – это именно компетенция программиста.



Системные Требования

Как известно, программирование — это разработка и реализация структур данных и алгоритмов.

Таким образом, по большому счету, все, что нужно знать программисту, — это структуры данных, необходимые для реализации системы, и алгоритмы, которые ими манипулируют. При разработке системы мы использовали объектно-ориентированный подход, а поскольку в основе ООП лежат понятия класса и объекта, наши структуры данных представляют собой описания классов.

Термин «класс» специфичен для программирования, поэтому мы использовали «объект».

Что.

объект в требованиях равен классу в объектно-ориентированном языке программирования (в скобках отмечу, что в паре разделов требований пришлось изрядно постараться, чтобы разделить объект-класс и объект-экземпляр этого класса в тексте).

Описание каждого объекта располагалось на одной вики-странице и состояло из следующих частей:

  • Определение объекта (копия из списка терминов)
  • Описание свойств объекта
  • Описание операций и прав
  • Данные
  • Дополнительная информация
Мы постарались описать все, что можно, в табличной форме, так как таблица более наглядна, ее структура помогает систематизировать информацию, и таблица легко расширяется.

В первой таблице каждого объекта описывались характеристики его свойств, необходимые программисту для создания структур данных в базе данных и реализации объекта на сервере приложений: Имя Имя свойства управляет как пользователем (например, «Я изменил номер счета», «Номер» — свойство объекта «Учетная запись»), так и командой проекта.

В документации ссылки на свойства использовались в форме простой нотации Object.Property, понятной любому участнику проекта.

Тип Мы использовали Datetime, Date, Time, GUID, String, Enum, Int, Money, BLOB, Array(), Float, Timezone, TimeSpan. Тип отражался на всех уровнях приложения: на уровне базы данных, сервера приложений, в пользовательском интерфейсе в виде кода и графического представления.

Каждому типу было дано определение, чтобы их реализация не вызывала вопросов у программистов.

Например, типу Деньги было дано следующее определение: содержит вещественное число с точностью до 4-го знака после запятой, число может быть отрицательным и положительным; одновременно со стоимостью система сохраняет валюту; Валютой по умолчанию является российский рубль.

Знак редактируемости Да или Нет в зависимости от того, позволяет ли система пользователям изменять значение этого свойства в операции редактирования.

В нашей системе это ограничение было реализовано на сервере приложений и в пользовательском интерфейсе.

Признак наличия нуля Да или Нет в зависимости от того, может ли поле не содержать значения.

Например, поле типа Бул должно содержать одно из возможных значений, а строковое поле обычно может быть пустым ( НУЛЕВОЙ ).

Это ограничение было реализовано на уровне базы данных и на сервере приложений.

Знак уникальности Да или Нет в зависимости от того, уникально ли это поле.

Часто уникальность определяется по группе полей; в этом случае все поля в группе имели Да+ .

Это ограничение было реализовано на уровне базы данных (индекса) и на сервере приложений.

Комментарий Описание поля: что оно означает, для чего нужно, как используется.

Если значение свойства рассчитывается, то это указывается явно с описанием алгоритма расчета этого значения.

Помимо этих, было еще две колонки, которые заполнялись серверными программистами при реализации объекта:

  • Имя свойства объекта в интерфейсе программы.

  • Имя поля в базе данных.

Оба эти поля являются необязательными, так как, например, свойство объекта может не храниться в базе данных, а рассчитываться как сумма счета.

Еще раз отмечу, что в написании требований принимали участие программисты.

Это важно по многим причинам.

Во-первых, так программисты были лучше осведомлены о требованиях, более того, требования становились «ближе к телу», а не просто какой-то бумажкой, написанной каким-то аналитиком.

Во-вторых, документация по API была автоматически сгенерирована.

В-третьих, поддерживалась прослеживаемость требований, т.е.

всегда было понятно, реализовано то или иное свойство, что становилось особенно важным при модификации требований.

Конечно, такая методология требовала от программистов большей дисциплины, что на самом деле было положительным фактором.

Кроме того, благодаря этим колонкам программисты, работающие на разных уровнях приложения, всегда могли найти общий язык, т.е.

понять соответствие между свойством объекта в требованиях, полем в базе данных и свойством в API. Как я уже писал, табличный вид очень удобен для расширения.

Например, для описания первоначальной миграции у нас был столбец с названием свойства устаревшей системы или алгоритма преобразования данных.

Мы также использовали специальные значки для описания того, как выглядит объект в пользовательском интерфейсе.

У нас в свое время была колонка для имени индекса в базе данных, чтобы программисты не забывали создавать их для уникальных полей.

При необходимости вы можете добавить столбец измерения типа данных для каждого свойства.

Вот типичное описание свойств нашего объекта.



Пример написания функциональных требований к системе Enterprise

Вторая таблица объекта содержала описание его операций и их прав.

Каждая операция в системе имела уникальное имя (столбец Операция ), но в пользовательском интерфейсе (в меню) операции отображались под короткими именами ( Короткое имя ).

Для совершения какой-либо операции нужно было иметь определенное право( Верно ).

Столбец Комментарий для сложных методов содержал описание алгоритма или ссылку на него или более общий вариант использования.

CRUD-операции над всеми типами объектов были стандартизированы, поэтому для них обычно не требовались алгоритмы.

Столбец Имя в коде снова заполняется программистом, что, как и описание объекта, было необходимо для документирования API, повышения вовлеченности программистов в написании требований и прослеживаемости.

Ниже приведен пример описания операций над объектом:

Пример написания функциональных требований к системе Enterprise

В этом разделе также содержались таблицы, описывающие переход по статусам.



Пример написания функциональных требований к системе Enterprise

В ячейках содержится краткое название операции, преобразующей исходный статус в целевой статус.

Для более сложных случаев приходилось рисовать полноценные схемы.

После установки системы в ней должны присутствовать определенные данные.

Например, пользователь с правами администратора или список стран по общероссийскому классификатору стран мира.

Эти данные описаны в разделе Данные .

Все, что не вошло в стандартные разделы выше, ушло в раздел Дополнительная информация .

Например, у нас была информация о связях этого объекта со старой системой.

Подводя итог, можно сказать, что системные требования к объекту содержали всю необходимую информацию для его реализации программистом: структуру данных в базе данных, описание предметной области, ограничения на данные и методы, алгоритмы реализации методов, данные, которые должны присутствовать при установке системы.

Структура описания проста для понимания и расширяема.

Многие скажут, что такие подробные требования отнимают много времени и не нужны.

На что я возражаю — как программист может угадать, что именно нужно реализовать? Достаточно ли фразы «Нам нужно реализовать объект User», чтобы через некоторое время получить работающий код? Сколько чая нужно выпить с аналитиком, чтобы извлечь из него данные по 40 (именно столько у нас было) пользовательских свойств? Кто, как не аналитик или дизайнер, должен приложить усилия и описать все объекты?

Постановка задач программистам

Описав, как выглядят требования, рассмотрим интересный вопрос: как следует формулировать задачи для программистов (ограничимся серверной частью многоуровневого приложения)? Получается, что большинство проблем (не дефектов) сводятся к трем вариантам: Типичная задача 1 Название: Реализовать такой-то объект. Текст задачи — ссылка на страницу с системными требованиями к объекту.

В такой задаче программисту необходимо:

  • создавать структуры в базе данных (таблицы, ключи, индексы, триггеры и т. д.);
  • реализовать объект домена;
  • реализовать создание исходных данных.

Все это можно сделать на основе описания объекта в системной части требований.

Программист также должен дополнить таблицу описания свойств названиями полей таблицы базы данных и объекта API. Типичная проблема 2 Рубрика: Реализовать такую-то операцию над таким-то объектом и правами на него Текст задачи — ссылка на страницу с системными требованиями к объекту.

Программист находит на странице название операции и права, а по ссылке в графе «Комментарий» — алгоритмы, проверки и сценарий использования.

Типичная проблема 3 Заголовок: Настройка объекта и/или операции.

Эта задача необходима в случае изменения требований.

Текст задачи содержит описание изменений или ссылку на страницу сравнения версий требований.



Инструмент для написания и управления требованиями

Как многие уже догадались, для работы с требованиями мы использовали Atlassian Confluence. Хотелось бы кратко перечислить преимущества данного продукта.

  • Удаленная работа.

    Собственно, как и любая вики.

  • Ссылки.

    Как вы видели выше, ссылки являются для нас одним из основных инструментов для связывания отдельных частей требований.

  • Возможность разбить требования на части (каждая часть на своей странице).

  • Оповещения при изменении.

    Это один из важнейших способов сотрудничества.

    Например, получив такое оповещение по одному из сценариев, руководитель разработки может ставить задачи разработчикам, а тестировщики знают, что им необходимо скорректировать сценарии тестирования.

  • Комментарии.

    Многие из наших страниц требований были заросли разросшейся иерархией комментариев.

    В Confluence работать с ними довольно удобно, поскольку иерархия не плоская, а в виде дерева.

    Кроме того, есть возможность использовать полноценный редактор, а не только текстовый.

  • Наличие мощного текстового редактора.

    Не буду здесь вдаваться в подробности, отмечу лишь, что на протяжении всей нашей работы Atlassian совершенствовал редактор, и если поначалу глюков было довольно много, то подавляющее большинство из них было исправлено.

  • Хранение истории, сравнение разных версий страниц, возможность отката на старую версию.

  • Просмотрите иерархию страниц в виде дерева.

Однако было несколько проблем:
  • Поскольку во всех требованиях используются одни и те же имена объектов и их свойств, было бы очень удобно иметь инструмент, который при изменении имени менял бы его по всей документации.

    А когда я его удалил, то обнаружил все ссылки на него, которые уже недействительны.

  • Не было возможности собрать статистику.

    Например, у каждого требования был статус, но мы не могли автоматически собирать статусы всех требований и иметь динамическую картину процесса разработки требований.

    Но похоже, что на данный момент нечто подобное уже появилось в Confluence.

  • Диаграммы пришлось рисовать в другой системе, сохранять в формате PNG, а затем размещать изображение на странице Confluence. При этом еще нужно было прикрепить исходный код, чтобы через пару месяцев его можно было найти и исправить.

  • Я не нашел способа экспортировать иерархию страниц в MS Word. «Экспорт в XML и PDF очень часто глючил (возможно, это было связано с размером иерархии).

В заключение хотелось бы выразить благодарность Вадиму Лободе и Артему Каратееву за ценные советы и внимательное рассмотрение данной статьи.

Антон Стасевич Теги: #требования #системный анализ #дизайн #разработка сайтов #Системный анализ и дизайн

Вместе с данным постом часто просматривают:

Автор Статьи


Зарегистрирован: 2019-12-10 15:07:06
Баллов опыта: 0
Всего постов на сайте: 0
Всего комментарий на сайте: 0
Dima Manisha

Dima Manisha

Эксперт Wmlog. Профессиональный веб-мастер, SEO-специалист, дизайнер, маркетолог и интернет-предприниматель.