Энтерпрайз разработка с нуля
На днях у меня появилась довольно интересная идея для статьи, основанная на следующей предпосылке: на Хабре ни разу не рассказывали об организации энтерпрайз разработки «от и до». В плане совсем с нуля и хотя бы до комфортного минимума. За отправную точку я буду брать ситуацию полнейшего хаоса, когда какой-то код существует на машине единственного разработчика, нет системы контроля версий, нет тестовых сред, код объектов БД существует только внутри эталонной продуктивной базы данных, нет никаких процессов сборки и установки, контроля качества кода и так далее. Возможно читатель задастся вопросом «Такое бывает в 2020 году? Разве кто-то еще так разрабатывает?» и будете только отчасти правы. Предлагаю обсудить детали под катом.
Мне пришло в голову как минимум три возможных сценария:
Примечание: за свои 10+ лет опыта в разработке я сталкивался с каждым из сценариев (на прошлых местах работы).
То есть мы с вами сейчас попробуем набросать план превращения некоторой энтерпрайз разработки с околонулевой организацией процессов и поддержкой инструментов в довольно комфортную и современную. В меньшей степени статья будет касаться стартапов и геймдев студий, поскольку там свои правила и особенности. Хотя бывают и исключения, я вспомню про стартапы в главе про дистрибутивы.
Начало начал — git
Итак, предположим вы попали в одну из трех гипотетических ситуаций, перечисленных мной во вступлении. С чего начать? Конечно с git. Это стандарт индустрии, самая распространенная система контроля версий, лучший инструмент организации разработки из базовых и необходмых. Просто установите его на свою машину, зайдите в папку проекта и наберите последовательно эти команды:
С этого момента у вас теперь будет идти летоисчисление. С этого момента всегда будет предельно однозначен ответ на вопрос «когда и какие были сделаны изменения в коде». Теперь можно делать точечные коммиты отдельных по смыслу правок, можно откатывать их и возвращаться к любому моменту в истории, можно экспериментировать в отдельной ветке и так далее. Даже на одной единственной машине безо всякой синхронизации git уже открывает уйму возможностей.
Работа в команде — GitLab
Что делать, если в команде более одного разработчика? Все в порядке, git был создан в том числе и для командной работы. И если еще 5-7 лет назад следующим я бы порекомендовал установить и настроить git на сервер, то теперь есть GitLab. GitLab это превосходный пример OpenSource проекта, получившего повсеместное распространение и признание в индустрии.
GitLab — веб-инструмент жизненного цикла DevOps с открытым исходным кодом, представляющий систему управления репозиториями кода для git с собственной вики, системой отслеживания ошибок, CI/CD пайплайном и другими функциями.
Вы можете просто взять дистрибутив GitLab и развернуть его внутри корпоративной приватной сети — получится свой маленький «карманный» GitHub. У GitLab есть платные корпоративные версии, можете изучить список доступных фич здесь. Из приятного — платные фичи постепенно перетекают в бесплатные версии.
Итак, GitLab может стать краеугольным камнем организации разработки. Он позволяет оперировать понятием «проект». Проект имеет отдельный git-репозиторий, список задач/дефектов (issues), agile-доску, wiki и т.д. Иначе говоря, из коробки и бесплатно вы получаете практически все инструменты, необходимые для эффективной и распределенной разработки. Но обо всем по порядку.
Хранение кода
Первое и самое главное — GitLab позволяет эффективно и удобно работать над изменениями в коде. Он полностью поддерживает парадигму Github Flow. Это довольно легковесный процесс, при котором каждое новое изменение реализуется в отдельной ветке, а для слияния с основной кодовой базой существуют т.н. запросы на слияние или merge requests (разг. мерджреквест). Последовательность шагов примерно следующая: из задачи по нажатию одной кнопки создается и новая ветка, и одновременно с ней merge request. Далее разработчик переключается на эту ветку, реализует новый функционал или исправляет ошибку, коммитит изменения локально, пушит на сервер. Вся его работа будет максимально удобно отображена на вкладках запроса на слияние: описание, перечень коммитов, построчный дифф. В момент окончания работы над задачей разработчик снимает флаг «work in progress» (WIP). Для тиммейта это может стать сигналом к проведению кодревью. Отдельные замечания можно заводить прямо из диффа, там же можно предлагать свои изменения, и там же можно их сразу принять. Это очень удобный и эффективный способ проведения кодревью. Можно настроить правило: нельзя слить изменения, пока все дискуссии не будут закрыты. В последних версиях можно так же добавить нужных ревьюверов без назначения задачи.
Трекинг задач
Под задачей далее подразумевается некоторая сущность с описанием проблемы/нового функционала/идеи, в рамках которой могут выполняться изменения. GitLab позволяет работать с задачами двумя способами:
и т.д.), вставлять картинки, файлы, смайлы, графики, математические формулы, mermaid-графы и др. Это весело, интересно, модно-молодежно, что помогает поддерживать здоровые отношения в команде!
Можно так же завести шаблоны задач (текстовые файлы в репозитории), например типичный шаблон для ошибки мог бы выглядеть так:
Со временем команда наверняка придет к созданию шаблонов, включающих критерии завершения задачи (definion of done, DoD).
Прочее
Так же в GitLab изначально есть замечательная возможность завести wiki проекта. Лично я воспринимаю вики как способ перенести знания из голов сотрудников в текстовую форму. Благодаря тому, что вики встроена в GitLab, можно прозрачно ссылаться на различные сущности вроде коммитов, задач, пользователей, меток и т.д. Это очень удобно. Так же wiki-странички хранят историю изменений. По ощущениям, они являются обычными Markdown-файлами в выделенном git-репозитории. Так же Wiki можно воспринимать как хранилище документации по проекту. О ведении документов вкратце рассказано в последующих главах.
Майлстоун (milestone) — еще один вид сущностей в GitLab, более высокого уровня чем задачи и merge request’ы. С его помощью можно реализовать организацию последних в осмысленные группы. Примером такой группы может быть поставка (установка новой версии ПО) на определенную дату.
GitLab так же умеет в CI/CD, но вряд ли это понадобится вам сейчас, если еще вчера код хранился на девелоперской машине. Об этом я расскажу в двух словах в отдельной главе.
Крайне рекомендую заводить ко всему README. И самой маленькой утилите, и крупному проекту пригодится README.md. Практика документирования инструкций хоть и отнимает время, но в перспективе экономит гораздо больше. GitLab отлично отображает README как и весь остальной Markdown, позволяет удобно редактировать и коммитить его в прямо в Web-IDE.
Примечание: да, безусловно есть опция вести проект в приватном репозитории на GitHub’е, однако это может противоречить корпоративным стандартам безопасности, есть ограничение на количество таких проектов. Собственное развертывание GitLab’а внутри организации выигрывает по многим статьям.
Миграции БД
Ок, мы разобрались с кодом системы, но что насчет объектов БД, таких как таблицы и хранимые процедуры? Очевидно, что хранение оригиналов скриптов в самой БД рано или поздно приведет к проблемам: либо они «разъедутся» между контурами, либо будет безвозвратно утрачена история, либо еще что-то в таком роде. На помощь придут миграции баз данных. Миграция это переход от одной структуры БД к другой без потери консистентности. Подход к организации миграций ограничен лишь фантазией и смекалкой разработчика, однако известное мне разнообразие подходов может быть классифицировано на:
Если любой из этих подходов применяется не с самого начала разработки, то потребуются подготовительный работы по созданию первоначального снимка. В дальнейшем с каждым подходом довольно удобно работать, но лично мне CREATE-подход импонирует больше.
Хорошая статья по теме: Версионная миграция структуры базы данных: основные подходы.
Особенности миграции базы в случае канареечных релизов: На пути к Canary (там же рассказано про этот тип релизов).
Поставка изменений
Примечание: альтернативный подход описан в главе про Docker.
Итак, у Вас цивилизованно и централизованно хранится код. Но что насчет выкладки нового функционала для конечных пользователей? Способ доставки может отличаться. Например, в случае внутрикорпоративного толстого клиента (а-ля АРМ) это может быть выкладка установочного пакета в сетевую папку, а в случае сайта архив со скриптами. Важно ввести понятие дистрибутива — полноценного комплекта ПО, пригодного для распространения. Важно, что дистрибутив не должен содержать контурозависимых настроек и специфичных конфигурационных файлов. Вы должны иметь возможность «раскатать» однажды собранный дистрибутив на любой контур, и одной этой установкой сразу довести систему до зафиксированной в дистрибутиве версии, включая миграции БД, о которых мы говорили ранее.
Дистрибутив системы следует формировать на отдельной машине, именуемой сервером сборки (билдсервер). Причина тривиальна: если сборка «боевого» дистрибутива происходит на конкретном компьютере разработчика, повторить эту конфигурацию в случае какой-то поломки или нештатной ситуации может быть очень нелегко. Также можно попасть в неудобное положение, если разрабочик уйдет в отпуск или уволится.
Продолжим тему билдсервера. На нем должно быть установлено идеальное окружение, необходимый набор инструментов и настроек, позволяющих превратить исходный код в конечный дистрибутив. Шаги, правила и команды сборки обычно настраиваются в ПО класса Jenkins, GitLab или TeamCity. В узком смысле их можно назвать системой сборки. На билдсервер нужно будет установить специальный агент, позволяющий ему взаимодействовать с системой сборки. Результатом прохождения шагов сборки обычно является т.н. артефакт (или несколько артефактов), примером такого артефакта может быть дистрибутив. Имеет смысл хранить артефакты сборки вне системы сборки, например в Nexus/Artifactory. Это поможет решить вопрос надежного хранения всех версий сборок систем.
Версия системы
В главе про поставку изменений хочу дополнительно затронуть понятие версии системы. Формат версии может быть любым:
Правило: ПО получает уникальный последовательный номер версии при сборке. В TeamCity, например, есть специальный build counter, автоматически инкрементирующийся счетчик, который идеально подходит для присвоения уникальных номеров. Из правила следует что:
Дополнительно отмечу, что не стоит вводить отдельные версии для БД, серверной части, толстого клиента и т.д. Наоборот, все компоненты системы, находящиеся в дистрибутиве, должны иметь единый сквозной номер версии. Такой подход поможет при установке обновлений системы, заведении ошибок и регистрации инцидентов.
Автоматическое развертывание
Итак, теперь у вас создаются дистрибутивы: по кнопке или автоматически при слиянии в master-ветку. Можно, конечно, устанавливать их на целевые контура и вручную, но дальновиднее будет настроить автоматическую установку. Фактически это некоторый набор инструкций и команд, выполняющихся последовательно. При этом важно помнить, что некоторые команды требуют параметризации под целевой контур (поскольку дистрибутив контуронезависимый). Реализовать такое с помощью TeamCity, GitLab или даже python-скриптами. Я же рассмотрю кейс использования Ansible/AWX.
Процитирую одну из статей об Ansible на Хабре:
Ansible — это программное решение для удаленного управления конфигурациями. Оно позволяет настраивать удаленные машины. Главное его отличие от других подобных систем в том, что Ansible использует существующую инфраструктуру SSH, в то время как другие (chef, puppet, и пр.) требуют установки специального PKI-окружения.
Важно, что Ansible позволяет проигрывать некоторые сценарии на удаленном хосте. Типичный сценарий автоустановки может включать скачивание дистрибутива, распаковку, остановку сервиса, параметризацию конфигурационных файлов, копирование файлов, запуск миграций БД, перезапуск сервиса. Контурозависимые переменные можно организовать с помощью inventories, заведя по одному инвентори на каждый контур.
Менеджер пакетов
Небольшое дополнение к теме билдсервера: для внешних подключаемых модулей и библиотек удобно пользоваться менеджером пакетов. Например pip для Python, nuget для C#, npm для Node.JS. Конфигурации менеджеров пакетов следует хранить в системе контроля версий вместе с основным кодом системы. В шаги сборки на билдсервере следует включить шаги по восстановлению и загрузке пакетов в соответствии с конфигурационным файлом/файлами. Так же это облегчит приход нового разработчика в команду — ему не нужно будет специальном образом настраивать свое рабочее окружение, обо всем позаботится менеджер пакетов. Единственный минус — некоторые экземпляры грешат тяжеловесностью, но с этим мало что можно поделать, пользы они все-таки приносят больше.
Тестовые среды
Примечание: альтернативный подход описан в главе про Docker.
Контур тестирования или тестовая среда это совокупность серверов и софта, которые полностью повторяют продуктивный сервер. Различие тестового и продуктивного контуров связано с их предназначением: как можно догадаться, тестовые контура нужны для экспериментов и проверки работоспособности нового функционала без риска навредить реальным пользователям.
Стандартом индустрии является наличие трех контуров:
Часто нужны отдельные контура для нагрузочного тестирования, опытной эксплутации, симуляционного тестирования, гостевого внешнего тестирования. В идеале каждый контур повторяет продуктивный по своим характеристикам, но имеет соответствующие среде настройки и интеграции. Управлять этим вручную довольно трудно, поэтому рекомендую использовать инструменты вроде Ansible (читайте далее), chef и др.
Важно: на тестовых контурах нужно затереть чувствительные данные, а так же заменить пользователей и их контакты фейковыми. В противном случае вы рискуете отправлять отчеты и уведомления реальным пользователям с тестовых контуров.
Наиболее простым способ организации тестовых контуров являются виртуальные машины. Однажды настроенный образ может клонироваться произвольное количество раз. Это не совсем моя сфера, поэтому я не могу здесь подсказать, однако идея в целом достаточно интуитивная: «нарезать» виртуалок сильно проще, чем каждый раз настраивать железный сервачок.
Автотесты
Я намеренно не стал касаться темы unit-тестов, потому что в статье рассматривается сценарий превращения гадкого утенка в лебедя. По моему опыту неожиданное внедрение unit-тестов в проект без культуры написания тестируемого кода не принесет никаких легких побед. Напротив, создание автотестов, и неких автоматизированных сценариев проверки, поможет в короткие сроки сократить объемы ручного труда разработчиков и тестировщиков. Не могу посоветовать конкретный софт, все находится в руках QA-инженеров и разработчиков.
Эксплуатация
Существует подход к промышленной эксплуатации системы, при котором команда разработки не принимает непосредственное участие в процессе, поскольку для этого выделена отдельная команда сопровождения. Подобное разделение может быть даже зафиксировано в нормативных документах организации. В этом случае следует с особенным вниманием отнестись к теме организации логгирования и мониторинга.
Логгирование
Рано или поздно появится необходимость технологичнее управлять логами. Вероятно, логгирование в каком-то виде присутствует в коде системы. Например, пишется в файл или стандартный вывод. Возможно даже настроена ротация файлов (например, на предмет достижения максимального размера файла), и старые файлы упаковываются в архив. Пожалуй, главная проблема такого подхода заключается в том, что файлы физически находятся в конкретной среде (контуре), и разработчику их как-то нужно оттуда вытаскивать. Поиск по истории осложнен, обработка большого объема логов так же осложнена. Эти и другие сопутствующие проблемы могут быть решены с помощью специального ПО управления логами.
Наиболее известные представители — ELK-стек и Graylog. Основной сценарий использования подразумевает разворачивание подобной системы внутри организации и дальнейшую отправку в нее логов из приложений и систем. Как правило отправка происходит по UDP, чтобы не нарушать работу приложений в случае отказа системы логгирования. В Graylog есть возможность воспользоваться т.н. стримами — онлайн-обработчами потока, которые применяются к логам сразу на входе в систему управления логами. Как пример: можно завести отдельный стрим на каждый контур.
Мониторинг
Из бесплатного ПО для решения задач мониторинга я бы посоветовал Grafana. Позаимствовал список особенностей Grafana из статьи:
Отличный софт, чтобы оценить удобство и красоту стоит хотя бы раз воспользоваться.
Контроль качества кода
В поддержании кодовой базы в «здоровом» состоянии кроется секрет многих успешных проектов. Роберт Мартин в своих лекциях рассказывает о том, что в разработке не так важно ускоряться, как не замедляться. Сохранение высокого темпа внесения изменений возможно не только благодаря хорошей архитектуре, но и постоянному рефакторингу, поэтапному улучшению кода, сокращению технического долга. Автоматизированные средства не сделают всю работу за вас, но сильно помогут в поддержании кодовой базы в хорошем состоянии.
Sonarqube
Проведение кодревью помогает держать кодовую базу в чистоте и порядке. Однако просмотреть весь существующий код на предмет ошибок и недостатков обычно не представляется возможным — это займет слишком много времени. Статические анализаторы кода помогут выполнить эту задачу с должным уровнем качества и практически без усилий. Рекомендую обратить внимание на бесплатный инструмент SonarQube:
SonarQube — платформа с открытым исходным кодом для непрерывного анализа (англ. continuous inspection) и измерения качества программного кода.
Однажды настроенный сонар может выполнять регулярные проверки качества кода в разрезе ошибок, технического долга, информационной безопасности и кода «с душком» (code smells).
Как можете видеть на скриншоте, по итогам проверки выводится полный отчет с итоговой оценкой по каждому критерию. Поддерживаются все основные языки, существуют продвинутые платные плагины.
Стиль кодирования
Практика фиксации единого стиля кодирования присуша многим сформировавшимся командам. Code guide, code style — это все примерно об одном и том же. Открывать ли фигурные скобки для одного выражения в if, переносить ли на новую строку каждый параметр функции, табы или пробелы — все эти необднозначности помогает решить договоренность команды о едином стиле кодирования. Единообразная кодовая база позволяет не только облегчить чтение кода участниками команды, но и понизить планку вхождения в проект новичкам.
Линтеры — специальный класс программ, который может помочь в неуклонном соблюдении стиля кодирования. Историческая справка: Lint это имя собственное статического анализатора языка C, «который сообщал о подозрительных или непереносимых на другие платформы выражениях». Его название стало именем нарицательным для всех программ такого рода. Сейчас линтеры существует для большинства известных языков программирования: в виде отдельных утилит, плагинов для редакторов или фичей IDE. Стиль кодирования задается некоторым конфигурационным файлом, который распространяется в команде.
Безопасность
Тема информационной безопасности была кратко затронута в главе по SonarQube. Существуют и более продвинутые статические анализаторы кода на предмет уязвимостей, например ChechMarx. Это гибкое и настраиваное ПО поддерживает все основные языки программирования и виды уязвимостей. Механизм работы примерно следующий: в коде программы отслеживается поток данных из всех входных точек (пользовательский ввод, аргументы командной строки, данные из БД) в выходные (UI, отчеты, БД) на предмет отсутствия санитайзеров. Санитайзером называется любая функция или метод, проверяющий содержимое ввода на легитимность. Если санитайзер отсутствует, то анализатор сообщает о потенциальной уязвимости. ChechMarx так же приводит полную пошаговую цепочку преобразований данных, допускающую возможную уязвимость.
Бесплатное ПО из этой же категории: wapiti, nikto
Общее неинструментальное
Большинство рекомендаций это статьи затрагивают применение специализированного ПО для улучшения того или иного аспекта процессов разработки. Однако значимая доля задач построения эффективной разработки решается не использованием софта, а применением методологий, организацией процессов и принятием соглашений о процессе.
Методология разработки
Использование методологий разработки помогает сделать коллективную разработку организованной. Если ранее не использовалась вообще никакая методология, то стоит задуматься об адаптации какой-либо из них. Есть большое количество статей по теме, которые помогут определиться. Мне понравились эти две: issoft.by и geekbrains.ru.
В статье в основном факты, но в этом абзаце разбавлю материал субъективным мнением. На практике в энтерпрайзе чаще всего можно встретить водопад и различные модификации agile. Первый традиционно хаят, а второй восхваляют На практике и водопад может быть достаточно удобен, если обогатить его обратной связью на каждом этапе. И, например, крупные законодательные правки с фиксированной датой вступления в силу, затрагивающие сразу нескольких систем организации, проще оркестрировать в водопаде. С другой стороны, новые самостоятельные проекты бывает удобнее делать в режиме гибкой разработки, особенно если бизнес принимает в них активное участие.
Релизы
Планировать нагрузку на команду и управлять ожиданиями заказчика помогут релизы — приуроченные к конкретной дате изменения в ПО. Релиз можно считать кроссистемной версией, инструментом оркестрирования разработки между несколькими самостоятельными системами.
В слеучае длинных циклов полезной будет практика ведения календаря релизов: полноценного графика дат начала и окончания разработки, тестирования, опытной эксплуатации. Один из вариантов планирования — конвейерная разработка, когда фазы разработки текущего и следующего релизов сдвинуты относительно друг друга. Так отдельные команды не простаивают и всегда обеспечены работой. Среди минусов данного подхода необходимость держать во внимании несколько релизов одновременно: разрабатывать перспективный, исправлять ошибки в текущем (и еще поддерживать продуктивный).
Примечание: в GitLab есть специальная сущность — Release, однако для планирования релизов в agile-режиме можно использовать и Milestone’ы. Рекомендую изучить обе функциональности.
Документация
Ведение документации достаточно древняя практика, сопровождающая разработку. Она помогает создать общее информационное пространство проекта, позволяет участникам команды говорить «на одном языке» и облегчает погружение новых членов команды. В плане организации процесса есть опции:
Желательно определиться с подходом «на берегу», так как описать зрелую или легаси систему с нуля может быть довольно трудно (и нужно ли, если проект успешно обходился без нее?). Самое главное свойство документации — она должна приносить пользу.
Физически документацию можно вести рядом с кодом системы в git-репозитории (например, в MarkDown), в задачах багтрекера/agile-доски (GitLab, Jira, Redmine), в специально выделенной цiki (GitLab, Confluence), в Word-документах (в т.ч. с использованием sharepoint) и т.д. Важно иметь возможность просматривать историю изменения документации, чтобы можно было заглянуть в любой момент из прошлого проекта.
Что дальше?
К этому моменту процесс разработки уже нельзя назвать хаотичным: ведется история изменения кода, есть культура написания документации, организован процесс поставок изменений на целевые среды, настроено логиррование и мониторинг. Однако это только базовая организация, всегда есть куда расти. Поэтому далее рекомендую присмотреться к более продвинутым темам: CI/CD & DevOps и Docker & K8s.
CI/CD & DevOps
Простыми словами, CI/CD (Continuous Integration, Continuous Delivery — непрерывная интеграция и доставка) это технология автоматизации тестирования и доставки новых модулей разрабатываемого проекта заинтересованным сторонам (разработчики, аналитики, инженеры качества, конечные пользователи). Принцип действия в чем-то похож на конвейер: методика выполняет интеграционную функцию, объеденяет перечисленные ранее процессы в единую цепочку, цель которого — доставка готового продукта конечному пользователю.
Важно: полноценные CI/CD в энтерпрайзе не все могут себе позволить. Ограничения могут быть связаны с неготовностью бизнеса, требованиями законодательства и регуляторов, незрелостью инструментариев, отсутствием компетенций. Проще говоря, слияние кода в мастер (пусть даже с автоматическим прохождением тестов и прочими проверками) это совсем не повод для установки в продуктив.
DevOps (Development Operation) – это набор практик для повышения эффективности процессов разработки и эксплуатации программного обеспечения за счет непрерывной интеграции и активного взаимодействия профильных специалистов с помощью инструментов автоматизации. Методология предназначена для эффективной организации процессов создания и обновления ПО и услуг.
Специалистов по настройке CI/CI называют DevOps инженерами или просто DevOps’ами.
Docker & K8s
Контейнеры (Docker в частности) это большая тема, попробую покрыть её в двух словах и приправить ссылками (на Хабре много качественных материалов по теме).
Краткая шпаргалка
Для простоты контейнер можно считать облегченной виртуальной машиной без накладных расходов на гостевую ОС, так сказать, песочницей для процесса. Контейнеры позволяют запускать изолированные друг от друга процессы в идеальном окружении на одной и той же хостовой ОС без конфликтов зависимостей. Можно начать изучение темы со статьи такого плана.
Сами по себе контейнеры уже были прорывом, однако засияли они в момент появления зрелых средств оркестрации, таких как Kubernetes. В плане разработки контейнеры предоставляют больше преимуществ для небольших сервисов (aka микросервисов) нежели монолитов. Да, придется затронуть тему микросервисов, чтобы понять преимущества докера и оркестрации контейнеров. Поверхностное сравнение архитектур:
Монолит | Микросервисы |
---|---|
Создан с помощью одного языка программирования/фреймворка | Каждый микросервис создается с использованием наиболее удобного и уместного языка программирования/фреймворка |
Не имеют проблем с зависимостями, одно приложение — один набор зависимостей | Каждый микросервис может зависеть от своего собственного набора библиотек и даже версии ОС |
Не подразумевает горизонтального масштабирования | Микросервисы часто stateless, как следствие прозрачно масштабируются горизонтально |
Подход микросервисной архитектуры требует подходящего инструментария, такого как тандем Docker и Kubernetes. Docker обеспечивает:
Kubernetes в свою очередь обеспечивает:
Практическое использование
Заключение
Спасибо за внимание! Буду рад дополнениям и предложениям.