Обновить

Бэкенд

Сначала показывать
Порог рейтинга

Задолбался искать вакансии на HH - сделал бота(пока бесплатный), который фильтрует мусор и показывает только релевантные, уведомляет периодически и тд. Если найдутся люди кому не в падлу посмотреть потыкать, может даже найдут работу себе - отпишитесь в тг. Бот - @WorkFinderRuBot. Мой тг @vernawork. Буду рад конструктивной критике.

Теги:
-1
Комментарии0

Худший бэкап — не тот, что не восстановился. А тот, что положил прод.

Что, если post-script не отработал? Моргнула сеть или случился таймаут. Внешний оркестратор просто пишет в лог failed и снимает задачу. А вот PostgreSQL об этом не знает. База остается в режиме бэкапа и начинает непрерывно копить WAL-файлы, ожидая команды на завершение.

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

Уметь дернуть pg_backup_start( ) — мало. Если СРК не имеет встроенного watchdog-механизма для сброса зависших сессий, резервное копирование превращается в угрозу доступности. Разделение ответственности — правильный архитектурный подход, но он означает, что защита базы от переполнения диска полностью ложится на ваши плечи.

О зависшем backup mode, разрывах PITR и других неудобных вопросах эксплуатации PostgreSQL совместно с Акурой поговорим в режиме live-демо на вебинаре 26 марта в 11:00 (МСК).

Регистрация по ссылкеПриносите в комментарии свои вопросы.

Теги:
0
Комментарии0

Расширение AI-Less Habr — Чистим Хабр от ИИ

Надоела лента, забитая ИИ? У меня есть готовое решение для вас. Shut up and take my money:

Интерфейс расширения
Интерфейс расширения

Расширение для Chrome (и совместимых браузеров) позволяет скрывать статьи про «Искусственный интеллект». Скрывается не контент, написанный ИИ (LLM), а контент про ИИ (что сейчас обычно под этим подразумевается). Бесконечные статьи об очередной революции, вызванной тем, что такая‑то LLM модель опередила конкурентов на 0.1 балл в одном из 186 имеющихся бенчмарков, и вот этот вот всё.

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

Есть следующие возможности:

  • скрывать хаб «Искусственный интеллект»

  • скрывать по словам в заголовке (настраиваемый список)

  • скрывать по тегам (настраиваемый список)

  • инвертированный режим (показать, попадающее под фильтры, и скрыть остальное)

По умолчанию включено только скрытие хаба «Искусственный интеллект». Фильтры по словам/тегам с большей вероятностью допускают ложноположительные срабатывания, поэтому выключены по умолчанию. По этой же причине в фильтрах по словам по умолчанию нет слов «ии»/«ai», так как есть достаточно много статей, содержащих что‑то вроде «без ИИ». Внимательно относитесь к добавлению слов в фильтры, чтобы минимизировать ложноположительные срабатывания.

Теги:
+18
Комментарии4

Всем привет! На связи Иван, руководитель НИИ Крокодил 😀

Это продолжение истории про медицинское приложение для клиники.

Часть 2. Как устроена медицинская система изнутри

Когда начинаешь работать с медицинской системой, быстро понимаешь: это не продукт, который можно «собрать и доработать потом». Любое изменение проходит через внутреннюю ИТ-команду клиники, потому что за каждым экраном стоят реальные процессы — расписания врачей, лаборатория, регистратура, страховые компании.

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

🧪 Отдельная история — тестирование. Мы проверяли не только интерфейс, а связку «мобильное приложение + сервер клиники». Запись и отмена приёма, конкуренция за слот, обработка ошибок, загрузка PDF-документов, корректная работа вложенных структур в истории посещений — всё это нужно было прогонять реальными сценариями.

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

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

Теги:
0
Комментарии1

Всем привет! На связи Иван, руководитель НИИ Крокодил 😀

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

Часть 1. Как устроена медицинская система изнутри

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

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

📦 Данные в приложении не хранились

Каждое действие пользователя превращалось в запрос на сервер: авторизация, запись к врачу, список анализов. Сервер возвращал актуальное состояние системы в ответе. Если данные менялись на сервере, пользователь сразу видел это в приложении.

🕓 Запись к врачу была не одной формой, а сценарием

Выбор врача или направления, дата, свободное время, подтверждение или финальный статус. Сервер решал, какие данные запрашивать на каждом этапе, а приложение лишь следовало сценарию. Пользователь не мог перескочить шаги или отправить некорректные данные — интерфейс был привязан к логике backend’а.

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

Теги:
0
Комментарии0

Заметил обнову на Хабре.

Появился новый счетчик "охвата" который судя по моим наблюдениям измеряется в степенях двойки (2,4,8,16,32 и тд). Но я так и не мог найти официальных данных как он работает. Смотрел официальный changelog Хабра — никаких недавних обновлений не вижу.

Как Хабр считает метрику "Охват", которая стала первым делом выводиться возле пользователя?

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

UPD
Наглая реклама, но после того как соберу инфу, сделаю еще один репост на Хабре и в своем-тг

Теги:
+1
Комментарии2

Руководитель группы серверной разработки «Криптонита» Артём Корсаков ведёт проект Scalabook. Это уникальная русскоязычная база знаний по Scala.

И недавно он добавил туда новые страницы — делимся с вами. Отправляйте знакомым «скалистам»!

Теги:
0
Комментарии0

Сравнивал разные методы сделать call-once (а точнее инициализацию синглтона) - далее небольшие заметки.

Итак, нам нужно создать какой-то объект, пусть даже что-то нетривиальное (для определённости можно представлять что мы хотим прочитать данные из файла/эмбеддет ресурсов и разложить эти данные в хеш-мапу для будущих чтений). Рассмотрим следующие варианты:

  1. статичный глобальный объект, вычисление вызывается в конструкторе

  2. статичный глобальный объект + std::call_once

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

Чтобы в годболте видеть что и где вызывается - определяем тип имеющий только объявления методов (это заставит компилятор ставить явный call)

struct TSomeExternal {
    TSomeExternal(int);
    ~TSomeExternal();

    int DoCalc();
    char Data[60]; // просто чтобы было проще увидеть
};

Особенности первого варианта:

  • вычисление "до main"

  • неконтролируемый порядок инициализации глобальных ответов => нельзя иметь зависимости между такими объектами

  • вычисление только в рамках 1 потока

  • уникальный плюс: нет проверок "на горячем цикле" - при обращении к объекту вызывающая функция может считать что данные уже готовы (но именно этот плюс стоит нам второго пункта)

В годболте можно посмотреть на func1 - видно что она скомпилилась просто в забор поинтера, и call

TSomeExternal GlobalValue(7);
int func1() {
    int x = GlobalValue.DoCalc();
    return x + 14;
}

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

GlobalValue:
        .zero   60

А также _GLOBAL__sub_I_example.cpp - там видно, что вызывается конструктор, и регистрируется указатель на деструктор в пост-колбеках (__cxa_atexit)

Второй вариант (более правдоподобно было бы внести call_once внутрь глобального объекта, но для простоты сравнения сделал как ниже)

static std::once_flag flag;
static std::unique_ptr<TSomeExternal> ptr;
void init() {ptr = std::make_unique<TSomeExternal>(7);}

int func2() {
    std::call_once(flag, init);
    int x = ptr->DoCalc();
    return x + 14;
}
  • настоящая инициализация случается только в момент первого использования (ленивая инициализация). Это может быть как плюс (позволяет не тратиться на инициализацию если данные не будут использоваться), так и минус (первые запросы/итерации будут медленными)

  • разные инициализаторы данных вполне могут работать одновременно из разных потоков

  • имхо - дорогой основной цикл (очень много инструкций до TSomeExternal::DoCalc) - это всё подготовка вызова call pthread_once, который будет выполнен каждый раз

Третий вариант годболт

int func3() {
    static TSomeExternal prepared = {7};
    int x = prepared.DoCalc();
    return x + 14;
}
  • в рантайме есть проверка - но это проверка и так нужного нам указателя на null

push    rbx
movzx   eax, byte ptr [rip + guard variable for func3()::prepared]
test    al, al
je      .LBB0_1
  • если указатель не нулевой, то переход не будет выполнен и мы сразу переходим к

        lea     rdi, [rip + func3()::prepared]
        call    TSomeExternal::DoCalc()@PLT
  • в случае если указатель не готов, то подготовка случается под блокировкой (__cxa_guard_acquire)

  • конкретно в годболте не видно, но создаются слоты для guard variable for func3()::prepared и func3()::prepared (в этом смысле разницы со вторым вариантом мало)

Итого: ленивое вычисление, также возможна многопоточная инициализация, а также дешёвый success-path

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

Теги:
0
Комментарии1

Друзья, Digital Q.DataBase позволяет Вам не только сохранить прикладную логику СУБД Microsoft и Oracle.

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

Что это означает на практике:

🔹 ABAP-приложения продолжают работать на новой платформе
🔹 Данные и обработка переносятся в Digital Q.DataBase
🔹 Вся бизнес-логика сохраняется без изменений
🔹 Формируется импортонезависимый стек из отечественного ПО

🔹 В этом видео: 

ABAP-код → сохранение → активация → преобразование в C++ → компиляция → установка на сервер → запуск

📎 Полезные ссылки
🔹 Отдельный лендинг по замене SAP: renovation.diasoft.ru
🔹 Бесплатное получение СУБД дистрибутива: database.diasoft.ru
🔹 Документация: доступна внутри дистрибутива
🔹 Telegram-сообщество Digital Q.DataBase: t.me/dqdatabase

Теги:
+3
Комментарии0

Как стать Go-разработчиком?

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

Ловите подборку таких инструментов — они идеально подойдут как для входа в профессию, так и для развития профессиональных навыков, а на Хабр Карьере уже полно актуальных учебных программ по каждому из них:

— SQL. Работаем с базами данных

— Git. Отслеживаем изменения в коде.

— CI/CD. Автоматизируем сборки, тестирование и развёртывание кода.

Postman. Тестируем API через отправку HTTP-запросов и анализа ответов.

Bash. Работаем с операционной системой и пишем скрипты.

Полезные курсы по вашей специальности

Теги:
+2
Комментарии0

Бесплатный тест для Java‑разработчиков: оцени уровень и найди точки роста

Хороший Java‑разработчик — это не просто тот, кто пишет код, а специалист, который держит в голове целую экосистему: от базовых принципов языка до архитектуры распределенных систем.

Чтобы осознанно развиваться дальше, важно понимать:

  • какие технические навыки уже прокачаны;

  • где есть пробелы в знаниях архитектуры и проектировании;

  • какие технологии стоит изучить подробнее;

  • насколько уверенно ты ориентируешься в современном Java‑стеке.

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

За 20 минут ты получишь:

  • оценку текущего уровня;

  • карту компетенций — наглядную схему всех навыков, необходимых для развития в профессии;

  • подборку материалов для самостоятельного изучения.

Пройти тест

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

Теги:
+2
Комментарии1

🔹 Стоит ещё раз подчеркнуть важную мысль: переход на российскую СУБД не обязательно означает полное переписывание системы.

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

При этом важно понимать условие: такой подход работает, если выбранная СУБД изначально к этому подготовлена. В ней должны быть реализованы необходимые доработки для совместимости, включая клонирование функциональности систем Microsoft и Oracle.

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

🔹 Мы предлагаем другой подход. В нашем подходе меняется само представление о миграции: не обязательно адаптировать приложение под PostgreSQL. Можно пойти другим путём, реализовать в СУБД функциональность, совместимую с зарубежными системами.

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

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

📎 Полезные ссылки
🔹 Бесплатное получение дистрибутива: database.diasoft.ru
🔹 Документация: доступна внутри дистрибутива
🔹 Telegram-сообщество Digital Q.DataBase: t.me/dqdatabase

Cnews 12.03.2026 | МОСКВА
Cnews 12.03.2026 | МОСКВА
Теги:
+4
Комментарии2

Scala 3.8: три вещи, которые сломают ваш проект при апгрейде

Scala 3.8 вышла в январе 2026. Фич много, но сначала - про то, что может больно ударить при переезде.

1. JDK 17 - жёсткое требование

Никакого JDK 11, никакого JDK 8. Реализация lazy val переписана с sun.misc.Unsafe на VarHandles - потому что в JDK 26 Unsafe будет бросать исключения. Компилятор просто откажется работать на старой JVM.

Если ваша инфраструктура ещё на JDK 11 - апгрейд Scala придётся отложить до апгрейда JVM. Scala 3.3 LTS пока остаётся на JDK 8+.

2. stdlib скомпилирована Scala 3 - и один неочевидный момент

Бинарная совместимость сохранена, но явная передача ClassTag теперь требует using:

// ❌ Перестало компилироваться
Array.empty(reflect.ClassTag.Int)

// ✅
Array.empty(using reflect.ClassTag.Int)
// или просто
Array.empty[Int]

Хорошая новость: компилятор умеет чинить такие места автоматически. Запускаем на Scala 3.7.4 перед апгрейдом:

scalac -source:3.7-migration -rewrite YourFile.scala

3. betterFors меняет тип результата - молча

betterFors теперь включён по умолчанию. Новый дешугаринг убирает промежуточный map - это хорошо для производительности, но меняет поведение кода с Map и несколькими val-алиасами:

// Этот код даёт РАЗНЫЕ результаты в 3.7 и 3.8
val result: Iterable[(Int, Int)] =
  for
    (k, v) <- Map(1 -> 1, 2 -> 1, 3 -> 1)
    x = k + v
    (a, b) = f(x)
    (y, z) <- Map(42 -> 27)
  yield (a + y, b + z)
// 3.7 → List((43,29), (43,30), (43,31))
// 3.8 → Map(43 -> 31)

В 3.7 синтетический map превращал Map в Iterable. В 3.8 этого шага нет - Map остаётся Map, дубли ключей исчезают. 3.8.2 выдаёт предупреждение на такие места. Не игнорируйте.

Фикс простой - явно приводить к Seq:

(k, v) <- Map(1 -> 1, 2 -> 1, 3 -> 1).toSeq

В своем канале в Telegram и в канале Max о разработке в стартапах рассказываю ещё больше интересного и делюсь опытом, заходите, найдете полезные кейсы!

И ещё одно: не берите 3.8.0 и 3.8.1

Сразу после релиза в 3.8.0 нашли JVM linkage errors при использовании Scala 2.13 библиотек. 3.8.1 закрыл это, но оставил баг в for-comprehension. Стабильная версия — 3.8.2 (вышла 24 февраля 2026).

Обновляться - только на неё.

Теги:
0
Комментарии0

Ближайшие события

WT CDEK library v.1.3.0 - обновление PHP SDK для Joomla + CDEK.

Небольшая нативная PHP Joomla библиотека для работы с API v.2 службы доставки CDEK. Библиотека представляет собой клиент для авторизации в CDEK API по OAuth, работы с некоторыми методами API: получения ряда данных и расчета стоимости доставки. Поддерживается Joomla 4.2.7 и выше.

В пакет входят:

  • библиотека Webtolk/Cdekapi

  • системный плагин System — WT Cdek для хранения настроек и AJAX‑интеграций

  • task‑плагин Task — Update WT Cdek data для обновления локальных копий справочников CDEK по расписанию

  • web asset с официальным JavaScript‑виджетом СДЭК

👉 v.1.3.0. Что нового?

  • Полный рефакторинг библиотеки. Библиотека переработана в entity‑based API с фасадом Cdek и отдельным слоем запросов. Обратная совместимость не нарушена, поэтому версия библиотеки — 1.3.0.

  • Добавлена поддержка новых разделов API СДЭК. Добавлена поддержка новых разделов API СДЭК: webhooks, prealert, печатные формы, payment, passport, reverse, intakes и других сущностей.

  • Улучшена интеграция с Joomla. Улучшена интеграция с Joomla: installer script для layouts, новые поля Joomla Form для тарифов и обновлённые js виджета CDEK.

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

Пример запроса — запрос информации о городе.

<?php

use Webtolk\Cdekapi\Cdek;

\defined('_JEXEC') or die;

// Вариант 1: брать credentials из настроек плагина
$cdek = new Cdek();

// Вариант 2: передать credentials явно
$cdek = new Cdek(test_mode: true, client_id: 'your_client_id', client_secret: 'your_client_secret');

$result = $cdek->location()->getCities([
    'postal_code' => '410012',
    'city'        => 'Саратов',
    'size'        => 1,
]);

Результат запроса:

Array
(
    [0] => Array
        (
            [code] => 428
            [city_uuid] => 7e54a0b3-76f0-41e2-92e0-f1e600ad84fd
            [city] => Саратов
            [fias_guid] => bf465fda-7834-47d5-986b-ccdb584a85a6
            [country_code] => RU
            [country] => Россия
            [region] => Саратовская область
            [region_code] => 47
            [fias_region_guid] => df594e0e-a935-4664-9d26-0bae13f904fe
            [sub_region] => городской округ Саратов
            [longitude] => 46.034266
            [latitude] => 51.533562
            [time_zone] => Europe/Saratov
            [payment_limit] => -1
        )

)

Библиотека эта нужна для разработчиков, создающих свои расширения для интеграции Joomla и курьерской службы CDEK.

Страница расширения

GitHub расширения

Теги:
+2
Комментарии0

Зелёные тесты ≠ хорошие тесты

Впервые в истории писать тесты стало легко и совсем не страшно. Вокруг теперь у всех покрытие 80%, 90%, а то и вовсе 100%. И вот тут начинается проблема: зелёные тесты ≠ хорошие тесты.

Проблема в метрике, которой мы все привыкли доверять. Code coverage считает строку протестированной, если она выполнилась во время теста. Всё. Не «поймает ли тест баг в этой строке», не «проверяет ли он правильность результата» — просто выполнилась. Можно написать тест без единого assert, и покрытие вырастет. 500 тестов, 90% coverage, а пользы ноль.

Мутационное тестирование — это совершенно другой путь. В простейшей реализации этот инструмент тупо берёт твой код и намеренно ломает его: меняет > на >=, + на ‑, True на False. Каждая такая поломка — мутант. Если после мутации все тесты по‑прежнему зелёные — значит они ничего не проверяют. Покрытие есть, защиты нет.

Почему это важно именно сейчас?

Потому что нейронка любит зелёненькое. Чем больше зелёных тестов — тем субъективно лучше. 100 тестов внушают больше доверия, чем 10, правда? А внутри там assert response.status_code == 200. assert result is not None. assert len(items) > 0. Тест проверяет, что функция вернула хоть что‑то — и радостно зеленеет. Поменяй логику условия, перепутай знак, сломай граничный случай — тест всё равно зелёный. Потому что он проверяет не правильность, а наличие.

Мутационное тестирование — единственный автоматический способ это поймать. Метрика называется mutation score: процент убитых мутантов. 60% — плохо. 90%+ — тесты реально что‑то защищают.

Кое‑какие инструменты для такого тестирования уже есть: mutmut и cosmic‑ray для Python, Stryker для JS/TS, PIT для Java. Медленно? Да, значительно медленнее обычного тест‑рана. Но запускать его не нужно на каждый коммит — достаточно на PR в критические модули.

Но есть нюансы. А где их нет, правда?

Первый: мутации рандомные. Замена > на >= — это не баг, который кто‑то реально допустит. Это синтетическая поломка. Половина мутантов генерирует код, который в реальности никогда не появится. Ты тратишь время на убийство мутантов, которые не имеют отношения к настоящим ошибкам. Это как тестировать замок, ковыряя его вилкой — формально проверка, по факту мимо.

Второй — ещё хуже. Чтобы убить мутанта, тест должен зафиксировать конкретное поведение. Каждую ветку, каждое значение, каждый edge case. Доведи mutation score до 100% — и ты прибил гвоздями каждую строчку кода. Буквально. Теперь попробуй отрефакторить. Переименовал внутренний метод — 40 тестов красные. Поменял порядок полей в ответе — ещё 20. Тесты превращаются из страховки в кандалы: код работает правильно, но тесты падают, потому что они проверяют не поведение, а реализацию.

Это реально ловушка. Слишком гонишься за mutation score — получаешь хрупкие тесты. Не гонишься — получаешь видимость тестирования.

Перемены — впереди!

И вот тут становится по‑настоящему интересно. Представь, что мутации генерирует не тупой набор правил «замени плюс на минус», а нейронка, которая понимает контекст. Которая знает, какие баги реально встречаются в таком коде. Которая мутирует не синтаксис, а логику: меняет порядок проверок, путает граничные условия, забывает обработать edge case — ровно так, как ошибается человек. Или другая нейронка.

Сейчас есть явный сдвиг в сторону таких инструментов, но всё еще ничего достойного не вышло. Но уже скоро точно появится. И это будет совсем другой уровень. Не «выжили ли тесты после рандомной поломки», а «выжили ли тесты после правдоподобной ошибки».

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

Ждём, когда мутанты станут умнее.

Теги:
+7
Комментарии3

«Просто добавь кнопку» и недели работы

Однажды заказчик пришёл с задачей, которая звучала как пара часов работы «Просто добавь кнопку — нажал, выгрузил данные, всё». Я открыла код и поняла, что эта кнопка стоит не пару дней, а недель — и это если повезёт..

Сложнее всего оказалось не сделать, а объяснить так, чтобы услышали.

С технической стороны всё понятно: сервис писался под дедлайн, архитектура не предусматривала роста, и каждое новое изменение тянет за собой минимум три соседних. Но заказчик смотрит на задачу и видит один экран. Кнопки ещё нет, но она же просто кнопка. Что тут может быть сложного?

Архитектурное объяснение я попробовала и оно не зашло. Слои, связи, зависимости: всё правильно, всё мимо.

Что работает вместо «красивого кода»

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

Заказчик услышал третий пункт. Именно его.

Нетехнический человек воспринимает разработку примерно так: «нажал кнопку → произошла магия → получил результат». Это не незнание — просто другая роль. Заказчик и не должен думать об архитектуре, это моя работа. Значит, говорить на его языке — тоже моя.

Как я считаю стоимость следующей фичи

Со временем сложился свой фреймворк. Не из учебника, а из разговоров, где меня не понимали, пока я не поменяла подход.

Три вещи, которые я оцениваю перед тем, как называть сроки: 1. Базовая сложность: сколько займёт в идеальных условиях, на нормальной архитектуре. 2. Архитектурный коэффициент — во сколько раз реальность дороже идеала. Код без тестов, с жёсткими связями между модулями — это 2-4× к оценке. Не абстракция: вот здесь нельзя менять, не затронув вот это. Рисую буквально на бумаге. 3. Риск‑налог — что может пойти не так. Что сломается, насколько быстро заметят, сколько займёт починка. Не чтобы напугать, а чтобы показать, что «быстро» и «надёжно» здесь в противоречии.

Когда эти три числа стоят рядом — разговор меняется. Заказчик видит, что не «разработчик тормозит», а «вот цена, вот риск, вот выбор».

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

Что осталось в голове

Техдолг — это не технический вопрос. Это финансовый.

Пока объясняешь его как технический — тебя не услышат. Как только переводишь в деньги, сроки и риски — начинают слышать.

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

А вы как объясняете техдолг тем, кому важен результат, а не архитектура? Есть формулировка, которая сработала лучше всего?

Теги:
+3
Комментарии3

Валидация полей форм в Joomla 6

У каждого поля в форме Joomla есть поле type, но за валидацию значения отвечает атрибут validate.

Например: number — очевидно что значение должно быть числом.

<field
	name="count"
	type="number"
	label="MOD_ARTICLES_FIELD_COUNT_LABEL"
	description="MOD_ARTICLES_FIELD_COUNT_DESC"
	default="5"
	filter="integer"
	min="0"
	validate="number"
/>

Или UserId — тут сложнее, значение должно быть реальным id сужествующего пользователя.

<field
    name="default_value"
    type="user"
    label="PLG_FIELDS_USER_DEFAULT_VALUE_LABEL"
    validate="UserId"
/>

А в моём компоненте нужна валидация id компании.

Добавляем в поле атрибут validate="CompanyId":

<field
    name="company_id"
    type="text"
    label="COM_WISHBOXBONUSSYSTEM_FIELD_COMPANY_LABEL"
    required="true"
    validate="CompanyId"
/>

Добавляем класс правила (в моём случае по сути копия правила UserId):

<?php
/**
 * @copyright   (c) 2013-2026 Nekrasov Vitaliy <nekrasov_vitaliy@list.ru>
 * @license     GNU General Public License version 2 or later;
 */
namespace Joomla\Component\WishboxBonusSystem\Administrator\Form\Rule;

use Joomla\CMS\Form\Form;
use Joomla\CMS\Form\FormRule;
use Joomla\Database\DatabaseAwareInterface;
use Joomla\Database\DatabaseAwareTrait;
use Joomla\Database\ParameterType;
use Joomla\Registry\Registry;
use SimpleXMLElement;
use function defined;

// phpcs:disable PSR1.Files.SideEffects
defined('_JEXEC') or die;
// phpcs:enable PSR1.Files.SideEffects

/**
 * Form Rule class for the Joomla Platform.
 *
 * @since  1.0.0
 */
class CompanyIdRule extends FormRule implements DatabaseAwareInterface
{
    use DatabaseAwareTrait;

    /**
     * Method to test the validity of a Joomla User.
     *
     * @param   SimpleXMLElement  $element  The SimpleXMLElement object representing the `<field>` tag for the form field object.
     * @param   mixed              $value    The form field value to validate.
     * @param   ?string            $group    The field name group control value. This acts as an array container for the field.
     *                                       For example if the field has name="foo" and the group value is set to "bar" then the
     *                                       full field name would end up being "bar[foo]".
     * @param   ?Registry          $input    An optional Registry object with the entire data set to validate against the entire form.
     * @param   ?Form              $form     The form object for which the field is being tested.
     *
     * @return  boolean  True if the value is valid, false otherwise.
     *
     * @since   1.0.0
     *
     * @noinspection PhpMissingReturnTypeInspection
     */
    public function test(SimpleXMLElement $element, $value, $group = null, ?Registry $input = null, ?Form $form = null)
    {
        // Check if the field is required.
        $required = ((string) $element['required'] === 'true' || (string) $element['required'] === 'required');

        // If the value is empty, null or has the value 0 and the field is not required return true else return false
        if (($value === '' || $value === null || (string) $value === '0')) {
            return !$required;
        }

        // Get the database object and a new query object.
        $db    = $this->getDatabase();
        $query = $db->createQuery();

        // Build the query.
        $query->select('COUNT(*)')
            ->from($db->qn('#__wishboxbonussystem_companies'))
            ->where($db->qn('id') . ' = :companyId')
            ->bind(':companyId', $value, ParameterType::INTEGER);

        // Set and query the database.
        return (bool) $db->setQuery($query)->loadResult();
    }
}

И в модели нашей сущности (в моём случае OperationModel) подключаем префикс класса:

	public function getForm($data = [], $loadData = true)
	{
		FormHelper::addRulePrefix("\\Joomla\\Component\\WishboxBonusSystem\\Administrator\\Form\\Rule");

		return parent::getForm($data, $loadData);
	}
Теги:
+2
Комментарии0

Два факта об int в Python

Один забавный факт привел меня к открытию другого :)

Читал Fluent Python и наткнулся на пример кода, который меня заинтересовал (помимо миллиона других, книга – топ). В главе про конкурентность и работу GIL была константа NUMBERS с необычным значением:

NUMBERS = 5_000_111_000_222_021

Нижние подчеркивания

Если не встречали в работе или документации, то вряд ли знаете (как и я): в числах можно использовать _ для читаемости. Интерпретатор их игнорирует:

>>> x = 1_2
>>> y = 12
>>> x == y
True
>>> x is y
True

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

Кеш малых чисел

Примеры ниже разбирал на домашнем ноуте с Cpython 3.13.11 и 3.14.3.

Пока игрался, меня заинтересовал один прикол. Я попробовал тот же пример с большими числами:

>>> x = 100_500
>>> y = 100500
>>> x == y
True
>>> x is y
False # Но ведь в примере выше было True..

Почему переменные больше не ссылаются на один объект?

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

Ответ на вопрос: «где граница, до которой числа закешированы?» я решил не гуглить, проверил небольшим скриптом:

>>> x = 0
>>> y = 0
>>> for n in range(1000):
...     print(f'If {x=} and {y=}, x is y: {x is y}')
...     x += 1
...     y += 1

# Пропустим часть строк
If x=254 and y=254, x is y: True
If x=255 and y=255, x is y: True
If x=256 and y=256, x is y: True
If x=257 and y=257, x is y: False # Вот и граница
If x=258 and y=258, x is y: False 
...

Сначала я сделал эмпирически вывод, что закеширован диапазон 0 – 256. Но после самопроверки с гуглом узнал, что также в амортизированный диапазон входят числа от -5 до -1. Итого : от -5 до 256 включительно.

UPD 15.03.2026. Добрый дядя в комментах принес ссылку на pr в Cpython 3.15, где кеш малых чисел увеличен до 1024 :). Ух, заживем..

Для присвоения переменным чисел вне диапазона, интерпретатор начнет выделять уже раздельные области памяти и is станет возвращать False.

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

Теги:
+9
Комментарии15

Неудобные вопросы про бэкап PostgreSQL: открытый разбор на вебинаре

Вокруг бэкапа PostgreSQL легко создать иллюзию, что все уже решено. Достаточно добавить в текст WAL, PITR, пару слов про консистентность и назвать агент «умным». Проблема в том, что в проде такие формулировки мало что гарантируют.

Можно ли вообще считать решение PostgreSQL-aware, если оно не живет внутри логики самой СУБД? Где проходит граница между нативными механизмами PostgreSQL и внешней платформой? Что происходит, если не доехал WAL-сегмент, не завершился post-script или восстанавливать нужно не весь инстанс, а один объект?

Из таких вопросов и вырос отдельный вебинар про PostgreSQL в Акуре, в формате открытого инженерного разбора: что здесь должна делать сама СУБД, что имеет смысл выносить во внешний слой, где начинаются реальные эксплуатационные проблемы и какие ограничения в таком подходе нельзя замалчивать.

План такой:

  • отдельно пройтись по WAL, PITR и консистентности;

  • обсудить, где файловый агент уместен, а где уже нет;

  • разобрать сценарии с ошибками pre/post-скриптов;

  • поговорить про восстановление в безопасную локацию и ручной recovery;

  • отдельно затронуть вопрос масштаба: почему на двух базах хватает shell-скриптов, а на пятидесяти уже начинается совсем другая жизнь.

26 марта 2026, 11:00 (МСК) Регистрация по ссылке. Приносите в комментарии вопросы, которые особенно хочется поднять в эфире.

Теги:
+4
Комментарии0

ai_query() в StarRocks 4.1: вызываем LLM прямо из SQL. Разбор результатов тестов.

Зачем это нужно аналитику и как вписывается в архитектуру, я описал в своем Telegram-канале Selena (powered by StarRocks). Здесь — технические детали и результаты тестирования.

Архитектура StarRocks 4.x: два направления интеграции с языковыми моделями
Архитектура StarRocks 4.x: два направления интеграции с языковыми моделями

На схеме два потока данных между языковой моделью и базой данных:

Синий (вверху) — LLM → База через MCP (4.0). Пользователь задаёт вопрос на обычном языке. Агент сам формулирует SQL-запрос, отправляет его в StarRocks через MCP-протокол и возвращает ответ. Об этом я также подробно писал в нашем сообществе.

Зелёный (внизу) — База → LLM через ai_query() (4.1). Аналитик пишет SELECT с вызовом ai_query(). StarRocks на каждом сервере кластера отправляет запрос к языковой модели и возвращает её ответ как обычную текстовую колонку.

В версии 4.0 появилось первое направление, в 4.1 — второе. Полный цикл.

Что такое ai_query()

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

Обязательные параметры: model (название модели) и api_key (ключ доступа). Дополнительно можно указать адрес сервера модели, температуру, максимальную длину ответа и таймаут.

Функция работает с любым сервисом, совместимым с протоколом OpenAI: это и сам OpenAI, и локальные модели через Ollama, и DeepSeek, и vLLM.

Как тестировали:

Функция планируется к релизу в версии 4.1. Когда пришло время её проверить, привычный способ — развернуть готовый образ в Docker — не сработал. В образе обнаружился небольшой баг: функция была скомпилирована и лежала внутри сервера, но сервер о ней не знал. Исправление заняло одну строку в исходном коде. Но чтобы её применить, пришлось собирать BE из исходников.

Среда тестирования: виртуальная машина (8 CPU, 32 ГБ RAM), StarRocks 4.1.0-rc01 (собранный из исходников), языковая модель Ollama gemma3:1b (работает локально на процессоре). Тестовые данные — шесть отзывов о товарах.

Тест 1. Анализ тональности

Задача: определить, позитивный отзыв или негативный.

(SQL код по каждому тестированию я напишу в комментариях)

Вывод: четыре из шести точных.

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

Время: ~три секунды на шесть строк.

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

Тест 2. Суммаризация

Задача: сжать отзыв в одно предложение.
Вывод: адекватные резюме на русском языке. Длину ответа стоит контролировать параметром max_tokens.
Время: ~одна секунда на строку.

Тест 3. Извлечение характеристик

Задача: вытащить из текста ключевые свойства товара.
Вывод: характеристики извлекаются
Время: ~1 секунда на строку.

Тест 4. Классификация

Задача: определить категорию товара по тексту отзыва.
Вывод: категории определены верно. MacBook, монитор, наушники — «Электроника», мышь — «Периферия».
Время: ~0.5 секунды на строку.

Тест 5. Перевод

Задача: перевести отзыв с русского на английский.
Вывод: качественный перевод даже на модели в один миллиард параметров.
Время: ~1 секунда на строку.

Ограничения:

  1. Нельзя задать роль модели (нет системного промпта) — только сообщение от пользователя

  2. Нет повторных попыток при ошибке — если сервис модели вернул ошибку, это сразу ошибка SQL-запроса

  3. Кеш хранится на каждом сервере отдельно и теряется при перезапуске

Итого:

ai_query() — простая обёртка над протоколом языковых моделей с кешем и дедупликацией. Не революция, но именно такие простые интеграции оказываются самыми полезными.

Функция появится в StarRocks 4.1.

Теги:
+1
Комментарии1
1
23 ...