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

Не ошибусь, если предположу, что многие Java разработчики знают об Sun code style conventions https://www.oracle.com/java/technologies/javase/codeconventions-contents.html
Их автоматическая проверка реализована в Checkstyle https://checkstyle.org/styleguides/sun-code-conventions-19990420/CodeConvTOC.doc.html

Но это еще не все, что предлагают себе и нам разработчики Java.

Во-первых Sun уже давно нет, есть Oracle, который его купил.
И есть более новая версия code style от Oracle https://cr.openjdk.org/~alundblad/styleguide/index-v6.html#toc-introduction (доступ по VPN)

А кроме того, у Oracle есть свои правила для проверки качества кода https://wiki.sei.cmu.edu/confluence/pages/viewpage.action?pageId=88487665
Ссылка выше ведет на раздел по обработке исключений, чтобы можно было оценить объем и глубину требований на конкретной фиче языка.
Что интересно - не все правила есть в SonarQube, по тем же исключениям увидел несколько новых для себя вещей.
Некоторые из них я полностью поддерживаю, они вроде как логичны и можно сказать очевидны https://wiki.sei.cmu.edu/confluence/display/java/ERR03-J.+Restore+prior+object+state+on+method+failure
Некоторые https://wiki.sei.cmu.edu/confluence/display/java/ERR06-J.+Do+not+throw+undeclared+checked+exceptions можно кратко суммировать так: да, я нашем языке есть дыры, но не надо их использовать, пожалуйста)))
Что хорошо - в конце страницы с описанием правила есть секция со ссылками на соответствующие правила SonarQube и прочих утилит статического анализа кода.

#java #code_static_analysis
👍3🔥2
Всем привет!

В последнее время все чаще вижу, как LLM в IDE используют для генерации каркаса приложения. Казалось бы - это же задача генератора, а не LLM. Какого-нибудь Spring Initializr (https://start.spring.io/) Почему он этого не делает, а ограничивается по сути только pom-ников или *.gradle?

Потому что это отдельный сервис, требующий поддержки. Который со временем - по мере развития библиотеки, фреймворка или платформы, для которой он предназначен - будет обрастать все более сложной логикой. Обновился фреймворк - нужно обновлять генератор. Появился смежный компонент - будут запросы на добавление интеграции с ним. И при этом генератор все равно будет ограничен в возможностях. Возьмем тот же Amplicode - контроллеры, обработчики ошибок и тесты для Spring приложения он генерировать умеет, что-то еще - нет. Со временем возможностей для генерации будет больше, но 100% кейсов не будет покрыто.

А LLM в теории может сгенерировать что угодно, главное "скормить" ей побольше типового кода. Ключевое слово здесь - типового. Т.е. какую-то сложную логику тоже можно сгенерировать в LLM, но если размер диалога будет в 10 раз больше сгенерированного кода, а время - сравнимо, есть ли в этом смысл? Да, модель нужно будет периодически "подкармливать", но видится, что эту процедуру можно автоматизировать взяв за исходные данные открытый код из того же github.

Есть еще нюанс. LLM - это аналог MP3 128 kBit Joint Stereo ))) Сжатие с потерями. Это если что моя оценка вида "пальцем в небо", очень может быть степень сжатия больше. Распаковка - генерация кода - тоже приведет к потерям. Как проверить, что потерь нет - компиляция, тесты, а главное - предварительная оценка того, насколько типовой код нужен. В итоге для простых задач мы получаем универсальный генератор. И это круто!

P.S. Может показаться, что я наехал на Spring Initializr. Нет, штука полезная. Фактически Spring задали стандарт на рынке - все конкуренты Spring сделали свои инициализаторы: https://code.quarkus.io/ и https://start.microprofile.io/ И в Enterprise я видел попытки сделать свои инициализаторы разной степени успешности.

#llm #ai #code_generation
Качественное ли у вас API? А чем докажете?)

Как мы проверяем код на качество? SonarQube, покрытие кода тестами. Если говорить о code style - CheckStyle-ом. Если говорить об уязвимостях - проверка по базам уязвимостей (разные тулы), Checkmarx.

А можно ли как-то проверить API на соответствие лучшим практикам? В частности, OpenAPI как самый типовой на данный момент вариант.
Да - для этого есть Spectral linter https://meta.stoplight.io/docs/spectral/a630feff97e3a-concepts

У него три основных достоинства:
1) это linter и его можно включить в CI pipeline

2) у него есть наборы предустановленных правил, в частности:
а) OpenAPI rules https://meta.stoplight.io/docs/spectral/4dec24461f3af-open-api-rules
б) URL rules https://apistylebook.stoplight.io/docs/url-guidelines - использование kebab-case, не использование get в URL...
в) OWASP rules https://apistylebook.stoplight.io/docs/owasp-top-10 - безопасность, например, использование uuid вместо чисел в идентификаторах
...

3) возможность добавлять свои правила https://meta.stoplight.io/docs/spectral/01baf06bdd05a-create-a-ruleset в том числе наследуясь от существующих

Ну и отдельно отмечу, что есть плагин для IDEA https://plugins.jetbrains.com/plugin/25989-spectral-linter

Итого - штука полезная, настоятельно рекомендую попробовать.

#api #arch #code_quality
code style бывает разный

Меня сложно чем-то удивить в области разработки. Но недавно, изучая разные code style, я решил посмотреть, какие бывают варианты расположения фигурной скобки. Изначально в моём понимании их два - на одной строке с управляющей конструкцией и с новой строки. Но жизнь оказалась богаче любых ожиданий:
https://en.m.wikipedia.org/wiki/Indentation_style
Девять! Девять, Карл!

Хотя в Java наиболее распространён один, пришедший из C, от Кернигана и Ричи.

Вывод будет такой. Не важно сколько разных code style существует, важно, чтобы в проекте использовался один. А это значит .editorconfig https://editorconfig.org/ и автоматическое форматирование в Actions on Save в IDEA.

#code_style #idea
😱3👍1
Нужно ли читать чужой код?
Я не про код-ревью, а про процесс разработки.

С одной стороны я всегда топлю за то, что если есть проблемы с внешней библиотекой - не понятно, почему метод возвращает такой результат или ошибку - залезь в ее код, и посмотри.
Но Стив Макконнелл (да, снова Совершенный код) подсвечивает один интересный момент - разницу между синтаксической и семантической инкапсуляцией внутренностей класса.
С синтаксической все просто - объявил по максимуму все private и профит.
А вот семантическая, а точнее ее нарушение - это наши догадки насчет внутреннего устройства чужого класса, влияющие на наш код.
А догадки эти обычно возникают после чтения кода...
Тут еще хорошая аналогия с работой разработчика и тестировщика. Для тестировщика код - это черный ящик. А для разработчика - нет. Или все же да?)

Я для себя решил так. Если найдено неочевидное\неправильное поведение внешней библиотеки, ее автор доступен и готов к сотрудничеству - стоит по советам Стива зайти к нему и попросить поправить.
А если хотя бы одно из этих условий не выполняется - можно и нужно залезть в код. И возможно даже завязаться на его внутренние детали реализации. Да, да, я про костыль.
Но с обязательными условиями:
1) proxy слой
2) с подробным комментарием что делается и почему.
И с заведением техдолга если все же есть возможность поправить эту проблему прямым образом.

#book_review #code
👍1💯1
Два подхода к написанию функций (методов).

Первый - у метода должна быть одна точка выхода (в общем случае - как можно меньше точек выхода). А точка входа по умолчанию одна. Потому что это улучшает читаемость.
Второй - если у метода есть предусловия, то их нужно проверять с помощью охранных выражений в начале метода. Т.к. это уменьшает вложенность и опять же улучшает читаемость)

Т.е

public void processUser(User user) {
if (user != null) {
if (user.getAge() >= 18) {
if (validateEmail(user.getEmail()) != null && user.getEmail().contains("@")) {
if (user.isVerified()) {
performUserRegistration(user);
}
}
}
}
}


vs

public void processUser(User user) {
if (user == null) return;

if (user.getAge() < 18) return;

if (user.getEmail() == null || !user.getEmail().contains("@")) return;

if (!user.isVerified()) return;

performUserRegistration(user);
}


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

В Совершенном коде автор тоже за второй вариант. Присоединяюсь и считаю, что второй вариант - это база.
Возможно есть кейсы, когда он не применим, но я пока придумать не могу.
Даже на 2 условиях. Даже если недостаточно простого return.

#book_review #code
Снова минутка цитат на канале:
«Термин „структурное программирование“ был введён в 1969 году.
С тех пор термин „структурный“ применялся к любой деятельности в области ПО, включая структурный анализ, структурный дизайн и структурное валяние дурака».
Стив Макконнелл «Совершенный код».


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

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

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

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

#book_review #java #code #structure_programming #structure_xxx #principles
Сколько багов в час делает разработчик?

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

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


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

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

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

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

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

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

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

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

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

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


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

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

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


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

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

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


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

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

private final String label;
private final double ratePerKwh;
}

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

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

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

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



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

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

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


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

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

#code_review #code
👍2
Ты что такое или как AI ревьювит код?

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

Первый. Модель постоянно берет куски нового кода и запускает их с каким-то параметрами, например, с краевыми случаями.
Для начала куски метода, потом методы целиком.
Это явно плюс в карму AI, т.к. живому человеку это делать долго и дорого.
Кто-то так делал вообще?)
Надо отметить, что в Java это еще и сложнее сделать, чем в том же Python или JS, хотя подвижки есть - https://t.me/javaKotlinDevOps/412
Тут видится хорошая область, где AI ревьювер может дополнить человека.

Для демонстрации второго момента приведу кусок кода - это лог выполнения скрипта, который агент прогонял на этапе ревью. Код python, но он простой:

{"command":"python -c \"
import pandas as pd
import numpy as np

# Probe: does max_pos + 1 in rng.integers allow the window to exceed bounds?
# n = 10, duration = 3, max_pos = 7
# rng.integers(0, max_pos + 1) -> [0, 7] inclusive
# pos=7: index[7], window is [index[7], index[7]+3min)
# If index has exactly 10 rows (0..9), this is index[7], index[8], index[9] -> 3 rows OK
# pos=8: This would only happen if max_pos=8, which requires n-duration=8, so n=11 - fine
print('Boundary logic: max_pos = n - duration_minutes')
print('rng.integers(0, max_pos+1) is [0, max_pos] inclusive')
print('At max_pos, window covers indices [max_pos..max_pos+duration-1], which is valid')
print()

\"
","description":"Verify off-by-one and boundary correctness in random placement logic"},


На первый взгляд бред какой-то. Код ничего не делает, только печатает очевидные вещи, частично как комментарии, частично через print.
Но если подумать - похоже модель проверяет логику на отсутствие ошибки "ошибки на единицу".
Проговаривая ее для себя. Ну и возможно для меня, но видится в первую очередь для себя.
Т.к. это все попадает в историю диалога с LLM и далее в ее контекст, влияющие на результат выполнения последующих запросов.
С таким проговариванием человек справляется успешно, но радует, что модель тоже, хоть и вот так, странненько.

P.S. Ошибка между прочим известная, не у каждой ошибки своя статья в wikipedia есть https://ru.wikipedia.org/wiki/Ошибка_на_единицу

P.P.S. Но импорты явно лишние)

P.P.P.S. Риски самому перестать думать возрастают)))

#ai #code-review
🔥2