job4j.ru - Практика. Ревью. Результат.
771 subscribers
75 photos
186 links
О жизни в IT:
- Языки: Go, Rust, Java, Kotlin
- Технологии: PostgreSQL, Kafka
- Инфраструктура: Docker, Kubernetes
- Soft Skills: общение, интервью, карьера
Download Telegram
Лингво-тренер | Обновления


Теперь бот сохраняет историю переписки и отвечает в контексте вашего общения.
Ранее он терял нить повествования.
Проблема оказалось в том, что передавать уникальный ключ чата было недостаточно, чтобы бот помнил историю. Пришлось отправлять ему последние 5 сообщений переписки. Это увеличивает кол-во токенов, но зато дает релевантный ответ. Ответы стали короче и соответствуют обычному разговору, а не запросы в справочник.

Пробуйте @TalkSharp_bot
🔥6
Генерация речи в текст

В моем проекте @TalkSharp_bot изначально я использовал gTTS библиотеку для генерации речи.

Для первый версии этого достаточно.

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

Плюс gTTS довольно медленно работает. А для бота, который предполагает активное общение это критично.

Я посмотрел несколько решений и выбрал https://rhasspy.github.io/piper-samples/

Потрясающая библиотека.

Добавил ее в бота. Теперь приятно случать и фразы и просто истории, которые создает бот. Далее добавлю выбор акцента.

И конечно главное, скорость стала заметно быстрее.

Пробуйте @TalkSharp_bot
🔥7
Решение о рефакторинге метода на 267 строк

В этом посте я показал одно из своих решений в проекте @TalkSharp_bot.

https://t.me/java_kotlin_hub/137

На самом деле таких мест в проекте несколько. Вот ещё одно.

public Optional<Integer> sent(Content content) {  
try {
if (content.getDeleteMessageId() != null) {
execute(
new DeleteMessage(
content.getChatId().toString(),
content.getDeleteMessageId())
);
return Optional.empty();
} else if (content.getPinMessageId() != null) {
var message = new PinChatMessage();
message.setChatId(content.getChatId());
message.setMessageId(content.getPinMessageId());
message.setDisableNotification(true);
execute(message);
return Optional.of(message.getMessageId());
} else if (content.getUnpinMessageId() != null) {
var message = new UnpinChatMessage();
message.setChatId(content.getChatId());
message.setMessageId(content.getUpdateMessageId());
execute(message);
return Optional.of(message.getMessageId());
} else if (content.getMenu() != null) {
SendMessage message = new SendMessage();
message.setChatId(content.getChatId());
message.setText(md5Corrector.correctPairSymbols((content.getText())));
message.setParseMode("MarkdownV2");
var markup = new ReplyKeyboardMarkup();
markup.setKeyboard(content.getMenu());
markup.setResizeKeyboard(true);
message.setReplyMarkup(markup);
return Optional.of(execute(message).getMessageId());
}


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

https://github.com/peterarsentev/code_quality_principles?tab=readme-ov-file#2-dispatch-pattern-instead-of-multiple-if-statements-and-switch-anti-pattern

Это так называемый шаблон диспетчер.

Давайте на примере кода выше я объясню, как его можно было применить.

В коде выше есть один неудобный нюанс: метод execute – это не метод одного класса, а метод перегруженый в 100500 классах.

То есть мы не можем описать интерфейс
interface Route {
boolean check(Content content);
Message process(Content content);
}

поскольку нет однородного объекта для передачи.

В этом случае нам нужно явно передать сервис в параметр, чтобы обойти эту проблему.
public interface ContentType {  

boolean check(Content content);

Optional<Integer> process(ReceiveUpdateService sender, Content content)
throws TelegramApiException;
}


Теперь перепишем метод вызова.
public Optional<Integer> sent(Content content) {  
try {
return contentRoute.process(this, content);
} catch (TelegramApiException e) {
log.error("The error occurred sending a message", e);
}
return Optional.empty();
}

Класс ContentRoute, который аккумулирует обработчики.
@Service  
@AllArgsConstructor
public class ContentRoute {
private final List<ContentType> contentType;

public Optional<Integer> process(ReceiveUpdateService sender, Content content)
throws TelegramApiException {
for (var type : contentType) {
if (type.check(content)) {
return type.process(sender, content);
}
}
return Optional.empty();
}
}

Но это ещё не всё. Есть сценарий, который должен выполниться, если мы не нашли подходящего потребителя. В данном случае, если объект Content не содержит ничего, кроме текста, то нужно отправить только текст.

Важно отметить, что мы не можем просто проверить
content.text != null

поскольку текст отправляется в других сценариях, например, в случае отправки кнопок.

Опять же, оставляю этот пост с открытым вопросом: как в этом случае поправить код?
👍4
Обработчик отправки только текста.
public class TextContent implements ContentType {  
private final MD5Corrector md5Corrector;

@Override
public boolean check(Content content) {
return true;
}

@Override
public Optional<Integer> process(ReceiveUpdateService sender, Content content)
throws TelegramApiException {
var message = new SendMessage();


Хотите прокачаться в теме ООП, Spring Core и написать своего телеграм-бота?

Заходите сюда: https://job4j.ru/profile/courses/34
👍8
ITTalkBot | Обновления

1. Поправил запросы, теперь отвечает четко по теме.
2. Вернул возможность отвечать текстом.

Пробуйте @ITTalkBot
🤣2🔥1😁1
Системный дизайн | Хранение пользовательских настроек

После завершения блока DevOps напомню, что его ещё можно приобрести со скидкой за 7990 рублей (https://job4j.ru/profile/courses/35).

Я планирую выбрать в качестве самой сложной темы системный дизайн.

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

То есть однозначных решений здесь не может быть и не должно быть.

Это напоминает мне аналогию с решением уравнений с неизвестными параметрами, где всегда можно добавить новое условие.

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

Однако можно рассмотреть ряд популярных задач и на их основе вести рассуждения.

В сегодняшней статье я хотел предложить вам решение задачи по проектированию хранения пользовательских настроек.

Проблема появилась в моём проекте (https://t.me/talksharp_bot).

В первоначальной версии каждая настройка имела только одно значение, например: изучаемый язык, язык интерфейса, скорость речи и т.д.

После подключения языковой модели (https://github.com/rhasspy/piper) пользователь сможет выбирать голос для озвучивания сообщений, но для каждого языка необходимо указывать свою модель.

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

Изначально настройки ассоциируются с ключом

public enum UserConfigKey {
TARGET_LANG(0),
LEVEL_LANG(1),
TOPIC_ID(2),
QUESTION_ID(3);

public final int key;

UserConfigKey(int key) {
this.key = key;
}
}


Постановка задачи.

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

Решения пишите в комментариях.
🔥1
Тренды IT

Перенёс функции мониторинга рынка с CheckDev на Job4J. Это единственное, что осталось от проекта CheckDev.

Планирую добавить графики и общий индекс.

По трендам можно сказать, что лидеры — это JS, Java, C#, PHP, ML и Rust.

Не пишу здесь про Python, так как он требует сочетания с ML.

В целом, по общему индексу, рынок падает.

Я связываю это с ситуацией с СВО, которая оказывает огромное влияние на все сферы.

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

https://job4j.ru/profile/
👍8🔥4
DevOps | Обновления

Добавил урок.

Объекты K8s | ConfigMap [#505383]

Описание курса https://job4j.ru/profile/courses/35
Еще пока со скидкой - 7990 р.
👍8❤3
DevOps | Новые уроки

Объекты K8s | Secret [#505384]
Pipeline для K8s [#505385]

В уроке Pipeline для K8s [#505385] ваша любимка - настройка агента Jenkins через SSH ☺️
🔥9👍4
TalkSharp | Лингво-тренер | Короткие диалоги

Добавил в Лингво-тренера @talksharp_bot возможность установить роль для отработки коротких диалогов. Например, заказ такси или регистрация в отеле.

Бывает, нет настроения общаться на сложные темы, а такой режим отлично подойдет для тренировки повседневных фраз.

Он также отлично подойдет для начального уровня языка.

Также добавил график словарного заучивания. Настройки / Профиль / График словаря.

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

Пробуйте @talksharp_bot
👍3🔥1
Системный дизайн | Код-ревью

Ниже приведен кусок кода из микросервиса.

Statistics statistics = statisticsRepository.findById(query).orElse(null);
if (s == null) {
s = new Statistics(query);
s.setNumberOfUses(0);
}
statistics.setNumberOfUses(statistics.getNumberOfUses() + 1);
statisticsRepository.save(statistics);


Напишите в комментариях, какие могут возникнуть ошибки и предложите несколько возможных решений с указанием достоинств и недостатков.
👍4
Курс DevOps | Новые уроки

Добавил урок по Helm
Обзор | Установка Helm Charts [#505387]

Залейте https://job4j.ru/profile/courses/35 еще со скидкой за 7990.
❤6
Курс DevOps | Завершения раздела K8s

Обзор | Установка Helm Charts [#505387]
Объекты K8s | Ingress [#505386]
Создание Helm Charts для проекта job4j_k8s [#505388]
Тестовое задание | DevOps | 6 дней [#505389]

Последнюю задачу не дописал. Это тестовое задание, которые мне скинули на стажировку DevOps.
👍5❤4
Предложения и баги | Проекта IT-Тренер и Лингво-Тренер

Добавил issue трекеры для проектов @ITTalkBot и @talksharp_bot

https://github.com/peterarsentev/ITTalkBotFeaturesBugs/issues
https://github.com/peterarsentev/TalkSharpFeaturesBugs/issues

Буду раз услышать предложения или баги.
❤4👍2👌2
Документация как источник истины

Доделаю курс по DevOps. Сегодня открыл два урока по версионированию.

1. Версии в Git | Git Tags [#505390]
2. Версии Rest API [#505395]

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

В этой статье хотел поговорить в целом о версионировании.

У меня был такой случай. В TrackStudio появился новый клиент. Причем это была крупная розничная сеть, поэтому мы старались его удержать. С их стороны с нами общался крайне скрупулезный человек. Из-за этого мне пришлось несколько дней тестировать TrackStudio с разными версиями JDBC под разные базы данных. В целом, этому человеку нужна была подробная спецификация нашего продукта, в том числе, какие версии баз данных мы поддерживаем и так далее. На тот момент я считал этого человека странным клиентом.

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

Здесь же хочу упомянуть частый случай конфликтов между командами серверной и клиентской частей, а также отделом тестирования. Если у тебя не работает интеграция или тесты, то, конечно, виноваты они.

Чтобы избежать таких проблем, нужно вести спецификацию, в которой указываются версии совместимых продуктов, проектов, API и программного окружения. Если коротко, то у вас должны быть версии всего, и они должны быть где-то зарегистрированы.

Приведу список, а вы можете проверить по своему проекту:

1. Версия проекта, указанная в Maven или Gradle.
2. Версии проекта, указанные в Git Tags.
3. Версии окружения, описанные в Ansible.
4. Версии REST API, описанные в Swagger. Лучше, если это отдельная спецификация, а не обертка поверх контроллеров.
5. Версии асинхронного API (например, Kafka).
6. Версии бэкапов баз данных.
7. Версии бинарных архивов.
8. Версии зависимостей (например, библиотек, фреймворков, плагинов) в проекте.
9. Версии контейнеров Docker, если проект использует контейнеризацию.
10. Версии инструментов CI/CD (например, Jenkins, GitLab CI, CircleCI).
11. Версии операционных систем и их зависимостей, используемых для разработки и развертывания.
12. Версии базы данных (например, PostgreSQL, MySQL) и используемых драйверов.
13. Версии библиотек и фреймворков для фронтенда (например, React, Angular, Vue).
14. Версии инструментов мониторинга и логирования (например, Prometheus, ELK stack).
15. Версии схем баз данных, описанные в миграциях (например, Flyway, Liquibase).
16. Версии системы управления конфигурациями (например, Consul, etcd).
17. Версии систем безопасности (например, VPN, аутентификация).

Частично эти элементы рассматриваются в курсе по DevOps: https://job4j.ru/profile/courses/35.

Пока еще со скидкой 7990 рублей.
🔥11
Свидетели идиоматического Kotlin | Секта СИК

Где-то неделю назад я в чате написал, что свои хобби-проекты @ITTalkBot и @talksharp_bot начал на Java, а не на Kotlin.

Хотя платформу job4j.ru/profile и антиспам-бота @dionea_bot я делаю на Kotlin.

В этой статье я хотел бы объяснить причину.

В целом, стоит начать с того, что всех программистов, которые постоянно используют Kotlin, можно разделить на две группы. Деление происходит по тому, как этот язык используется. Первая группа относится к Kotlin как к Java++ — то есть улучшенной версии языка Java.

Приведу пример. Работа с шаблонами в Java 21 сделана через форматтеры.
"""
User name: %s
"""
.formatted(user.name)

Или
STR."User name: \{ user.name }"

Второй вариант уже лучше, но он не сравнится с лаконичностью Kotlin:
"User name: ${usern.name}"


Такие некрасивые элементы в языке Java заменяются идиомами в Kotlin.

Ниже приведён список распространённых идиом (приёмов) при программировании на Kotlin, которые помогут писать более читаемый, лаконичный и безопасный код:

- Использование `val` вместо `var` по умолчанию:
По возможности следует объявлять переменные неизменяемыми с помощью val, что улучшает безопасность и предсказуемость кода.

- Null Safety и операторы `?.`, `?:`, `!!`:
Kotlin обладает встроенной поддержкой безопасного обращения с null-значениями. Оператор безопасного вызова (?.) и оператор Элвиса (?:) помогают избежать ошибок, связанных с NullPointerException.

- Data-классы:
Использование ключевого слова data позволяет автоматически генерировать методы equals(), hashCode(), toString(), copy() и компонентные функции для классов, предназначенных для хранения данных.

- Smart Casts:
После проверки типа или на null переменные автоматически приводятся к нужному типу без явного кастинга.

- Extension Functions:
Позволяют добавлять новые функции в существующие классы без необходимости наследования или использования шаблонов проектирования.

- Функции высшего порядка и лямбда-выражения:
Kotlin широко использует лямбды и функции высшего порядка, такие как let, run, apply, also, для упрощения работы с объектами и цепочек вызовов.

- Запечатанные (sealed) классы:
Используются для ограничения иерархии классов, что особенно полезно при моделировании состояний или событий с исчерпывающим перечислением вариантов.

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

- Компаньон-объекты:
Вместо статических членов, как в Java, используются объекты-компаньоны для размещения функций и переменных, связанных с классом, но не зависящих от экземпляров.

- Деструктуризация:
Позволяет распаковывать объекты и коллекции в отдельные переменные, что делает код более лаконичным и удобочитаемым.

- Перегрузка операторов:
Kotlin позволяет перегружать стандартные операторы для пользовательских типов, делая использование классов интуитивно понятным.

- Инфиксная запись:
Для функций с одним параметром можно использовать инфиксную запись, что улучшает читаемость при вызове функций, имитирующих операционные выражения.

И в сочетании с множеством библиотек и готовых решений экосистемы Java, Kotlin выглядит действительно классным решением. Именно в таком контексте я и применяю Kotlin. Он позволяет убрать шум из моего Java-кода. По такому же принципу я создавал курс по Kotlin: [https://job4j.ru/profile/courses/22](https://job4j.ru/profile/courses/22).

Но есть еще другая группа программистов, которых я называю сектой свидетелей идиоматического Kotlin.

Адептов этой секты крайне просто узнать. Если ты говоришь, что пишешь на Kotlin и показываешь своё решение, то любой код будет оценён одним и тем же вердиктом: ты пишешь как на Java, ты не умеешь писать на истинном Kotlin, ты не из «голубой крови».

Любые просьбы объяснить или показать истинный Kotlin-код ссылаются на реализованные библиотеки от компании JetBrains с комментарием: «Смотри исходники и учись».
👍4
В 2018 году я также увлёкся лозунгами языка нового уровня с новой парадигмой. До этого я уже поработал в проекте со Scala, но продолжать на нём я не хотел. Посмотрев все выступления Андрея Бреслава, прочитав и пройдя его курсы, я понял, что не могу уловить смысл новой парадигмы. Есть идиомы, нет парадигмы, нет принципов. В одном из докладов я всё же нашёл примерный манифест адептов СИК:

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

Пройдем по ним. Сначала выделим те принципы, которые я также пропагандирую в своём курсе и считаю крайне важными:
1. Неизменяемые состояния. Это крайне важный принцип, который стоит учитывать в любом языке.
2. Наследование. У меня даже был урок в курсе, где я однозначно указывал, что наследование — это зло. Я придерживаюсь этого мнения до сих пор, но не так радикально. Его можно использовать, например, в описании моделей данных, но не в сервисах.
3. Зона видимости. Я бы даже добавил, что здесь речь идёт не о зоне видимости, а о зоне ответственности. То есть, это принципы, описанные в книге Роберта Мартина «Чистая архитектура», а зона видимости — это просто частный случай того, как это можно реализовать.

Гибридная парадигма — это общие слова, ведь сегодня любой современный язык позволяет писать в трёх парадигмах.

Теперь обсудим то, чего не хватает.

Разделение данных и функций, а не создание объектов с состояниями и методами.
Использование функций там, где можно обойтись без классов.

Давайте попробуем спроектировать классический проект Rest API без базы данных.

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

fun server(port: Int) : ??


Я хотел описать функцию, которая является точкой входа в приложение и сразу возвращала объект Server. Но так нельзя, потому что этот объект будет хранить состояние сервера. Давайте предположим, что функция ничего не возвращает.

fun server(port: Int) {
var socket = ServerSocket(port)
while (socket.accept()) {

}
}


Ха. Получилось. Нам нужно добавить обработчики запросов.
fun route(): Map<String, (req: Req) -> Resp> {
return mapOf(
"home" to home(req),
"about" to about(req)
)
}


Поправим server:
fun server(port: Int, routes: Map<String, (req: Req) -> Resp>) {
var socket = ServerSocket(port)
while (socket.accept()) {
routes[socket...]
}
}


В целом получается, что можно продолжать писать так. В проекте Job4j.ru я поступил именно так: не стал использовать никакие библиотеки и решил написать всё сам с нуля.

Конечно, можно было бы использовать библиотеки, но важно отметить, что в истинном Kotlin следует использовать только библиотеки, описанные в манифесте Kotlin; нельзя взять Spring Boot, Spring JPA — нужно выбрать альтернативное решение, написанное на Kotlin. Например, Ktor + Exposed.
👍3
import io.ktor.application.*
import io.ktor.http.*
import io.ktor.response.*
import io.ktor.routing.*
import io.ktor.server.engine.*
import io.ktor.server.netty.*
import org.jetbrains.exposed.sql.*
import org.jetbrains.exposed.sql.transactions.transaction
import org.jetbrains.exposed.sql.SchemaUtils

// Определение таблицы пользователей
object Users : Table() {
val id = integer("id").autoIncrement()
val name = varchar("name", length = 50)
override val primaryKey = PrimaryKey(id)
}

// Инициализация подключения к базе и создание таблицы с тестовыми данными
fun initDB() {
// Подключаем in-memory базу H2
Database.connect("jdbc:h2:mem:test;DB_CLOSE_DELAY=-1;", driver = "org.h2.Driver")
transaction {
// Создание таблицы
SchemaUtils.create(Users)
// Добавление тестовой записи
Users.insert {
it[name] = "John Doe"
}
}
}

fun main() {
// Инициализируем базу данных
initDB()

// Запускаем Ktor-сервер
embeddedServer(Netty, port = 8080) {
routing {
// Корневой маршрут
get("/") {
call.respondText("Hello, Ktor with Exposed!", ContentType.Text.Plain)
}
// Маршрут для получения списка пользователей из базы
get("/users") {
// Чтение данных из таблицы Users
val users = transaction {
Users.selectAll().map { "${it[Users.id]}: ${it[Users.name]}" }
}
call.respondText(users.joinToString(separator = "\n"), ContentType.Text.Plain)
}
}
}.start(wait = true)
}


Но код всё равно будет написан в идеологии, которую я привёл выше, то есть функция принимает объекты, которые содержат состояние (например, пул коннектов), что в целом уже нарушает описанный манифест.

fun poolDb() : Pool

fun server(port: Int, routes: Map<String, (req: Req) -> Resp>, poolDb) {
var socket = ServerSocket(port)
while (socket.accept()) {
routes[socket...]
}
}


То есть, не всё так гладко.

Я не затронул отдельную тему — корутины. Корутины действительно являются одним из хедлайнеров в языке Kotlin. Отдаю своё уважение команде Роман Елизаров. Но, как я уже писал в другом посте, существует множество сеньоров и тимлидов, которые даже обычный thread pool не используют в проектах. Поэтому корутины — это не элемент для большинства программистов.

А теперь минусы.

Если рассматривать первую группу программистов, которые относятся к Kotlin как к Java++, то тут можно сказать многое. Перейти на использование Kotlin в таком ключе несложно. У меня много примеров выпускников, которые сразу начинали работать в проекте с Kotlin, даже несмотря на то, что у них 0 опыта в этом языке.

А теперь перейдём к минусам второй группы.

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

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

Второй момент. Даёт ли использование истинного Kotlin значительные плюсы? Не особо. Нам всё равно нужно придумывать и проектировать архитектуру, писать тесты, использовать системы статического анализа, заниматься приемочным и ручным тестированием. Код становится проще поддерживаемым? Уверяю вас, код становится проще поддерживаемым, если его спроектировали правильно, по принципам чистой архитектуры, а не с помощью идиом языка.

Третий момент. Найм. Я могу посадить любого ученика на свой проект, который прошёл блок SQL [https://job4j.ru/profile/courses/7](https://job4j.ru/profile/courses/7), и он сразу будет выполнять задачи, потому что ему будет понятен код.
А что там с рынком? Ищем по слову ktor — 48 вакансий. Удачи, ребята: если что, всегда можно пойти курьером в ДОДО или завести пасеку.
👍8
Подведу итог.

В целом, язык Kotlin — прекрасный язык, который позволяет писать выразительный код в экосистеме Java. Его стоит попробовать любому Java-программисту, но ожидать от него решения глобальных проблем в проектах не стоит. Это не волшебная палочка, а всего лишь удобный инструмент.
👍11