(java || kotlin) && devOps
342 subscribers
13 photos
2 videos
7 files
419 links
Полезное про Java и Kotlin - фреймворки, паттерны, тесты, тонкости JVM. Немного архитектуры. И DevOps, куда без него
Download Telegram
Снова минутка цитат на канале:
«Термин „структурное программирование“ был введён в 1969 году.
С тех пор термин „структурный“ применялся к любой деятельности в области ПО, включая структурный анализ, структурный дизайн и структурное валяние дурака».
Стив Макконнелл «Совершенный код».


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

Я уже писал про структурный дизайн - https://t.me/javaKotlinDevOps/400.
Напомню, там была речь о том, что не обязательно заводить кучу объектов и интерфейсов, если процесс простой и раскладывается на три части - подготовка данных, преобразования и запись.
Тогда его можно реализовать в виде сервиса, "чистой" модели и репозитория. К слову: ETL = Extract -> Transform -> Load - о том же.

Так вот - структурное программирование тоже раскладывает любой процесс на три части, только уже на уровне метода: последовательный код, условия и циклы.
Казалось бы - ну да, очевидно это так. Но есть нюанс - тут речь идет о проблеме множественных выходов из метода.
Структурное программирование появилось как ответ на широкое распространение оператора go to и призвано бороться с ним.
Опять вопрос - go to мы победили, тогда зачем эти "предания старины глубокой"?
Нет, не победили. Точнее победили, но не до конца.
Т.к. у нас есть множественный return и throw. А если копнуть глубже, и расширить проблему на любые прыжки по коду - break и continue.
Надо ли от них отказываться, учитывая что парадигма то вроде полезная?
На мой взгляд - нет.
Охранные выражения (guard pattern) - однозначно полезны.
throw для сигнализации об ошибке - тоже ок, они для этого создавались.
С рядом условий:
1) unchecked
2) не надо просто для передачи управления
3) должен быть определен слой\процесс перехвата исключений на уровне сервиса
break и continue - если без них код сложнее читать - тоже допустимы. Прыжки то небольшие, в рамках цикла. Хотя я ими пользуюсь редко, кейсов мало.

Т.е. снова приходим к искусству компромиссов.
Всякий раз, перед написанием такого оператора стоит подумать - нет ли других вариантов? Не усложняется ли он чтение и понимание кода?
Суть структурного программирования - код каждой операции должен просто читаться. Нет "спагетти-коду"!

#book_review #java #code #structure_programming #structure_xxx #principles
Медленная загрузка сервиса - значит Java?)

Есть такая проблема у Java, в частности при использовании Spring Framework. А точнее когда поверх Spring-а накручиваются свои компоненты.
Загружается куча классов, много кода инициализации = много времени. Как с этим борются - см. по тэгу #java_start_boost
Но только ли это проблема Java?

Запускал небольшую AI модельку на Python. Один скрипт. Скрипт стартует более 20 секунд.
Ну, думаю, модель так долго грузится. Померил - нет, модель порядка 3 секунд. Она уже в виде кэша скачана на локальный компьютер, да и не LLM это.
Антивирус? Интерпретатор Python?

Нет, всего лишь библиотека sentence-transformers.
На самом деле конечно не всего лишь. Она под капотом грузит движок для инференса - PyTorch. Плюс другие зависимости.
Но за 20 секунд старта ответственна именно строчка c импортом:

from sentence_transformers import SentenceTransformer


Тут еще видится "выстрелил" высокий уровень абстракции, принятый в Python.
Пишешь пару строк - у тебя все работает. Удобно? Да. Но есть нюансы)

Вывод - высокий уровень может превратится в айсберг. Ты видишь вершину, а под ней еще...

#python #java
Сколько багов в час делает разработчик?

И снова цитата из Совершенного кода:

Исследования, проведенные в Институте
разработки П0 (Software Engincering Institute), показали, что разработчики допускают в среднем
от 1 до З дефектов в час при проектировании и от 5 до 8 дефектов в час при кодировании
(Humphrey, 1997). Ясно, что устранение дефектов — обязательное условие эффективного конструирования.


Хоть ты код не пиши!)))

А если серьёзно - рулит код-ревью и тесты. Именно в таком порядке.

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

Неформальный обзор дизайна (тех. проекта) 35% 
Формальные инспекции дизайна (тех. проекта) 55% 
Моделирование и прототипирование 65% 
Неформальный обзор кода (code review) 25% 
Самостоятельная проверка код 35%
Формальные инспекции кода 60% 
Юнит-тестирование 30% 
Регрессионное тестирование 25% 
Тестирование новой функции (компонента) 30% 
Интеграционное тестирование 35% 
Системное тестирование 40% 
Бета-тестирование (<10 тестеров) 35% 
Бета-тестирование (>1000 тестеров) 75%

Что бросается в глаза:
1) очень высокая эффективность ревью кода. Что понятно, т.к. получаем shift left и, главное, при код-ревью мы не просто находим баг, но ещё и его причину, в отличие от тестирования. Т.е. не тратим время на отладку.
2) формальные ревью сильно эффективнее неформальных. Немного странно на первый взгляд. Но объяснение есть - формальное = выделено время + есть чек-лист + нужен отчёт. Т.е. его сложно провести "формально" (да, велик и могуч русский язык))) Т.е. правильно было бы назвать его глубокое код-ревью
3) Хорошая эффективность у самостоятельного ревью, а его провести проще всего. Например, при подготовке pull request. И снова понятно почему - своей код ты уже знаешь, главное чтобы между кодированием и ревью прошло время, чтобы сменился фокус.
4) низкая эффективность unit тестов, сравнимая с регрессом и тестированием нового функционала. Это странно. Но если подумать - TDD тогда ещё только появилось, да и сейчас его проникновение оставляет желать лучшего. А без него UT - это в основном регресс. И немного тестирование нового функционала, но методом "прозрачного" ящика.
5) высокие цифры у прототипирования и бета-тестах на большом числе людей. Пр сути они связаны - это проверка гипотезы и реализации. Провести сложно, но эффект похоже есть.

Ну и банальная вещь - ни один метод сам по себе не даёт 100%. Только в комплекте.

#code_review #quality_assurance
Хорошо сказано!
Письмо от CTO, который не взял тебя в команду

Привет! Хочу объяснить, почему мы не сделали тебе оффер.

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

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

Сейчас без контейнеров почти никуда, особенно если сервис живой и его часто релизят. И docker — это не “инструмент devops”. Это среда выполнения. Если ты понимаешь docker, ты контролируешь архитектуру, ресурсы сервиса, изоляцию процессов, сетевое поведение, стратегию масштабирования. Sidecar, Service Mesh, сетевые политики, безопасность, observability — всё это превращается в набор страшных слов, которые вроде слышал, но не трогал руками.

Да, ты пишешь код, но вся система шире чем код. Мы используем кубер, масштабируемся автоматически, деплоим десятки раз в день. И нам нужен человек, который понимает, что происходит на уровне контейнера. Не «где-то там в инфраструктуре». Рынок меняется. Стартапам нужны T-shape инженеры, нельзя быть “только про код” — приходится лезть во всё. Не потому что мы экономим на devops. Знание инфры вообще и контейнеров в частности — снижает операционные риски до того, как они станут инцидентами. Когда ты понимаешь, как это работает под капотом, сюрпризов становится сильно меньше. Меньше неопределённости — выше скорость команды. А скорость для стартапа — это не "удобство", а шанс выжить.

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

Мы бы с радостью поработали с тобой. Когда будешь понимать не только код, но и прод. Хочешь расти — закрой этот пробел как можно быстрее. ASAP. Не призываю тебя получать черный пояс по YAML-driven development, но базу знать нужно. Сейчас это база для синьоров, а скоро станет обязательным минимумом для мидлов. А docker — не сложнее, чем любой фреймворк, который ты учишь за несколько недель.

--
Если серьезно, мы из этого сделали отдельный курс — потому что с этим реально у многих пробел. Приходи на "Docker и Kubernetes: основы разработки под облачную инфраструктуру". Старт через неделю, курс ведет Коля Ихалайнен, и он, кстати, и меня несколько лет назад докеру обучил (не шутка).
🔥2👍1
Алексей Рыбак: системный дизайн, хайлоад, разработка с агентами
Письмо от CTO, который не взял тебя в команду Привет! Хочу объяснить, почему мы не сделали тебе оффер. Сразу скажу, с кодом у тебя всё ок. Вопрос вообще не в этом. У меня ощущение, что ты мало сталкивался с тем, что происходит после коммита/мержа. Помнишь…
Что интересно - хотя в конце поста есть реклама, я ее заметил только после пересылки в канал. Т.е. пост зацепил.
Что ещё интересно - судя по реакциям и комментариям в канале автора - проблема актуальна.

Отмазки следующие:
1) в кровавом enterprise меня не пускают к инфре. Видимо не очень то и хотелось)
2) AI придёт и все сгенерит. Видимо вместе с кодом)
3) функции, faas, докера там нет. На самом деле по капотом есть, но главное - я в России работающих faas не видел.
4) ну и очевидное - это все реклама, не нужен мне ваш Docker. Пусть даже и реклама, но написано хорошо, а Docker и k8s - это база!)

#комментарии
Чем заменить сложный if?

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

Но ближе к коду)
Вот есть у нас, предположим, такое условие:

int getDaysInMonth(int month) {
if (month == 1) return 31;
else if (month == 2) return 28;
else if (month == 3) return 31;
// ... ещё 9 веток
}


Сложно, т.к. много кода. Но вот так намного проще:

private static final int[] DAYS_IN_MONTH = {
31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31
};

int getDaysInMonth(int month) {
return DAYS_IN_MONTH[month];
}


Собственно таблица - это коллекция, из которой по ключу получаем нужное значение в одну строчку без if.
Хорошо. Но не всегда ключ можно прямо замапить на значение.
Возможно, ключ однозначно преобразуется в требуемый. Тогда получаем табличный доступ с преобразованием:

public int resolveMonth(LocalDate date) {
return date.getMonthValue();
}

int getDaysInMonth(LocalDate date) {
return DAYS_IN_MONTH[resolveMonth(date)];
}


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

@Getter
@RequiredArgsConstructor
enum Zone {
NIGHT ("Ночная", 2.15),
SEMI_PEAK ("Полупиковая", 4.30),
PEAK ("Пиковая", 6.45);

private final String label;
private final double ratePerKwh;
}

private static final int[] HOUR_THRESHOLDS = { 6, 9, 16, 20, 22, 23 };

private static final Zone[] TARIFF_TABLE = { Zone.NIGHT, Zone.PEAK, Zone.SEMI_PEAK, Zone.PEAK, Zone.SEMI_PEAK, Zone.NIGHT };

private static Zone lookupZone(int hour) {
for (int i = 0; i < HOUR_THRESHOLDS.length; i++) {
if (hour <= HOUR_THRESHOLDS[i]) {
return TARIFF_TABLE[i];
}
}
}

public static double getZonePrice(int hour) {
var zone = lookupZone(hour);
return zone.getRatePerKwh();
}



Ну и возвращать можно не только значение, но и лямбду:

private static final Map<String, Consumer<String>> HANDLERS = Map.of(
"ERROR", msg -> System.err.println("[ERROR] " + msg),
"WARNING", msg -> System.out.println("[WARN] " + msg),
"INFO", msg -> System.out.println("[INFO] " + msg),
"DEBUG", msg -> { /* no-op in prod */ }
);

public static void dispatch(String level, String message) {
HANDLERS.getOrDefault(level, m -> System.out.println("[UNKNOWN] " + m))
.accept(message);
}


Важный плюс такого решения - "таблица" легко выносится в файл при необходимости настройки.
Или любой внешний источник.
Таблица может быть мапой, а не только массивом или списком.

P.S. Да, это снова Совершенный код)

#code_review #code
👍2
Сложно не заметить, что последние пару месяцев у меня основная тема блока - "жемчужины" из книги Совершенный код.

Сегодня еще один пост, пост-"мысль вслух".

Самое удивительное в этой книжке - это два факта:
1) на английском она издана в 2004 году
2) она актуальна.

Я пытаюсь вспомнить, что же там устарело и вспоминаю лишь следующее:
а) в Java появились Enum (на тот момент еще нет)
б) Visual Basic умер (там есть примеры в числе прочего на VB)
в) IDE стали сильно лучше, считать открывающие и закрывающие скобки самому уже не надо
г) кажется все языки получили свой аналог xUnit, "костылить" свой подобный фреймворк тоже не надо
д) у нас есть git и история изменений в IDEA, "прихранивать" копию исходников перед изменением не нужно
е) на способы проверки повреждения данных в памяти из-за кривых указателей смотришь с ужасом, но все такие главы подписаны - для C\C++ и при желании их можно пропускать. Да и скорее всего большая часть этой информации актуальна до сих пор.

И пожалуй это все! За 20+ лет.
Даже обсуждение go to и глобальных переменных лишь на первый взгляд кажется не актуальным для Java...

Вот как надо книжки писать!
Вспоминается: "Новые песни сочиняет тот, у кого старые плохие". И это тот редкий случай, когда фраза справедлива)

#book_review #java
💯5
Есть мнение, что разработка - это молодая отрасль. Этим часто объясняется ее несовершенство. Нет чётких законов, парадигмы меняются, всегда приходится идти на компромиссы. Как пример - те же принципы чистого кода нельзя понимать буквально, за что книгу часто критикуют.

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

Закон Конвея, упомянутый ранее https://t.me/javaKotlinDevOps/301 - структура кода повторяет структуру организации - 1967 год. Практически 60 лет назад.

Спор о вреде go to (а это значит, что он уже активно использовался) начал Дейкстра в письме Go To Statement Considered Harmful практически тогда же - 1968 году. 58 лет назад. Дискуссии, к слову, тогда велись через письма в журналы. В рамках дискуссии о go to понятие спагетти-код стало общепринятым примерно в 1977 году. 49 лет назад.

В 1970 году сотрудник IBM Эдгар Кодд опубликовал статью «A Relational Model of Data for Large Shared Data Banks», которая стала фундаментом для всех реляционных баз данных. В 1979 году появилась первая коммерческая реляционная СУБД Oracle. 56 и 47 лет соответственно.

Мифический человеко-месяц был написан в 1975 году. Знаменитый закон Брукса: добавление разработчиков ближе к концу проекта лишь увеличивает его сроки. А на русский книга была переведена в 1979 году, соответственно, в СССР. Это 51 и 47 лет назад. Я ещё не родился)

Совершенный код (да, опять), первое издание - 1993 год. 33 года назад.

Java 1.0 (еще без collections, не говоря уже и стримах, generic, enum, JDBC и многого другого) появилась в 1996 - 30 лет в этом году.

В 1999 был сформирован DRY («Don't Repeat Yourself» — «Не повторяй себя») Эндрю Хантом и Дэвидом Томасом в книге The Pragmatic Programmer. 27 лет назад. Принцип KISS сильно моложе, но его снимем с дистанции - он появится в ВВС США.

Принципы SOLID были сформулированы на год позже Робертом Мартином в статье Design Principles and Design Patterns. 26 лет назад.

Тогда же вышла книжка Кента Бека про XP (экстремальное программирование, включая TDD). И в 2001 Agile Manifest, который с ним связан.

В 2001 году появилась IDEA и рефакторинг стал доступен всем. Ну почти всем , Community появилась на 8 лет позже и началось ее победное шествие. 25 лет назад.

И лишь Docker, Kafka, DevOps, Kotlin можно считать относительно новыми - 15-20 лет назад. K8s так вообще чуть больше 10 лет.

Т.е. отрасль все же достигла зрелости.

И тут пришёл AI и хочет ее отменить)
Но это уже другая история)

#it_history
👍41
Чем похожи LLM и blockchain?

И там, и там для генерации следующего элемента используют данные предыдущих. И LLM также не может изменить уже сгенерированные токены, даже если в какой-то момент поняла, что они не верны. Ну и ещё потому что у нас как правило стриминг и пользователь уже их увидел)
Разница конечно есть - в blockchain вся цепочка живёт вечно, а LLM ограничена длиной контекстного окна и возможностью чата/агента хранить историю

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


#ai
В продолжение предыдущего поста - почему модели галлюцинируют?

Попробую собрать все возможные причины:

1) как уже говорил - один раз сделав неверный вывод модель не может его исправить, он ложится в ее контекст, на основании которого выдаются все последующие ответы.
Да, не все токены из контекста выбираются при генерации ответа, только наиболее близкие (скалярное произведение векторов). И когда в диалоге появляются новые токены, более релевантные - например, "твой ответ неверный, обрати внимание на ..." - они "забивают" старые.
Иначе модель будет строить всю цепочку рассуждений опираясь на неверный вывод.

2) неверные данные от пользователя. Модель верит пользователю. Почему?
Модель обучалась на каких-то открытых документах. Форумы, новости, GitHub,... Сколько там неверной информации?
Она конечно есть, но в большинство документов содержит более менее актуальную информацию.
Модель, соответственно, ожидает, что пользователь тоже дает ей верную информацию.
Особенно в тех областях, по которым нет явно опровергающих это фактов.
Земля плоская - опровержений много. Я уже установил последнюю версию драйверов - как это проверить?) Есть OpenClaws, но я бы не рискнул ставить его на свой компьютер)

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

4) И самое интересное.
Если точных данных нет, а это как раз самые интересные и сложные кейсы - модель их может угадать.
Может угадать, может не угадать.
Но на обучении - Supervised fine-tuning (SFT) и Reinforcement Learning (RL) - модель оценивают по проценту верных ответов.
Все бенчмарки для людей о том же - процент правильных ответов.
Т.е. для модели лучшая стратегия - что-то придумать, чем честно сказать "Я не знаю".
Об этом даже авторы OpenAI пишут и предлагают менять систему оценок: https://openai.com/ru-RU/index/why-language-models-hallucinate

В итоге имеем то, что имеем)

#ai
Все AI dev агенты делятся на 2 категории по тому, как хранят свои правила.

Один файл:
Claude Code — CLAUDE.md
Gemini CLI — GEMINI.md
OpenAI Codex CLI — AGENTS.md
JetBrains Junie — .junie/guidelines.md

Папка с несколькими файлами:
Cursor — .cursor/rules/*.mdc
Windsurf — .windsurf/rules/*.md
Cline — .clinerules/*.md
Roocode — .roo/rules/*.md
GitHub Copilot — .github/instructions/*.instructions.md
JetBrains AI Assistant — .aiassistant/rules/*.md
Continue — .continue/rules/*.md
GigaCode - .gitverse/pr_rules/*.md (для версии на GitVerse)

Cline тут выделяется тем, что вообще говоря позволяет оба варианта - файл .clinerules или одноименный каталог.

Особенность Claude - кастомные команды, хранятся в кастомные команды в ~/.claude/commands/<name>.md. В принципе можно использовать и как аналог rule при явном указании в чате. Но без автозагрузки.

И еще сильнее выделяется OpenCode.
Он вначале пытается быть совместимым с OpenAI (AGENTS.md), если не нашел - с Claude Code (CLAUDE.md). А кроме того позволяет через customInstructions в opencode.json подключать произвольные файлы, включая загрузку по http, что является уникальной фичей.

Какие плюсы\минусы у этих подходов?

Один файл - простота.
Особенно учитывая тот факт, что практически ни один агент не дает менять имя файла\папки - т.е. имеем дело с convention over configuration во всей красе.
Пару слов про смену имени: единственное исключение - Gemini CLI, позволяет задать contextFileName в settings.json.
Еще в теории с этим подходом больше шансов получить непротиворечивые правила, но с ростом размера файла это преимущество нивелируется.

Каталог - а мне этот вариант кажется более удобным - имеет следующие плюсы:
1) если в репо лежит разнородный код, который пишут разные люди: фронт, бэк, devops, автотесты то удобнее, если у каждого будет свой файл с правилами. Да, AI способствует T-shape, но это пока что процесс, а не свершившийся факт.
2) несколько файлов точно легче читать и править человеку. А со слабыми моделями - возможно и LLM.
3) в теории при наличии нескольких файлов LLM исходя из текущей задачи сама могла бы выбрать только нужные, тем самым уменьшив расход токенов и контекстного окна и ускорив работу.

Обходной вариант для single rule агентов - можно указать в промте или rules: "прочитай инструкции из каталога xxx", - и т.об решить проблему. Но это менее надежно.

И главный вопрос - как избежать vendor lock и не нарушать DRY?
Делаем базовую папку с правилами, дале в Windows - создаем ссылку с помощью junction, в Linux - symlink.
И лучше добавить в .gitignore для надежности, чтобы случайно не задублировалось.
Для single rule агентов можно навайбкодить утилиту по склеиванию всех файлов в один, или использовать готовую cursor2claude.

Но вообще говоря эта область прям напрашивается на стандартизацию - либо через convention, либо через использование отдельного компонента.

#ai #ai_agents
Agile мир победил, микросервисы оказались сильней)

Вот данные из моей любимой книжки по разработке.

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

Размер команды  | Доля
----------------|------
1–3 человека | 25%
4–10 человек | 30%
11–25 человек | 20%
26–50 человек | 15%
>50 человек | 10%

Источники: Beck and Perkins (1983), Highsmith (2002), Boehm and Turner (2003)


Медиану можно оценить ~9, среднее ~19, но среднее мало информативно из-за сильной правой асимметрии (маленьких команд сильно больше, чем больших).

Я бы из него сделал вывод, что микросервисы были всегда, просто раньше назывались по-другому — например, SOA (Service Oriented Architecture).
Но конечно ситуация меняется (хотел написать «не могла не измениться», но вспомнил свой же пост про двойное отрицание))).

А вот более современные данные, не такие подробные.

В исследовании Rodriguez et al. (JSS, 2012) на базе 951 проекта — 75% имели команду менее 10 человек. Медиана = 5, среднее ≈ 7.9.


Если сравнить 2003 и 2012 годы — медиана уменьшилась почти в 2 раза: 9 → 5.

Ещё есть данные опроса пользователей JetBrains (2024) https://www.jetbrains.com/lp/devecosystem-2024/

Размер команды  | Доля
----------------|------
1 человек | 8%
2–7 человек | 49%
8–12 человек | 22%
13–20 человек | 10%
21–40 человек | 6%
>40 человек | 5%


Медиана ≈ 6 человек, среднее ≈ 11. Примерно то же, что в 2012, только как ни странно числа стали чуть больше). Зато видно, что больших команд стало в 2+ раза меньше.

По моему опыту (кровавый enterprise) медианный размер команды на данный момент выше, ближе к 9. Но то, что 80% команд укладываются в 12 человек — сходится.
В 2003 году было ~60%.
Т.е. мы пока одной ногой шагнули в новую эру) enterprise требует жертв. Но тренд понятен.

О причинах — часть уже назвал: Agile и микросервисы. Ещё — улучшившийся инструментарий: IDE и фреймворки/библиотеки под любую потребность. А сейчас ещё и AI.

Так что думаю, что к 6 людям в команде мы все скоро придём. Будет ли меньше? В теории да: ВП, аналитик, 1–2 разработчика, тестировщик.

P.S. Т.к. в таблицах процент команд, а не процент людей - в командах более 12 человек все еще работает более 50% разработчиков. Было - более 80%. Интересно кто это?

P.P.S. Станет ли медианным значением 1 — ВП, который с помощью AI пилит и тестирует сервисы сам? ...Где-то да, уже сейчас. Но массово — может помешать Skynet. Или поспособствовать — сидишь себе в матрице, вайб-кодишь с AI микросервисы.

#book_review #agile #ai #microservices
👍1
Иногда кажется, что если попросить какую-то LLM модель проверить информацию, указав при этом, что получил ее от другой модели - она это делает с какой-то особой тщательностью)))
Скорее всего кажется - как проекция взаимоотношений создателей на их модели. Я про OpenAI, Claude, Grok и Gemini.

Но в любом случае это отличный быстрый способ проверки информации из LLM.

И тут хорош Perplexity, т.к. там все перечисленные модели есть.

#ai
Как AI интегрируется в существующие программные продукты?

Пример.
Есть такое понятие как API шлюз - прокси, позволяющий согласовать API из разных источников, навесить туда аутентификацию и авторизацию, rate limiting, мониторинг и некоторые другие функции.
Один из самых известных open source шлюзов - Kong Gateway.

Где тут AI?
А вот https://developer.konghq.com/plugins/?category=ai

Что можно навесить на шлюз:

1) AI Proxy - API разных LLM моделей отличается, поэтому актуальной является конвертация API. Если быть точным: на вход OpenAI API, на выходе - другие модели.
Вот список https://developer.konghq.com/plugins/?category=ai
Почему на входе OpenAI формат думаю понятно, так работает большинство AI Proxy.

2) AI Prompt Decorator - добавляет текст в начало и конец промта. Область применения видится для случаев, когда есть некий AI чат-клиент и недостаточно функционала для AI агента (сервера).
Тогда данный компонент может сформировать системный промт или обогатить промт историей.
Еще важный момент - текст в начале и в конце промта (который мы добавляем таким образом) имеет больший вес для модели.

3) AI Request Transformer\AI Response Transformer - обогащение\преобразование промта с использованием ИИшки.
Например, добавить страну к каждому городу в промте.
Тут конечно важен вопрос скорости, прокси не должен тормозить запрос

4) AI Prompt Guard - ограничение доступных запросов по regexp. Не 100% гарантия, но зато быстро.

5) AI Prompt Template - создаем набор типовых запросов к модели, от клиента просим только заполнить {{параметры}}

Есть еще опции, требующие лицензии, среди них я бы отметил:

1) AI LLM as Judge - валидацию ответа LLM другой моделью из коробки

2) AI MCP Proxy - преобразование протокола MCP в HTTP или объединение нескольких MCP серверов в один + возможность навесить на MCP запрос все фичи API шлюза - аутентификацию, rate limiting, мониторинг. Видится полезным для быстрого подключения существующих сервисов компании как тулов для AI агента.

3) AI Prompt Compressor - сжатие пользовательского промта. Цель - снижение стоимости. Интересно, но обязательно надо тестировать качество получаемых ответов.

4) AI Semantic Cache - кэшируем в векторной БД ответы LLM опять же для снижения стоимости

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

#ai
Магические 80% покрытия кода тестами.

Часто возникает вопрос - почему 80?
Краткий ответ - почему бы и нет)

А если серьезно: оценка может быть числовой (процент покрытия) или бинарной (достаточно или нет).
Бинарная плоха тем, что не документирована и зависит от мнения эксперта. Это может привести к проблемам на код-ревью.
А тут умные люди из SonarQube придумали метрику покрытия https://docs.sonarsource.com/sonarqube-server/user-guide/code-metrics/metrics-definition#coverage
и дали её рекомендуемое значение. И это не 100%, т.е. запас есть.
Считаю, что от этой рекомендации стоит отталкиваться.
К тому же JaCoCo, обычно используемый для расчета покрытия, позволяет гибко настраивать сам процесс.

Следующий вопрос, который может возникнуть: "Почему ради достижения 80% я должен писать тесты на элементарный код?" Я про getter, setter и все, все, все https://t.me/javaKotlinDevOps/34
Ответ: "Нет, не нужны эти тесты".
Лишний код можно исключить из покрытия:
1) generated код исключается по умолчанию начиная с JaCoCo 0.8.2
2) generated код Lombok - через lombok.addLombokGeneratedAnnotation = true (жаль, что только для Lombok это можно сделать, но есть еще альтернатива - см. следующий абзац.)
3) тривиальный код - явно исключая классы или пакеты по маске через настройки JaCoCo. Если какой-то код не получается исключить таким образом - это повод задуматься о рефакторинге.

Еще вопрос: "Зачем писать модульные (unit) тесты ради покрытия на интеграционный код: сервисные классы или классы контроллеров, которые представляют линейную цепочку вызовов методов?"
Ответ: "Не обязательно писать unit тесты".
Для простых микросервисов является нормальной перевернутая пирамида тестирования - когда интеграционных тестов разработки больше, чем модульных.
Простой код можно покрыть интеграционными тестами, включив их в общее покрытие. JaCoCo позволяет это настроить. Единственная проблема может быть, если тесты находятся в одном модуле, а код для покрытия - в другом, но и она решается.

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

Ну и если ничего не помогло - тогда уже стоит задуматься о новом Quality Gate в SonarQube с меньшим процентом покрытия.
Никакой магии нет - https://t.me/javaKotlinDevOps/403.
Но числовой показатель (80% по умолчанию), задокументированные исключения и цель покрыть максимум кода, в котором возможны ошибки, быть должны.

P.S. Еще AI можно попросить тесты написать, у него это неплохо получается)

#unittests #integration_tests #java
Что такое Agile для разработчика?

Возьмем SCRUM, как наиболее типичный его вариант.

IMHO конечно.

Это не церемонии - Daily, планирование, ретро, демо.
Не наличие Scrum мастера в команде.
Это красивый burn-down chart и velocity.
Не poker planing и оценка в story point.
Не работа с досками.
И даже не работающий инкремент каждую неделю.

База - это две вещи:

1) навык декомпозиции задач.
Который приводит к небольшим инкрементам.
А конечные цели:
- минимум зависимостей от других
- маленькие PR = быстрое ревью и вливание
- инкрементная интеграция кода = возможность вливания в целевую ветку и отправку на тестирование без ожидания других доработок.

2) точность оценки задач.
Не важно в чем - в story point, человеко-днях или в конкретных датах.
Оценка, кстати, зависит от декомпозиции, т.к. мелкие задачи можно оценить точнее, а потом оценки просуммировать.
Конечная цель - совпадение прогнозируемых сроков с реальными.
В данном случае речь про сроки этапа разработки, за соблюдение сроков интеграции со смежниками отвечает PO, DL и команда в целом.

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

И традиционные ответы на не заданные вопросы)
Вопрос: "А разве это не навыки сеньоров?"
Ответ: когда-то возможно да, но с учетом последних тенденций (не могу не вставить AI в пост) кажется всем надо повышать уровень "сеньорности".

#agile
👍3
Почему победил компактный стиль форматирования кода?

Я про сталь Oracle\Sun и очень похожий на него стиль Google:

public class OrderService {

private static final int MAX_RETRIES = 3;

public String processOrder(Order order, boolean isPriority) {
if (order == null) {
...

vs

public class OrderService
{
private static final int MAX_RETRIES = 3;

public String processOrder(Order order, boolean isPriority)
{
if (order == null)
{
...


Базовый ответ - потому что он компактнее)

Но я бы накинул еще один аргумент. Рассуждение:

- стиль форматирования нужен человеку, и не нужен машине
- человеку важно понимать, где начало и конец логического блока кода или управляющей конструкции
- "железобетонный" способ это понять - четкие маркеры, хороший пример: begin-end в Delphi, If-End If в VB
- хороший, но не идеальный, т.к. слишком много символов - 8 вместо минимально возможных 2
- скобки на той же строке, что и управляющая конструкция, у Google\Sun - это по сути компактная эмуляция begin-end. Эмуляция потому что они не обязательны. Скобки как бы становятся частью управляющей конструкции - if, for, while, switch, try.
- а вот альтернативный вариант - это что-то странное, т.к. скобки по отступам находятся на уровне управляющей конструкции, но зачем тогда перенос на новую строку?

Тогда логичнее вот так:

public class OrderService
{
private static final int MAX_RETRIES = 3;

public String processOrder(Order order, boolean isPriority)
{
if (order == null)
{
...

Но это еще страннее смотрится.

Ну и напоследок каких целей должно достичь хорошее форматирование кода:
1) разделение логических блоков кода: пробелы, отступы, скобки
2) единообразие
3) улучшение читаемости
4) облегчение правок - возможность быстро понять, какой блок кода нужно перенести или удалить целиком
5) компактность. Причем как раз компактность с увеличением размера монитора становится менее
важна

P.S. Спонсор выпуска - Совершенный код, Стив Макконнелл)

#java #formating
👍1
Почему LLM может писать хороший код, но плохо генерит JSON?
Может и плохой код писать, к слову, зависит от модели и задачи)

Я уже писал о том, что генерация JSON - не конёк LLM https://t.me/javaKotlinDevOps/484
Сейчас попробую расписать почему.
Возьмем ситуацию до появления паттерна structured output. LLM отвечает текстом, если нам нужно вызвать тулу и передать туда определенный набор параметров - можно описать этот набор JSON схемой.
JSON схема как валидатор объекта параметров.
Получался примерно такой запрос к модели на формирование вызова тулы:
{
"model": "gpt-4o-mini",
"messages": [
{
"role": "user",
"content": "Создай заказ: 2 товара по цене 10"
}
],
"tools": [
{
"type": "function",
"function": {
"name": "create_order",
"description": "Создание заказа",
"parameters": {
"type": "object",
"properties": {
"items": {
"type": "array",
"items": {
"type": "object",
"properties": {
"price": { "type": "number" },
"quantity": { "type": "integer" }
},
"required": ["price", "quantity"]
}
}
},
"required": ["items"]
}
}
}
]
}


Здесь две проблемы.
1) модель конечно знает что такое JSON, но она генерирует токены, а не текст по формату.
2) у агента\чата есть задача уменьшить контекст, поэтому даже если ему дать полную схему с minimum, minimum, pattern, minItems - он ее может порезать (и режет) для оптимизации.
Поэтому модель могла вернуть и некорректный по структуре JSON, и проигнорировать ряд атрибутов схемы. Первое реже, второе - всегда.

Когда появился structured json - модель же гарантирует корректность возвращаемого JSON (JSON = возвращаемый объект) проблема казалось бы должна уйти в прошлое?
В модель летит такой запрос:

{
"model": "gpt-4.1",
"messages": [
{
"role": "user",
"content": "Создай заказ: 2 товара по цене 10"
}
],
"response_format": {
"type": "json_schema",
"json_schema": {
"name": "order_response",
"schema": {
"type": "object",
"properties": {
"items": {
"type": "array",
"items": {
"type": "object",
"properties": {
"price": { "type": "number" },
"quantity": { "type": "integer" }
},
"required": ["price", "quantity"],
"additionalProperties": false
}
},
"total": {
"type": "number",
"description": "Общая стоимость"
}
},
"required": ["items", "total"],
"additionalProperties": false
},
"strict": true
}
}
}


Тут даже "additionalProperties": false есть. И required само собой. И это все даже работает)
Но в целом гарантии точному соответствию схеме нет.

Как в большинстве моделей реализуется structured output?
Схема превращается в набор т.наз. грамматик.
Например
  "type": "array",
"items": { "type": "integer" }

превратится в что-то такое:
  [ INT (, INT)* ]

А далее по этим грамматикам строится state machine.
И при генерации очередного токена во внимание принимается не только его вероятность (как при обычном выводе), но и идет проверка на разрешенное состояние по этой state machine.
И если не разрешено - выбираем следующий.
Процесс называется constrained decoding, вот пример библиотеки, его реализующей: https://xgrammar.mlc.ai/

И я бы из ее описания обратил внимание на фразу:

aiming at bring flexible zero-overhead structure generation everywhere
zero-overhead. Т.е. при генерации state machine под схему из схемы снова выкидывается все лишнее, для скорости. А скорость ответа для LLM критична.
Т.е. constrained decoding работает не так строго, как обычный валидатор JSON Schema в Java или Python.
И minimum, maximum, pattern, format снова не работают.
Зато быстро. Но все равно чуть медленнее, чем без structured output, т.к. и дополнительная валидация появляется, и перебор вариантов.

В теории можно было бы взять open-source движок инференса и прикрутить туда нормальный JSON валидатор. Но подозреваю LLM станет отвечать очень медленно.

Как с этим бороться?
Все уже придумано, паттерн Retry.

MAX_RETRIES = 3

def generate_order(user_input: str):
prompt = f"Сформируй заказ в JSON: {user_input}"

last_error = None

for attempt in range(MAX_RETRIES):
raw = call_llm(prompt)

try:
order = Order.model_validate_json(raw)
return order

except ValidationError as e:
last_error = str(e)

# repair prompt
prompt = f"""
Ты вернул невалидный JSON.

Ошибка:
{last_error}

Твой предыдущий ответ:
{raw}

Исправь JSON строго по схеме.
Верни только JSON, без комментариев.
"""

raise Exception(f"Не удалось получить валидный ответ: {last_error}")


Да, и structured output - это все равно шаг вперед. Модель не генерирует как бы json, есть валидатор, проверяющий ну скажем 80-90% схемы.

#ai #llm
Кто же изобрел ноутбуки (Jupiter notebook)?

Ранее уже писал, что это питонисты, а точнее ML инженеры питонисты.

Выяснилось, что это не совсем так.
Дональд Кнут Грамотное программирование, 2001. Тот самый, что написал 3 тома Искусства программирования.
Собственно концепция грамотного программирования - это писать код для людей, а не для машин. Т.е. в формате рассказа, где главный - текст.

Т.е не
// Используем метод сортировки пузырьком, суть которого...
// Декларация метода:
void boubleSort(int[] array) {
...


а скорее
Используем метод сортировки пузырьком, суть которого...
Декларация метода:

@sort_method =
void (int[] array) {
...
@

А код - это переменная, которую можно переиспользовать.

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

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


Плюс понятен - читаемость. Минус - сложнее (дольше) писать. Мы же вообще уходим от документации к самодокументирующемуся кода. Но для сложного кода кажется смысл в нем есть.

#python #notebooks