Developer's mind
590 subscribers
16 photos
18 links
Программирую, обучаю, делюсь опытом
java/kotlin, spring boot, clean code, databases
обратная связь:
@Hcd5opza9bdcjid26fg
https://t.me/developers_mind
Download Telegram
У меня новая фраза-фаворит с последнего рабочего митинга (да-да, некоторые уже работают):

"It's double broken"
сегодня ночью @quattique написала новый пост после долго отсутствия и это напомнило мне и про мой маленький уютный канальчик который покрывается пылью.

И так как я пока сходу не готов писать лонгриды (хотя есть о чем) напишу про короткую рекрутерскую тему - не так давно узнал что у нас крутая дорогая рефералка в американскую компанию. Так что если у вас вдруг есть знакомые Java-разработчики (или вы сами таковой) с 3+ годами опыта работы и если вы хотите подзарботать $2-2.5к просто за рекоммендацию - то welcome ко мне в личку. А сами вакансии очень приятные - полная удаленка, привязка зп к долларам, опционы, работа в международной команде (с коллегами из америки) так что английский тоже must have 🙃
Немного о том, как быть “так себе саппортом” - просто отвечать что “ну это потому что ты используешь мобильный браузер и там может и не будет работать” и это типо окей. Можно было еще человека послать использовать macOS и только Safari, например 🙈
Задачка, которую я придумал год назад и периодически спрашиваю на собеседованиях. Интересный факт что знания которые требуются чтоб на нее ответить - это джуновые знания Java и школьный курс математики. Но не каждый Senior способен дать правильный ответ. Попробуйте ее решить и только потом посмотреть в комментарии и пишите туда ваш ответ, можно приблизительный, обсудим!

Вот ее текст:
Сколько различных объектов класса String можно создать таких, чтобы их hashcode был одинаковым (например, чтоб он был равен 42)?
Недавно обнаружил у себя на проекте примерно такое.
Это даже побудило меня написать статью, которую я наверное скорое опубликую. В этом куске кода есть одна серьезная проблема и ворох проблем поменьше, очень похоже, кстати, на задачки на собесах.

Пишите в комменты что здесь не так.

И кстати, если у кого-то есть интересные примеры кусков кода, или даже с собеседований - тоже скидывайте, хочу серию разборов сделать что такое хорошо и что такое плохо.
а я никак не мог найти конструкторы и методы и поля класса.
Всегда казалось, что проблемы связанные с equals и hashCode настолько редковстречающиеся что чего толку их изучатью А вчера увидел парочку в коде, причем их редковстречаемость вполне перевешивает их потенциальную серьезность.
Что было: hibernate entity у которой был переопределен только equals, при том что equals сравнивал только id-поля. Были написаны тесты - например, в одном из них создавался HashSet из пары этих entites, entities были не сохранены в БД поэтому их id были равны нулю. В итоге при вставке в Set они вставлялись всегда в новый бакет, но фактически были равны межды собой (конечно тут еще отдельныее вопросики про использование mutable объектов в set). Просто добавление правильного hashCode сломало работу тестов т.к. в сет стал попадать только один элемент, пришлось доработать еще и equals.

Но, вообще, иногда волосы встают дыбом когда понимаю потенциальные проблемы которые может доставить отсутствие вот этих важных 3-9 строчек кода.

А вот недавно студенты скидывали отличное StackOverflow обсуждение как вообще работать с equals/hashCode и JPA: https://stackoverflow.com/questions/5031614/the-jpa-hashcode-equals-dilemma
Всегда хочется объект, который используется в singleton-сервисе или контроллере, сделать полем класса (или static-полем класса если сам объект пересоздается), чтоб избежать его создания на каждый реквест. Именно так я сделал, когда начинал работать с Java и вынес в поле класса SimpleDateFormat. Через пару недель иногда стали прилетать ерроры. Оказалось, что этот класс не ThreadSafe и при работе в многопоточном режиме при попытке одновременного парсинга из разных потоков оно может падать.
Выхода тут несколько:
- отказаться от common-объектов и создавать каждый раз новенький SimpleDateFormat
- обернуть SimpleDateFormat ThreadLocal-переменную, чтоб создавать каждый экземпляр не более одного раза в каждом потоке
- использовать альтернативы FastDateFormat из Commons Lang или DateTimeFormat из JodaTime библиотеки

я предпочитаю второй вариант.

А вообще все вышеописанное относится к любым stateful-объектам. Будте осторожны и всегда проверяйте, что используемые вами инструменты являются ThreadSafe при работе в многопоточной среде.
а вы что думаете?

если баг не воспроизводится - он пофикшен или это не точно?
Недавно меня спросили - чем плохо большое количество аргументов в методе или конструкторе?

1. Это сложно читать в коде и на ревью, если IDE без подстветки - можно запросто перепутать пару аргументов
2. Если эти аргументы передаются через 2-3 слоя - то возникают длинные портянки аргументов где опять же очень легко многое напутать, а добавление/изменение чего-либо приводит к каскадным фиксам.

Как избежать большого количества параметров, передаваемых в метод?

способ 1: Рефакторинг
Если метод не удоволтворяет принципу #SRP, то при его рефакторинге количество параметров явно уменьшиться

способ 2: Группировка параметров в один объект
Если метод удовлетворяет принципу #SRP - значит и параметры этого метода можно как-то сгруппировать. Можно их сгруппировать в один объект, который, в свою очередь создавать с помощью #builder

способ 3: Вынос аргументов метода в поля класса
В некоторых ситуациях есть возможность все аргументы метода или их часть вынести в качестве полей класса, тогда сам класс можно создавать с помощью #builder, а потом вызывать метод с минимальным колиеством дополнительных аргументов
На LeetCode начался November Challenge. А здесь ребята собрались чтоб делиться своими успехами и обсуждать задачки: https://t.co/S2xMdrWPmT

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

@Transactional
public void method() {
try {
//logic
} catch (Exception e) {
// report with a new transaction
}
}

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

Как сделать правильно:

public void method() {
try {
// call transactional method
} catch (Exception e) {
// call report with a new transaction

}
}


И вообще любые транзакции надо делать максимально легкими и пытаться избегать любых внешних вызовов, иначе на консистентность данных начинают влиять внешние сервисы, а это очень сложно разгребать.
Chain-сеттеры
немного холиварная тема, но выскажу свои мысли по поводу таких сеттеров. Поводом послужило очередное использование библиотеки #Lombok и, в частности, аннотации @Accessors(chain = true)

public DomainObject setName(String name) {
this.name = name;
return this;
}


из плюсов:
- иногда это удобно
- это нормально для #immutable-объектов, хотя конвенциально для этого используют префикс with (например DomainObject withName(String name) )
- это нормально для #builder-ов

а вот минусов:
- во первых (в первую очередь если речь про Java), это не соответствуют конвеншенам о сеттерах
- во-вторых, это превращает метод из Consumer<T> в Function<T, K> что затрудняет его поведение там где требуется обычный #setter
- это затрудняет использование API класса - очень сложно понять, читая код, - мутабельный это класс или мне надо использовать новую переменную после вызова метода
- это так же может помешать в оптимизациях, которые JVM может делать для void-методов - методы становятся order-depended т.е. в цепочке сеттеров выключаются механизмы реордеринга

А вы что думаете? Используете такое?
FIRST принципы

Кроме уже всем известных прицнипов #SOLID есть и очень важный набор принципов #FIRST. Это принципы про написание тестов. В основном юнит тестов, но можно сюда добавить и интеграционные.

Итак, тесты должны быть:

Fast - быстрыми
Independent - независимыми
Repeatable - повторяемыми
Self-validated - самопроверяемыми (т.е. результатом теста может быть только “пройден” или “не пройден”)
Timely - вовремя написанными, в идеале до написания продакшн кода (TDD подход)

Если написанный юнит-тест не удовлетворяет одному из первых четырех принципов - это повод присмотреться правильный ли это юнит-тест.
First  принципы - Fast

Самый первый принцип из набора FIRST - это #Fast. Тесты должны быть быстрыми. В основном потому, что это кардинально меняет подход программиста к их запуску. Чем быстрее выполняются тесты, тем чаще девелопер видит что что-то пошло не так, и точнее знает где проблема, и быстрее ее решает. Сравните - увидеть ошибку через секунду после внесенной правки или спустя час после того, как они выполнятся где-нибудь в bamboo, и на почту прилетит отчет, что какие-то тесты не прошли. Хорошо, если во втором случае файл еще не закрыт, но в большинстве случаев уже переключился на другую задачу и потом нужно переключать мозг назад на старую задачу, смотреть в браузер, перепроверять, запускать - делать много дополнительных действий на несколько минут.

В общем, быстрые тесты ведут к сильному повышению эффективности разработки.
First принципы - Independent

#Independent (независимость) тестов - тоже очень важная штука. Имеется в виду независимость их как друг от друга, так и от окружения, в котором они запускаются. Если они зависимы друг от друга, то для запуска какого-то определенного теста придется запускать из скопом, что увеличивает время запуска (а это плохо). Если зависимы от окружения - значит где-то они не будут запускаться, либо где-то будут падать, чем начнут создавать проблемы либо вам, либо вашим коллегам. Так же нельзя забывать, что запуск тестов в многопоточной среде - это тоже запуск в определенном окружении. И если сами тестовые классы имеют stateful-природу (имеют состояние), то тесты получаются как зависимыми от этого состояния так и друг от друга. А потом еще и нарушают #Repeatable принцип, если начнут иногда падать при запуске с помощью многопоточного Runner’а, к которому многие проекты все равно приходят чтоб сделать запуск тестов быстрее.
First принципы - Repeatable

#Repeatable - повторяемость. Означает что ваш тест должен давать везде и всегда одинаковый результат - будь то ноутбук разработчика или суперкомпьютер в недрах компании. Смысл вполне должен быть очевиден - если тест иногда имеет false-negatives - т.е. иногда показывает что прошел, хотя должен был упасть - можно пропустить баги в продакшн. Когда падает изредка - приучает команду ретраить его просто до тех пор пока не пройдет, что не только раздражающе но и тратит время. Хотя само по себе “изредка падает” означает что что-то где-то пошло не так.

Есть у вас такие “изредка падающие” тесты?
Что не так с таймаутами в тестах

JUnit-аннотации @Timeout и @Test(timeout) позволяют добавлять проверку скорости выполнения тестов, запуская тесты в отдельных потоках. Так вот, недавно мне доводилось бороться с их большим количеством в проекте. Так что же с ними не так? Любая возможная цель их добавления вступает в какой-либо конфликт с принципами хороших юнит тестов.
- Их иногда добавляют чтоб убедиться что тест работает быстрее, чем указанный возможный временной диапазон. Но ведь тесты запускаются в разных окружениях - на серверах и рабочих машинах, и иногда ресурсы могут быть заняты другими процессами, а значит некоторым тредам может выделиться недостаточно времени. Т.е. могут появиться false-fails. Таким образом наши тесты перестанут быть #repeatable и станут зависимыми от окружения - среда запуска не должна быть нагружена другими процессами.
- Иногда таймауты добавляют чтоб заставить мейнетейнеров проектов не делать тесты слишком длинными и чтоб держать общее время работы тестов под контролем. Проблема в том, что тесты с таймаутом создают новый тред для выполнения каждого теста, что само по себе дорогая операция, а значит общее время выполнения тестов растет. Не говоря уж о том, что нет четких критериев по длительности теста - в одних командах может быть 1000 тестов по 0,1 сек и это будет все равно в 2 раза дольше, чем в другой команде 100 тестов по 0.5 сек. Для изредка падающих по времени тесты разработчики либо увеличивают таймауты, что делает цель добавления таймаутов бесполезным делом, либо чаще ретраят запуски, что еще более увеличивает общее время прохождения тестов.
- Еще таймауты используют, чтоб убедиться что функционал работает оптимально. Тут проблема в том что при запуске юнит тестов мы не узнаем насколько оптимально написан код. Для этого нужны бенчмарки, но юнит тесты должны тестировать функциональность, а не время ее отработки. Для тестирования производительности есть нагрузочные и стресс тесты, и подходы к их написанию сильно отличаются от концепций юнит-тестирования. При попытке же интегрировать нагрузочный тест в юнит-тест появляются конфликты с принципом #selfvalidated.

Мои поиски о том как правильно использовать #timeout не привели к каким-то хорошим результатам, а опыт был только негативным. Поделитесь, пожалуйста, своим мнением.
Почему вам нужен хотя-бы простейший автоматический checkstyle

Не так давно начал внедрять автоматизированные инструменты контроля качества кода. И вот почему в вашем проекте, причем буквально любом, нужен как минимум самый простейший #checkstyle, например в виде плагина maven checkstyle plugin. 
- один из самых важных поинтов - он буквально ловит баги. Две недели назад я выловил несколько ошибок в equals/hashcode в первый же запуск чекстайла после добавления плагина в pom.xml
- несколько метрик чекстайла считают complexity методов и другие readability-метрики и запрещает делать слишком сложные методы. Это, в свою очередь, улучшает читабельность и увеличивает эффективность дальнейшей разработки
- оно избавляет ваши пулл-реквесты от стайлинг-холиваров
- и в то же время, после того как люди знают что о стайлинге уже позаботился робот, они концентрируются на бизнесовой части и поиске багов во время ревью, а не на стайлинге и нейминге
- общий стайлинг по коду ускоряет интуитивную навигацию разработчиков по проекту и по классам
- checkstyle не требует никаких дополнительных инструментов или серверов, прозрачно встраивается в build-процесс и не требует особых навыков или скиллов.

Используете такое?
First принципы - Self-validated

#Selfvalidated, или, по-русски, самовалидация (в других переводах - очевидность) - еще один принцип из набора о том что результатом выполнения теста должно быть четкое булево значение. Не должно быть ситуации когда разработчику нужно глазами просматривать результат каждого запуска в поисках, например, варнингов. Если же результатом теста по какой-то причине все равно является не булево значение, например, тестируется производительность, то этому тесту не место среди юнит тестов - это должен быть отдельный скоуп тестов, запуск которых должен осуществляться в другом месте и он не должен запускаться в стандартом билд-процессе.
Когда НЕ нужен индекс на SQL базе данных

Одна из частых причин торможения баз данных - лишние индексы. Индексы тормозят любые операции записи т.к. их нужно пересчитывать при любом изменении данных. Если же они создают высокую IO-нагрузку на дисковую систему, то это так же аффектит и замедляет операции чтения. Вместе с тем по моему опыту очень большая часть индексов либо не нужны продукту, либо не используются базой данной. Когда же не нужен индекс?
- Данные по выбранному полю не вычитываются - нет запросов где это поле встречалось бы в блоке WHERE
- У индекса низкая селективность. Селективность - отношение количества уникальных значений по полю, к количеству строк в таблице - чем хуже селективность, тем большее количество строк будет возвращено по каждому полю индекса и тем более количества случайных операций чтения придется сделать базе данных при походе за данными - а это означает что в какой-то момент для БД выгоднее сделать более быструю операцию последовательного чтения, чем много операций случайного чтения. Грубо говоря - индекс на обычном булевом поле или enum-поле с небольшим количеством значений скорее всего будет вреден
- Индекс “перекрыт” другим составным индексом

Самый простой способ посмотреть, как используются индексы - залезть в статистику которая собирает сама БД. Например, для PostgreSQL это pg_stat* и pg_index* сервисные таблицы.