Цифровой элемент
15 минут на чтение
600
Отправь статью на почту?

Как повысить качество кода: инструменты для автоматизации

Подписаться

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

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

  • Корректность — код должен соответствовать стандартам платформы и избегать очевидных ошибок, таких как запросы к базе данных внутри циклов или логики внутри шаблонов.
  • Совместимость — обеспечение работы с актуальными версиями PHP и других используемых пакетов, модулей.
  • Единые стандарты — следование принятому в команде соглашению о стиле кодирования.
  • Защищённость — отсутствие уязвимостей, таких как XSS, CSRF, выполнение загруженных файлов от пользователя.
  • Оптимальность — исключение неоптимальных решений, усложняющих поддержку проекта.

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

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

Критерии качественного кода

Для достижения стабильного качества кода важно внедрить в команде следующие методологии:

  1. Версионирование — использование систем контроля версий (Git).
  2. Тестирование — написание модульных и интеграционных тестов для проверки функционала.
  3. Code Review — регулярная проверка кода другими членами команды.
  4. Continuous Integration — автоматизация сборки и тестирования кода при каждом изменении.
  5. Линтеры в IDE - проверка кода в программе в момент его написания в программе, которую используют разработчики.

Как повысить качество кода

Рассмотрим инструменты, которые помогут автоматизировать проверку кода при разработке на PHP.

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

Инструменты анализа кода

1. PHP Code Sniffer

Что делает:

  • Проверяет PHP, CSS и JavaScript на соответствие стандартам кодирования.
  • Поддерживает кастомные правила.
  • Широко используется благодаря удобству и гибкости.

Используем для:

Инструмент чистит оформление кода, исправляет short_tags, проверяет наличие и корректность PHPDoc. Логику не проверяет. Используем для форматирования только.

image001.png


Пример команды запуска для анализа:


	 bash
	 .\vendor\bin\phpcs > check.txt

Пример команды запуска для автоисправления:


	 bash
	 .\vendor\bin\phpcbf

Пример файла конфигурации (phpcs.xml):


	 xml
		 <?xml version="1.0"?>
	 <ruleset xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance&quot; name="PHP_CodeSniffer" xsi:noNamespaceSchemaLocation="phpcs.xsd">
	 <file>.</file>
	 <arg name="basepath" value="."/>
	 <arg name="extensions" value="php"/>
	 <arg name="colors"/>
	 <exclude-pattern>/php_interface/migrations/*</exclude-pattern>
	 <exclude-pattern>/vendor/*</exclude-pattern>
	 <!-- Include the whole PEAR standard -->
	 <rule ref="PEAR">
	 <exclude name="PEAR.NamingConventions.ValidFunctionName"/>
	 <exclude name="PEAR.NamingConventions.ValidVariableName"/>
	 <exclude name="PEAR.Commenting.ClassComment"/>
	 <exclude name="PEAR.Commenting.FileComment.MissingCategoryTag"/>
	 <exclude name="PEAR.Commenting.FileComment.MissingPackageTag"/>
	 <exclude name="PEAR.Commenting.FileComment.MissingLinkTag"/>
	 <exclude name="PEAR.Commenting.FileComment.MissingVersion"/>
	 <exclude name="PEAR.Commenting.InlineComment"/>
	 <exclude name="PEAR.Commenting.FileComment.Missing"/>
	 <exclude name="Generic.Commenting.DocComment.MissingShort"/>
	 </rule>
	 <!-- Include some sniffs from other standards that don't conflict with PEAR -->
	 <rule ref="Squiz.Arrays.ArrayBracketSpacing"/>
	 <rule ref="Squiz.Arrays.ArrayDeclaration"/>
	 <rule ref="Squiz.Commenting.ClosingDeclarationComment"/>
	 <rule ref="Squiz.ControlStructures.ControlSignature"/>
	 <rule ref="Squiz.ControlStructures.ElseIfDeclaration"/>
	 <rule ref="Squiz.Commenting.BlockComment"/>
	 <rule ref="Squiz.Commenting.DocCommentAlignment"/>
	 <rule ref="Squiz.Commenting.EmptyCatchComment"/>
	 <rule ref="Squiz.Commenting.InlineComment"/>
	 <rule ref="Squiz.Commenting.LongConditionClosingComment"/>
	 <rule ref="Squiz.Commenting.PostStatementComment"/>
	 <rule ref="Squiz.Commenting.VariableComment"/>
	 <rule ref="Squiz.Formatting.OperatorBracket"/>
	 <rule ref="Squiz.Functions.FunctionDeclarationArgumentSpacing"/>
	 <rule ref="Squiz.Operators.ComparisonOperatorUsage">
	 <exclude name="Squiz.Operators.ComparisonOperatorUsage.NotAllowed"/>
	 <exclude name="Squiz.Operators.ComparisonOperatorUsage.ImplicitTrue"/>
	 </rule>
	 <rule ref="Squiz.PHP.DisallowInlineIf"/>
	 <rule ref="Squiz.Scope.MethodScope"/>
	 <rule ref="Squiz.Strings.ConcatenationSpacing"/>
	 <rule ref="Squiz.WhiteSpace.ControlStructureSpacing"/>
	 <rule ref="Squiz.WhiteSpace.FunctionClosingBraceSpace"/>
	 <rule ref="Squiz.WhiteSpace.FunctionSpacing"/>
	 <rule ref="Squiz.WhiteSpace.MemberVarSpacing"/>
	 <rule ref="Squiz.WhiteSpace.OperatorSpacing"/>
	 <rule ref="Squiz.WhiteSpace.SuperfluousWhitespace"/>
	 <rule ref="Generic.Arrays.DisallowLongArraySyntax"/>
	 <rule ref="Generic.Commenting.Todo"/>
	 <rule ref="Generic.ControlStructures.DisallowYodaConditions"/>
	 <rule ref="Generic.ControlStructures.InlineControlStructure"/>
	 <rule ref="Generic.Formatting.DisallowMultipleStatements"/>
	 <rule ref="Generic.Formatting.SpaceAfterCast"/>
	 <rule ref="Generic.NamingConventions.ConstructorName"/>
	 <rule ref="Generic.PHP.DeprecatedFunctions"/>
	 <rule ref="Generic.PHP.LowerCaseKeyword"/>
	 <rule ref="Generic.Strings.UnnecessaryStringConcat"/>
	 <rule ref="Generic.WhiteSpace.IncrementDecrementSpacing"/>
	 <rule ref="PSR2.Classes.PropertyDeclaration"/>
	 <rule ref="PSR2.Methods.MethodDeclaration"/>
	 <rule ref="PSR2.Files.EndFileNewline"/>
	 <rule ref="PSR12.Files.OpenTag"/>
	 <rule ref="Zend.Files.ClosingTag"/>
	 <!-- PEAR uses warnings for inline control structures, so switch back to errors -->
	 <rule ref="Generic.ControlStructures.InlineControlStructure">
	 <properties>
	 <property name="error" value="true"/>
	 </properties>
	 </rule>
	 <!-- We use custom indent rules for arrays -->
	 <rule ref="Generic.Arrays.ArrayIndent"/>
	 <rule ref="Squiz.Arrays.ArrayDeclaration.KeyNotAligned">
	 <severity>0</severity>
	 </rule>
	 <rule ref="Squiz.Arrays.ArrayDeclaration.ValueNotAligned">
	 <severity>0</severity>
	 </rule>
	 <rule ref="Squiz.Arrays.ArrayDeclaration.CloseBraceNotAligned">
	 <severity>0</severity>
	 </rule>
	 <rule ref="Squiz.Arrays.ArrayDeclaration.CloseBraceNewLine">
	 <severity>0</severity>
	 </rule>

	 <!-- Check var names, but we don't want leading underscores for private vars -->
	 <rule ref="Squiz.NamingConventions.ValidVariableName"/>
	 <rule ref="Squiz.NamingConventions.ValidVariableName.PrivateNoUnderscore">
	 <severity>0</severity>
	 </rule>
	 <!-- Only one argument per line in multi-line function calls -->
	 <rule ref="PEAR.Functions.FunctionCallSignature">
	 <properties>
	 <property name="allowMultipleArguments" value="false"/>
	 </properties>
	 </rule>
	 <!-- Have 12 chars padding maximum and always show as errors -->

	 <rule ref="Generic.Formatting.MultipleStatementAlignment">
	 <properties>
	 <property name="maxPadding" value="12"/>
	 <property name="error" value="true"/>
	 </properties>
	 </rule>
	 <!-- Ban some functions -->
	 <rule ref="Generic.PHP.ForbiddenFunctions">
	 <properties>
	 <property name="forbiddenFunctions" type="array">
	 <element key="sizeof" value="count"/>
	 <element key="delete" value="unset"/>
	 <element key="print" value="echo"/>
	 <element key="is_null" value="null"/>
	 <element key="create_function" value="null"/>
	 </property>
	 </properties>
	 </rule>
	 <!-- Private methods MUST not be prefixed with an underscore -->
	 <rule ref="PSR2.Methods.MethodDeclaration.Underscore">
	 <type>error</type>
	 </rule>
	 <!-- Private properties MUST not be prefixed with an underscore -->
	 <rule ref="PSR2.Classes.PropertyDeclaration.Underscore">
	 <type>error</type>
	 </rule>
	 <!-- The testing bootstrap file uses string concats to stop IDEs seeing the class aliases -->
	 <rule ref="Generic.Strings.UnnecessaryStringConcat">
	 <exclude-pattern>tests/bootstrap\.php</exclude-pattern>
	 </rule>
	 <!-- This test file specifically *needs* Windows line endings for testing purposes. -->
	 <rule ref="Generic.Files.LineEndings.InvalidEOLChar">
	 <exclude-pattern>tests/Core/Tokenizer/StableCommentWhitespaceWinTest\.php</exclude-pattern>
	 </rule>
	 <rule ref="Generic.PHP.DisallowShortOpenTag">
	 <exclude name="Generic.PHP.DisallowShortOpenTag.EchoFound" />
	 </rule>
	 <rule ref="Internal.NoCodeFound">
	 <severity>0</severity>
	 </rule>
	 <rule ref="Generic.Files.LineLength">
	 <properties>
	 <property name="lineLimit" value="1110" />
	 <property name="absoluteLineLimit" value="0" />
	 </properties>
	 </rule>
	 </ruleset>

2.PHP Mess Detector (PHPMD)

  • Ищет неиспользуемые переменные, избыточную сложность и другие проблемы.
  • Генерирует отчёты в текстовом, HTML или XML-формате.

Для нас:

Отслеживаем названия методов, переменных, правила SOLID. Следим за чистотой кода.

image003.png
image005.png


Пример запуска в формате html:


	 bash
	 php phpmd.phar . html phpmd.xml > phpmd_file.html

Пример запуска в формате xml:


	 bash
	 php phpmd.phar . xml phpmd.xml > phpmd_file.xml

Пример файла конфигурации (phpmd.xml):


	 xml
	 <?xml version="1.0" encoding="UTF-8"?>
	 <ruleset name="My first PHPMD rule set" xmlns="https://phpmd.org/xml/ruleset/1.0.0&quot; xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance&quot;
	 xsi:schemaLocation="https://phpmd.org/xml/ruleset/1.0.0.xsd&quot; xsi:noNamespaceSchemaLocation="http://phpmd.org/xml/ruleset_xml_schema_1.0.0.xsd&quot;&gt;
	 <rule ref="rulesets/codesize.xml" />
	 <rule ref="rulesets/cleancode.xml">
	 <exclude name="StaticAccess" />
	 </rule>
	 <rule ref="rulesets/controversial.xml" />
	 <rule ref="rulesets/design.xml" />
	 <rule ref="rulesets/naming.xml" />
	 <rule ref="rulesets/unusedcode.xml" />
	 <exclude-pattern>*vendor\*</exclude-pattern>
	 <exclude-pattern>*vendor/*</exclude-pattern> <exclude-pattern>*php_interface\migrations\*.php</exclude-pattern> <exclude-pattern>*php_interface/migrations/*.php</exclude-pattern>
	 </ruleset>

3. SonarLint

SonarLint — это open-source-инструмент для анализа кода, который помогает выявлять и исправлять проблемы, связанные с безопасностью и качеством, прямо во время написания программного кода. Этот плагин поддерживает работу в различных интегрированных средах разработки (IDE), среди которых популярные решения, такие как IntelliJ IDEA, Eclipse и Visual Studio.

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

SonarLint входит в экосистему SonarQube, расширяя её возможности для разработчиков.

Что нам позволяет SonarLint:

  • Подсвечивает ошибки разработки
  • Исправляет ошибки разработки
  • Помечает возможные проблемы
  • Проверяет правописание
  • Проверяет стандарты разработки (PSR)
image007.png

4. PHPStan

PHPStan — статический анализатор кода для языка PHP, который проверяет код и выявляет ошибки без его выполнения.

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

Что нам позволяет PHPStan:

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

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

Код вызова

	 bash
	 vendor/bin/phpstan analyse .

Анализ кода на совместимость с версиями PHP

1. Rector: больше, чем просто обновление версий PHP

  1. Автоматический перевод кода на новые версии PHP
    Rector умеет переводить сайты на более новые версии PHP практически в автоматическом режиме. Он анализирует код, находит устаревшие конструкции и заменяет их на современные аналоги, соответствующие выбранной версии PHP. Это значительно упрощает процесс обновления и снижает риск ошибок.
  2. Проверка и исправление phpDoc
    Инструмент проверяет корректность phpDoc-аннотаций, включая типы параметров, возвращаемые значения и описание методов. При необходимости Rector может автоматически исправить неточности, что особенно полезно для поддержания актуальной документации в больших проектах.
  3. Оптимизация кода с помощью "раннего return"
    Rector умеет преобразовывать вложенные условия в "ранние возвраты" (early returns), что делает код чище, понятнее и легче для поддержки. Например, вместо:
    if ($condition) {
     // Действия
     return $value;
    } else {
     return null;
    }
    Rector предложит:
    if (!$condition) {
     return null;
    }
    // Действия
    return $value;
  4. Миграция между фреймворками
    Rector позволяет автоматизировать переход с одного фреймворка на другой, например, с Laravel на Symfony или с устаревшего кода на современные стандарты. Он анализирует структуру проекта и вносит изменения в соответствии с правилами целевого фреймворка, значительно сокращая время и усилия, затрачиваемые на ручной рефакторинг.
  5. Связка с PHPStan для улучшения кода
    Комбинация Rector и PHPStan — это мощный дуэт для повышения качества кода. Сначала PHPStan выявляет проблемы: некорректные типы, неиспользуемые переменные, ошибки в вызовах методов и т.д. Затем Rector автоматически исправляет эти проблемы, применяя predefined-правила. После этого можно снова запустить PHPStan, чтобы убедиться, что все ошибки устранены. Такой итеративный подход особенно полезен для legacy-проектов, где ручное исправление кода может занять месяцы.
  6. Архитектурный рефакторинг
    Rector способен вносить изменения не только на уровне синтаксиса, но и на уровне архитектуры. Например:
    • Переименование классов, методов и свойств в соответствии с новыми стандартами.
    • Изменение структуры наследования.
    • Выделение повторяющегося кода в отдельные компоненты.
    Однако стоит отметить, что Rector не перемещает файлы физически — для этого потребуются дополнительные инструменты, такие как rsync.
  7. Оптимизация кода
    Инструмент поддерживает множество правил для улучшения читаемости и производительности кода, таких как:
    • Замена устаревших функций на современные аналоги.
    • Упрощение условий (например, замена вложенных if на ранние возвраты).
    • Удаление неиспользуемого кода.
  8. Поддержка PSR и других стандартов
    Rector помогает привести код в соответствие с актуальными стандартами, такими как PSR-12, автоматически исправляя отступы, оформление методов и другие стилистические элементы.

Пример рабочего процесса с Rector и PHPStan

  1. Анализ кода
    Запустите PHPStan, чтобы получить список проблем:
    vendor/bin/phpstan analyse .  
  2. Автоматическое исправление
    Примените Rector для устранения выявленных проблем:
    php vendor/bin/rector --dry-run > rector-report.txt  
    php vendor/bin/rector  
    
  3. Повторная проверка
    Запустите PHPStan снова, чтобы убедиться, что все ошибки исправлены:
    vendor/bin/phpstan analyse .  

Пример файла конфигурации (rector.php):

 withPaths([
	 __DIR__ . '/php_interface',
	 __DIR__ . '/templates',
	 ])
	 ->withSkipPath(__DIR__ . '/php_interface/migrations')
	 ->withParallel(360)
	 ->withImportNames()
	 ->withSets([
	 SetList::PHP_84,
	 SetList::TYPE_DECLARATION,
	 SetList::CODE_QUALITY,
	 SetList::EARLY_RETURN,
	 ]);

Такой подход позволяет постепенно улучшать код, минимизируя риски и экономя время.

Rector — это не просто инструмент для обновления версий PHP, а многофункциональное решение для рефакторинга, миграции и поддержки качества кода. Его интеграция с PHPStan и другими инструментами делает его незаменимым в арсенале разработчика, особенно при работе с legacy-проектами. Автоматизация рутинных задач позволяет командам сосредоточиться на создании новой функциональности, а не на исправлении старых ошибок.

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

Проверка уязвимостей кода

Для сканирования уязвимостей раньше был инструмент по названию ai-bolit от revisium. Его код можно до сих пор найти на просторах github.

С помощью ai-bolit можно было просканировать папки и файлы на предмет уязвимостей с использованием php.

Пример запуска


	 bash
	 php ai-bolit.php --mode=2 --path=/var/www/web/
image010.png

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

Как использовать инструменты в работе

Пример внедрения автоматизации анализа кода:

  1. Настройте интеграцию выбранных инструментов в процесс CI/CD.
  2. Определите базовые правила проверки, которые соответствуют потребностям вашего проекта.
  3. Обучите команду пользоваться отчётами инструментов и вносить необходимые исправления.
  4. Проводите регулярный аудит кода с помощью этих инструментов.

Стандарты оформления кода

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

На самом деле, PHP-FIG (PHP Framework Interop Group) — сообщество, занимающееся разработкой PSR (PHP Standards Recommendations), — продвинулось гораздо дальше.

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

Если говорить проще, участники PHP-FIG выявляют универсальные подходы в разработке на PHP и формируют на их основе рекомендации (стандарты), которые могут использовать все.

PSR (PHP Standards Recommendations) — это набор проверенных на практике правил и концепций. Скорее всего, при создании PSR авторы вдохновлялись Java Community Process, а первый стандарт был официально утверждён ещё в 2010 году.

PSR уже давно переросло просто рекомендации по стилю кодирования. Сегодня PHP-FIG предлагает стандарты, которые дают разработчикам мощные абстракции для решения фундаментальных проблем повторного использования кода. Эти стандарты помогают создавать более гибкие и независимые от фреймворков решения (хотя на практике реализация может быть сложнее, база уже сформирована).

PSR — это действительно ценные стандарты, разработанные для:

  • Упрощения повторного использования кода
  • Облегчения командной разработки
  • Создания совместимых между собой компонентов

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

Список PSR-стандартов: https://www.php-fig.org/psr/ (оригинал) и на русском языке https://github.com/BlankBuffoon/PSRRus

Вывод

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

Специалисты компании «Цифровой Элемент» активно используют подобные инструменты в своей работе. Они проверяют код своих проектов, чтобы быть уверенными в его качестве, производительности и поддерживаемости. Более того, эксперты компании могут провести аудит кода, написанного предыдущими подрядчиками. Такой аудит позволяет выявить потенциальные проблемы, устранить технический долг и повысить общую надёжность продукта.

Работая с «Цифровым Элементом», клиенты получают не только профессиональную разработку, но и тщательный контроль качества кода. Это помогает минимизировать риски, связанные с эксплуатацией продукта, и закладывает основу для его дальнейшего развития.

Статьи по теме:

Мне не нравится
Россия, Челябинская область, Челябинск, ул. Энтузиастов, 2, оф. 200 Телефон: +7 (351) 220-45-35

Читайте в нашем блоге

Все статьи
Новый Битрикс24 «Невесомость»: обзор новинок

Новый Битрикс24 «Невесомость»: обзор новинок

Компания 1С-Битрикс совершила качественный скачок в развитии своей платформы, представив масштабное обновление - вышел Новы...

16.05.2025
12
Как начать работать в Битрикс24

Как начать работать в Битрикс24

Битрикс24 — это платформа, объединяющая все нео...

14.05.2025
60
Как ускорить скорость загрузки сайта? Гайд по оптимизации

Как ускорить скорость загрузки сайта? Гайд по оптимизации

Скорость загрузки страниц — один из ключевых факторов, влияющих на успех сайта в интернете. Она влияет на пользовательский опыт, конверсию и SE...

30.04.2025
270
Как повысить качество кода: инструменты для автоматизации

Как повысить качество кода: инструменты для автоматизации

Программирование — это искусство. Каждый разработчик, вооружённый своими знаниями, внутренними стандартами и установленными дедлайнами, создает...

16.04.2025
385
Обеспечение безопасности сайтов на 1С-Битрикс

Обеспечение безопасности сайтов на 1С-Битрикс

1С-Битрикс — это популярная в России коммерческая система управления сайтами (CMS) и корпоративными порталами, разработанная компанией «1С-Битр...

01.04.2025
657
Подборка AI-инструментов для автоматизации бизнеса от «Цифрового Элемента»

Подборка AI-инструментов для автоматизации бизнеса от «Цифрового Элемента»

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

27.03.2025
893
11 советов по защите сайта на WordPress

11 советов по защите сайта на WordPress

WordPress — это мощная и гибкая платформа управления сайтом, которая используется миллионами сайтов по всему миру. Однако её популярность делае...

28.02.2025
808
11 лучших платформ для онлайн-совещаний

11 лучших платформ для онлайн-совещаний

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

24.12.2024
1668