
Kotlin *
Статически типизированный язык программирования
Почему ИИ-агент для кода промахивается мимо нужного метода
Несколько последних постов об ИИ-агентах, которые я читала на этой неделе, сошлись в одну мысль: агенты часто ведут себя по-разному на одной и той же задаче, и разгадка почему - находится не там, где ожидаешь. Вот конкретный пример, на котором это видно сразу.
Агент, который ищет код текстом, физически не отличает OrderService.validate от UserDto.validate: для него это просто совпадение символов «validate». Агент, который спрашивает у IDE «кто на самом деле вызывает этот метод», получает точный ответ, потому что IDE знает типы, разрешённые ссылки и видимость каждого символа. Разница между этими двумя подходами не абстрактная, она измеряется в конкретных цифрах.
Точка отсчёта: почему grep и RAG промахиваются
Кодовый агент ищет по проекту двумя способами: grep/ripgrep по содержимому файлов и векторный поиск по эмбеддингам кусков кода. На маленьком проекте оба варианта работают сносно. На enterprise-репозитории на миллионы строк с десятками модулей поведение ломается одинаково: запрос «найди использования метода validate» возвращает тысячу с лишним совпадений, из которых подавляющее большинство - другие методы с тем же именем в других классах. Векторный поиск находит куски кода, которые «похожи по смыслу», но это может быть валидация в совершенно другом домене.
Что показали цифры на 27 задачах
Мы сравнили три версии агента на 27 задачах вида «найди все места, где используется метод X» из реальных тикетов трёх внутренних репозиториев на Java и Kotlin: агент на ripgrep, агент на ripgrep с векторным RAG и агент на PSI-индексе IntelliJ (find_usages, find_declaration, class_hierarchy вместо текстового поиска).

Precision показывает, сколько из найденного агентом оказалось реальными использованиями метода, а не случайным совпадением имён. Recall показывает, сколько из всех реальных использований в коде агент вообще нашёл, ничего не пропустив.
У ripgrep recall высокий (0,82): текстовый поиск почти никогда не пропускает совпадения по имени. Но precision низкий (0,41): больше половины найденного - чужие методы с тем же именем, и их приходится разбирать вручную. У PSI-индекса высоки обе величины (0,96 и 0,93): агент находит почти все нужные места и почти не приносит лишнего. F1 сводит обе величины в одно число, и у ripgrep он проседает именно из-за мусора в выдаче, хотя нужные места он и находит.
Отсюда пятикратная экономия контекста и в пять с лишним раз более дешёвый ответ при кратно более высокой точности: агент на PSI-индексе не читает тысячи лишних строк, которые вернул бы текстовый поиск.
Точный поиск не отменяет проверку
Даже когда агент точно нашёл все места, где используется метод, и внёс правку во все, это не гарантирует, что правка корректна: точный поиск решает только задачу «найти нужные места», а не «проверить, что код после правки работает». Поэтому прежде чем объявить об успехе, агент должен прогнать компилятор и тесты: тест зелёный, ошибок компиляции нет, покрытие не просело. Пока в выводе компилятора или тестового прогона есть хоть одна ошибка, агент обязан либо исправить её, либо сообщить о проблеме, а не писать об успехе.
Но точный поиск снимает с агента часть ручной работы: не нужно самому перепроверять по всему проекту, все ли использования метода он нашёл - эту проверку берёт на себя IDE, как это устроено в Veai, а оставшуюся часть верификации - компилятор и тесты.
Как я научил свою читалку различать примечания и комментарии в «Войне и мире»
Я не профессиональный программист. Пишу Android-читалку MRead для себя и для тех, кому тоже надоели комбайны. Код закрытый, всё работает локально, без серверов и трекинга. Раздаю через RuStore, 4PDA и GitHub.
В версии 1.4.0 я наконец победил баг, который меня лично бесил как читателя. Расскажу именно про него, потому что задача оказалась интереснее, чем выглядела.
Проблема
Открываю «Войну и мир». В одном предложении сразу два маркера сноски: примечание (перевод французского) и научный комментарий. В разных изданиях они нумеруются независимо, поэтому в тексте легко встретить два маркера с одинаковой цифрой. Старая логика по тапу вытаскивала из текста ближайшую цифру и искала абзац, начинающийся с этого числа, по всем главам, и показывала первое совпадение. Итог: тап по «6» открывал не ту сноску, а тап по числу вроде «1805» вообще пытался найти несуществующую сноску.
Почему наивный подход не работает
Цифра это не идентификатор. Она повторяется и в каждой главе, и между разделами «Примечания» и «Комментарии». А вот в исходнике всё однозначно: каждый маркер это ссылка с уникальным адресом.
EPUB: 1 FB2: [6] и {4}
То есть книга всегда знает правильный ответ. Беда была в том, что мой парсер при импорте выбрасывал href и оставлял только видимый текст маркера.
Что я поменял
При разборе HTML сохраняю не только текст абзаца, но и ссылки-сноски с точной позицией маркера. Позицию считаю надёжно: оборачиваю маркер невидимыми символами из Private Use Area до очистки текста, а после очистки нахожу их и вычисляю смещение. Так цифры в обычном тексте (годы, числа) не путаются с маркерами.
Позиции храню в абсолютных координатах главы. Это важно, потому что мой пагинатор режет абзацы на куски по строкам. Абсолютные смещения переживают нарезку без отдельной возни в пагинаторе.
По тапу беру не цифру, а ссылку под пальцем, и резолвлю сноску по точному id из нужного файла. Для FB2 пришлось ещё и сохранить id секций сносок при конвертации в HTML, потому что примечания и комментарии лежат в отдельных главах.
Старый поиск по цифре оставил как запасной вариант для книг без нормальной разметки, но убрал срабатывание на голые числа.
Результат: тап по примечанию открывает перевод, тап по комментарию открывает комментарий, а год «1805» больше никого не трогает.
Контекст для тех, кому интересно
Рендер текста у меня не WebView, а нативный движок на Canvas с собственной пагинацией (Jetpack Compose, Kotlin). Это даёт контроль над переносами, выравниванием по ширине и стабильной привязкой цитат и закладок, но за каждую такую фичу приходится платить ручной работой вроде этой истории со сносками.
Что ещё в 1.4.0, кратко
• Полнотекстовый поиск по PDF (для PDF с текстовым слоем)
• Озвучивание (TTS): голоса, скорость, таймер сна, автопереход, пауза с памятью места
• Передача слова во внешний словарь и контекст предложения в экспорт Anki
• Настраиваемые блоки настроек чтения, межабзацный интервал, Bold/Italic
• Полки, фоновый импорт из папок, оценки и заметки, Material You
Ссылки
• RuStore
• GitHub
• 4PDA
Если у вас была книга FB2, переоткройте её, чтобы заработали точные сноски (HTML генерится при импорте). EPUB подхватит сам.
Буду рад замечаниям по подходу. Если делали резолв сносок иначе, расскажите как, мне правда интересно.
Риски ИИ- генерируемого кода - качество, производительность, “УСПЕВАЮ ЛИ Я ЗА ТЕМПОМ”
ИИ генерирует код быстрее, чем мы успеваем ловить его реальные проблемы.
В JPA/Hibernate это особенно больно: код компилируется, тесты зелёные — а внутри N+1 и лишние запросы. Veai проверяет такие гипотезы на фактах проекта прямо в IDE: семантические usages, реальные прогоны тестов, coverage, debugger, доступ к исходникам Hibernate.
Не “этот код, возможно, тормозит”, а проверка через запуск. Скорость растёт — контроль над качеством остаётся. Большой разбор продукта сделан в статье
От падающего теста до правки: как General ведёт задачу в любимой IDE
Когда разработчик открывает AI-чат в IDE, он не думает категориями режимов. Он не формулирует задачу как «сначала Plan, потом Code, затем Test и Review» — он пишет проще:
Почини тест
И только по ходу становится понятно, что это за задача: иногда хватит поправить одну строку, иногда — пройти по нескольким модулям, разобраться в зависимости, изменить production-код и обновить тесты. Заранее это знать нельзя — и не нужно.
Под этот сценарий в Veai сделан режим General: вы описываете цель обычными словами, а агент сам выбирает маршрут — проход по коду, планирование, тесты, ревью, отладка или подключение субагентов. Специализированные режимы (Ask, Code, Test, Plan, Review, Debug) остаются для случаев, когда вы хотите управлять процессом явно.
Почему ручной выбор режима мешает
Реальная задача редко укладывается в один режим. «Исправить падающий тест» — это сразу несколько подзадач: понять причину, решить, где ошибка (в тесте, production-коде, моках, данных или окружении), внести правку и запустить проверку. Если режим нужно выбрать заранее, новый разработчик начинает не с решения проблемы, а с изучения классификации агентов. General убирает этот выбор из начала задачи.
Что происходит по шагам
На запрос «в сервисе оплаты падает тест, найди причину и почини» General в простом случае ведёт задачу сам:
находит и запускает тест через IDE run configuration — в том же окружении, что и разработчик (SDK, профиль, переменные, модули), а не в собранном из терминала, которое может отличаться;
читает стектрейс, открывает связанный production-код, при необходимости смотрит usages, warnings и inspections;
вносит минимальную правку и перезапускает проверку: тест прошёл или упал — это факт из IDE, а не предположение модели.
Если стектрейса не хватает, агент опирается на отладчик (breakpoints, значения переменных, call tree), а если стектрейс уводит в библиотеку — открывает её код или декомпилированный класс через IDE, а не угадывает API по памяти модели.
Когда подключаются субагенты
Маршрут выбирает не отдельный классификатор, а сама модель: по тексту задачи и первым фактам из проекта она решает, достаточно ли пройтись по коду или стоит разложить работу на субагентов (один исследует причину, второй — зависимости, третий — тесты). Поправить одну строку General сделает сам, большую задачу — распараллелит. Многоагентность здесь не самоцель, а инструмент для задач, где она реально ускоряет результат.
Полностью исключить ошибки модели нельзя. Но General опирается не только на LLM, grep и RAG, а на JetBrains IDE как на источник проверяемых фактов: run configurations, SDK и classpath, структуру кода, usages и inspections, coverage, код зависимостей и ошибки компиляции так, как их видит IDE. Отсюда меньше галлюцинаций API и ситуаций «у агента прошло, а в IDE или CI падает».
Разницу можно измерить.
Мы прогнали 8 enterprise-задач на Java/Spring через четыре агента на одной модели — Cursor, Claude Code, JetBrains Junie и Veai:

Контроль остаётся у разработчика
Даже когда агент ведёт задачу автономно, последнее слово за человеком: разработчик смотрит diff в окне Agent Changes и решает, что принять. Перед этим General может сам прогнать несколько субагентов-ревьюеров по своим изменениям и устранить критические проблемы ещё до того, как покажет результат человеку, — авторевью встроено в маршрут, а не остаётся отдельным ручным шагом. Идея не в том, чтобы убрать review, а в том, чтобы убрать лишнюю ручную маршрутизацию до него.
Обратная связь — support@veai.ru и чат с командой.
Вайб-кодинг на корпоративном контроле: как превращать хаос генерации в инженерную дисциплину

Приглашаем вас на совместный вебинар ITFB Group и компании Veai, посвящённый практике контролируемого применения ИИ в разработке.
Когда: 24 июня, 11:00
Где: онлайн
Ключевой вопрос: как использовать скорость вайб-кодинга, но при этом не допустить падения качества, потери управляемости и рисков безопасности?
О продукте
Veai — первый российский ИИ-агент, сочетающий высокую скорость генерации кода с жёстким контролем на основе формальных методов. На вебинаре мы на реальных примерах покажем, чем Veai отличается от Cursor, Copilot и других доступных на рынке решений.
Программа вебинара
— Практическая демонстрация: работа агента на реальном коде — от постановки задачи до готового результата.
— Сравнительный анализ: объективная оценка сильных и слабых сторон популярных ИИ-ассистентов.
— Метрики и экономика: данные по экономии часов, ROI, доле принятого сгенерированного кода и росту тестового покрытия (до 80% с использованием символьного исполнения и data-flow анализа).
— Работа с некомпилируемым кодом: агент автоматически выявляет и исправляет ошибки компиляции и предупреждения IDE, используя нативные инструменты без дополнительных вызовов.
— Безопасность и архитектура: варианты развертывания (on‑premise, self‑hosting, VPC, SaaS), механизмы контроля доступа и встроенный SAST-движок, проверяющий каждую генерацию.
Спикеры
— Константин Волков, менеджер по техническим решениям Veai.
— Наталья Романова, директор по развитию ITFB Group.
Формат — открытая дискуссия. Вы сможете задать любые вопросы, включая самые сложные и нестандартные.
Кому будет полезно: ИТ-директорам, руководителям разработки, архитекторам и всем, кто внедряет или планирует внедрять ИИ-инструменты в корпоративную среду.
JEP 524 в JDK 26 — второй preview PEM API
Наконец-то работа с PEM в Java становится похожа на API, а не на набор ручного парсинга, Base64 и странных телодвижений.
Справка: PEM или Privacy-Enhanced Mail - это текстовый контейнер для криптографических данных. Проще говоря – это способ хранить или передавать ключ, сертификат или другой crypto-объект не в бинарном виде, а в текстовом.
Раньше с PEM работали так:
String pem = "-----BEGIN PUBLIC KEY-----\n"
+ Base64.getMimeEncoder(64, "\n".getBytes())
.encodeToString(publicKey.getEncoded())
+ "\n-----END PUBLIC KEY-----";А в обратную сторону, но уже с ручной нормализацией PEM, Base64-декодированием и KeyFactory:
String normalized = pem
.replace("-----BEGIN PUBLIC KEY-----", "")
.replace("-----END PUBLIC KEY-----", "")
.replaceAll("\\s", "");
byte[] der = Base64.getDecoder().decode(normalized);
PublicKey key = KeyFactory.getInstance("EC")
.generatePublic(new X509EncodedKeySpec(der));По факту PEM в Java долгое время был не отдельным API, а набором низкоуровневых шагов, которые разработчик собирал руками.
А теперь это выглядит так:
var encoder = PEMEncoder.of();
String pem = encoder.encodeToString(keyPair);
var decoder = PEMDecoder.of();
KeyPair decoded = decoder.decode(pem, KeyPair.class);То есть ключевую пару можно закодировать в PEM и декодировать обратно буквально в несколько строк.
Во втором preview:
PEMRecordпереименовали в PEMдобавили
decode()расширили поддержку
KeyPairиPKCS8EncodedKeySpecупростили шифрование через
EncryptedPrivateKeyInfo
А так, как все это дело еще в preview, не забываем использовать --enable-preview.
❓ Минус еще один кусок криптографической копипасты из Java-кода. PEM в Java постепенно перестает быть унылым?

Присоединяйтесь к русскоязычному сообществу разработчиков на Spring Boot в телеграм — Spring АйО, чтобы быть в курсе последних новостей из мира разработки на Spring Boot и всего, что с ним связано.
Открытый проект WebToApp позволяет превратить сайт в полноценное Android‑приложение прямо на саартфоне без ПК, Android Studio или знаний кодинга. Можно сделать приложение из обычного HTML‑сайта, React, Vue или Next.js. Также можно добавить иконку, включить блокировку рекламы и защиту приватности, тёмную тему, медиа‑инструменты и даже собственные скрипты.

Для Android вышло приложение‑брандмауэр ShizuWall, которое делает смартфон безопаснее:
умеет полностью отключать доступ к интернету для выбранных приложений;
допускает к сети только избранные приложение;
запрещает фоновую интернет‑активность нежелательных приложений;
при этом никаких VPN‑туннелей и Root‑прав не требуется — всё работает из коробки;
бесплатно приложение на Kotlin доступно на GitHub, есть версия и в Google Play.

⚠️ Java продолжает готовиться к удалению finalize()
В рамках JDK 27 у ThreadPoolExecutor-а, очень популярного API в Java, будет удалён метод finalize(). Несмотря на то, что в силу dynamic dispatch вызов метода finalize() всё равно будет транслирован в Object, данное изменение не source level compatible.
А все потому, что у Object.finalize() сигнатура содержит throws Throwable, в то время как ThreadPoolExecutor.finalize() — нет. Пока метод был в ThreadPoolExecutor, компилятору было ок. Как только его удалят, вызов super.finalize() начнет резолвиться в Object.finalize(), и тогда прилетит “unreported exception Throwable”.
Комментарий от Михаила Поливаха:
Вот это кстати довольно редкий пример того, что имзенение может быть binary level compatible, но не source level compatible.
Например:
class MyPool extends ThreadPoolExecutor {
@Override
protected void finalize() {
super.finalize(); // JDK 27: теперь это Object.finalize() throws Throwable
}
}Как избежать?
поискать и удалить любые
finalize()/super.finalize()(и вообще любые авто-cleanup через финализацию)управлять жизненным циклом executor’ов явно:
shutdown()/awaitTermination()или просто close() в try-with-resources (да,ExecutorService—AutoCloseable)
Spring АйО рекомендует не использовать finalize() в целом, но если подобного рода хук нужен, то лучше использовать Java Cleaner API. С его помощью нельзя "случайно" воскресить объект, сломать integrity объекта или т.п.
Ждем JDK 27 🫠

Присоединяйтесь к русскоязычному сообществу разработчиков на Spring Boot в телеграм — Spring АйО, чтобы быть в курсе последних новостей из мира разработки на Spring Boot и всего, что с ним связано.
Автоимпорт при копировании кода — штука настолько приятная и удобная, что без неё уже невозможно представить работу в IDE.
Мы пошли дальше и вслед за умным импортом во время набора кода сделали автоматическую инжекцию бинов при копировании кода!
Теперь при копировании кода Amplicode автоматически добавляет нужную инжекцию бинов. С учётом контекста, @Primary, @Qualifier, дженериков, @Bean-методов, Java и Kotlin — без ручной возни после вставки.
Будет доступно всем пользователям Amplicode, без подписки.
Ближайшие события
При написании интеграционных тестов для Spring Boot приложения часто возникает проблема, что разработчики бездумно добавляют аннотации @MockBean, @SpyBean, @DirtiesContext или переопределяют прямо в тестовом классе различные property. Всё это приводит к изменению Spring Context, невозможности использовать закэшированный контекст и следовательно созданию нового. Часто создание нового контекста это длительная операция.
Существуют инструменты по отслеживанию этих процессов. Самым простым способом увидеть количество контекстов и количество попаданий в кэш является добавление логирования либо через свойство logging.level.org.springframework.test.context.cache=DEBUG либо настройкой вашего логгера.
Один известный автор статей про тестирование на Java / Spring Boot, Philip Riecks (со товарищи), создал инструмент с открытым исходным кодом Spring Test Profiler при помощи которого можно получить html отчёт о поднимаемых контекстах во время тестов, о количестве и типе бинов в этих контекстах. На Хабре есть перевод его статьи в сообществе Spring АйО.
У нас на проекте стал вопрос, как нам показать разработчикам, что их тест порождает новый Спринг Контекст. Мы решили считать контексты в тестах и при превышении ожидаемого количества падать. Это "руинит" сборку и CI/CD пайплайн.
Для этого мы добавили реализацию интерфейса ContextCustomizer
class LimitingSpringContextCustomizer implements ContextCustomizer {
private static final AtomicInteger CONTEXT_COUNTER = new AtomicInteger();
private static final int EXPECTED_SPRING_TEST_CONTEXT_COUNT = 16;
@Override
public void customizeContext(ConfigurableApplicationContext context, MergedContextConfiguration mergedConfig) {
int numberOfContexts = CONTEXT_COUNTER.incrementAndGet();
log.info("Number of Spring Test Contexts: {}/{}", numberOfContexts, EXPECTED_SPRING_TEST_CONTEXT_COUNT);
Assert.state(numberOfContexts <= EXPECTED_SPRING_TEST_CONTEXT_COUNT,
() -> "Number of test contexts exceeds configured maximum: " + EXPECTED_SPRING_TEST_CONTEXT_COUNT);
}
@Override
public boolean equals(Object obj) {
return (obj != null) && (getClass() == obj.getClass());
}
@Override
public int hashCode() {
return getClass().hashCode();
}
}Здесь, согласно документации интерфейса ContextCustomizer необходимо корректно переопределить методы equals и hashCode.
WARNING: implementations must implement correct equals and hashCode methods since customizers form part of the MergedContextConfiguration which is used as a cache key.
Далее добавляем фабрику.
class LimitingSpringContextCustomizerFactory implements ContextCustomizerFactory {
@Override
public ContextCustomizer createContextCustomizer(Class<?> testClass,
List<ContextConfigurationAttributes> configAttributes) {
return new LimitingSpringContextCustomizer();
}
}Затем регистрируем эту фабрику при помощи spring.factories чтобы она применялась ко всем тестам.
Для этого создаём в тестовых ресурсах файл по пути src/test/resources/META-INF/spring.factories со следующим содержимым
org.springframework.test.context.ContextCustomizerFactory=\
com.mycompany.app.support.spring.LimitingSpringContextCustomizerFactoryТеперь, если во время выполнения тестов будет превышено количество инициализированных тестовых контекстов, то мы увидим ошибку в тестах и сборка завершится неудачей.

Возможно, это пример поможет кому-нибудь повысить скорость прохождения своих тестов путём отслеживания количества запускаемых тестовых Спринг контекстов.
Kotlin и Hyperskill: как я искал курс и что получил в итоге.
Когда я решил изучать Kotlin, ожидал, что найти хороший курс будет просто: язык популярный, используется в Android и бэкенде, вокруг много материалов. Искал менторов и упирался в людей которые знаю java и вроде как используют в работе Kotlin. Это одновременно пугало и заинтересовывало, я решил поступить как мне казалось правильным, найти готовый курс — особенно если хочется не “смотреть видео”, а именно учиться через практику и задачи.
Я перепробовал разные форматы обучения (платные и бесплатные), поэтому в этот раз подход был простой: найти платформу, где есть структурированная программа и много практики. В итоге я добрался до Hyperskill (hyperskill.org). Это не реклама — просто личный опыт, кому-то он может сэкономить время.
Как я пришёл к ресурсу.
Изначально искал курсы по Kotlin на привычных площадках. На Stepik в тот момент не нашёл того, что мне подходило по структуре (возможно, сейчас ситуация лучше). Видео-курсы на крупных “известных сайтах” сознательно не рассматривал: мне удобнее формат “прочитал → сделал → получил проверку”.
Дальше — обычный путь через поисковик и сравнение нескольких платформ. Из того, что выглядело цельно и практично, больше всего зацепил Hyperskill. Отдельно сыграло роль то, что платформа связана с JetBra…. (то есть ребята явно понимают, как устроена экосистема вокруг Kotlin и IDE).После регистрации быстро становится понятно: платформа активно ведёт к подписке.Раньше в сети встречались статьи про возможность оформить бесплатную подписку на полгода, но это устаревшая информация — сейчас такой опции нет (по крайней мере, в том виде, в каком её описывают старые гайды).
При этом у Hyperskill есть бесплатный режим, и я проходил курс именно так.
Что я проходил: Introduction to Kotlin.
На платформе несколько треков по Kotlin, я начал с Introduction to Kotlin. По ощущениям, это “введение с практикой”:
около 9 учебных проектов
порядка 60–70 тем
внутри тем — задачи/тренажёры с автоматической проверкой
В целом структура понравилась: материал подаётся дозировано, и почти сразу закрепляется практикой. Похожая на Степик.
Система “кристаллов” и лимиты.
Самая спорная часть бесплатного режима — ограничения на попытки.
У Hyperskill есть внутренняя валюта (“кристаллы”): ошибаешься в заданиях — кристаллы списываются. Когда кристаллы заканчиваются, обучение может блокироваться на 12–24 часа. Да, кристаллы можно зарабатывать активностью и выполнением некоторых задач, но при активном обучении и регулярных ошибках (что нормально) этого может не хватать.
Подписка проблему снимает, но именно этот момент сильнее всего влияет на комфорт обучения в бесплатном режиме.
Проекты: что внутри и зачем это полезно.
Сильная сторона Hyperskill — проекты. Они не выглядят как “игрушки ради галочки”, а позволяют постепенно потрогать основные конструкции языка.
Из того, что запомнилось:
“Сапёр”
“Крестики-нолики”
“Чат-бот”
“Кофемашина”
Например, в проекте “кофемашина” уже нормально используются циклы, классы и базовые элементы ООП. В таком формате проще понять, “зачем оно нужно”, чем на изолированных задачках.
Минус: часть проектов закрыта платной подпиской, и это немного обидно — именно проекты дают максимальную пользу и ощущение прогресса.
Проверка решений: не всегда понятно, почему “не принято”
Ещё один недостаток — качество обратной связи в тестах. Иногда тесты “падают” так, что ты видишь только факт ошибки, но не понимаешь причину: что именно ожидалось, на каком кейсе сломалось, где расхождение.Часть проектов проверяется через IntelliJ IDEA, и здесь иногда всплывают технические нюансы: несовпадение версий, необходимость обновить IDE или компилятор, странные падения на конкретном проекте.
Хороший момент: поддержка отвечает. По моему опыту, вопросы не игнорируют, и проблемы реально разбирают.
Итоги:
бесплатный режим может раздражать лимитами на ошибки (кристаллы и блокировки)
часть контента (включая проекты) закрыта подпиской
обратная связь тестов местами недостаточно информативная
Если готовы, то вперед!
Здравствуйте, уважаемые читатели. Обращаем ваше внимание, что в блоге SSP-Soft вышел детальный обзор нашей новой книги о технологии Jetpack Compose для Android. Jetpack Compose (в книге разобрана версия 1.6) - это передовой инструментарий для Kotlin-разработчиков, предназначенный для проектирования и модернизации пользовательских интерфейсов, рассчитанных именно на работу с мобильными устройствами. В книге также рассмотрены основы языка Kotlin для Android и работа с Android Studio. Заказывайте книгу у нас на сайте и читайте с удовольствием!
P.S. Эта книга - одна из наших лучших находок в области англоязычного самиздата, однако нас в целом интересует тема разработки на Kotlin. Если у вас есть гитхаб с черновиками, либо вы прямо сейчас готовите рукопись - не стесняйтесь написать об этом Валентину Холмогорову @Holmogorov, Олегу Сивченко @OlegSivchenkoили просто в личные сообщения в этом блоге.
Спасибо вам за ваш интерес и Сергею Березину @sergbeза вышеупомянутую рецензию.
Дело не в том как ты проходишь собеседование
Дело в том хочет человек тебя нанять или нет
Т.е. проходишь ты дальше или нет основывается не на твоих желаниях и знаниях, а на желаниях и знаниях собеседующего
Так что не парься, будь счастлив 😁
Воспринимай собеседования не как путь именно к этой работе, а как путь к чему-то вообще :)
В любом случае этот шаг делает тебя на шаг ближе к цели
Если ты не прошел собес, то выбор простой: сражайся с этим или наслаждайся этим, и даже если сражаешься, то насладись сражением 😁
P.S. И так во всём..
В продолжение темы серверного HTML рендеринга на Kotlin.
Если вы не читали статью, кратко напомню о чём речь: я написал небольшой можно сказать фреймворк для генерации HTML на стороне сервера на Kotlin. И поддержку строготипизированных routes для удобного создания ендпоинтов, ссылок на них и форм. Теперь Spring Views можно создавать на Kotlin и по крайней мере в моих проектах (а один из них очень большой - сотни отдельных страниц и десятки виджетов) это дало мне огромное удобство, уверенность, безопасность, рефакторо-пригодность, простое версионирование и переиспользование кода, например, теперь я точно не сделаю опечатку в URI или имени параметра и не смогу передать неверный тип данных на endpoint.
Одна из проблем которые до недавнего времени была в связи с использованием данного фреймворка - это CSS-стили. Приходилось либо инлайнить стили непосредственно для HTML элемента, либо мейнтейнить огромный (или несколько поменьше) CSS файл без возможности внятно следить за старыми более неиспользуемыми классами и селекторами. Кто создавал большие CSS файлы знает о чём я говорю - со временем количество классов накапливается и вычистить их не задев чего-то всё ещё нужного очень сложно. Вот так приходилось работать с CSS стилями раньше:
DIV("css-class-name") {...}
// или
DIV {
style("color: red;")
+"Hello World"
}В принципе жить можно, но хотелось чего-нибудь:
что-то на подобии Styled-Components из ReactJS, но так же на Kotlin
по возможности поддержку JVM hot-reload в режиме отладки
как следствие кода на Kotlin - иметь возможность всегда отслеживать зависимости и удалять более неиспользующиеся CSS-правила.
Долго думал как это получше сделать, и пока что первая версия получила вот такой синтаксис:
@Component // Spring @Component
object MyCssClass : CssClass({
style = "color: black;"
hover = """
color: red;
text-decoration: underline;
"""
})
...
A(MyCssClass) {
href(SomeUsefulRoute(param = 1))
+"Click me"
}То есть объявляем object который будет нести информацию о CSS свойствах элемента. И далее просто используем его в тех же местах где раньше можно было указать css-class-name. CssClass поддерживает массу "псевдоклассов" типа hover, active, firstOfType, before, after и так далее, так же можно добавлять media брейкпоинты и всякие другие штуки. Вот более насыщенный пример:
@Component
object Container : CssClass({
style = "padding: 25px;"
hover = "background: #eee;"
add(">a", "color: green;")
add(">a:hover", "text-decoration: underline;")
media("max-width: 991px") {
style = "padding: 10px;"
}
})
Вы наверняка заметили двойные скобочки ({...}) - в конструктор я передаю функцию-инициализатор стилей. Это нужно для того, чтобы в дебаг режиме JVM можно было на лету менять свойства CSS класса без перезапуска приложения: код который генерирует сам css-файл может быть запущен в режиме dev-mode, когда на каждый запрос файла будут выполнены все функции-инициализаторы ещё раз и файл будет собран заново.
Если по какой-то причине вам не нравится object - можно просто объявить class и использовать его:
@Component
class Container : CssClass({...})
...
DIV(Container::class) { ... }Теперь в проекте порядок с CSS стилями - нет давно умерших, всегда можно найти точки использования, стили лежат рядом с виджетами и т.п.
Смотрите исходники тут. Фидбэк приветствуется, всем хорошего дня.
🎲 Retrofit 3.0 — что изменилось?

При поддержке блога @dolgo_polo_dev
Вышел Retrofit 3.0. А точнее в один день вышло 2 версии — Retrofit 3.0 и 2.12
Библиотека важная, поэтому попробовал разобраться, что изменилось
Самое интересное случилось в 2.12 — добавили стриминговую сериализацию из Kotlin/Java-классов в Json/Protobuf
Зачем это нужно было?
➡ чтобы большие классы не сериализовывались целиком перед отправкой запроса, а начинали это делать во время передачи данных на бэк
Это позволит чутка снизить нагрузку на процессор и оперативку, если
передаете объемные данные в теле запроса (1 мегабайт+)
где-то вызываете
Retrofit.Call.enqueue()с главного потока — стриминг перенесет сериализацию с главного UI-потока в бэкграунд
Чтобы изменения заработали, нужно создавать конвертер с помощью функции withStreaming()
MoshiConverterFactory.create(moshi).withStreaming() // пример
В Retrofit 3.0 просто апнули версию OkHttp (3.14.9 -> 4.12.0). И немного поправили внутреннего кода, пару строк для совместимости с 4.12.0
Так что если обновите версию Retrofit, у вас транзитивно апнется OkHttp — будьте внимательны, берегите себя и своих тестировщиков
Из хороших новостей — Retrofit 3.0 формальный мажор, то есть бинарно совместим с предыдущими версиями (по словам разработчиков). Мажорное версию апнули для хайпа, чтобы подчеркнуть обновление OkHttp
Пруфы:
compare 3.0 with previous (https://github.com/square/retrofit/compare/2.12.0...3.0.0#diff-ee4546957dd484579c54b92114186cb4b3181a8906d98fbf94fbc526bf755943)(там много ченжей, но 90% из них — это обновление их website с документацией)
compare 2.12 with previous (https://github.com/square/retrofit/compare/2.11.0...2.12.0) (тут можно посмотреть, за счет чего сериализация стала асинхронной)
остальные посты про Android публикую в https://t.me/dolgo_polo_dev
Основа Kotlin K2 компилятора — это FIR‑дерево (Frontend Intermediate Representation).
Вкратце: FIR — это AST (абстрактное синтаксическое дерево), обогащённое семантической (смысловой) информацией. Оказывается, что у этой основополагающей технологии есть своя небольшая документация: fir‑basics.md и в той части, где написано про контракты указано (в моём вольном переводе), что:
Компилятор разрешает использовать контракты в свойствах, функциях и конструкторах классов
Вот это поворот! Ведь ранее было замечено их использование только внутри тела функций. В доке написано, что для свойств должно работать, но на практике получаем ошибку.

А где находится то самое ограничение на использование контрактов вне функций описал Android‑разработчик Виталий Перятин в новой статье о Kotlin Contracts, где он поделился любопытными моментами, которые удалось накопать самостоятельно, потому что как парсится список эффектов, как работает новый Contracts API изнутри, и почему, чёрт возьми, на уровне компилятора можно использовать контракты не только на уровне функций, в доках не пишут.
Как ускорить Android-разработку и избавиться от мучительно долгих запусков эмуляторов ради простого теста?
Ответ — Robolectric — мощный инструмент для UI‑тестирования Android‑кода без эмулятора.
Позволяет запускать юнит-тесты Android-приложений прямо в JVM, без эмуляторов и физических устройств. Экономия на каждом тестовом прогоне, обратная связь почти мгновенная.
В Android‑комьюнити у Robolectric неоднозначная репутация из‑за трудностей совместимости с другими библиотеками. Но…его почти бесценные возможности пробудили любопытство и желание копнуть глубже и осмыслить этот инструмент.
Так и родилась статья «Мечтают ли андроиды о Robolectric? Разбираем фреймворк по косточкам» от Павла Нестеренко, нашего Android-разработчика.
Если вы устали ждать, пока эмулятор запустится, и хотите гонять юнит-тесты за секунды прямо в JVM, то рекомендуем статью к прочтению!
Довелось мне в последнее время поглядеть на "современные" проекты на Kotlin, и я признаюсь был поражен повсеместным использованием корутин в коде. Используют их просто везде: для обработки входящих соединений, для запросов в базы данных, для вызова внешних API, для операций с файлами на сетевых файловых системах. Знаете, в языке есть слова-паразиты — так вот кажется мне что в Kotlin корутины — это паттерн-паразит. Кажется корутины — это как магические заклинания, которые волшебным образом запустят код "где-то там, в локальном облаке".
Каждый раз когда я сталкиваюсь с туториалами про корутины — самым первым примером идёт конечно sleep на 10 тысяч "потоков". Притом как вы понимаете, вызывать нужно специально подготовленный для этого sleep, а не настоящий Thread.sleep — потому что с ним никакой магии не получится. Это создаёт обманчивое впечатление будто бы любая блокирующая операция автоматически волшебным образом превращается в асинхронную, за бесплатно увеличивая пропускную способность приложения. Хотелось бы напомнить любителям корутин что это не так.
Никто за вас не перепишет обычное чтение из сокета/файлового дескриптора на асинхронные ввод-вывод с проверкой наличия байт для чтения во входящем потоке через Selector::select — чудес не бывает. И как бы вы не увеличивали kotlinx.coroutines.io.parallelism рано или поздно при безудержном использовании корутин пул потоков забьётся чем-нибудь медленным и ваше приложение просто встанет раком.
Для примера попросите чатжпт написать вам простейший echo socket server на корутинах. Ну и клиента, который попробует работать с этим сервером в 100 обычных потоков (не так уж и много для нормального продакшн бэкенда). Надеюсь для вас не будет сюрпризом то, что сервер будет обрабатывать только 64 первых счастливых клиентов, а к остальным доберётся только когда эти первые клиенты закроют соединения.
Android-приложение «Контакты»: работает — не трогай

У многих полей контакта есть типы. Например, у номеров телефонов или адресов электронной почты по умолчанию есть типы «рабочий» и «домашний». Аналитики сказали, что нужно реализовать дополнительные типы и дать пользователю возможность создавать свои. Звучит легко, но на деле оказалось совсем непросто.
Чтобы заменить стандартные типы полей, нужно добавить новую базу данных с доступными типами полей, а также реализовать отображение и возможность выбора поля. Для этого пришлось во многом переписать код редактирования полей контакта, где и без этого логика была непростой. Теперь стало совсем сложно: при добавлении или обновлении поля в contentProvider нужно указывать тип поля, так что мы начали указывать «custom».
После добавления кастомных типов приложение потеряло стабильность, появились кейсы, в которых кастомные типы работали неправильно. Один из них приводил к крашу: база данных не успевала создаваться при открытии интента на редактирование контакта.
Изначальный путь пользователя — это открыть приложение и увидеть список контактов с аватаркой, именем и номером телефона. Пока этот список подгружался на экране, база данных обычно успевала создаться. Но мы не учли, что при открытии интента на редактирование контакта из другого приложения сразу появляется экран редактирования — в этом случае база данных не успевала инициализироваться.
Создание схемы БД и инициализация стандартными значениями происходила в отдельном потоке, так что у нас получилась небольшая гонка потоков. UI готов отобразить экран и идет в БД за данными, но БД не успевает инициализироваться, так что запрос приводил к критической ошибке. Баг воспроизводился при попытке обратиться к БД только при первом холодном запуске на устройстве или после очистки данных, так как здесь приложению приходится заново создавать схему базы данных.
Пришлось опять добавлять костыль: в отдельной корутине ожидать инициализации БД и только потом начинать рисовать экран...
Дмитрий Бражник, старший инженер-программист в департаменте разработки мобильных приложений YADRO, рассказал в статье, как его команда пыталась допилить стандартное AOSP-приложение «Контакты» и к чему они в итоге пришли.
Он переписал сортировку в Java! Интервью с Владимиром Ярославским
На JPoint 2025 мы пообщались с Владимиром Ярославским — разработчиком, чья работа вошла в историю Java. Именно Владимир стал автором улучшенного алгоритма сортировки, который используется в JDK.
Делимся ссылками на интервью на других ресурсах:
Это только начало — впереди ещё больше бесед с интересными людьми из мира Java и Spring.
Присоединяйтесь к русскоязычному сообществу разработчиков на Spring Boot в телеграм — Spring АйО, чтобы быть в курсе последних новостей из мира разработки на Spring Boot и всего, что с ним связано.
Ждем всех, присоединяйтесь
Добавлен Room! :)
Ранее я уже делился статьей про Delight SQL Viewer — библиотеку, позволяющую отлаживать SQLDelight-базу данных напрямую внутри вашего Kotlin Multiplatform-приложения. Однако в одном из наших внутренних проектов используется Room Multiplatform и хотелось иметь возможность отладки БД и в нем. Так и появилась вторая версия библиотеки, которая теперь поддерживает и Room, и SQLDelight.
Что делает библиотека?
Позволяет просматривать, добавлять, редактировать и удалять записи в базе через удобный UI прямо в приложении.
Автоматически добавляет иконку-ярлык для быстрого запуска (Android и iOS).
На Desktop ярлыки не поддерживаются. Для запуска интерфейса используйте обычную кнопку или меню в вашем UI.
Легко отключается в продакшен-сборках — можно либо не вызывать init, либо подключать stub-модуль вместо основной библиотеки.
Как использовать?
Зависимости (shared/build.gradle.kts)
val isRelease = /* Ваша логика определения релизной сборки */
dependencies {
if (isRelease) {
// В релизе – stub, чтобы не вшивать весь отладочный UI.
api("ru.bartwell.delightsqlviewer:stub:2.0.0")
} else {
// В debug – полная функциональность.
api("ru.bartwell.delightsqlviewer:runtime:2.0.0")
api("ru.bartwell.delightsqlviewer:core:2.0.0")
// Выберите, что используете:
api("ru.bartwell.delightsqlviewer:sqldelight-adapter:2.0.0")
// или
// api("ru.bartwell.delightsqlviewer:room-adapter:2.0.0")
}
}Инициализация
SQLDelight (Android):
DelightSqlViewer.init( object : SqlDelightEnvironmentProvider() { override fun getDriver() = sqlDelightDriver override fun getContext() = context } )Room (Android):
DelightSqlViewer.init( object : RoomEnvironmentProvider() { override fun getDriver() = roomDatabase override fun getContext() = context } )iOS (пример для SQLDelight):
let provider = SqlDelightEnvironmentProvider(driver: sqlDriver) DelightSqlViewer.shared.doInit(provider: provider, isShortcutEnabled: true)Аналогично и для RoomEnvironmentProvider.
Также, чтобы ярлык корректно работал на iOS, в вашем AppDelegate нужно переопределить методы обработки ярлыков, например:
class AppDelegate: NSObject, UIApplicationDelegate { func application( _ application: UIApplication, configurationForConnecting connectingSceneSession: UISceneSession, options: UIScene.ConnectionOptions ) -> UISceneConfiguration { return ShortcutActionHandler.shared.getConfiguration(session: connectingSceneSession) } }Или вызовите DelightSqlViewer.shared.launch() в обработчике вашего Shortcut.
Desktop:
DelightSqlViewer.init( object : SqlDelightEnvironmentProvider() { override fun getDriver() = sqlDelightDriver } )
Аналогично и для RoomEnvironmentProvider.
Запуск
После инициализации вызов DelightSqlViewer.launch() откроет интерфейс просмотра и редактирования (на Android/iOS можно также запустить через ярлык при долгом тапе на иконку приложения).
Button(onClick = { DelightSqlViewer.launch() }) {
Text("Open DB Viewer")
}Итог
Теперь Delight SQL Viewer покрывает оба основных способа хранения данных в мультиплатформенных проектах — SQLDelight и Room Multiplatform. Если нужно быстро проверить содержимое вашей БД или вручную поправить что-то прямо во время тестирования, эта библиотека может сэкономить кучу времени. Ну и, конечно, легко «прячется» в релизе, чтобы никакого отладочного UI не оставалось.
Ссылка на репозиторий: https://github.com/bartwell/delight-sql-viewer
Буду рад фидбэку и вопросам!
Kotlin Multiplatform на практике: как работать с технологией (видео-туториал)

Выпустили серию видео о базовых аспектах работы с Kotlin Multiplatform, технологией для мультиплатформенной разработки (iOS, Android, Web, Desktop).
KMP позволяет использовать общую кодовую базу для нескольких платформ. Причём мы можем гибко регулировать, какую часть кода делаем общей (например, оставляем нативный UI-слой, а бизнес-логику делаем общей).
Рассказали, как и для чего применять технологию на практике, и как к ней подступиться.
Уже доступны все шесть выпусков туториала:
1. Что из себя представляет технология KMP?
2. Настройка окружения
3. Структура проекта
4. Верстка на SwiftUI
5. Верстка на Compose
6. Сетевой слой
Посмотреть туториал можно здесь:
К каждому выпуску приложен документ с пояснением некоторых терминов и полезными ссылками (см. в описании видео), которые пригодятся при ознакомлении с материалом.

Привет, друзья! Меня зовут Александр Минкин, в компании SM Lab я работаю тимлидом Android-разработки на продукте МП Спортмастер. Сегодня я хочу поделиться с вами подробностями об одном из самых мощных архитектурных паттернов для мобильных приложений - MVI (Model-View-Intent). Этот паттерн помогает создавать приложения с четко разделенной логикой, улучшенной тестируемостью и высокой масштабируемостью. Давайте разберемся в его особенностях и посмотрим примеры кода на языке программирования Kotlin в Android с использованием Kotlin Coroutines, StateFlow и Fragment, а также давайте расширим наш пример с использованием чистой архитектуры, где добавим уровни domain-слоя (UseCases) и data-слоя (Repository). Этот подход поможет вам управлять состояниями приложения более эффективно и реагировать на изменения в реальном времени.
Основные компоненты MVI с чистой архитектурой:
1. Model: Хранит состояние приложения.
2. View: Отображает текущее состояние Model.
3. Intent: Интерпретирует действия пользователя и направляет их в UseCase.
4. UseCase: Содержит бизнес-логику и выполняет операции.
5. Repository: Обеспечивает доступ к данным, будь то локальные или удаленные источники.
Преимущества MVI:
- Однозначное управление состоянием: Все состояния приложения проходят через один поток данных, что упрощает их отслеживание.
- Масштабируемость: Каждый компонент отвечает за свою часть логики, что позволяет легко масштабировать приложение.
- Тестируемость: Изолированные компоненты упрощают написание тестов.
Давайте рассмотрим пример использования MVI в приложении на Kotlin. Допустим, у нас есть приложение для управления задачами. Ссылка на gist:
https://gist.github.com/Rasalexman/53f9be0fc4e295b987e0921839a9a84e
Здесь мы рассмотрели базовый пример использования MVI. Хочу отметить, что в разных реализациях этот архитектурный паттерн, может включать в себя несколько дополнительных компонентов, таких как Reducer - это компонент помогающий преобразовать входное намерение в состояние. В примере выше таким reducer может выступать функция `processIntent`, но чаще его делают как абстракцию в виде интерфейса с функцией reduce, которая принимает Intent и возвращает State. Также мы можем добавить такой компонент как Store - это общая часть для работы с намерениями, которая получает результат обработки намерения и может производить как SideEffects, так и само состояние (State) экрана. Количество компонентов может меняться от сложности реализации, вплоть до добавления специальных объектов middleware и сайд-эффектов (SideEffects). Подробное рассмотрение данных компонентов не входило в текущую статью. С более сложными реализациями вы можете ознакомиться по ссылкам ниже
ORBIT MVI (https://orbit-mvi.org/Core/overview)
MVIKotlin (https://github.com/arkivanov/MVIKotlin)
MVICore (https://github.com/badoo/MVICore)
Roxie (https://github.com/ww-tech/roxie)
Reductor (https://github.com/g000sha256/reduktor)
Заключение:
MVI - это мощный архитектурный паттерн, который помогает разрабатывать структурированные, масштабируемые и легко тестируемые приложения. Его использование позволяет разработчикам четко разделять логику представления, состояния и намерений пользователя, упрощая сопровождение и расширение приложения.
Использование Kotlin Coroutines и StateFlow с архитектурой MVI позволяет вам эффективно управлять состояниями и асинхронными операциями в вашем приложении. Интеграция чистой архитектуры с использованием UseCases и Repository делает ваше приложение более модульным и легко масштабируемым. Это упрощает написание кода, при этом ваше приложение становится более отзывчивым и масштабируемым, а так же удобным в управлении данными и бизнес-логикой, улучшая тестируемость и поддержку кода.
Спасибо, что прочитали данную статью. Буду признателен за комментарии и реакции к этому посту. Делить какую реализацию MVI вы используете у себя в проектах. Всем отличного настроения, помните, что его, как и архитектуру, вы можете выбирать сами ;)
Вклад инженеров Axiom JDK в развитие OpenIDE
На текущий момент среди разработчиков Java в качестве основной среды разработки применяется IntelliJ IDEA. Однако, коммерческая версия этого и других продуктов JetBrains, включая IDE, Code With Me, Upsource, TeamCity и Space, а также техническая поддержка теперь не доступны в России. Это побудило нас на создание продукта OpenIDE с открытым исходным кодом и всей инфраструктурой, размещенной на территории РФ.
Как было анонсировано ранее, OpenIDE создается на базе исходного кода IntelliJ IDEA CE и будет развиваться в рамках некоммерческого партнерства Axiom JDK, «Группы Астра» и Haulmont. В этом посте мы расскажем о вкладе команды Axiom JDK в проект.
Рантайм Axiom JDK будет предоставляться в качестве выбора по умолчанию для разработки на Java/Kotlin в OpenIDE. Дополнительно будет возможна установка Axiom JDK из интерфейса OpenIDE. При этом релизный цикл Axiom JDK синхронизирован с OpenJDK и регулярными обновлениями.
Команда Axiom JDK будет выпускать и поддерживать рантайм, используемый для запуска OpenIDE, с набором улучшений. Это, например, расширенное переопределение классов c помощью DCEVM и поддержка JCEF фреймворка для встраивания браузера на базе Chromium. Также планируется ряд улучшений для рендеринга шрифтов, поддержка режимов HiDPI, что обеспечит лучшее масштабирование интерфейса пользователя. А еще это позволит исправлять специфичные для работы IDE ошибки, исправлений для которых еще нет в OpenJDK.
Несмотря на то, что исходный код IntelliJ IDEA CE открыт, в процессе работы IDEA обращается к серверам JetBrains для обновлений, поддержки маркетплейса плагинов, а также других нужд. Этот функционал сейчас перерабатывается с участием инженеров Axiom JDK, что позволит создать локальную российскую библиотеку плагинов, локализованный (и отключаемый) сбор статистики и механизм обновления OpenIDE.
Наконец, команда Axiom JDK занимается настройкой сборочного конвейера OpenIDE, и со временем произведет анализ всех OSS зависимостей OpenIDE и будет обеспечивать оперативное исправлений уязвимостей в ОSS зависимостях в рамках релизного цикла OpenIDE.
Релиз продукта намечен на март 2025 года.
Axiom JDK — единственный российский разработчик JDK. Инженеры команды стояли у истоков Java в России и развивают платформу более 25 лет.
OpenIDE можно использовать взамен IntelliJ IDEA CE. По данным нашего исследования, 78% Java разработчиков используют IntelliJ IDEA Ultimate, а 47% - работают на Community Edition. Мы хотим предоставить сотням тысяч разработчиков открытый инструмент, не уступающий по удобству привычным IDE, чтобы они могли быстро и эффективно работать.
Типизация на основе вывода типов — популярная функция в Kotlin, и её использование не зависит от характеристик проекта, таких как размер, популярность или количество разработчиков. Например, в тестовом коде вывод типов чаще применяется в объявлениях полей и возвращаемых значениях функций, чем в других местах.

Прошлый мой пост снискал некоторую солидарность, так что имеет смысл написать, что же делать, чтобы починить IDEA/gradle.
Итак, если у вас что-то ошибочно красится красным в IDE, у нас есть все этапы принятия (Отрицание и Гнев можно менять местами):
Отрицание
File -> Invalidate caches... -> Clear file system cache and Local History (опционально отметить галочкой ClearVCS Log caches and indexes, мне иногда почему-то помогало)
Перезагружаем, ждём градл индекса, потенциально - продолжаем гореть
Гнев
Пробуем почистить проект через
./gradlew cleanПробуем собрать через
./gradlew build
Торг - Тут обычно есть два варианта: оно собралось во время Гнева или нет. Если нет - пропускаем второй подпункт
Удаляем контент по пути
~/.m2/repositorygoto Отрицание
Депрессия
НЕ ТРОГАЕМ
~/.gradle/gradle.properties. Удаляем контент по пути~/.gradle/. Я обычно это делаю через файловый менеджер - выделить всё, развыделитьgradle.properties, shift+deletegoto Отрицание
Принятие
Можно переустановить систему, но может не помочь
Лучше проделоть все предыдущие шаги (а вдруг второй раз поможет)
Переклонировать проект (не забудьте закоммитить изменения)
Переустановить IDE (2024.1.6 на данный момент относительно нормально работает, можно даже новый UI выключить) (желательно тогда ещё конфиги и кэши версий удалить, но сами понимаете - потом надо будет ковырять настройки и скачивать IDE)
Другого рецепта счастья у меня для вас сейчас нет, искренне желаю продуктивной работы на фоне стабильных IDE и систем сборки :)
Мы в компании все проекты, какие возможно, пишем на Kotlin. Я всем сердцем его люблю. Оттого больнее то, что с ним происходит...
Сегодня утром мне написал коллега. Говорит, помоги разобраться:
renderConstructor mustn't be called with an intersection type argument. Call render to simply render the type or filter out intersection types on the call-site.Решилось какой-то там пересборкой, возможно, какой-то очисткой кэша. Казалось бы, ну мы же инженеры, почему у нас "какая-то пересборка" и "возможно"? Вот в этом и проблема.
Примерно с год-два я наблюдаю, как в котлине творятся проблемы с:
Надёжностью сборок. То тут, то там какие-то приколы со странными ошибками, которые то есть, то нет
Тулингом. Постоянные косяки в подсветке в IDE, отсутствие удобных визардов для создания проектов, особенно для проектов вроде на базе Kotlin Multiplatform
Библиотеками. Не все базовые вещи закрыты (привет, kotlinx-io без поддержки файлов в JS Browser), Compose Multiplatform пытается сделаться кроссплатформенным (привет, Compose Web, который не использует нативные HTML тэги и CSS стили)
Я понимаю, что сейчас происходит переход на K2 (не все библиотеки на него перешли => вскрываются новые проблемы время от времени). Я понимаю, что язык и его рабочее окружение пытается двигаться. Но я не понимаю, почему я не могу запустить gradlew clean build и не бояться, что какие-то кэши сборки затрутся во время этой же сборки.
Я надеюсь, что котлин будет становиться лучше. Но я пока не понимаю, когда.
Мы создали свой инструмент для доставки сборок Android-приложений
В условиях блокировки официального сервиса Firebase App Distribution в России, перед нашим отделом мобильной разработки встала задача создать собственный инструмент для доставки сборок Android-приложений тестировщикам.
Да, мы могли бы пользоваться Firebase App Distribution с помощью VPN, но это не очень удобно. VPN-сервисы ненадежны, потому что подвержены блокировкам. К тому же создание собственного решения позволяет добавлять новые функции и адаптировать инструмент под конкретные нужды команды.
На многих проектах огромное количество времени уходило на то, чтобы достать сборку из GitLab или собрать ее вручную и отправить тестировщику. Также это сильно отвлекает от работы над задачами. Решением стала разработка инструмента, который будет принимать, хранить и раздавать файлы сборок.
Для его реализации выбрали фреймворк Ktor. Благодаря этому любой разработчик, знакомый с Kotlin, сможет быстро разобраться в кодовой базе, поддерживать инструмент и интегрировать его с различными проектами. А в качестве интерфейса решили выбрать Telegram. Наши рабочие чаты чаще всего находятся именно там. Кроме того, Telegram Bot API предоставляет много возможностей, хоть и имеет некоторые ограничения.
Делимся исходным кодом этого инструмента. А подробная инструкция по работе с ним — в отдельной статье.
В scala 3 есть свои context receivers и они не похожи на то что есть в Kotlin!
object PostConditions:
opaque type WrappedResult[T] = T
def result[T](using r: WrappedResult[T]): T = r
extension [T](x: T)
def ensuring(condition: WrappedResult[T] ?=> Boolean): T =
assert(condition(using x))
x
end PostConditions
import PostConditions.{ensuring, result}
val s = List(1, 2, 3).sum.ensuring(result == 6)Разберу по строчкам:
opaque type WrappedResult[T] = T: непрозрачный тип. Внутри объекта PostConditions компилятор "знает" что это один и тот же тип, снаружи смотрит на них как на два разных типа. Это не даст перепутать тип с типом обёртки, и, например, случайно присвоить одно в другое.
def result[T](using r: WrappedResult[T]): T = r: функция, которая принимает неявный параметр откуда-то из контекста и возвращает его как нормальное значение.
extension [T](x: T): extension метод, который принимает лямбду. Лямбда принимает неявный параметр, а метод ensuring его предоставляет.
def ensuring(condition: WrappedResult[T] ?=> Boolean): T = ....
val s = List(1, 2, 3).sum.ensuring(result == 6)Метод списка sum возвращает Int, для него вызывается вышенаписанный метод ensuring, в который передаётся лямба. внутри лямбды есть неявный параметр и потому там (и только там, больше нигде) можно вызвать функцию result, которая вернёт этот самый T.
В коде где-либо снаружи PostConditions объект типа WrappedResult[T] не получится создать.
Мнение: почему мультиплатформа Kotlin не приживется в мире разработки
Спойлер: из-за нелюбви к изменениям и дефицита специалистов.
Наш руководитель направления Flutter и iOS Саша Ворожищев делится основными тезисами статьи о судьбе Kotlin Multiplatform Mobile (KMM). Автор статьи — разработчик Донн Фелкер (Donn Felker) — не пророчит KMM мировой славы. И вот почему.
Люди не любят что-то менять. Особенно программисты. Редко встретишь iOS-разработчика, который перешел на Android. И наоборот.
Кросс-платформа чаще не оправдывает надежд. Мы уже не раз наблюдали попытки создать кросс-платформенные суперхиты — взять к примеру Flash и ActionScript, Mono и Xamarin, — но ни одна из этих технологий так и не покорила мир программирования.
Сейчас конкуренцию КММ может составить только Flutter. Пока это лучшее кросс-платформенное решение.
Охота на единорога. Чтобы стать классным кросс-платформенным разработчиком, нужно разбираться не в одном, а сразу в нескольких языках и инструментах. Между тем, хороших программистов-полиглотов мало и они стоят дорого. Еще они часто выгорают, ведь технологии развиваются слишком быстро, а успеть сразу за всеми очень сложно.
Универсальность != эффективность. Здесь стоит повторить, как важно подбирать правильные инструменты для решения конкретных задач. Мультиплатформа не может быть панацеей для всех проблем.
Полную версию статьи читайте тут.
А больше про технологии и разработку найдете в телеграм-канале Саши. Приходите, там интересно :)
Таймер на LaunchedEffect в Compose
LaunchedEffect(keyN) запустит свою лямбду, в начале композиции. LaunchedEffect будет запускать свою лямбду снова каждый раз, когда изменится один из заданных ключей keyN
Причем в лямбде будет находится coroutine context
@Composable
fun Timer() {
var seconds by remember { mutableStateOf(0) }
LaunchedEffect(seconds) {
delay(1000)
seconds++
}
Text(text = "Seconds: $seconds")
}mutableStateOf - Создает State при изменении которого compose функция перезапускается
remember - сохраняет этот State при каждом перезапуске функции
Можно ли организовать свое собственное расширение в Kotlin? Это можно сделать через Delegate
class MyString {
fun myPrint(text: String) {
println(text)
}
}Есть класс MyString с методом myPrint(). Допустим нам надо сделать собственное расширение printRealExt() без использование встроенных в Kotlin возможностей
fun MyString.printRealExt(text: String) {
println(text)
}Реализуем интерфейс и класс с нашим методом "расширения"
interface ExtensionPrint {
fun printExt(text: String)
}
class ExtensionPrintImpl : ExtensionPrint {
override fun printExt(text: String) {
println(text)
}
}А затем воспользовавшись Delegation делегируем этот метод
class MyString : ExtensionPrint by ExtensionPrintImpl() {
fun myPrint(text: String) {
println(text)
}
}Синтаксис вызова из расширения и из делегата выглядит одинаково
val myString = MyString()
myString.printRealExt("Real ext")
myString.printExt("Text from ext")Таким же образом можно организовать множественное наследование в Kotlin
interface OneClassInterface {
fun someOne()
}
class OneClassImpl : OneClassInterface {
override fun someOne() {
println("some One")
}
}
interface TwoClassInterface {
fun someTwo()
}
class TwoClassImpl : TwoClassInterface {
override fun someTwo() {
println("someTwo")
}
}
class DerivedClass : OneClassInterface by OneClassImpl(), TwoClassInterface by TwoClassImpl()
Есть другие способы?

