Developer's mind
589 subscribers
16 photos
18 links
Программирую, обучаю, делюсь опытом
java/kotlin, spring boot, clean code, databases
обратная связь:
@Hcd5opza9bdcjid26fg
https://t.me/developers_mind
Download Telegram
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* сервисные таблицы.
Большая проблема больших индексов баз данных

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

В реальных системах это вырастает в серьезную проблему обычно довольно внезапно - как только часто используемые индексы перестают помещаться в ОЗУ начинает очень быстро расти IO-нагрузка на дисковую систему, а дисковая система на несколько порядков медленнее. К счастью есть несколько разных способов сделать размеры индексов в разы меньше. Знаете их?
First принципы - Timely

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

Знаете, что тесты - это не только про отсутствие багов? С точки зрения программирования у юнит-тестов есть намного более важная причина для существования.
Partial индексы

Использование #partial индексов - один из способов оптимизации работы баз данных. Представьте, что у вас есть таблица с заказами с 10млн строк и часто выполняющийся типовой запрос SELECT * FROM orders WHERE state IN ('new', 'processing') ORDER BY order_time, возвращающий не более пары сотен заказов. Индекс по state в данном случае будет достаточно эффективным ввиду хорошей селективности по указанным значениям, но будет весить пару сотен мегабайт. И, кажется, поднимать с диска в память индекс, величиной в пару сотен мегабайт, для выгрузки нескольких килобайт информации - очень непродуктивно. Что можно сделать? Ну, например, CREATE INDEX order_time_processing_idx ON order_time(state) WHERE state in ('new', 'processing'). Индекс теперь будет весить несколько мегабайт и, более того, он будет в отсортированном варианте. БД будет его использовать для нашего запроса. Даже если изменить запрос и не сортировать и не запрашивать поле order_time - все равно этот индекс будет использоваться, т.к. в нем есть информация о расположении нужных строк.

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

Пример был для PostgreSQL, но подобные штуки можно проворачивать и на БД, не поддерживающих partial индексы. Например, с помощью горизонтального деления таблиц по состояниям. Правда это уже ближе к application-level оптимизациям.
Как оптимизировать хранение строковых индексов

Раньше занимался обработкой больших объемов информации из сети на слабом оборудовании. И хочется сказать об оптимизации строковых индексов без привнесения дополнительных технических решений в проект. Чтоб было с цифрами - на серверах по $5-10 крутились реляционные базы с сотнями гигабайт индексированной информации. Лет 6 назад использовались сервера на пару ядер и 256-512 Мб оперативки. С тех пор научился ужимать индексы на порядки.

Индексирования строк создает большие индексы. Если хочется обойтись базовой базой, но нужно сделать индекс по строке, например, чтоб искать дубликаты, то для сокращения размера индекса разумно индексировать только начало строки. PostgreSQL, например, поддерживает такое, правда при таком подходе падает селективность индекса.

Альтернативный путь - индексировать дополнительный столбец char(32) содержащий md5-преобразование строки. Тем не менее, результат md5-преобразования занимает 32 байта на каждую строку, что тоже затратно. md5 - это набор из 32-х 16-ричных цифр, охватывающие пространство в 2^256 или больше чем 10^38. Но для большинства задач хватает намного меньшего диапазона значений. Например, сокращение char(32) до char(16) экономит 50% размера ключа, а значит и индекса. Но в 16 байтах хранится 16 символов или 16 8-ричных цифр, которые кодируют 8 байт информации. А значит это заменяется на bigint без потерь. Чтоб сделать это - сначала получить md5 строки, далее обрезать полученный хеш до 16 символов, затем выполнить преобразование из 16-ричной системы счисления в 10-тичную:

text_index = conv(substring(md5(url), 1, 16), 16, 10);


Значения идентификаторов, полученные так, имеют также равномерное распределением по 8-байтовому пространству. Это обеспечивает достаточную селективность. В жертву конечно будут принесены операции LIKE и другие подобные.

Разумеется если нет проблем с железом, то такие хаки лучше заменить другими подходящими инструментами. Кстати, если вы знаете альтернативные подходы - поделитесь в комментариях.
AvoidStarImport

AvoidStarImport - это настройка чекстайла на Maven Checkstyle плагине. Плагин проверяет, что в блоке импортов не используется wildcard - символ звездочки для автоматического импорта классов указанного пекейджа.

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

Что меня лично смущает - в IDEA в качестве значений по-умолчанию выставлены 3-5 импортов перед автоматическим схлопываением импортов под звездочку. Но в самых распространенных стайлгайдах включен AvoidStarImport. В итоге многие программисты используют эту дефолтную настройку, другие же - выключают. И, во-первых, это ведет к постоянным изменениям в блоке импортов. Во-вторых, к сложностям при внедрении чекстайла в проект, в третьих к росту размеров пулл реквестов. До сих пор не понимаю почему в IntelliJ решили так сделать.
Завтра провожу вебинар по Java “от Junior до Middle”.
О чем буду рассказывать:
- что необходимо знать об архитектуре, чтобы пройти собеседование на Middle-позицию
- как прокачать софт скилы перед собеседованием
- как знание Docker, Kafka и K8S влияет на твое конкурентное преимущества
- как продать себя на более высокую должность

Участие бесплатное, регистрируйтесь по ссылке: https://cutt.ly/ST3yKQr
Снова об оптимизации баз данных

Один из часто используемых инструментов для мониторинга состояния БД остается slow queries log - это лог куда СУБД складирует запросы, которые медленнее установленной величины n. Туда попадают и неверно написанные запросы, однако это редко приводит к снижениям нагрузки на БД и главная причина в том, что чаще основную нагрузку генерируют не низкочастотные неоптимизированные запросы, а высокочастотные быстрые.  

Предположим, есть два запроса. Первый выполняется 30 секунд, но раз в час. Второй выполняется за 100 мс, но "бомбит" БД 10 раз в секунду. Первый генерирует нагрузку ~1% времени потока, а второй занимает ровно 100%. Оптимизация второго запроса даст в ~100 раз больше профита.

Правда мониторить такие запросы сложнее, но на рынке присутствует много средств мониторинга и профилирования - okmeter, datadog и т.д. Если используете другие - поделитесь в комментариях.
#повседневное

Вчерашний запрос, "заморозивший" миграцию на три часа:

SELECT entity_id, MAX(start_date), MAX(end_date)
FROM (
  SELECT entity_id, MAX(start_date) start_date, NULL AS end_date FROM audit_log
  WHERE attr = 'reviewed' AND newValue = 'true'
  GROUP BY entity_id
   UNION ALL
  SELECT entity_id, NULL AS start_date, MAX(end_date) end_date FROM audit_log
  WHERE attr = 'overriden' AND newValue = 'true'
  GROUP BY entity_id
) a GROUP BY entity_id; 

можно было бы написать сильно оптимальнее вот так:

SELECT entity_id, 
  MAX(if(attr = 'reviewed', NULL, start_date)) AS start_date,
    MAX(if(attr = 'reviewed', end_date, NULL)) end_date 
FROM audit_log
WHERE (attr = 'reviewed' OR attr='overriden') AND newValue = 'true'
GROUP BY entity_id;

При сильно похожих WHERE в таких конструкциях, удобнее схлопнуть UNION и "на лету" подставлять в SELECT нужное значение через if.
В последнее время периодически раздумываю запустить какой-нибудь проект типа “Контрольный собес”. Знаете - как “Контрольная закупка”, только о собеседованиях на разработчиков в IT-компании. 
Так уж сложилось что я подготовил многих людей к собеседованиям, и сам часто проводил собеседования, и проходил их соискатель, в том числе в европейские и азиатские компании. Так вот - определить набор критериев и оценивать компании с точки зрения того, как у них устроен процесс найма IT-шников. 

Как думаете - зашло бы такое?
Я знаю что это не по теме, и я никогда не хотел тут писать на темы, слабо связанные с IT. Но если честно у меня ощущение, что у нас всех, в том числе айтишников, отбирают будущее. Если еще вчера казалось, что, если что, всегда можно улететь, спрятать голову в песок и т.д., то уже сегодня перспектива не улететь стала реальностью. Реальностью стало и то, что будучи программистом, можно будет не найти работу через месяц. Потому что отрезанный от глобального рынка локальный - не вывезет то количество айтишников и программистов которые на него придут, после разрыва отношений с западными заказчиками.

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

https://docs.google.com/forms/d/e/1FAIpQLScEsxsoXl_7R4aD5F8-B7fCCBVwU_BXBaOVJsKszbFyRHRkkw/viewform
Планирую выступать на JPoint в июне. Опять с своей любимой многопоточностью.
Ссылка: https://jpoint.ru/talks/82e4c9067a0fae2844e9d6dbc47023f1/
если вдруг кто будет из подписчиков там - буду рад любому фидбеку ну и вопросам, а если вдруг есть идеи/вопросы заранее - буду рад если зададите, а я смогу доработать доклад чтоб было интереснее и мне и вам.
все еще не чувствую себя на 100% комфортно, когда пишу даже короткие приглашения на английском. Поэтому загоняю их иногда в гугл транслейт и проверяю, что все правильно написано, переводя тексты туда-сюда.

Сегодняшний перевод выглядит так:

Я приглашаю вас на запланированную встречу в Zoom.

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

Хотели бы послушать про бамбуковые спецификации? 😏