(java || kotlin) && devOps
369 subscribers
6 photos
1 video
6 files
306 links
Полезное про Java и Kotlin - фреймворки, паттерны, тесты, тонкости JVM. Немного архитектуры. И DevOps, куда без него
Download Telegram
Всем привет!

Хочу разбавить тему микросервисов. Сегодня поговорим про Spring и Dependency Injection (DI).
Есть три способа внедрения бина:
1) через конструктор
2) через поле
3) через setter
Практически все слышали, что предпочтительнее внедрять через конструктор. Вопрос почему?

Первая и самая главная причина: внедрение зависимостей - это часть инициализации объекта. А конструктор - это простой и естественный способ инициализировать объект.
Что из этого следует:
1) список внедряемых бинов легко читается. Через setter-ы очень плохо читается, через поля получше, но конструктор вне конкуренции.
2) становится проще создать полностью инициализированный объект т.к. для этого есть готовый конструктор. Полезно в тестах, можно написать тест на чистом JUnit, без @SpringBootTest и других инструментов для инициализации контекста из Spring Test.
3) если классу нужно слишком много бинов для работы - SonarQube будет ругаться, что у конструктора слишком много полей. Считаю это плюсом, если ругается - это повод задуматься над рефакторингом
4) если у бинов циклические ссылки - инициализация контекста Spring упадет. Многие считают это минусом, но я не согласен: чем раньше найдешь циклическую зависимость - тем быстрее исправишь. См. https://t.me/javaKotlinDevOps/53
5) если с внедряемыми бинами надо что-то делать, то у нас есть место, где гарантированно все бины будут доступны - конструктор. При внедрении зависимостей через setter или поля вначале выполнится дефолтный конструктор, потом будут проиницилизированы поля.
6) для инициализации не используется рефлексия, как в случае autowired полей, т.е. инициализации работает немного быстрее
7) если конструктор единственный, то начиная со Spring 4.3 не нужно указывать аннотацию @Autowired. Для Kotlin в случае default конструктора есть особенности, для того, чтобы с ними не сталкиваться лучше использовать Spring 5+ ) Не сказать, что это большое преимущество, т.к. аннотацию для маркировки бина все равно придется использовать, поэтому класс без import Spring зависимостей так просто сделать не получится.

И второй важный момент - поддержка иммутабельности объектов, т.к. только в конструкторе можно инициализировать final поля в случае Java и val - в случае Kotlin.

#spring #java #kotlin #interview_question
Всем привет!

Продолжается перерыв в теме микросервисов.
Сегодня будет немного холивара)

Можно ли использовать static методы?

Исторически static-и прошли 3 стадии в своем восприятии.

1 этап: о круто, можно не создавать объект, давайте все методы делать статическими.
Если что - я конечно утрирую насчет всех, но static-и активно использовались, это факт, который легко увидеть в legacy коде. Еще факт: static-и есть во многих языках, что как бы намекает. Даже в Kotlin они есть, просто выглядят странно - я про companion object.

2 этап: static-и трудно тестировать, это антипаттерн, не надо их использовать. Ну разве что для Util классов, и то ...
Вот пример, демонстрирующий почему возникают такие мысли.
Возьмем другой антипаттерн - синглетон.

class Singleton {
private static final instance = new Singleton();
public static Singleton getInstance() {
return instance;
}
....
}
Сразу скажу - это не одна из простейших реализаций, не потокобезопасная, взятая для примера, так делать не надо.

Предположим, мы хотим сделать вместо Singleton заглушку для теста. Ну например, он ходит в БД или читает данные из файла, а мы хотим, чтобы тесты работали быстро.
Можно сделать так:
class TestSingleton extends Singleton {
private static final testInstance = new TestSingleton();
public static Singleton getTestInstance() {
return testInstance;
}
....
}
Но это работает только если Sigleton можно как-то передать в тестируемый класс, он же System under Test (SUT).
А если это зависимость зависимости SUT? Или в более общем случае его нельзя передать через public API?
Основная проблема в том, что static методы не наследуются, а следовательно переопределить их нельзя. Можно создать метод с тем же именем, но вызываться будет метод того класса, который указан в конкретном куске кода. Или декларируемого, а не фактического класса переменной, поля или параметра, если static вызывается на объекте. В этом плане Java позволяя вызывать static метод из объекта только путает людей( К слову, в Kotlin так нельзя.

Есть конечно грязные хаки с мокированием static методов, даже Mockito это умеет. Но тот факт, что для включения этой фичи нужно добавить настройку в classpath https://www.baeldung.com/mockito-mock-static-methods говорит о том, что авторы Mockito не рекомендуют так делать.

3 этап, наше время: все не так однозначно. Точнее однозначно вот что: если static метод используется в паре со static полями - это точно зло.
И самостоятельное создание синглетонов тоже зло) Это я на всякий случай уточняю)
Но если присмотрется к вот такому static методу:
1) он не имеет доступа к полям объекта по определению
2) в классе нет static полей как я писал выше
3) пусть метод не имеет побочных эффектов. Т.е. не лезет в БД, в файловую систему, в другие сервисы
Т.е метод получает что-то на вход, вычисляет что-то и возвращает результат.
Это типичный метод Util класса, да.
Но еще это определение функции из математики. А функция - это функциональное программирование. А Java начиная с 8 версии умеет передавать ссылки на функции. И хотя в ней нет функциональных типов, но есть функциональные интерфейсы, которые делают тоже самое, просто немного с большим количеством кода. Java же, все как мы любим)

Подводя итог - считать static злом не надо. Надо лишь правильно его использовать.

#interview_question #holy_war #java #kotlin
Всем привет!

Возвращаясь к Kotlin и переходу на него с Java.
Может возникнуть вопрос - как начать писать в стиле Kotlin?
Ответ - начни с официальной документации.

Ключевые отличия от Java по мнению авторов: https://kotlinlang.org/docs/comparison-to-java.html
Идиомы - часто используемые куски кода, можно сказать низкоуровневые паттерны языка: https://kotlinlang.org/docs/idioms.html
Coding conventions https://kotlinlang.org/docs/coding-conventions.html
Примеры кода от авторов языка: https://play.kotlinlang.org/byExample/01_introduction/01_Hello%20world
Migration guide с Java https://kotlinlang.org/docs/java-to-kotlin-idioms-strings.html

P.S. Надо было раньше этот пост написать)
P.P.S Документацию иногда стоит почитать)
P....S А для самых хардкорных - как известно Kotlin написан на Kotlin. https://github.com/JetBrains/kotlin

#java #kotlin
Всем привет!

Снова про Kotlin.
Возможно не все знают, что в Kotlin изобрели свои стримы - sequences https://kotlinlang.org/docs/sequences.html#sequence.
Зачем? Во-первых есть Kotlin/JS и Kotlin/Native, где нет JDK и стримов. А еще Kotlin может работать на Java 6.
А во-вторых - реализация стримов сделана под Java, что приводит к более сложному API, чем "принято" в Kotlin и проблемами с null safety из-за использования типов Java под капотом. Но у стримов есть и плюсы) Сравнение см. https://proandroiddev.com/java-streams-vs-kotlin-sequences-c9ae080abfdc

P.S. Есть некая ирония в том, что стримы, которые сильно упрощают код Java, выглядят все же более тяжеловесно по сравнению с реализацией в Kotlin)

#kotlin #java
Всем привет!

Небольшое замечание. О важности проблемы null safety в Java говорит вот этот список различных видов @Null\@NotNull аннотаций Java, которые поддерживает Kotlin при проверке типов: https://kotlinlang.org/docs/java-interop.html#nullability-annotations К слову, все они не входят в стандартную библиотеку Java.

#java #kotlin #nullsafety
Всем привет в 2023 году!

Уже писал про плюсы Kotlin, забыл про еще один - язык подталкивает к правильному написанию кода по умолчанию. Что имеется в виду:

1) все типы по умолчанию not null, для nullable типа нужно добавить ? к названию типа. String и String? Что дает: уменьшает число переменных с null в программе, следовательно, уменьшает число NPE
2) все коллекции по умолчанию immutable, чтобы создать изменяемую коллекцию надо использовать метод с mutable в названии: listOf(1,2) и mutableListOf(1,2). Что дает: упрощает оптимизацию байт-кода компилятором или JVM.
3) все классы и методы по умолчанию final, чтобы сделать открытый для расширения метод или класс - нужно указать ключевое слово open. Это приводит к тому, что открытыми будут только те классы и методы, которым это точно нужно)

Сюда же я бы добавил паттерны, реализованные в языке Kotlin. По сути это и синтаксический сахар, и эталонные реализации по умолчанию, уменьшающие вероятность ошибок при самостоятельной реализации:
4) синглтон - объявление класса-синглтона с помощью ключевого слова object
5) делегат - делегирование функционала класса или отдельного property через ключевое слово by

Почему важно по умолчанию показывать, как писать код правильно?
Приведу два примера:
1) Когда давным давном в далекой галктике Borland была среды быстрой разработки (RAD) Delphi. Хорошая была IDE, сделала одну большую ошибку - сильно завязалась на Windows и проиграла конкуренцию Visual Studio при массовом переходе на .NET.
Но был еще один серьезный недостаток. При создании нового проекта по умолчанию создавалось 3 файла: файл проекта, файл формы с UI компонентами и файл для обработчиков событий на форме. БольшАя часть разработчиков в файле для обработчиков хранила и бизнес-логику - Model + Controller. Лично я первое время делал именно так((( А всего-то надо было сделать еще один файл, назвать его скажем Logic.pas.
2) Еще похожий случай более близкий к нам по времени. Есть такой язык PHP. Язык простой в обучении, без компиляции, с нестрогой типизацией, с мощными и простыми средствами для обработки HTTP запросов, генерации HTML и работой с БД. Такая простота приводит к тому, что опять же многие PHP разработчики не думают о разделении кода по классам. Получаются огромные php скрипты, где смешаны M, V и C.

#kotlin #languages #nullsafety
Всем привет!

Уже писал про Kotlin DSL как одно из преимуществ языка: https://t.me/javaKotlinDevOps/38
А как насчет примеров? Легко)
Где же используется Kotlin DSL?
1) В Spring для динамических конфигураций: Spring https://spring.io/blog/2023/03/16/kotlin-dsls-in-the-world-of-springdom
2) В Gradle как скрипт сборки вместо Groovy: https://docs.gradle.org/current/userguide/kotlin_dsl.html
3) TeamCity (конечно же!) для скриптов CI\CD - https://www.jetbrains.com/help/teamcity/kotlin-dsl.html#Editing+Kotlin+DSL

В статье про Spring также говорится о фичах Kotlin, которые позволяют использовать его как DSL.
А подробнее эта тема раскрыта тут: https://www.jmix.ru/cuba-blog/kotlin-dsl-from-theory-to-practice/

И на сладкое пример как пошагово сделать свой DSL на Kotlin: https://www.baeldung.com/kotlin/dsl

#kotlin #kotlin_dsl #dsl
Всем привет!

Еще немного про Maven. Есть несколько способов настроить версию Java в Maven, все они описаны в этой статье - https://www.baeldung.com/maven-java-version
Оптимальным начиная с Java 9 выглядит вариант с указанием
<properties>
<maven.compiler.release>9</maven.compiler.release>
</properties>
т.к. данное свойство заменяет собой три: source, target и bootclasspath. Последнее означает, что начиная с Java 9 все JDK умеют притворяться более ранними версиями с точностью до classpath, т.е. до предоставляемого API. А это важно при переходе на 11-ю и 17-ю Java - в первой выпилили Java EE модули, типа SOAP, во второй - закрыли internal пакеты.

Но это не все, что нужно знать про настройки версии JDK.
Если в проекте есть Kotlin - надежнее явно указать ему версию JDK. Для справки - старые версии Kotlin поддерживали только JDK 6 как target bytecode, потом был период JDK 8, сейчас поддерживается все версии https://kotlinlang.org/docs/maven.html
Настраивается так:
<plugin>
<artifactId>kotlin-maven-plugin</artifactId>
<groupId>org.jetbrains.kotlin</groupId>
<configuration>
<jvmTarget>1.9</jvmTarget>
По хорошему плагин Kotlin должен переиспользовать maven.compiler.release, но я не нашел подтверждающей это информации.

Второй тонкий момент связан с IDEA. IDEA смотрит на maven.compiler.release, в результате правильно проставляет:
а) Source language level для корневого и дочерних модулей Maven - это можно увидеть в Project Structure -> Modules
б) Target bytecode для всех модулей - Double Shift, далее вбиваем "java compiler".
Но есть еще одна важная опция Project Structure -> Project -> SDK. Она определяет версию JDK, на которой будет собираться проект. Повторюсь, из-за сильных изменений в структуре JDK как минимум в 11 и 17 версиях сборка на более новой JDK может сломаться. Причем из логов причина ошибки будет не ясна(
Так вот, IDEA в данном кейсе игнорирует настройки Maven. Прямого решения у проблемы я не нашел, судя по всему, ребята из IntelliJ считают, что корневой модуль в проекте Maven и проект IDEA это не одно и тоже. И это фича, а не баг)
Но есть обходной путь. Если все или большинство ваших проектов живут на определенной версии JDK, то ее можно выставить в IDEA по умолчанию. Настраивается вот здесь: Double Shift, далее вбиваем "structure for new projects"

#maven #java #kotlin
Всем привет!

В последние годы стала "модной" тема null safety. Суть в том, что не нужно хранить и передавать null значения, чтобы не напороться на Null Pointer Exception. В том же Kotlin null safety встроена в язык - все типы по умолчанию не могут содержать null.
И на самом деле это правильный подход. Но есть нюансы)

Рассмотрим такой случай - мы идем куда-то за данными, данные по бизнес-процессу там обязаны быть. Например, мы прихранили id записи где-то в пользовательском контексте в начале процесса и идем за данными в конце процесса. Но данных нет. Следуя null safety можно просто создать пустой объект - например, с помощью конструктора. Как вариант, часть полей этого объекта будет проинициализирована значениями по умолчанию.

Так вот - в случае, когда данных нет из-за какой-то нештатной редко воспроизводимой ситуации: неверные тестовые данные, на сервис идет атака с перебором всех возможных значений, в процессе операции данные некорректно мигрировали, кривая архитектура - лучше просто "упасть", т.е. выбросить исключение. Есть такой принцип - fail fast. Т.к. создавая пустой объект, мы во-первых надеемся что он будет корректно обработан выше, а это может быть не так. А во-вторых - а зачем передавать управление дальше?

P.S. Как всегда - напомню каждую ситуацию нужно рассматривать индивидуально, чтобы различать отсутствие данных как часть бизнес-процесса и нештатную ситуацию.

#kotlin #code #patterns #principles #nullsafety #fail_fast
Всем привет!

Есть такая отличная штука в Kotlin, как контекстные функции.
Вот документация https://kotlinlang.org/docs/scope-functions.html
Вот пример, хорошо иллюстрирующий зачем они нужны:

val man = Person("Vasya").apply {
age = 20 // same as this.age = 20
city = "Moscow"
}

Код стал проще, читается хорошо. Если, конечно, ты знаешь про контекстные функции)

Но как и любая вещь, контекстные функции могут быть использованы не только во благо(
Вот несколько антипаттернов:

val params = claim.systemState?.let {
FailureParams(
claim.partnerName,
it.name,
)
}

Что мне здесь не нравится - читаемость. Если читать сверху вниз, то получается, что мы берем статус из заявки и присваиваем переменной params ... не его, а совершенно другой объект, созданный внутри let. Скорость понимания кода страдает.

return claim.also {
saveToCache(it)
}

Опять же, мне не нравится читаемость кода. Мы возвращаем результат метода, claim. А нет, не возвращаем. Вначале пишем его в кэш. А потом уже возвращаем.
Гораздо проще было бы:

saveToCache(claim)
return claim

Ну и наконец самый хит:

return claim.also {
saveToCache(someOtherObject)
}

Зачем? Почему? Не понятно)

P.S. По ссылке выше есть неплохая табличка https://kotlinlang.org/docs/scope-functions.html#function-selection Это по сути навигатор по контекстным функциям - позволяет выбрать одну из 6 для вашего конкретного случая. На первое время точно будет полезной

#kotlin #antipatterns
Всем привет!

Сегодня пост о крутой фиче Kotlin, которая решила одну важную проблему. И добавила другую)
Я о Null safety.
Суть ее в том, что в Kotlin любой тип представлен в двух ипостасях - одна может содержать null значения, другая - нет.
String - не может содержать null,
String? - может.
Это два разных типа, неявное приведение второго к первому "без приседаний" невозможно.
Что дает Null safety?
По умолчанию предлагается использовать тип not null, и если так и делать, то кажется, что про NPE - NullPointerException - можно забыть. А заодно забыть о проверках на null как в коде, так и в тестах. Небольшой оффтоп - проверка not null значений на null в тестах - еще один антипаттерн. Говорит о том, что пишущий этот код еще не до конца познал Kotlin)
Вроде бы все хорошо. Но есть нюанс. Что будет, если присвоить not null переменной nullable значение? Ошибка компиляции. А всегда ли компилятор знает, что это nullable значение? Если тип Kotlin - то всегда. Если тип Java - то в общем случае не знает. Что же он делает в таком случае? А ничего, просто разрешает присваивание.

//Java
public class JavaNullability {
private Boolean value;

public Boolean getValue() {
return value;
}
}

// Kotlin
class KotlinNullability {
constructor(test: JavaNullability) {
val nullValue: Boolean? = test.value
val notNullValue: Boolean = test.value
// компилятор разрешает оба варианта
}
}

Что же будет в runtime? Привычный нам в Java NPE на втором присваивании. Да, null safety не защищает от NPE в Kotlin коде.

Что тут можно сделать?

1) при взаимодействии Kotlin-Java знать nullability Java параметров, с которым мы работаем. Это может быть описание контракта - OpenAPI спецификация или любой другой контракт.
2) явная проверка на null в коде присваивания
3) самый плохой вариант, антипаттерн - всегда присваивать значения из Java nullable типам в Kotlin. Почему это плохо? nullable типы расползаются по всей программе, и в итоге мы теряем все преимущества Kotlin null safety.

P.S. Но в целом: null safety - это круто! Подробнее можно о ней можно почитать в официальной документации: https://kotlinlang.org/docs/null-safety.html#nullable-receiver

#kotlin #java #antipatterns #nullsafety