Два подхода к написанию функций (методов).
Первый - у метода должна быть одна точка выхода (в общем случае - как можно меньше точек выхода). А точка входа по умолчанию одна. Потому что это улучшает читаемость.
Второй - если у метода есть предусловия, то их нужно проверять с помощью охранных выражений в начале метода. Т.к. это уменьшает вложенность и опять же улучшает читаемость)
Т.е
vs
Второй вариант может быть длиннее первого, если всегда "как завещано" использовать фигурные скобки. Но в данном случае видится, что можно их убрать для улучшения читаемости.
И тогда мы получаем более понятный из-за линейной структуры.
В Совершенном коде автор тоже за второй вариант. Присоединяюсь и считаю, что второй вариант - это база.
Возможно есть кейсы, когда он не применим, но я пока придумать не могу.
Даже на 2 условиях. Даже если недостаточно простого return.
#book_review #code
Первый - у метода должна быть одна точка выхода (в общем случае - как можно меньше точек выхода). А точка входа по умолчанию одна. Потому что это улучшает читаемость.
Второй - если у метода есть предусловия, то их нужно проверять с помощью охранных выражений в начале метода. Т.к. это уменьшает вложенность и опять же улучшает читаемость)
Т.е
public void processUser(User user) {
if (user != null) {
if (user.getAge() >= 18) {
if (validateEmail(user.getEmail()) != null && user.getEmail().contains("@")) {
if (user.isVerified()) {
performUserRegistration(user);
}
}
}
}
}vs
public void processUser(User user) {
if (user == null) return;
if (user.getAge() < 18) return;
if (user.getEmail() == null || !user.getEmail().contains("@")) return;
if (!user.isVerified()) return;
performUserRegistration(user);
}Второй вариант может быть длиннее первого, если всегда "как завещано" использовать фигурные скобки. Но в данном случае видится, что можно их убрать для улучшения читаемости.
И тогда мы получаем более понятный из-за линейной структуры.
В Совершенном коде автор тоже за второй вариант. Присоединяюсь и считаю, что второй вариант - это база.
Возможно есть кейсы, когда он не применим, но я пока придумать не могу.
Даже на 2 условиях. Даже если недостаточно простого return.
#book_review #code
Снова минутка цитат на канале:
Лично я вижу смысл в этом термине. И суть его в том, что в мире победившего ООП не стоит забывать об операциях (функциях) и о простоте.
Я уже писал про структурный дизайн - 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
«Термин „структурное программирование“ был введён в 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
Telegram
(java || kotlin) && devOps
Всем привет!
Я уже писал про то, что не люблю код, в котором интерфейсы делаются ради интерфейсов. Самый яркий антипаттерн: интерфейс с единственной реализацией, лежащей рядом. Подозреваю, одной из причин такого проектирования является принцип Dependency…
Я уже писал про то, что не люблю код, в котором интерфейсы делаются ради интерфейсов. Самый яркий антипаттерн: интерфейс с единственной реализацией, лежащей рядом. Подозреваю, одной из причин такого проектирования является принцип Dependency…
Медленная загрузка сервиса - значит Java?)
Есть такая проблема у Java, в частности при использовании Spring Framework. А точнее когда поверх Spring-а накручиваются свои компоненты.
Загружается куча классов, много кода инициализации = много времени. Как с этим борются - см. по тэгу #java_start_boost
Но только ли это проблема Java?
Запускал небольшую AI модельку на Python. Один скрипт. Скрипт стартует более 20 секунд.
Ну, думаю, модель так долго грузится. Померил - нет, модель порядка 3 секунд. Она уже в виде кэша скачана на локальный компьютер, да и не LLM это.
Антивирус? Интерпретатор Python?
Нет, всего лишь библиотека sentence-transformers.
На самом деле конечно не всего лишь. Она под капотом грузит движок для инференса - PyTorch. Плюс другие зависимости.
Но за 20 секунд старта ответственна именно строчка c импортом:
Тут еще видится "выстрелил" высокий уровень абстракции, принятый в Python.
Пишешь пару строк - у тебя все работает. Удобно? Да. Но есть нюансы)
Вывод - высокий уровень может превратится в айсберг. Ты видишь вершину, а под ней еще...
#python #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
Сколько багов в час делает разработчик?
И снова цитата из Совершенного кода:
Хоть ты код не пиши!)))
А если серьёзно - рулит код-ревью и тесты. Именно в таком порядке.
Типичный концер процент нахождения багов в ПО при использовании различных практик из той же книги:
Неформальный обзор дизайна (тех. проекта) 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
И снова цитата из Совершенного кода:
Исследования, проведенные в Институте
разработки П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: основы разработки под облачную инфраструктуру". Старт через неделю, курс ведет Коля Ихалайнен, и он, кстати, и меня несколько лет назад докеру обучил (не шутка).
Привет! Хочу объяснить, почему мы не сделали тебе оффер.
Сразу скажу, с кодом у тебя всё ок. Вопрос вообще не в этом. У меня ощущение, что ты мало сталкивался с тем, что происходит после коммита/мержа.
Помнишь, мы обсуждали прод, я спрашивал тебя: как устроен пайплайн деплоя, как собирается сервис, как управлять образами, как контейнеры оркестрируются на проде? А ты говорил, что это всё докер-кубер и вообще команда инфры. Но нам нужен инженер, который понимает это практически.
Сейчас без контейнеров почти никуда, особенно если сервис живой и его часто релизят. И 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 - это база!)
#комментарии
Что ещё интересно - судя по реакциям и комментариям в канале автора - проблема актуальна.
Отмазки следующие:
1) в кровавом enterprise меня не пускают к инфре. Видимо не очень то и хотелось)
2) AI придёт и все сгенерит. Видимо вместе с кодом)
3) функции, faas, докера там нет. На самом деле по капотом есть, но главное - я в России работающих faas не видел.
4) ну и очевидное - это все реклама, не нужен мне ваш Docker. Пусть даже и реклама, но написано хорошо, а Docker и k8s - это база!)
#комментарии
Чем заменить сложный if?
В рамках борьбы за читаемость кода, конечно же.
Ответ - табличным методом.
Название, возможно, неизвестное, но многие этим подходом пользовались, не зная как его назвать.
Но ближе к коду)
Вот есть у нас, предположим, такое условие:
Сложно, т.к. много кода. Но вот так намного проще:
Собственно таблица - это коллекция, из которой по ключу получаем нужное значение в одну строчку без if.
Хорошо. Но не всегда ключ можно прямо замапить на значение.
Возможно, ключ однозначно преобразуется в требуемый. Тогда получаем табличный доступ с преобразованием:
Еше кейс. Предположим, у нас целевое значение зависит от положения точки в диапазоне, тогда получаем табличный метод со ступенчатым доступом:
Ну и возвращать можно не только значение, но и лямбду:
Важный плюс такого решения - "таблица" легко выносится в файл при необходимости настройки.
Или любой внешний источник.
Таблица может быть мапой, а не только массивом или списком.
P.S. Да, это снова Совершенный код)
#code_review #code
В рамках борьбы за читаемость кода, конечно же.
Ответ - табличным методом.
Название, возможно, неизвестное, но многие этим подходом пользовались, не зная как его назвать.
Но ближе к коду)
Вот есть у нас, предположим, такое условие:
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
Сегодня еще один пост, пост-"мысль вслух".
Самое удивительное в этой книжке - это два факта:
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
Но посмотрим, когда появились некоторые ключевые идеи.
Закон Конвея, упомянутый ранее 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
Telegram
(java || kotlin) && devOps
Всем привет!
Разработка ПО - очень динамичная сфера. Мэйнфреймы, ассемблер, CSV, RDBMS, C, Delphi, Java, REST, MQ, git, DevOps, Docker, k8s, Kafka, noSQL, microservices, reactive programming, DataLake, GitOps, ChatGPT...
Но есть вещи, которые не меняются.…
Разработка ПО - очень динамичная сфера. Мэйнфреймы, ассемблер, CSV, RDBMS, C, Delphi, Java, REST, MQ, git, DevOps, Docker, k8s, Kafka, noSQL, microservices, reactive programming, DataLake, GitOps, ChatGPT...
Но есть вещи, которые не меняются.…
👍4❤1
Чем похожи LLM и blockchain?
И там, и там для генерации следующего элемента используют данные предыдущих. И LLM также не может изменить уже сгенерированные токены, даже если в какой-то момент поняла, что они не верны. Ну и ещё потому что у нас как правило стриминг и пользователь уже их увидел)
Разница конечно есть - в blockchain вся цепочка живёт вечно, а LLM ограничена длиной контекстного окна и возможностью чата/агента хранить историю
И эта особенность является одной из причин галлюцинаций LLM. Т.к. придя к неверному выводу модель опирается на него в дальнейших ответах.
#ai
Разница конечно есть - в 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
Попробую собрать все возможные причины:
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
OpenAI
Почему языковые модели испытывают галлюцинации
Новая научная публикация OpenAI объясняет, почему языковые модели выдают галлюцинации. Результаты показывают, как улучшенные оценки могут повысить надежность, честность и безопасность ИИ.
Все 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
Один файл:
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 мир победил, микросервисы оказались сильней)
Вот данные из моей любимой книжки по разработке.
Медиану можно оценить ~9, среднее ~19, но среднее мало информативно из-за сильной правой асимметрии (маленьких команд сильно больше, чем больших).
Я бы из него сделал вывод, что микросервисы были всегда, просто раньше назывались по-другому — например, SOA (Service Oriented Architecture).
Но конечно ситуация меняется(хотел написать «не могла не измениться», но вспомнил свой же пост про двойное отрицание))) .
А вот более современные данные, не такие подробные.
Если сравнить 2003 и 2012 годы — медиана уменьшилась почти в 2 раза: 9 → 5.
Ещё есть данные опроса пользователей JetBrains (2024) https://www.jetbrains.com/lp/devecosystem-2024/
Медиана ≈ 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–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
JetBrains: Developer Tools for Professionals and Teams
Software Developers Statistics 2024 - State of Developer Ecosystem Report
Explore key software developer statistics for 2024 in the State of Developer Ecosystem Report. Trends, insights, and tools shaping the developer world
👍1
Иногда кажется, что если попросить какую-то LLM модель проверить информацию, указав при этом, что получил ее от другой модели - она это делает с какой-то особой тщательностью)))
Скорее всего кажется - как проекция взаимоотношений создателей на их модели. Я про OpenAI, Claude, Grok и Gemini.
Но в любом случае это отличный быстрый способ проверки информации из LLM.
И тут хорош Perplexity, т.к. там все перечисленные модели есть.
#ai
Скорее всего кажется - как проекция взаимоотношений создателей на их модели. Я про 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
Пример.
Есть такое понятие как 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
Kong Docs
Kong Plugin Hub | Kong Docs
Extend Kong Gateway and Kong Konnect with powerful plugins and easy integrations.
Магические 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
Часто возникает вопрос - почему 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
Sonarsource
Understanding measures and metrics | SonarQube Server | Sonar Documentation
Measures and metrics used in SonarQube to evaluate your code.
Что такое Agile для разработчика?
Возьмем SCRUM, как наиболее типичный его вариант.
IMHO конечно.
Это не церемонии - Daily, планирование, ретро, демо.
Не наличие Scrum мастера в команде.
Это красивый burn-down chart и velocity.
Не poker planing и оценка в story point.
Не работа с досками.
И даже не работающий инкремент каждую неделю.
База - это две вещи:
1) навык декомпозиции задач.
Который приводит к небольшим инкрементам.
А конечные цели:
- минимум зависимостей от других
- маленькие PR = быстрое ревью и вливание
- инкрементная интеграция кода = возможность вливания в целевую ветку и отправку на тестирование без ожидания других доработок.
2) точность оценки задач.
Не важно в чем - в story point, человеко-днях или в конкретных датах.
Оценка, кстати, зависит от декомпозиции, т.к. мелкие задачи можно оценить точнее, а потом оценки просуммировать.
Конечная цель - совпадение прогнозируемых сроков с реальными.
В данном случае речь про сроки этапа разработки, за соблюдение сроков интеграции со смежниками отвечает PO, DL и команда в целом.
Не всегда эти цели достижимы на 100%, но ставить их нужно. Если их себе ставить, то навык рано или поздно появится.
И традиционные ответы на не заданные вопросы)
Вопрос: "А разве это не навыки сеньоров?"
Ответ: когда-то возможно да, но с учетом последних тенденций (не могу не вставить AI в пост) кажется всем надо повышать уровень "сеньорности".
#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:
vs
Базовый ответ - потому что он компактнее)
Но я бы накинул еще один аргумент. Рассуждение:
- стиль форматирования нужен человеку, и не нужен машине
- человеку важно понимать, где начало и конец логического блока кода или управляющей конструкции
- "железобетонный" способ это понять - четкие маркеры, хороший пример: begin-end в Delphi, If-End If в VB
- хороший, но не идеальный, т.к. слишком много символов - 8 вместо минимально возможных 2
- скобки на той же строке, что и управляющая конструкция, у Google\Sun - это по сути компактная эмуляция begin-end. Эмуляция потому что они не обязательны. Скобки как бы становятся частью управляющей конструкции - if, for, while, switch, try.
- а вот альтернативный вариант - это что-то странное, т.к. скобки по отступам находятся на уровне управляющей конструкции, но зачем тогда перенос на новую строку?
Тогда логичнее вот так:
Но это еще страннее смотрится.
Ну и напоследок каких целей должно достичь хорошее форматирование кода:
1) разделение логических блоков кода: пробелы, отступы, скобки
2) единообразие
3) улучшение читаемости
4) облегчение правок - возможность быстро понять, какой блок кода нужно перенести или удалить целиком
5) компактность. Причем как раз компактность с увеличением размера монитора становится менее
важна
P.S. Спонсор выпуска - Совершенный код, Стив Макконнелл)
#java #formating
Я про сталь 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 схема как валидатор объекта параметров.
Получался примерно такой запрос к модели на формирование вызова тулы:
Здесь две проблемы.
1) модель конечно знает что такое JSON, но она генерирует токены, а не текст по формату.
2) у агента\чата есть задача уменьшить контекст, поэтому даже если ему дать полную схему с minimum, minimum, pattern, minItems - он ее может порезать (и режет) для оптимизации.
Поэтому модель могла вернуть и некорректный по структуре JSON, и проигнорировать ряд атрибутов схемы. Первое реже, второе - всегда.
Когда появился structured json - модель же гарантирует корректность возвращаемого JSON (JSON = возвращаемый объект) проблема казалось бы должна уйти в прошлое?
В модель летит такой запрос:
Тут даже "additionalProperties": false есть. И required само собой. И это все даже работает)
Но в целом гарантии точному соответствию схеме нет.
Как в большинстве моделей реализуется structured output?
Схема превращается в набор т.наз. грамматик.
Например
превратится в что-то такое:
А далее по этим грамматикам строится state machine.
И при генерации очередного токена во внимание принимается не только его вероятность (как при обычном выводе), но и идет проверка на разрешенное состояние по этой state machine.
И если не разрешено - выбираем следующий.
Процесс называется constrained decoding, вот пример библиотеки, его реализующей: https://xgrammar.mlc.ai/
И я бы из ее описания обратил внимание на фразу:
Может и плохой код писать, к слову, зависит от модели и задачи)
Я уже писал о том, что генерация 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.
Да, и structured output - это все равно шаг вперед. Модель не генерирует как бы json, есть валидатор, проверяющий ну скажем 80-90% схемы.
#ai #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
Telegram
(java || kotlin) && devOps
LLM как серебряная пуля?
Конечно же нет.
А если серьезно - что не умеет LLM?
1) выдавать актуальную информацию. Фиксится подключением веб-поиска
2) выдавать 100% точные ответы. LLM вероятностна по своей природе, поэтому даже самая мощная модель с огромным…
Конечно же нет.
А если серьезно - что не умеет LLM?
1) выдавать актуальную информацию. Фиксится подключением веб-поиска
2) выдавать 100% точные ответы. LLM вероятностна по своей природе, поэтому даже самая мощная модель с огромным…