Привет, Хабр. Подготовили подборку бесплатных открытых уроков от Otus, которые пройдут на этой неделе. Опытные практики проводят вебинары в живом формате, что позволит не только освоить новые знания, но и задать вопросы экспертам. Зарегистрируйтесь и присоединяйтесь!
Свой класс события для плагинов Joomla. Продолжение.
Продолжение потому, что начало уже было в статье Виталия Некрасова на Хабре.
Кратко.
В Joomla 5+ для событий аргументы упаковываются в собственные классы событий: ContentPrepareEvent, AfterSaveEvent и т.д. Данные из них мы получаем в виде $event->getArgument('argument_name') или [$var, $var2] = array_values($event->getArguments());. Также для разных типов событий могут быть специфичные методы типа $article = $event->getItem(); в ContentPrepareEvent и т.д. И в статье Виталия как раз об этом рассказывается.
А так же рассказывается о методах onGet и onSet. В ядре Joomla в классе \Joomla\CMS\Event\AbstractEvent сказано:
/**
* Add argument to event.
* It will use a pre-processing method if one exists. The method has the signature:
*
* onSet($value): mixed
*
* where:
*
* $value is the value being set by the user
* It returns the value to return to set in the $arguments array of the event.
*
* @param string $name Argument name.
* @param mixed $value Value.
*
* @return $this
*
* @since 4.0.0
*/
Добрался я тоже до своего класса события для плагинов, порылся в ядре и подумал, что onSet... и onGet... методы не обязательно делать (хотя в статье по ссылке об этом не упоминается). Это методы для "предварительных проверок и манипуляций" с данными перед тем, как они будут отданы через getArgument() или get<ArgumentName>. Метод getData() отдаст данные, которые предварительно будут обработаны методом onGetData(). Но обработаны они будут только в том случае, если метод реализован. Если нет, то ничего страшного. Ошибки не будет.😎
Эти методы напоминают своеобразные плагины внутри плагинов. На мой взгляд излишнее усложнение, хотя сеттеры и геттеры должны заниматься по идее только сеттерством и геттерством, а проверку/ приведение типов можно отдать в методы onSet... / onGet....
Новый тайпчекер для Python от авторов ruff и uv, написанный на Rust
Вышло видео про новый тайпчекер и lsp: ty (старое название red-knot) от авторов ruff и uv. Пока по первым впечатлениям – бомба! Не смотря на версию 0.0.0a8 🌚
Из плюсов:
Быстрый
На расте
Куча новых фичей для типов
Полная спецификация
Интеграция с ruff и IDEшками
Из минусов:
Пока есть баги (но их поправят, конечно же)
Нет плагинов (и скорее всего никогда не будет)
Софт от молодой и маленькой компании
Как сделать поддержку ty и mypy вместе? А если использовались ty_extensions?
Сегодня хочу рассказать о GIN индексах в PostgreSQL. Это один из мощных инструментов, которые есть в БД PostgreSQL. Но почему-то очень многие незаслуженно обходят его стороной.
Что такое GIN индекс
GIN (Generalized Inverted Index) – это инвертированный индекс, который предназначен для ускорения поиска в структурах данных, содержащих составные типы. Он имеет встроенную оптимизацию, позволяющую искать по элементам внутри сложных структур. По своей сути, это обратный индекс, где для каждого уникального элемента хранится список указателей на записи, в которых он встречается. Это дает возможность быстро находить записи, соответствующие запросу.
Для каких типов данных используется GIN индексы
GIN-индексы особенно эффективны для следующих типов данных:
Массивы
Хранение списков значений
Быстрый поиск по элементам массива
Пример: теги, категории, списки ID
JSONB
Хранение полуструктурированных данных
Быстрый поиск по ключам и значениям
Поддержка сложных запросов к JSON-документам
Полнотекстовый поиск
Индексация текстовых полей
Быстрый поиск по словам и фразам
Поддержка различных языков
Преимущества GIN индексов
Эффективность поиска по структурам данных: Хорошо подходит для обработки массивов и структурированных данных типа JSONB. Позволяет быстро находить нужные строки даже среди миллионов записей. Хранит только уникальные элементы и их местоположение, вследствии этого более экономный по сравнению с полным сканированием.
Поддержка различных типов данных: Работает с различными типами - строки, числа, массивы, объекты JSONB и даже геопространственные данные.
Подходит для оптимизации полнотекстового поиска: Улучшает производительность запросов с использованием операторов @@ и функций вроде to_tsvector() и to_tsquery(). Особенно полезен там, где требуются операции пересечения (&&), включения (@>), проверки существования элементов массива (?, ?&) и другие специфические условия.
Недостатки GIN индексов
Обновление: Каждый раз, когда изменяется запись, содержащая поля, входящих в GIN индекс, индекс обновляется целиком. Это увеличивает нагрузку на систему при частых изменениях данных.
Больший размер: GIN индекс занимает больше места на диске по сравнению с традиционными B-tree индексами, так как хранит список всех значений, содержащихся в колонке.
Низкая производительность на малых объемах данных: При небольших объемах данных GIN индекс может быть менее эффективным.
Сортировка:: По умолчанию не поддерживает эффективные запросы с сортировкой. Стоит учитывать при разработке, можно использовать решения в комбинации с другими индексами.
Заключение
При работе с массивами, JSONB полями и полнотекстовым поиском стоит рассмотреть использование GIN индексов для данных полей. Это позволит повысить эффективность и производительность БД PostgreSQL. Но, в то же время, стоит учитывать особенности его обслуживания и требования к системе. Очень аккуратно применять к часто изменяемым данным. Очень хорошая статья о GIN индексах https://habr.com/ru/companies/postgrespro/articles/340978/
Парни из AWS Fundamentals запилили каталог AWS анимаций. В настоящее время он включает в себя 14 сервисов и паттернов, но, ожидается, что команда будет добавлять новые еженедельно!
В текущем каталоге:
The Dark Reader Deployment Pattern
Polling vs. WebSockets via Amazon API Gateway
Running Code on the Edge with CloudFront
Adaptive Capacity with DynamoDB
Bidirectional Sync across Regions with DynamoDB Global Tables
Async vs. Synchronous Invocations at AWS Lambda
What’s Happening at a Cold Start in AWS Lambda?
DNS Resolution with Route 53
Region Failovers with Route 53 DNS Records
Multi-Region Routing with Route 53 Latency Records
Рэймонд Чен — ветеран компьютерной индустрии, который работает в Microsoft c 1992 года. Рэймонд участвовал в разработке OS/2, Windows 95, DirectX и оболочки Windows, а последние десятилетия отвечает за сохранение обратной совместимости системы. В своём блоге Old New Thing Чен регулярно делится забавными историями из разработки софта, но также показывает действительно полезные примеры.
На этот раз Чен показал, почему история буфера обмена не отражает быстрые изменения содержимого буфера. Рэймонд приводит следующий фрагмент кода от клиента. Этот код был написан для некой утилиты, вставляющей в историю буфера обмена объекты. В некотором роде историю прошлых изменений превращали в будущее — целью было предугадать, какие элементы пользователь хотел бы видеть в истории буфера обмена.
// В целях наглядности вся проверка ошибок опущена
#include <windows.h>
void SetClipboardText(HWND hwnd, PCWSTR text)
{
OpenClipboard(hwnd);
EmptyClipboard();
auto size = sizeof(wchar_t) * (1 + wcslen(text));
auto clipData = GlobalAlloc(GMEM_MOVEABLE, size);
auto buffer = (LPWSTR)GlobalLock(clipData);
strcpy_s(buffer, size, text);
GlobalUnlock(clipData);
SetClipboardData(CF_UNICODETEXT, clipData);
CloseClipboard();
}
// Чтобы они были под рукой, разместим эти строки в истории буфера обмена
static constexpr PCWSTR messages[] = {
L"314159", // номер бага, который мы хотим исправить
L"e83c5163316f89bfbde7d9ab23ca2e25604af290", // коммит, к которому привязываем ошибку
L"Widget polarity was set incorrectly.", // комментарий, который нужно добавить
};
int wmain([[maybe_unused]] int argc,
[[maybe_unused]] wchar_t* argv[])
{
auto tempWindow = CreateWindowExW(0, L"static", nullptr, WS_POPUPWINDOW,
0, 0, 0, 0, nullptr, nullptr, nullptr, nullptr);
for (auto message : messages)
{
SetClipboardText(tempWindow, message);
}
DestroyWindow(tempWindow);
return 0;
}
Код записывает в буфер обмена последовательно три строковые переменные. Однако при запуске утилиты в истории буфера обмена оказывалась лишь одна — последняя. Куда делись две остальные?
Дело в том, что служба истории буфера обмена работает асинхронно через механизм Clipboard Format Listener, существующий с эпохи Windows Vista. В этом механизме через функцию AddClipboardFormatListener приложение добавляет себя в качестве листенера. После этого никаких дополнительных опросов буфера обмена проводить не нужно — система сама оповестит приложение, если буфер изменился.
При получении уведомления служба истории буфера обновляет собственно историю буфера обмена. Но из-за асинхронности событие может происходить с задержкой. Как объясняет Чен, из-за асинхронной природы обновлений при получении WM_CLIPBOARDUPDATE от Clipboard Format Listener буфер может успеть обновиться ещё раз.
Как считает Рэймонд, это даже не баг, а фича. Так получается избегать приложений, которые быстро спамили бы в буфер обмена множество изменений. Если даже пользователь не успевает воспользоваться содержимым буфера, то сохранять это для истории смысла нет, указывает Чен.
В другом посте из своего блога Рэймонд объяснил механизмы утилит-просмотрщиков буфера обмена с синхронными обновлениями буфера. Здесь периодически выполняется опрос GetClipboardSequenceNumber. У данного подхода тоже есть проблемы: редкий опрос угрожает привести к пропуску изменения буфера, но слишком частые запросы создадут лишнюю нагрузку на систему.
Рэймонд обещает в следующий раз показать, как исправить код выше.
Всем привет! Это третий сезон курса «Паттерны и практики написания кода». Первая серия вводная: в ней бэкенд-инженер Юра Афанасьев знакомит вас с историей паттернов и их функциями, а также рассказывает, как с ними правильно работать.
Подписывайтесь на канал AvitoTech в Telegram, там мы рассказываем больше о профессиональном опыте наших инженеров, проектах и работе в Авито, а также анонсируем митапы и статьи.
Довелось мне в последнее время поглядеть на "современные" проекты на Kotlin, и я признаюсь был поражен повсеместным использованием корутин в коде. Используют их просто везде: для обработки входящих соединений, для запросов в базы данных, для вызова внешних API, для операций с файлами на сетевых файловых системах. Знаете, в языке есть слова-паразиты — так вот кажется мне что в Kotlin корутины — это паттерн-паразит. Кажется корутины — это как магические заклинания, которые волшебным образом запустят код "где-то там, в локальном облаке".
Каждый раз когда я сталкиваюсь с туториалами про корутины — самым первым примером идёт конечно sleep на 10 тысяч "потоков". Притом как вы понимаете, вызывать нужно специально подготовленный для этого sleep, а не настоящий Thread.sleep — потому что с ним никакой магии не получится. Это создаёт обманчивое впечатление будто бы любая блокирующая операция автоматически волшебным образом превращается в асинхронную, за бесплатно увеличивая пропускную способность приложения. Хотелось бы напомнить любителям корутин что это не так.
Никто за вас не перепишет обычное чтение из сокета/файлового дескриптора на асинхронные ввод-вывод с проверкой наличия байт для чтения во входящем потоке через Selector::select — чудес не бывает. И как бы вы не увеличивали kotlinx.coroutines.io.parallelism рано или поздно при безудержном использовании корутин пул потоков забьётся чем-нибудь медленным и ваше приложение просто встанет раком.
Для примера попросите чатжпт написать вам простейший echo socket server на корутинах. Ну и клиента, который попробует работать с этим сервером в 100 обычных потоков (не так уж и много для нормального продакшн бэкенда). Надеюсь для вас не будет сюрпризом то, что сервер будет обрабатывать только 64 первых счастливых клиентов, а к остальным доберётся только когда эти первые клиенты закроют соединения.
Кардинальное упрощение привязки GitHub, GitLab, Bitbucket в Amvera Cloud
Привязка репозиториев GitHub, GitLab, BitBucketвызывала у наших пользователей затруднения, и мы обещали упростить процесс.
Теперь для привязки репозитория достаточно указать токен и выбрать ветку и репозиторий.
Способ позволяет организовать максимально простой деплой и обновление приложений через git push. Для обновления приложения достаточно сделать коммит в привязанный репозиторий, и оно соберется и бесшовно запустится автоматически.
Заполняем три поля и CI/CD готов
Подробная инструкция по подключению доступна по ссылке.
Amvera Cloud — это облако для простого деплоя приложений через git push (или интерфейс). Встроенный CI/CD, бэкапы и мониторинг позволяют развернуть проект тремя командами в IDE и не думать о настойке инфраструктуры. Amvera проще, чем использование VPS.
Магические шестиугольники. Обзор соревнования по программированию
Приветствую всех любителей спортивного программирования!
В данной статье рассмотрим интересный сайт для программистов - Al Zimmermann's Programming Contests.
На этой площадке проводится несколько соревнований в год. Каждое соревнование (контест) длится в среднем несколько месяцев. Задачи по своей формулировке не очень сложные. Но вот над решениями вам придётся изрядно потрудится.
В ряде случаев Joomla может взаимодействовать с нашим компонентом. Например:
Роутер Joomla может использовать роутер нашего компонента для анализа и создания ЧПУ-адресов;
Если наш компонент поддерживает категории, то com_categories будет отображать в представлении категорий сводку по каждой категории, содержащую количество материалов с этой категорией, с разбивкой по статусу публикации;
Если наш компонент поддерживает пользовательские поля, то com_fields будет вызывать метод getContexts(), чтобы получить типы материалов, к которым можно привязать пользовательские поля;
Если наш компонент поддерживает мультиязычные связи, то com_associations потребуется знать типы материалов, для которых возможны связи;
И, конечно же, Joomla потребуется запустить наш компонент, чтобы получить вывод.
Причины введения класса расширения становятся понятнее, если рассмотреть, как другие части кода Joomla взаимодействовали с нашим расширением в Joomla 3.
В Joomla 3 все эти части кода обращались к кодовой базе нашего компонента довольно хаотичным образом — вызывая функции из различных вспомогательных файлов.
А в Joomla 4 это упрощено:
Начиная с Joomla 4, другие компоненты получают доступ к нашему компоненту com_example, вызывая метод:
$extension = $app->bootComponent("com_example");
Затем они могут вызывать необходимые им методы через этот экземпляр класса расширения.
Справка:
Класс расширения (Extension) компонента вы можете найти в административной части компонента. Например, для компонента com_content это файл Root/administrator/components/com_content/src/Extension/ContentComponent.php, для com_example — Root/administrator/components/com_example/src/Extension/ExampleComponent.php
Сразу после создания экземпляра класса расширения компонента код библиотеки Joomla вызовет метод boot вашего класса расширения, передавая дочерний экземпляр Контейнера внедрения зависимостей (Dependency Injection Container):
$extension->boot($container);
По сути, это возможность делать буквально всё, что вам нужно. Например, метод иногда используется для настройки определённых классов, которые будут использоваться с вызовами HtmlHelper::_(). Или же можно сохранить ссылку на ваш дочерний DI-контейнер (который иначе может быть сложно получить).
После первого создания экземпляра вашего компонента Joomla кэширует этот экземпляр. При повторном вызове
$extension = $app->bootComponent("com_example");
Joomla просто возвращает существующий экземпляр вашего класса расширения, вместо повторного создания объекта и вызова метода boot(). Вы даже можете вызвать bootComponent, передав свой компонент, чтобы получить ссылку на свой собственный объект расширения.
Всякий раз, когда Joomla загружает расширение, она создает дочерний Dependency Injection Container (далее контейнер), исключительно для использования этого расширения. Это показано на схеме ниже.
Child DIC
Дочерний контейнер содержит указатель на родительский контейнер и функционирует аналогично основному контейнеру, но с некоторыми отличиями:
При каждом вызове метода set() для этого дочернего контенера пара ключ-значение добавляется в дочерний контейнер;
При каждом вызове метода get() для этого дочернего контейнера ресурс извлекается из дочернего контейнера, но если он там не найден, то поиск выполняется также в родительском контейнере.
Начиная с версии Joomla 4, разработчики Joomla рекомендуют создателям расширений использовать внедрение зависимостей (dependency injection) для своих расширений, определяя файл services/provider.php. Загрузка расширения теперь выполняется в два этапа, которые обрабатываются внутри файла services/provider.php:
Класс расширения регистрируется в дочернем контейнере;
Класс расширения извлекается из дочернего контейнера, создавая экземпляр этого класса.
Давайте рассмотрим минимальный пример этого для компонента com_example с пространством имён Mycompany\Component\Example.
use Joomla\CMS\Extension\ComponentInterface;
use Joomla\DI\Container;
use Joomla\DI\ServiceProviderInterface;
use Mycompany\Component\Example\Administrator\Extension\ExampleComponent;
return new class implements ServiceProviderInterface
{
public function register(Container $container): void
{
$container->set(
ComponentInterface::class,
function (Container $container)
{
$component = new ExampleComponent();
return $component;
}
);
}
};
Мы видим, что когда Joomla выполняет require этого PHP-файла, возвращается экземпляр класса.
$provider = require $path; // $path points to the relevant services/provider.php file
Переменная $provider указывает на объект, который является экземпляром этого анонимного класса. Кроме того, класс реализует интерфейс Joomla\DI\ServiceProviderInterface, что, по сути, означает, что он содержит метод register с указанной выше сигнатурой.
Когда Joomla выполняет
if ($provider instanceof ServiceProviderInterface)
{
$provider->register($container);
будет вызван метод register, который добавит запись в дочерний контейнер с
key - ComponentInterface::class – это сокращённый способ в PHP указать строку 'Joomla\CMS\Extension\ComponentInterface';
value - функция, которая возвращает новый экземпляр класса ExampleComponent (основного класса расширения компонента com_example).
$extension = $container->get($type);
Функция выше будет запущена и вернет новый экземпляр ExampleComponent.
Вы можете самостоятельно изучить код Joomla в файле libraries/src/Extension/ExtensionManagerTrait.php и убедиться, что описанный выше шаблон также применяется к модулям и плагинам.
Обратите также внимание на следующую строку в этом файле:
if ($extension instanceof BootableExtensionInterface)
{
$extension->boot($container);
}
Таким образом, если класс расширения реализует интерфейс BootableExtensionInterface, Joomla немедленно вызовет метод boot() экземпляра расширения, как описано в документации по расширениям.
Последнее время замечаю тенденцию, что многие хостинги и публичные облака вводят платную поддержку.
Позиционируется поддержка как дополнительная опция и гарантия времени ответа. Но на мой взгляд это выглядит как вымогательство денег, когда компания может оказывать качественную поддержку, но если вы их не “подкупите” дополнительно, не будет.
Я основатель облака для простого деплоя проектов через Git push – Amvera Cloud. И вижу, что пользователи пишут нам в поддержку. И говоря честно – люди пишут только тогда, когда другие способы не помогли и они не знают, как решить их насущную проблему. А это значит мы не доработали и сделали что-то непонятно или неудобно. И это наша обязанность постараться им помочь. И я не вижу морального права просить за это с них деньги.
Поддержка может работать не идеально, можно даже разделять клиентов во время высокой нагрузки на постоянных или новых, с высоким или низким чеком или любым другим образом, чтобы кому-то помочь в первую очередь. Но помочь нужно всем.
И главное, я не верю, что на платной поддержке можно сильно заработать. Это просто предлог, чтобы работать хуже, чем может компания.
Поддержка должна быть бесплатной, всегда, и без всяких но!
Они объявлены устаревшими с Joomla 4.x и вместо них предлагается использовать Joomla\CMS\Factory::getContainer()->get('Соответствующий_интерфейс').
Например, вместо Joomla\CMS\Factory::getDbo() — Factory::getContainer()->get(DatabaseInterface::class);
Но давайте посмотрим на описание метода Joomla\CMS\Factory::getContainer():
Возвращает глобальный контейнер сервисов, создавая его только в случае отсутствия.
Этот метод рекомендуется использовать только в коде, ответственном за создание новых сервисов и требующем разрешения зависимостей. Его следует применять только когда контейнер недоступен другими способами.
Допустимые сценарии использования:
Статический метод getInstance(), вызывающий сервис из контейнера (пример: Joomla\CMS\Toolbar\Toolbar::getInstance());
Фронт-контроллер приложения, загружающий и исполняющий класс Joomla (пример: файл cli/joomla.php);
Получение опциональных зависимостей конструктора во время переходного периода для сохранения обратной совместимости (в этом случае следует добавлять уведомление об устаревании).
Не рекомендуется использовать этот метод как прямую замену статическим вызовам, например заменять Factory::getDbo() на Factory::getContainer()->get(DatabaseInterface::class). Вместо этого код следует рефакторить для использования внедрения зависимостей.
В последнем абзаце видим явное противоречие и рекомендацию использовать зависимости.
Как же внедрять зависимости?
Рассмотрим как это сделано в плагине joomla группы content:
В сервис-провайдере плагина Root/plugins/content/joomla/services/provider.php используются методы setDatabase() и setUserFactory() для установки зависимостей.
А в классе расширения плагина plugins/content/joomla/src/Extension/Joomla.php используются методы getDatabase() и getUserFactory(). Аналогично в компонентах.
Мой пример использования:
В компоненте обновления цен для JoomShopping, в моделях работающих с товарами я заменил драйвер базы данных на свой, соединяющийся с базой другого сайта. Код самих моделей при этом изменять не потребовалось.
Выводы:
Сервис-провайдер расширения — единая точка установки зависимостей для своего расширения.
Событие onAfterExtensionBoot - точка для замены зависимости в любом расширении.
Если бы расширения напрямую брали зависимости из глобального контейнера, такая замена была бы невозможна.
Есть такая мантра в гошке - "всегда обрабатывать ошибки"
А ведь так хочется чтобы они сами наверх прокидывались...
Недавно работал с либой валидатора, и нашел функцию у которой можно не проверять ошибку так как если она не отработает то приложение не запустится в любом случае
Апрель подошел к концу, и мы знакомим вас с обновлениями на платформе, которые произошли за прошедший месяц.
Функционал передачи проекта другому пользователю.
Теперь вы можете передать свой проект другому пользователю, имеющему аккаунт на платформе (для этого пользователю заранее нужно предоставить доступ к проекту). Новая фича доступна в проекте в разделе "Настройки/Пользователи".
Функционал будет полезен студиям, фрилансерам и всем тем, кто занимается заказной разработкой. Важная особенность: после передачи проекта на баланс заказчику, вы можете (опционально) оставить доступ к проекту для осуществления поддержки.
Дополнения и доработки:
Исправлена ошибка, которая могла приводить к зависанию статуса контейнер
Для «Сетевых дисков» добавлена поддержка subPath, что позволяет подключать отдельные папки «Сетевого диска» к контейнеру.
Исправлена ошибка, которая могла приводить к разрыву соединения через «Сетевой сервис».
Исправлена ошибка, которая приводила к излишней буферизации в «Сетевые сервисы / Маршруты».
Исправлена ошибка веб-интерфейса, в результате которой общее количество ресурсов не обновлялось при удаление из проекта контейнера или сетевого диска
Максимальное значение для переменных было увеличено до 1024 символов.
Продолжаем развивать наш каталог и в апреле добавили 3 новых приложения:
Совместно с Российской платформой NextBoxреализовали интеграцию в каталог приложений цифровой платформы для хранения и обработки данных.
NextBox помогает компаниям безопасно хранить и обмениваться корпоративными файлами, совместно работать с документами любых форматов в едином пространстве.
Также добавили 2 новых self-hosted приложения:
Redash - открытая платформа для визуализации данных и бизнес-аналитики, которая предоставляет простой интерфейс для создания дашбордов, запросов и отчетов. Сервис позволяет работать с различными источниками данных, включая базы данных, API и файлы, а также поддерживает создание пользовательских SQL-запросов для глубокого анализа информации.
KeyDB- высокопроизводительная база данных, полностью совместимая с Redis, которая использует многопоточную архитектуру для повышения пропускной способности. Она идеально подходит для приложений, требующих низкой задержки и высокой доступности, обеспечивая при этом простую интеграцию с существующими системами на базе Redis.
Dockhost— облачная платформа для хостинга приложений на основе Docker‑контейнеров (боты, сайты, базы данных и т. д.), которая позволяет запускать и масштабировать как простые проекты, так и сложные микросервисные приложения без необходимости настраивать и контролировать инфраструктуру.
Как вам такой сценарий. Некоторая группа специалистов по информационной безопасности (в плохом смысле) создаёт публичный сайт-прокси который рекламируется как бесплатное решение всех проблем программиста.
Когда программист делает запрос на генерацию кода скажем бекенда для веб сервера его запрос проксируется в ChatGPT, а потом модифицируется скриптом, который добавляет небезопасный код.
Попутно сайт собирает информацию о самом программисте, например, IP адрес, а в случае регистрации - email работодателя.
Информация сохраняется для разработки, через полгода-год когда код гарантировано попадает на прод происходит кибератака.
Читаемость Си-кода: грустный ликбез, чтобы жить стало веселее
Многие коллеги по цеху подтвердят, что читаемость кода на языке Си иногда оставляет желать лучшего. Как подтвердят и те, кто им плохо владеет, но так или иначе сталкивается в связи с рабочими задачами.
И всему виной (ожидаемо) выражения препроцессора и, как следствие, макросы. Да, вы правильно подумали. Именно те части кода, в которых вызываются такие легенды как:
Последние два очень легко спутать, если читать код невнимательно. Вместо них иногда рекомендуют использовать
#if defined // вместо #ifdef
#if !defined // вместо #ifndef
Но если же в компании/проекте/отделе нет определенного код-стайла, или он не предполагает написание длинного варианта, то, естественно, разрабы пишут короткую форму (я, честно признаться, тоже).
Чего далеко ходить - все open source проекты пестрят именно сокращенными вариантами этих выражений и с этим уже ничего не поделаешь.
А вот первое выражение из первой тройки игроков иногда вообще открывает врата ада, когда используется многострочный макрос с бэкслэш-символами (\) на концах строк.
Конечно же выражения препроцессора - это очень гибкий и полезный инструмент, позволяющий делать всё:
условную компиляцию;
выравнивание структур;
предотвращать повторные включения файла;
работать со строками;
грамотно оборачивать повторяющийся код и т.д. и т.п.
Но бывают случаи, когда упрощение кода с точки зрения алгоритма делает его слабо читаемым для того самого бедного программиста, пытающегося прочесть этот шедевр машинописного текста:
который определяется после объявления полей структуры.
Как обрабатывается аргумент x:
имя аргумента преобразуется в строку с помощью макроса "#x" и присваивается полю *name;
полю *value присваивается значение поля структуры b с названием аргумента x (тут нужно убедиться, что поле с именем x действительно существует в структуре b).
То есть чтобы при заполнении массива не писать каждый раз одинаковые строчки:
Ну и в самом конце вызываем удаление созданного макроса, чтобы оно не было использовано в коде в дальнейшем:
#undef _SPECIAL
Теперь после прочтения этого небольшого ликбеза вы можете смело пользоваться макросами и создавать более сложные и нечитаемые шедевры наконец-то разобрались в том, насколько гибкими могут оказаться выражения препроцессора ( вообще не понимаю, как вы жили без них раньше...)
Далее про то как сделать выделенный порт и http-пулл для promitheus-метрик, так вот докладываю👇
Мы используем embedded tomcat как и великое множество других Java-проектов(остальным сил и терпения), по этому этот пост применительно к нему.
📍Для создания выделенного порта достаточно создать отдельный коннектор спринговой java-конфигурацией и проинициализировать его через TomcatServletWebServerFactory.
❕Стоит обратить внимание что на данном порту будут доступны вообще все сервлеты доступные и на стандартном порту, но при наличии разграничения доступа обычно это не является проблемой.
P.S. Не хочу быть злом по этому в комменты в свою телегу выложу класс в текстовом варианте😁
Наверняка многие из вас юзают Apache POI. Для тех, кто не знает: это джаванская либа для работы с файлами Microsoft Office и OOXML — всякие Excel, Word и прочее.
А в чём опасность?
При генерации Excel-файла эта библиотека позволяет включить отслеживание ширины данных в колонках и автоподгонку ширины столбцов под содержимое (чтобы данные не скрывались из-за маленькой ширины столбца по умолчанию).
Включается это так: // Отслеживаем ширину для автосайзинга sheet.trackAllColumnsForAutoSizing(); // ... генерируем строки Excel-файла // Автосайзим столбцы sheet.autoSizeColumn(columnIndex);
Проблема: эта тема крайне ресурсоёмкая на больших объёмах данных. На тесте с 100 строками — всё быстро. На проде с 600К строк — 20 минут превращаются в 4 часа. 4 часа, Карл! Чего оно там делает вообще?!)
У нас такое не раз уезжало в прод и штука эта совсем неприятная я вам скажу:
Отчёт не собирается вовремя;
Заказчик негодует;
Паника, крики — в общем, ну его этот трекинг! 😅
Что делать?
В 99% случаев: достаточно автосайзинга только для заголовков:
В 1% случаев: нужно отавтосайзить 1-2 столбца из сотен. Тогда делаем точечный трекинг:
// Трекинг только нужных колонок sheet.trackColumnForAutoSizing(columnIndex); // ... генерация данных // Автосайзим sheet.autoSizeColumn(columnIndex);
Ну и можно конечно вручную задавать ширину столбцов, но это не всегда удобно.
P.S. Метод autoSizeColumn() очень тяжёлый. Выдержка из джавадоки:
Этот процесс может быть медленным на больших листах, поэтому его стоит вызывать только один раз для каждого столбца в конце обработки.
P.S.S. Еще кстати поя умеют не буферизовать весь excel’ник в память при генерации, надеюсь вы все этим пользуетесь ибо обратное дико некармическая тема)) Знаете ж как?