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

Постов долгое время не было, причина типичная - много работы. Вообще не помню времени, когда ее было мало((( И были ли вообще такие времена?)

Хотел бы поднять сегодня такую важную тему как взаимодействие разработчиков и сопровождения.
Для начала одна общеизвестная информация - разработчики и сопровождение исходя из своих задач обречены на противостояние.
Задача разработчиков - менять приложение, задача сопровождения - обеспечивать его работоспособность. А как известно: работает - не трогай) Любое изменение потенциальный источник проблем.
Отсюда часто следует одна крайность - сопровождение максимально критично относится к любым изменениям, разработка "виновна" по умолчанию, требуется строгое соблюдение регламентов, любая нестандартная просьба разработки встречается "в штыки".
Почему это плохо?
Сейчас основная методология разработки - это Agile в разных вариациях. Один из ключевых моментов в Agile - это команда, командная ответственность, гибкие решения в команде. А сопровождение в описываемом кейсе выступает внешним "врагом" - блокирует инициативы команды, замедляет скорость выпуска новых версий. А высокая частота выхода в ПРОМ - еще одна важная часть Agile.
С таким кейсом я, увы, сталкивался и наблюдал его губительный для команды эффект. Часто свои требования сопровождение объясняет требованиями надежности. Хорошее ли это объяснение - зависит от деталей. Если объяснение звучит как-то так - это снизит надежность, т.к. ... и идет описание причин - то да, хорошее.
Если слово надежность произносится, а никаких деталей не приводится - это признак того, что сопровождение боится изменений и не хочет развиваться.
Есть и другая крайность - сопровождение "согласно на все") Не выставляет никаких требований, принимает любые дистрибутивы. Кейс более редкий. Чем это плохо - разработчики опять же исходя из своих основных задач редко думают о том, как их код будет сопровождаться. Обычно на разработку времени хватает впритык.
Какой выход из данной ситуации?
Выставлять требования со стороны сопровождения.
Требования зависят от компании, отрасли, приложения, числа пользователей и много чего.
Но базовые требования могут быть такими:
1) список метрик, позволяющих отслеживать работоспособность
2) требования к логам - где и в каком объеме. Сюда же я бы добавил требования к фильтрации логов, с важным дополнением - возможность фильтрации зависит как от разработчиков бизнес-приложения, так и от разработчиков системы просмотра логов
3) требования к трассировке (tracing) - особенно важно если мы имеем дело с микросервисами
4) наличие инструкции для сопровождения в случае, если установка релиза требует ручных действий
5) наличие сценария отката на предыдущую версию. Это может быть выключение feature toggle или номер версии для отката. Самое важное - сама возможность отката. Это тоже требование, его нужно или соблюдать, или если это невозможно, например, в случае необратимых изменений в БД - составлять план действий на случай неудачной раскатки
6) фиксирование таймаутов внешних вызовов: я уже писал что бесконечные таймауты - одна из основных причин падения приложения
7) требования по UI для ручного разбора ошибок, если все предыдущие требования не помогли)

Почему я говорю "могут быть такие требования" - я все же изначально разработчик, поэтому имею некие предустановки и не вижу полной картины. Если есть возражения и дополнения - возражайте и дополняйте)

#dev #ops
Всем привет!

На собеседовании я иногда задаю вопрос: приведите пример нарушения принципа Single responsibility. Или альтернативный вариант - а вот если в методе, к примеру, activateCard мы заодно отбросим метрики или залогируем результат - это нарушение принципа или нет.
На первый взгляд ответ - нет. Метрики и логи - это технический код, не бизнес функционал. Он может понадобиться в любом месте кода. Часто такой функционал реализуют с помощью аспектов, т.к. во-первых - это можно реализовать с помощью аспектов, а во-вторых - это красиво))), т.е. некий синтаксический сахар, улучшающий читаемость кода.
Но можно рассмотреть немного другую ситуацию. Предположим, есть код с математическими вычислениям. Или любой алгоритм. Или логика обработки данных. То, что хорошо реализуется в функциональном стиле - входные данные метода, результат, никаких внешних зависимостей. В нём нет внешних взаимодействий, сохранения в хранилище. Чистая логика. В этом случае логирование и метрики - это уже некая обработка полученного результата. Мы же не просто так выводим что-то в лог - это либо данные для разбора ошибки, либо отслеживание пользовательского пути, сбор статистики, отслеживание времени выполнения кода... Т.е. есть отдельная логика по месту и составу того, что мы логируем. Опять же контекст логирования часто требует инициализации, что добавляет ненужные зависимости в нашу логику. Поэтому такой код лучше поместить на уровень выше.
Итого: бизнес функционал и логирование/метрики - да, "чистая" логика - нет.

#logging #metrics #interview_question #code_design #solid #dev_compromises
Всем привет!

Сегодня пятница, поговорим немного о магии.
Что я считаю одним из самых плохих качеств разработчика - веру в магию.
Симптомы магического мышления: фразы типа "ну вроде починил", "ну вроде работает". Или хаотичный перебор настроек в надежде, что все заработает. Или нежелание что-то менять в коде, из-за боязни поломать.
У любой проблемы при разработке ПО есть причины. Иногда они очевидны, иногда нужно потратить дни или недели, чтобы понять - почему оно так работает.
Бывает так, что не хватает времени, чтобы разобраться в причинах. Бывает так, что корни проблемы лежат в области, где не хватает компетенции - СУБД, внешняя система, pipeline. Это нужно понимать и признавать. Решение - искать специалиста или просить больше времени на разбор. Четыре лайфхака по самостоятельному разбору проблем:
1) посмотреть исходный код проблемного компонента, даже если он не ваш
2) погуглить)))
3) внимательнее читать логи. Часто вижу, что первая подозрительная запись в логах останавливает поиск. А если промотать еще десяток строк - лежит нормальное описание ошибки.
4) разбить проблему на части, т.к. часто самые загадочные случаи - это следствие череды ошибок.

Ну и прекрасная иллюстрация того, что любая магия имеет объяснение - вот эта статья: https://habr.com/ru/articles/759344/

#dev
Всем привет!

Я уже поднимал тему boolean параметров как антипаттерна https://t.me/javaKotlinDevOps/229. Давайте расширим ее до вопроса - когда стоит использовать if?
Является ли if антипаттерном?
По мнению некоторых товарищей - да, является: https://www.antiifprogramming.com/about-the-anti-if.php
Как по мне - не всегда, зависит от ситуации.
Чем плох if? // да, switch - это по сути тот же if.

1) может нарушать принцип Single Responsibility. Почему - думаю объяснять не нужно.
2) может ухудшать читаемость кода, я которую я всегда "топлю") Т.е. нарушает принцип KISS. Усугубляет ситуацию тот факт, что код как правило не остается неизменным. И обычный if else со временем может превратится в многоуровневого нечитаемого монстра.
3) может нарушать принцип Don't Repeat Yourself. Тут два очевидных варианта - либо во всех ветках if выражения есть дублирующийся код, либо чтобы обработать возврат некого метода всегда нужен if.
4) если в коде слишком много if (x != null) - это признак того, что вы неправильно работаете с nullability. Тут могу посоветовать Kotlin, т.к. он может сообщать о null значениях на этапе компиляции. Optional и его альтернативы в Java избавляют от NPE, но не избавляет от проверок на null. Я видел советы - просто не пишите код, который возвращает null - тогда проверки будут не нужны. Но это надежда на человеческий фактор, и компилятор (я про Kotlin) работает лучше)))

Да, я специально пишу везде слово "может". Бывают if-ы, которые не нарушают ни один из принципов.

Когда стоит волноваться?

1) подключаем SonarQube или Checkstyle и не игнорируем ошибки, связанные с цикломатической сложностью методов, см. https://t.me/javaKotlinDevOps/197
2) код просто сложно становится читать. Особенно хорошо эта проверка проходит на новых разработчиках)

Идеально конечно не писать код, приводящий к лишним if. Но я уже писал про человеческий фактор выше)

Что можно сделать? // будет некоторый повтор написанного тут https://t.me/javaKotlinDevOps/229

1) выделяем сложный код условия в отдельный метод.
2) вместо двух или более веток оператора if делаем несколько методов. Помогает в случае, если условно метод А всегда вызывает метод С с значением true, а метод Б - с значением false. Иначе будет как на знаменитой картинке - проблема не на моей стороне)))
3) используем not null объекты и переходим Kotlin
4) перепроектируем код, чтобы проверки выполнялись в одном месте, а не дублировались по коду. Для этого их придется перенести из вызывающего кода в вызываемый. И придумать правильное значение по умолчанию.
5) при необходимости вводим иерархию классов, чтобы каждый класс отвечал за одну ветку switch
6) используем паттерн Стратегия - по сути частный случай введения иерархии классов
7) используем паттерн Состояние (State), который кроме хранения состояния выполняет обработку, связанную с различными состояниями, тем самым убирая if из вызывающего кода

#antipatterns #if_antipattern #java #kotlin #solid #patterns #dev_compromises
Всем привет!

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

Для начала - когда полезны интерфейсы:

1) соблюдение принципа инверсии зависимостей, буква D из SOLID. См. https://t.me/javaKotlinDevOps/179 Высокоуровневые классы не должны зависеть от конкретных реализаций. На первый взгляд без интерфейсов тут никак. Да и на второй тоже) Но важно уточнить, что речь именно про базовые классы с логикой, т.наз. domain logic. Например, контроллера это не касается. Или адаптера к внешнему сервису.

2) соблюдение принципов гексагональной архитектуры. См. https://t.me/javaKotlinDevOps/232 Тут тоже есть нюанс - интерфейс и реализация должны находится в разных слоях приложения. Т.е. кейса, когда реализация лежит в подпакете impl рядом с интерфейсом это не касается. К слову, если мы говорим про слой бизнес-логики, то требования DI и гексагональной архитектуры - это одни и те же требования. И применимы они в основном для слоя бизнес-логики, она же предметная область.

3) Магии Spring проще работать с интерфейсами. Здесь важный момент - проще, но Spring вполне может работать и с классами без интерфейсов. Т.е. Spring или реализует интерфейс или наследуется от класса. Первое возможно всегда, второе - только если класс не финальный. И то, это важно для @Configuration, классов с методами, помеченными как @Transactional и @Async и возможно какими-то еще кейсами, о которых я забыл. Для Java просто не стоит делать их финальными. Для Kotlin - есть плагин kotlin-allopen, который сделает все за вас, а его актуальность увеличивает тот факт, что в Kotlin все классы по умолчанию финальные. И чтобы совсем уже не было вопросов - IDEA подсвечивает такие классы красным, предлагаю сделать их не финальными.

Почему если ваш случай не 1 и не 2 лучше не заводить интерфейс. Два аргумента:

1) меньше классов - лучше читаемость. А я за нее всегда топлю!) И за число строк кода не платят. Я надеюсь по крайней мере, что и у вас так)

2) мы не строим здания или мосты, мы пишем код. И при появлении второй реализации, например, тестовой, очень легко выделить интерфейс. Для этого есть готовый рефакторинг в IDEA. И еще есть рефакторинг переименование класса. Занимает 10 минут, шансов что-то сломать мало, особенно если есть достаточное покрытие кода тестами.

Вывод: интерфейсы полезны для соблюдения принципа Dependency Inversion и\или гексагональной архитектуры. Или когда есть хотя бы 2 реализации, включая тестовую. В остальных случаях нужно дважды подумать - нужны ли они вам.

#principles #dev_compromises
Всем привет!

Является ли ООП - объекто-ориентированное программирование - чем-то плохим? Ответ - ну нет. Благодаря объектам мы можем воспроизвести в коде реальные бизнес-объекты, а это стирает барьеры между заказчиком, аналитиком и разработчиком. Конечно, есть альтернативные подходы, например, функциональный. Или если посмотреть в другую сторону - декларативный. Но ООП жил, жив и будет жить)

Является ли архитектура сервиса, состоящая из нескольких слоев абстракции, какой-то излишней или неправильной? Нет, это стандартный подход в архитектуре - вводить новые уровни абстракции. Даже шутка на этот счет есть) Spring, Hibernate и куча других библиотек - это тоже новые слои абстракции. Цель введения нового слоя - упростить использование какой-то библиотеки или адаптировать ее для новой предметной области.

Что плавно подводит нас к паттернам Адаптер, Прокси и иже с ними https://t.me/javaKotlinDevOps/124 Паттерны - штука полезная, и да, я снова об этом уже писал https://t.me/javaKotlinDevOps/52 )))

И последний (риторический) вопрос: являются ли принципы DRY - Don't Repeat Yourself - и Single Responsibility вредными? Наоборот, они делают код более устойчивым к изменениям и упрощают его изучение.

Но почему же тогда в мире ПО не редкость встретить вот такую фабрику фабрик фабрик: https://factoryfactoryfactory.net ?

Ответ: ООП, принципы и паттерны не заменяют здравый смысл и чувство меры)

#oop #patterns #craftmanship #dev_compromises
Всем привет!

Недавно набрел на интересную статью - https://enterprisecraftsmanship.com/posts/domain-model-purity-completeness
Настоятельно рекомендую ее прочитать, но как всегда вкратце перескажу)

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

Варианта предлагается три:
1) внести ее в модель, скрыть обращение к данным за интерфейсами, но в любом случае в итоге наше ядро (модель) лезет в БД, что плохо. В первую очередь плохо концептуально, а если "спустится на землю" - сложнее писать модульные тесты, увеличиваются риски "загрязнения" ядра. Т.е. следующие поколения разработчиков видя, что из модели вызывается СУБД, скажут - а что, так можно было?) И будут тянуть в модель другие внешние зависимости. Теория еще такая есть, разбитых окон. К слову - автор статьи также автор отличной книги о модульном тестировании, я о ней уже писал https://t.me/javaKotlinDevOps/50, возможно поэтому ему данный вариант не нравится
2) оставить часть логики в контроллере. Но тогда получается, что логика размазана по двум слоям
3) заранее загрузить нужные данные в ядро. Допустимо, но только для каких-то маленьких и редко меняющихся справочников, типа регионов. Т.е. только в отдельных случаях.

В итоге у нас компромисс между полнотой модели, строгостью соблюдения архитектурных принципов и скоростью работы.
Что тут интересно - главная идея статьи не о том, как сделать правильно, а про то, что разработка ПО - это искусство компромиссов.
Теорема CAP, упоминаемая в статье, к слову о том же. Единственного правильного для всех случаев жизни решения нет. Увы(

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

validateLogic()
callModel()

превращается в:

val rules = getValidateRulesFromModel()
val request = buildValidateRequest()
validate(request)
callModel()

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

#arch #unittests #dev_compromises
Всем привет!

Вытяну ссылку из комментариев сюда: https://youtu.be/hUzpe73Oa3g?si=c_dY1YU2Cc_F8YiY
Хороший ролик про границы применимости паттерна Value Object.

На всякий случай - что такое Value Object и чем он отличается от DTO - https://matthiasnoback.nl/2022/09/is-it-a-dto-or-a-value-object/

Также стоит отметить, что данный паттерн является одним из основных в DDD - Domain Driven Development.

А по видео у меня такой краткий вывод - а точнее два:
1) у любого паттерна есть своя область применения
2) когда вы придумали некий хитрый лайфхак, перед тем как реализовывать его в коде стоит взять паузу и подумать.
Насколько он понятен для новичка? Не усложнит ли он код? Насколько? Не станет ли поддержка такого кода сложнее? Не добавит ли он в вашу модель "уязвимость", позволяющую использовать классы и методы не так, как задумывалось изначально?
Часто лучше написать больше простого кода, чем меньше, но неочевидного и допускающего неверное использование. И далее либо разбить этот код на микросервисы, либо на модули - например, см. мой пост про Modulith - https://t.me/javaKotlinDevOps/143

#patterns #arch #dev_compromises