Всем привет!
Раз уж я начал говорить про SOLID - рассмотрим еще одну букву - O.
Open-closed principe - принцип открытости-закрытости.
На первый взгляд все просто - не нужно менять существующие классы, нужно их расширять. Очень хорошо ложится на области видимости Java и концепцию наследования классов.
Но это все очевидные вещи, первый уровень сложности)
Второй уровень - как можно сделать Open, т.е. расширить функционал? Наследование. Да, наследование - это хорошо. Но я отлично помню монолит с огромными иерархиями наследования, уровней по 10+. Бегать по этой иерархии искать где и что используется и точно ли я ничего не сломаю в новом наследнике - такое себе удовольствие. Да и SonarQube ругается, и правильно делает).
Альтернативы:
1) композиция и агрегация - объект базового класса можно включить в себя как поле, и уже в новом классе реализовать нужные требования. https://metanit.com/sharp/patterns/1.2.php
2) частный случай предыдущего пункта - паттерны декоратор и прокси, когда мы реализуем тот же интерфейс, что и базовый класс. См. разницу https://t.me/javaKotlinDevOps/124
3) комбинация аннотации и аспекта, реализующего дополнительное поведение над определенным методом класса
4) аннотации и создание прокси класса через CGLib или аналогичную библиотеку
Третий уровень - как правильно сделать Close? Закрыли мы класс для изменений, поставили final. А там конструкция вида:
private final List<String> POSIBLE_STORAGES = List.of("Oracle", "EhCache");
и далее везде в коде сохранения выбор из этих двух вариантов...
При желании наверняка обойти можно, но это значит, что автор класса не подумал про принцип открытости-закрытости.
Нужно было выносить в отдельный метод, а еще лучше - в отдельный объект Storage.
#arch #solid #inteview_question
Раз уж я начал говорить про SOLID - рассмотрим еще одну букву - O.
Open-closed principe - принцип открытости-закрытости.
На первый взгляд все просто - не нужно менять существующие классы, нужно их расширять. Очень хорошо ложится на области видимости Java и концепцию наследования классов.
Но это все очевидные вещи, первый уровень сложности)
Второй уровень - как можно сделать Open, т.е. расширить функционал? Наследование. Да, наследование - это хорошо. Но я отлично помню монолит с огромными иерархиями наследования, уровней по 10+. Бегать по этой иерархии искать где и что используется и точно ли я ничего не сломаю в новом наследнике - такое себе удовольствие. Да и SonarQube ругается, и правильно делает).
Альтернативы:
1) композиция и агрегация - объект базового класса можно включить в себя как поле, и уже в новом классе реализовать нужные требования. https://metanit.com/sharp/patterns/1.2.php
2) частный случай предыдущего пункта - паттерны декоратор и прокси, когда мы реализуем тот же интерфейс, что и базовый класс. См. разницу https://t.me/javaKotlinDevOps/124
3) комбинация аннотации и аспекта, реализующего дополнительное поведение над определенным методом класса
4) аннотации и создание прокси класса через CGLib или аналогичную библиотеку
Третий уровень - как правильно сделать Close? Закрыли мы класс для изменений, поставили final. А там конструкция вида:
private final List<String> POSIBLE_STORAGES = List.of("Oracle", "EhCache");
и далее везде в коде сохранения выбор из этих двух вариантов...
При желании наверняка обойти можно, но это значит, что автор класса не подумал про принцип открытости-закрытости.
Нужно было выносить в отдельный метод, а еще лучше - в отдельный объект Storage.
#arch #solid #inteview_question
Telegram
(java || kotlin) && devOps
Всем привет!
Как я писал ранее - паттерны полезны для понимания кода, т.к. облегчают чтение кода, формирую некий язык.
Но этот язык должен быть достаточно четким.
Так вот - есть такие структурные паттерны. https://ru.wikipedia.org/wiki/Структурные_шабло…
Как я писал ранее - паттерны полезны для понимания кода, т.к. облегчают чтение кода, формирую некий язык.
Но этот язык должен быть достаточно четким.
Так вот - есть такие структурные паттерны. https://ru.wikipedia.org/wiki/Структурные_шабло…
Всем привет!
Продолжая тему SOLID. Есть в этой аббревиатуре буква L - принцип Барбары Лисков, который гласит, что все потомки должны соблюдать контракт, объявленный в родителе.
Объясню на пальцах. Есть класс или даже скорее интерфейс А. Есть класс Б, как-то использующий А - например, принимающий его как параметр в методе или конструкторе. И принцип Лисков будет соблюдаться в том случае, если класс Б не "упадет" при получении любого (!) потомка класса А, т.е. класс Б не должен разбираться в том, какого именно потомка А он получил.
На первый взгляд все просто - в Java и Kotlin строгая типизация и инкапсуляция, поэтому если мы объявили интерфейс с определенными методами и типами параметров - все потомки обязаны их реализовать. Что тут может сломаться?
Если копнуть глубже, то в Java наследники все же могут изменять контракт:
1) заменить тип возвращаемого методом результата на тип-наследник
2) добавлять в сигнатуру метода исключения-наследники и увеличивать число объявленных исключений, тоже наследниками
3) повышать область видимости метода
Все это расширяет ограничения в исходном контракте. Поломать корректно написанный класс Б, который знает только про А - интерфейс предка - так не получится. Хотя расширяя таким образом контракт, нужно быть осторожным, т.к. мы подталкиваем потребителей нашего нового класса к использованию конкретного класса вместо интерфейса, а это уже может привести к нарушению другой буквы - D )))
Как же можно точно нарушить контракт? Для этого нужно ответить на вопрос - что есть контракт?
Контракт не равно Java API. Это может быть:
1) назначение метода. Например, называя метод getXXX или checkXXX мы как бы говорим потребителю, что он не меняет состояние объекта, т.е. нет побочных эффектов
2) порядок вызова методов класса. Опять же исходя из названий методов мы можем предположить некий порядок вызовов - init() вызываем в начале, build() - в случае паттерна Builder в конце.
3) назначение класса. Если опять же исходя из названия класса он предназначен только для работы с кэшом - нужно в потомках лазить в БД, это должны быть разные сервисные классы. Если предполагается, что в сервисном классе данного уровня нужно отбрасывать логи и метрики - все потомки должны это делать. И наоборот.
4) политика по исключениям. Если предполагается, что метод возвращает внутреннюю ошибку как результат вызова (Pair или свой класс), то именно так и должны поступать все потомки
5) время выполнения. Если в контракте явно предполагается, что метод что-то рассчитывает in-process и не ходит во внешние системы - контракт нужно соблюдать.
6) зависимости от других классов. Нужно стремится к тому, чтобы все зависимости были явно указаны в API и передавались в класс при создании. Т.е. класс Б не должен как-то настраивать среду для работы конкретного потомка класса А. Да, речь про Inversion of Control.
7) иммутабельность. Если предполагается иммутабельная реализация - все потомки должны быть такими.
Как описать такой контракт? В первую очередь - в именах методов и классов. Если этого не хватает - в JavaDoc. Часто так бывает, что JavaDoc не несет новой информации для изучающего код, но в данном случае будет полезен.
И последняя очевидная, но IMHO важная мысль: чтобы наследники соответствовали контракту - для начала контракт нужно описать)
#arch #solid #interview_question
Продолжая тему SOLID. Есть в этой аббревиатуре буква L - принцип Барбары Лисков, который гласит, что все потомки должны соблюдать контракт, объявленный в родителе.
Объясню на пальцах. Есть класс или даже скорее интерфейс А. Есть класс Б, как-то использующий А - например, принимающий его как параметр в методе или конструкторе. И принцип Лисков будет соблюдаться в том случае, если класс Б не "упадет" при получении любого (!) потомка класса А, т.е. класс Б не должен разбираться в том, какого именно потомка А он получил.
На первый взгляд все просто - в Java и Kotlin строгая типизация и инкапсуляция, поэтому если мы объявили интерфейс с определенными методами и типами параметров - все потомки обязаны их реализовать. Что тут может сломаться?
Если копнуть глубже, то в Java наследники все же могут изменять контракт:
1) заменить тип возвращаемого методом результата на тип-наследник
2) добавлять в сигнатуру метода исключения-наследники и увеличивать число объявленных исключений, тоже наследниками
3) повышать область видимости метода
Все это расширяет ограничения в исходном контракте. Поломать корректно написанный класс Б, который знает только про А - интерфейс предка - так не получится. Хотя расширяя таким образом контракт, нужно быть осторожным, т.к. мы подталкиваем потребителей нашего нового класса к использованию конкретного класса вместо интерфейса, а это уже может привести к нарушению другой буквы - D )))
Как же можно точно нарушить контракт? Для этого нужно ответить на вопрос - что есть контракт?
Контракт не равно Java API. Это может быть:
1) назначение метода. Например, называя метод getXXX или checkXXX мы как бы говорим потребителю, что он не меняет состояние объекта, т.е. нет побочных эффектов
2) порядок вызова методов класса. Опять же исходя из названий методов мы можем предположить некий порядок вызовов - init() вызываем в начале, build() - в случае паттерна Builder в конце.
3) назначение класса. Если опять же исходя из названия класса он предназначен только для работы с кэшом - нужно в потомках лазить в БД, это должны быть разные сервисные классы. Если предполагается, что в сервисном классе данного уровня нужно отбрасывать логи и метрики - все потомки должны это делать. И наоборот.
4) политика по исключениям. Если предполагается, что метод возвращает внутреннюю ошибку как результат вызова (Pair или свой класс), то именно так и должны поступать все потомки
5) время выполнения. Если в контракте явно предполагается, что метод что-то рассчитывает in-process и не ходит во внешние системы - контракт нужно соблюдать.
6) зависимости от других классов. Нужно стремится к тому, чтобы все зависимости были явно указаны в API и передавались в класс при создании. Т.е. класс Б не должен как-то настраивать среду для работы конкретного потомка класса А. Да, речь про Inversion of Control.
7) иммутабельность. Если предполагается иммутабельная реализация - все потомки должны быть такими.
Как описать такой контракт? В первую очередь - в именах методов и классов. Если этого не хватает - в JavaDoc. Часто так бывает, что JavaDoc не несет новой информации для изучающего код, но в данном случае будет полезен.
И последняя очевидная, но IMHO важная мысль: чтобы наследники соответствовали контракту - для начала контракт нужно описать)
#arch #solid #interview_question
Всем привет!
Уже несколько раз упоминал в последних постах JavaDoc\KDoc. Нужен ли он вообще?
Мой ответ: да, но не всегда.
Основной критерий - JavaDoc должен приносить пользу. Т.е. давать дополнительную информацию. Дополнительную к чему?
1) названию модуля
2) названию пакета
3) названию класса\паттерна
4) названию метода или поля
Что это может быть?
1) контракт, как использовать и наследовать класс
2) причины выбора того или иного алгоритма
3) пример использования, его также можно поместить в unit тесты
4) TODO, причины объявления deprecated
5) ссылки на используемые в коде алгоритмы
6) краткое описание бизнес-логики, т.к. разработчики не ходят в wiki\Confluence )))
7) особые случаи использования, неочевидное поведение кода с некоторыми параметрами. Хотя это скорее антипаттерн, само наличие неочевидного поведения - повод подумать про рефакторинг
Почему все же не стоит писать "дежурный" комментарий над классом и публичными методами?
1) на это тратится время
2) главная причина - рефакторинг кода. При рефакторинге обычно меняют имена методов, полей и классов. Иногда забывают. Но если говорить о комментариях - тут все наоборот: иногда их обновляют, но чаще - забывают. И получаем код, на который было потрачено время, но который мало того, что не приносит пользу - так еще и вводит в заблуждение.
#java #javadoc
Уже несколько раз упоминал в последних постах JavaDoc\KDoc. Нужен ли он вообще?
Мой ответ: да, но не всегда.
Основной критерий - JavaDoc должен приносить пользу. Т.е. давать дополнительную информацию. Дополнительную к чему?
1) названию модуля
2) названию пакета
3) названию класса\паттерна
4) названию метода или поля
Что это может быть?
1) контракт, как использовать и наследовать класс
2) причины выбора того или иного алгоритма
3) пример использования, его также можно поместить в unit тесты
4) TODO, причины объявления deprecated
5) ссылки на используемые в коде алгоритмы
6) краткое описание бизнес-логики, т.к. разработчики не ходят в wiki\Confluence )))
7) особые случаи использования, неочевидное поведение кода с некоторыми параметрами. Хотя это скорее антипаттерн, само наличие неочевидного поведения - повод подумать про рефакторинг
Почему все же не стоит писать "дежурный" комментарий над классом и публичными методами?
1) на это тратится время
2) главная причина - рефакторинг кода. При рефакторинге обычно меняют имена методов, полей и классов. Иногда забывают. Но если говорить о комментариях - тут все наоборот: иногда их обновляют, но чаще - забывают. И получаем код, на который было потрачено время, но который мало того, что не приносит пользу - так еще и вводит в заблуждение.
#java #javadoc
Всем привет!
Когда-то на своей первой работе я услышал от коллеги совет: "Не надо бросаться реализовывать в коде первую попавшуюся идею. Подумай, найди пару альтернативных решений и сравни их".
Так вот, спустя более 15 лет практики хочу сказать - это очень важная и полезная мысль!)
По моему собственному опыту в 25-50% случаев первая пришедшая в голову идея не является оптимальной. Иногда приходится выкинуть все сделанное, иногда - сильно ее видоизменить. Причем часто это происходит еще в процессе работы над исходной задачей, при попытке вписать свой новый код в существующий сервис.
По сути речь идет о проектировании и его качестве. Когда нужно проектирование? Почти всегда, кроме исправления тривиальных багов - опечаток, замены текстовок например. А на самом деле даже простой в исправлении баг может указать на необходимость рефакторинга, а рефакторинг в любом случае требует проектирования.
Хорошим вариантом проверить качество идеи является практика TDD - в этом случае придется сразу написать типовые примеры использования вашего нового класса в виде тестов. Соответственно, сразу будет видно как новый код интегрируется с существующим, подходит ли для всех случаев использования.
При этом важно отметить, что TDD не заменяет проектирование.
Единственная альтернатива проектированию, которую я вижу - закладывать 1,5х или 2х времени для работы над задачей, чтобы была возможность все переделать) Альтернатива так себе, по моей оценке время на проектирование будет ниже.
Еще худший вариант: видя, что новый код придется переделывать - "докостылить" его до работающего варианта и записать себе задачу с техдолгом. К таким техдолгам, увы, долго не возвращаются.
Резюме - закладывайте время на предварительное проектирование по любой не тривиальной задаче. Ищите и сравнивайте альтернативные варианты, выбирайте оптимальный.
#arch
Когда-то на своей первой работе я услышал от коллеги совет: "Не надо бросаться реализовывать в коде первую попавшуюся идею. Подумай, найди пару альтернативных решений и сравни их".
Так вот, спустя более 15 лет практики хочу сказать - это очень важная и полезная мысль!)
По моему собственному опыту в 25-50% случаев первая пришедшая в голову идея не является оптимальной. Иногда приходится выкинуть все сделанное, иногда - сильно ее видоизменить. Причем часто это происходит еще в процессе работы над исходной задачей, при попытке вписать свой новый код в существующий сервис.
По сути речь идет о проектировании и его качестве. Когда нужно проектирование? Почти всегда, кроме исправления тривиальных багов - опечаток, замены текстовок например. А на самом деле даже простой в исправлении баг может указать на необходимость рефакторинга, а рефакторинг в любом случае требует проектирования.
Хорошим вариантом проверить качество идеи является практика TDD - в этом случае придется сразу написать типовые примеры использования вашего нового класса в виде тестов. Соответственно, сразу будет видно как новый код интегрируется с существующим, подходит ли для всех случаев использования.
При этом важно отметить, что TDD не заменяет проектирование.
Единственная альтернатива проектированию, которую я вижу - закладывать 1,5х или 2х времени для работы над задачей, чтобы была возможность все переделать) Альтернатива так себе, по моей оценке время на проектирование будет ниже.
Еще худший вариант: видя, что новый код придется переделывать - "докостылить" его до работающего варианта и записать себе задачу с техдолгом. К таким техдолгам, увы, долго не возвращаются.
Резюме - закладывайте время на предварительное проектирование по любой не тривиальной задаче. Ищите и сравнивайте альтернативные варианты, выбирайте оптимальный.
#arch
Всем привет!
Я слишком ударился в архитектуру в последних постах, возвращаюсь с небес на землю)
Уже был пост как правильно писать unit тесты https://t.me/javaKotlinDevOps/33 и https://t.me/javaKotlinDevOps/43. Но кажется, что не хватает примеров. Начнем с антипаттернов - что не нужно делать в тестах?
Есть такая всем известная в Java мире библиотека для создания заглушек как Mockito. И у него есть среди всего прочего такая конструкция как
doNothing().when(mock).someMethod();
На первый взгляд полезная штука - говорим, что при вызове определенного метода в "заглушенном" объекте ничего делать не надо.
Но копнув чуть глубже понимаешь, что результатом вызова
val mock = Mockito.mock(SomeClass.class);
как раз и является объект, который ничего не делает.
А точнее метод объекта не вызывается, а там, где нужно вернуть результат - возвращаются некие значения по умолчанию: https://site.mockito.org/javadoc/current/org/mockito/Mockito.html#2
А если так - явно писать doNothing() - это усложнять код теста, его читаемость и ценность как документации к коду. Держать в уме, что все методы заглушки, которые не переопределены в тесте, не делают ничего - проще и понятнее, чем явно перечислять их.
Теперь уже метод doNothing() выглядит бесполезным. Зачем же его тогда добавляли в библиотеку?
Есть два кейса:
1) когда вы используете spy, а не mock. Напомню, главное отличие spy - он по умолчанию вызывает методы существующего объекта.
Пример:
List list = new LinkedList();
List spy = spy(list);
doNothing().when(spy).clear();
spy.someMethodWithCLeaCall();
2) и об этой возможности я, признаюсь, не знал - в одном выражении можно задать несколько ответов на ряд последовательных вызовов одного метода заглушки:
doNothing()
.doThrow(new RuntimeException())
.when(mock).someVoidMethod();
//первый раз ничего не делаем
mock.someVoidMethod();
//бросаем RuntimeException во второй раз
mock.someVoidMethod();
В следующих постах будет продолжение...
#unittests #mockito #antipatterns
Я слишком ударился в архитектуру в последних постах, возвращаюсь с небес на землю)
Уже был пост как правильно писать unit тесты https://t.me/javaKotlinDevOps/33 и https://t.me/javaKotlinDevOps/43. Но кажется, что не хватает примеров. Начнем с антипаттернов - что не нужно делать в тестах?
Есть такая всем известная в Java мире библиотека для создания заглушек как Mockito. И у него есть среди всего прочего такая конструкция как
doNothing().when(mock).someMethod();
На первый взгляд полезная штука - говорим, что при вызове определенного метода в "заглушенном" объекте ничего делать не надо.
Но копнув чуть глубже понимаешь, что результатом вызова
val mock = Mockito.mock(SomeClass.class);
как раз и является объект, который ничего не делает.
А точнее метод объекта не вызывается, а там, где нужно вернуть результат - возвращаются некие значения по умолчанию: https://site.mockito.org/javadoc/current/org/mockito/Mockito.html#2
А если так - явно писать doNothing() - это усложнять код теста, его читаемость и ценность как документации к коду. Держать в уме, что все методы заглушки, которые не переопределены в тесте, не делают ничего - проще и понятнее, чем явно перечислять их.
Теперь уже метод doNothing() выглядит бесполезным. Зачем же его тогда добавляли в библиотеку?
Есть два кейса:
1) когда вы используете spy, а не mock. Напомню, главное отличие spy - он по умолчанию вызывает методы существующего объекта.
Пример:
List list = new LinkedList();
List spy = spy(list);
doNothing().when(spy).clear();
spy.someMethodWithCLeaCall();
2) и об этой возможности я, признаюсь, не знал - в одном выражении можно задать несколько ответов на ряд последовательных вызовов одного метода заглушки:
doNothing()
.doThrow(new RuntimeException())
.when(mock).someVoidMethod();
//первый раз ничего не делаем
mock.someVoidMethod();
//бросаем RuntimeException во второй раз
mock.someVoidMethod();
В следующих постах будет продолжение...
#unittests #mockito #antipatterns
Telegram
(java || kotlin) && devOps
Всем привет!
Каким должен быть хороший тест?
Я в первую очередь про модульные (unit), но в принципе правила применимы к любым.
Основные моменты:
1) правило Arrange, Act, Assert https://xp123.com/articles/3a-arrange-act-assert/
Тест делится на три части:…
Каким должен быть хороший тест?
Я в первую очередь про модульные (unit), но в принципе правила применимы к любым.
Основные моменты:
1) правило Arrange, Act, Assert https://xp123.com/articles/3a-arrange-act-assert/
Тест делится на три части:…
Всем привет!
Продолжим про антипаттерны в unit тестах.
Мы можем проверить в тесте метод на то, что он выбрасывает исключение.
В JUnit 5:
assertThrows(MyException.class, () -> myObject.myFunction(myString), "Expected myFunction() to throw, but it didn't");
и с помощью AssertJ:
assertThatExceptionOfType(MyException.class)
.isThrownBy(() -> { myObject.myFunction(myString); })
.withMessage("%s!", "boom")
.withMessageContaining("boom")
.withNoCause();
Это хорошая проверка, т.к. всегда нужно убедится, что выброшено именно то исключение, которое мы ожидаем. А иногда требуется проверить его параметры.
Но есть еще методы проверки, что исключения не было.
В JUnit 5:
assertDoesNotThrow(() -> myObject.myFunction(myString));
и AssertJ:
assertThatNoException()
.isThrownBy(() -> myObject.myFunction(myString));
А вот эти проверки выглядят излишними, т.к. выбрасывание исключения в любом случае прерывает тест, и он подсвечивается красным.
Возможно, если тест сложный и в нем много проверок - хочется локализовать место ошибки. Но в этом случае я бы задумался про разделение теста на несколько вместо использования assertDoesNotThrow. Опять же, в противном случае придется использовать данную проверку во всех тестах, кроме тех, что выбрасывают исключение. А это IMHO "замусоривает" код.
P.S. Интересный момент - библиотека Truth, о которой я писал ранее https://t.me/javaKotlinDevOps/51, вообще не содержит методов проверки на выбрасывание исключения, предлагая пользоваться соответствующим методом из JUnit, ведь assertThrows, описанный ранее, возвращает исключение:
val exception = assertThrows(MyException.class, () -> myObject.myFunction());
А уже его можно проверить более детально:
assertThat(exception)
.hasCauseThat()
.isInstanceOf(NumberFormatException.class);
assertThat(exception)
.hasMessageThat()
.startsWith("Bad");
Мне этот подход нравится.
#unittests #junit #assertj #truth #antipatterns
Продолжим про антипаттерны в unit тестах.
Мы можем проверить в тесте метод на то, что он выбрасывает исключение.
В JUnit 5:
assertThrows(MyException.class, () -> myObject.myFunction(myString), "Expected myFunction() to throw, but it didn't");
и с помощью AssertJ:
assertThatExceptionOfType(MyException.class)
.isThrownBy(() -> { myObject.myFunction(myString); })
.withMessage("%s!", "boom")
.withMessageContaining("boom")
.withNoCause();
Это хорошая проверка, т.к. всегда нужно убедится, что выброшено именно то исключение, которое мы ожидаем. А иногда требуется проверить его параметры.
Но есть еще методы проверки, что исключения не было.
В JUnit 5:
assertDoesNotThrow(() -> myObject.myFunction(myString));
и AssertJ:
assertThatNoException()
.isThrownBy(() -> myObject.myFunction(myString));
А вот эти проверки выглядят излишними, т.к. выбрасывание исключения в любом случае прерывает тест, и он подсвечивается красным.
Возможно, если тест сложный и в нем много проверок - хочется локализовать место ошибки. Но в этом случае я бы задумался про разделение теста на несколько вместо использования assertDoesNotThrow. Опять же, в противном случае придется использовать данную проверку во всех тестах, кроме тех, что выбрасывают исключение. А это IMHO "замусоривает" код.
P.S. Интересный момент - библиотека Truth, о которой я писал ранее https://t.me/javaKotlinDevOps/51, вообще не содержит методов проверки на выбрасывание исключения, предлагая пользоваться соответствующим методом из JUnit, ведь assertThrows, описанный ранее, возвращает исключение:
val exception = assertThrows(MyException.class, () -> myObject.myFunction());
А уже его можно проверить более детально:
assertThat(exception)
.hasCauseThat()
.isInstanceOf(NumberFormatException.class);
assertThat(exception)
.hasMessageThat()
.startsWith("Bad");
Мне этот подход нравится.
#unittests #junit #assertj #truth #antipatterns
Telegram
(java || kotlin) && devOps
Всем привет!
Не JUnit-ом единым...
Если говорить о фреймворках для unit тестирования все наверняка вспомнят JUnit и Mockito. Но есть ещё много чего полезного в этой области. Сегодня расскажу про библиотеки для улучшения читаемости проверок - assert. Про…
Не JUnit-ом единым...
Если говорить о фреймворках для unit тестирования все наверняка вспомнят JUnit и Mockito. Но есть ещё много чего полезного в этой области. Сегодня расскажу про библиотеки для улучшения читаемости проверок - assert. Про…
Всем привет!
Еще один антипаттерн в unit тестах - чрезмерное увлечение verify.
List<String> mockedList = mock(MyList.class);
mockedList.size();
verify(mockedList, times(1)).size();
Да, штука полезная. Полезная по большому счету в 2 случаях:
1) проверка тестируемого метода в случае, когда он ничего не возвращает. К слову - еще одна альтернативная проверка: для mutable объекта и меняющего состояние метода проверить поля SUT (System under test).
2) проверка важных шагов алгоритма.
Если с void методом все понятно, то с деталями алгоритма главное - найти ту черту, которая отделяет суть алгоритма от деталей реализации. И проверять только первое. Т.к. в противном случае мы получим "хрупкий" тест, падающий при каждом изменении реализации. Собственно, от этого критерия можно и отталкиваться. Пример: если назначение данного сервисного класса вызов удаленной системы, отбрасывание метрик и логов - то именно эти вызовы и нужно проверить. В других случаях вызов метода логирования и отправку метрики я бы не проверял.
Еще хороший критерий - проверять те вызовы, которые описаны в аналитике.
Ясно, что 100% гарантии того, что тест проживет в неизменном виде долго и счастливо это не даст, мы же не провидцы) Но частота исправлений теста снизится.
Есть еще одна радикальная альтернатива - вообще не тестировать в рамках unit тестов методы, где все, что мы можем проверить - это вызывался тот или иной метод. А оставить это на интеграционные тесты.
#unittests #antipatterns #mockito
Еще один антипаттерн в unit тестах - чрезмерное увлечение verify.
List<String> mockedList = mock(MyList.class);
mockedList.size();
verify(mockedList, times(1)).size();
Да, штука полезная. Полезная по большому счету в 2 случаях:
1) проверка тестируемого метода в случае, когда он ничего не возвращает. К слову - еще одна альтернативная проверка: для mutable объекта и меняющего состояние метода проверить поля SUT (System under test).
2) проверка важных шагов алгоритма.
Если с void методом все понятно, то с деталями алгоритма главное - найти ту черту, которая отделяет суть алгоритма от деталей реализации. И проверять только первое. Т.к. в противном случае мы получим "хрупкий" тест, падающий при каждом изменении реализации. Собственно, от этого критерия можно и отталкиваться. Пример: если назначение данного сервисного класса вызов удаленной системы, отбрасывание метрик и логов - то именно эти вызовы и нужно проверить. В других случаях вызов метода логирования и отправку метрики я бы не проверял.
Еще хороший критерий - проверять те вызовы, которые описаны в аналитике.
Ясно, что 100% гарантии того, что тест проживет в неизменном виде долго и счастливо это не даст, мы же не провидцы) Но частота исправлений теста снизится.
Есть еще одна радикальная альтернатива - вообще не тестировать в рамках unit тестов методы, где все, что мы можем проверить - это вызывался тот или иной метод. А оставить это на интеграционные тесты.
#unittests #antipatterns #mockito
Всем привет!
Еще одна проблема, которую я замечаю в unit тестах - это засилье copy-paste. Тесты есть, покрытие хорошее, тесты проходят. Все хорошо?
Не совсем.
Как я уже писал тест - это самая лучшая документация к коду. Поэтому если в тесте есть mock объект, скопированный с другого теста, но на самом деле не нужный для успешного прохождения теста - это вводит в заблуждение при изучении проекта. Аналогично - если есть лишние проверки verify, которые конкретно у данного теста всегда успешны или не проверяют ничего полезного, зато приводят к хрупкости теста.
Резюме: тесты - это тоже код, к их написанию нужно относится с той же серьезностью, как и к коду.
#unittests #antipatterns
Еще одна проблема, которую я замечаю в unit тестах - это засилье copy-paste. Тесты есть, покрытие хорошее, тесты проходят. Все хорошо?
Не совсем.
Как я уже писал тест - это самая лучшая документация к коду. Поэтому если в тесте есть mock объект, скопированный с другого теста, но на самом деле не нужный для успешного прохождения теста - это вводит в заблуждение при изучении проекта. Аналогично - если есть лишние проверки verify, которые конкретно у данного теста всегда успешны или не проверяют ничего полезного, зато приводят к хрупкости теста.
Резюме: тесты - это тоже код, к их написанию нужно относится с той же серьезностью, как и к коду.
#unittests #antipatterns
Всем привет!
Есть такая отличная штука в 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, как контекстные функции.
Вот документация 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 Help
Scope functions | Kotlin
Всем привет!
Сегодня пост о крутой фиче 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
Сегодня пост о крутой фиче 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
Kotlin Help
Null safety | Kotlin
Всем привет!
Чтобы проиллюстрировать предыдущий пост - предлагаю сравнить по критерию null safety три языка: Groovy, Java и Kotlin.
Тестовая задача такая - передать null Boolean значение в конструктор и проверить его в if.
Groovy
import groovy.transform.TupleConstructor
@TupleConstructor
class GroovyNullability {
Boolean value
void checkValue() {
if (!value) {
println 'groovy value is false'
}
}
}
Т.к. в Groovy все, что не true считается false - вызов checkValue распечатает строку.
Java
public class JavaNullability {
private Boolean value;
public JavaNullability(Boolean value) {
this.value = value;
}
public void checkValue() {
if (!value) {
System.out.println("java value is false");
}
}
}
Вызов checkValue упадет c NPE: "Cannot invoke "java.lang.Boolean.booleanValue()" because "this.value" is null" на преобразовании Boolean к boolean использования в логическом выражении (boolean expression).
Kotlin
class KotlinNullability {
var value: Boolean = false
constructor(test: Boolean?) {
value = test!!
}
fun checkValue() {
if (!value) {
println("kotlin value is false")
}
}
}
Чтобы присвоить null значение not null переменной придется пойти на хитрость - через !! указать, что мы уверены, что в nullable переменной не будет null значений. И если же null все же придет - тоже будет java.lang.NullPointerException, причем без подробностей. Но на шаг раньше, еще на присваивании not null переменной.
К слову - если передать Java объект с null значением, через класс-обвертку:
constructor(test: JavaNullability) {
value = test.value
}
NPE будет чуть более подробным: "java.lang.NullPointerException: test.value must not be null"
#kotlin #java #groovy #nullsafety
Чтобы проиллюстрировать предыдущий пост - предлагаю сравнить по критерию null safety три языка: Groovy, Java и Kotlin.
Тестовая задача такая - передать null Boolean значение в конструктор и проверить его в if.
Groovy
import groovy.transform.TupleConstructor
@TupleConstructor
class GroovyNullability {
Boolean value
void checkValue() {
if (!value) {
println 'groovy value is false'
}
}
}
Т.к. в Groovy все, что не true считается false - вызов checkValue распечатает строку.
Java
public class JavaNullability {
private Boolean value;
public JavaNullability(Boolean value) {
this.value = value;
}
public void checkValue() {
if (!value) {
System.out.println("java value is false");
}
}
}
Вызов checkValue упадет c NPE: "Cannot invoke "java.lang.Boolean.booleanValue()" because "this.value" is null" на преобразовании Boolean к boolean использования в логическом выражении (boolean expression).
Kotlin
class KotlinNullability {
var value: Boolean = false
constructor(test: Boolean?) {
value = test!!
}
fun checkValue() {
if (!value) {
println("kotlin value is false")
}
}
}
Чтобы присвоить null значение not null переменной придется пойти на хитрость - через !! указать, что мы уверены, что в nullable переменной не будет null значений. И если же null все же придет - тоже будет java.lang.NullPointerException, причем без подробностей. Но на шаг раньше, еще на присваивании not null переменной.
К слову - если передать Java объект с null значением, через класс-обвертку:
constructor(test: JavaNullability) {
value = test.value
}
NPE будет чуть более подробным: "java.lang.NullPointerException: test.value must not be null"
#kotlin #java #groovy #nullsafety
Резюмирую предыдущий пост:
1) в Groovy нет null safety
2) в Java проблемы с null проявятся при попытке работы с null объектом, в т.ч. при неявных преобразованиях
3) в Kotlin с использованием Java библиотек проблемы будут проявляться при присваивании в Kotlin коде
4) чистый Kotlin = null safety
#groovy #kotlin #java #nullsafety
1) в Groovy нет null safety
2) в Java проблемы с null проявятся при попытке работы с null объектом, в т.ч. при неявных преобразованиях
3) в Kotlin с использованием Java библиотек проблемы будут проявляться при присваивании в Kotlin коде
4) чистый Kotlin = null safety
#groovy #kotlin #java #nullsafety
Всем привет!
Если посмотреть на тему null safety с другой стороны - то это лишь один из аспектов более широкого принципа. Я бы сформулировал его как: "Знай свои данные".
Что же нужно знать:
1) кодировку текстовых данных - всегда явно ее указывать, не ждать, что всегда будет UTF-8
2) часовой пояс\locale - опять же не надеяться на то, что CI pipeline, тестовые и ПРОМ стенды работают в одном часовом поясе. А даже если это так - что на всех правильно настроен часовой пояс) И что никто не запусти код в другом часовом поясе. И никто не пришлет время из другого часового пояса
3) файловую систему, в частности разделители в пути к файлу. Для них есть специальные константы в Java Core, ОС в которой происходит запуск также можно определить
4) все проверки на injection - sql injection и аналоги
5) указание конкретных проверяемых исключений вместо Exception. Это не совсем данные, но часто исключения вызываются некорректными данными
6) unit тесты на граничные значения
7) ну и наконец null safety - может ли по бизнес-процессу значение принимать null или нет
Принцип можно расширить до инфраструктуры и добавить к примеру версии используемых библиотек и плагинов. Если версии не фиксировать - могут подтянутся новые в неожиданный момент с неожиданным поведением)
P.S. Если что-то забыл - дополняйте в комментах
#data #principles
Если посмотреть на тему null safety с другой стороны - то это лишь один из аспектов более широкого принципа. Я бы сформулировал его как: "Знай свои данные".
Что же нужно знать:
1) кодировку текстовых данных - всегда явно ее указывать, не ждать, что всегда будет UTF-8
2) часовой пояс\locale - опять же не надеяться на то, что CI pipeline, тестовые и ПРОМ стенды работают в одном часовом поясе. А даже если это так - что на всех правильно настроен часовой пояс) И что никто не запусти код в другом часовом поясе. И никто не пришлет время из другого часового пояса
3) файловую систему, в частности разделители в пути к файлу. Для них есть специальные константы в Java Core, ОС в которой происходит запуск также можно определить
4) все проверки на injection - sql injection и аналоги
5) указание конкретных проверяемых исключений вместо Exception. Это не совсем данные, но часто исключения вызываются некорректными данными
6) unit тесты на граничные значения
7) ну и наконец null safety - может ли по бизнес-процессу значение принимать null или нет
Принцип можно расширить до инфраструктуры и добавить к примеру версии используемых библиотек и плагинов. Если версии не фиксировать - могут подтянутся новые в неожиданный момент с неожиданным поведением)
P.S. Если что-то забыл - дополняйте в комментах
#data #principles
Всем привет!
Постов долгое время не было, причина типичная - много работы. Вообще не помню времени, когда ее было мало((( И были ли вообще такие времена?)
Хотел бы поднять сегодня такую важную тему как взаимодействие разработчиков и сопровождения.
Для начала одна общеизвестная информация - разработчики и сопровождение исходя из своих задач обречены на противостояние.
Задача разработчиков - менять приложение, задача сопровождения - обеспечивать его работоспособность. А как известно: работает - не трогай) Любое изменение потенциальный источник проблем.
Отсюда часто следует одна крайность - сопровождение максимально критично относится к любым изменениям, разработка "виновна" по умолчанию, требуется строгое соблюдение регламентов, любая нестандартная просьба разработки встречается "в штыки".
Почему это плохо?
Сейчас основная методология разработки - это Agile в разных вариациях. Один из ключевых моментов в Agile - это команда, командная ответственность, гибкие решения в команде. А сопровождение в описываемом кейсе выступает внешним "врагом" - блокирует инициативы команды, замедляет скорость выпуска новых версий. А высокая частота выхода в ПРОМ - еще одна важная часть Agile.
С таким кейсом я, увы, сталкивался и наблюдал его губительный для команды эффект. Часто свои требования сопровождение объясняет требованиями надежности. Хорошее ли это объяснение - зависит от деталей. Если объяснение звучит как-то так - это снизит надежность, т.к. ... и идет описание причин - то да, хорошее.
Если слово надежность произносится, а никаких деталей не приводится - это признак того, что сопровождение боится изменений и не хочет развиваться.
Есть и другая крайность - сопровождение "согласно на все") Не выставляет никаких требований, принимает любые дистрибутивы. Кейс более редкий. Чем это плохо - разработчики опять же исходя из своих основных задач редко думают о том, как их код будет сопровождаться. Обычно на разработку времени хватает впритык.
Какой выход из данной ситуации?
Выставлять требования со стороны сопровождения.
Требования зависят от компании, отрасли, приложения, числа пользователей и много чего.
Но базовые требования могут быть такими:
1) список метрик, позволяющих отслеживать работоспособность
2) требования к логам - где и в каком объеме. Сюда же я бы добавил требования к фильтрации логов, с важным дополнением - возможность фильтрации зависит как от разработчиков бизнес-приложения, так и от разработчиков системы просмотра логов
3) требования к трассировке (tracing) - особенно важно если мы имеем дело с микросервисами
4) наличие инструкции для сопровождения в случае, если установка релиза требует ручных действий
5) наличие сценария отката на предыдущую версию. Это может быть выключение feature toggle или номер версии для отката. Самое важное - сама возможность отката. Это тоже требование, его нужно или соблюдать, или если это невозможно, например, в случае необратимых изменений в БД - составлять план действий на случай неудачной раскатки
6) фиксирование таймаутов внешних вызовов: я уже писал что бесконечные таймауты - одна из основных причин падения приложения
7) требования по UI для ручного разбора ошибок, если все предыдущие требования не помогли)
Почему я говорю "могут быть такие требования" - я все же изначально разработчик, поэтому имею некие предустановки и не вижу полной картины. Если есть возражения и дополнения - возражайте и дополняйте)
#dev #ops
Постов долгое время не было, причина типичная - много работы. Вообще не помню времени, когда ее было мало((( И были ли вообще такие времена?)
Хотел бы поднять сегодня такую важную тему как взаимодействие разработчиков и сопровождения.
Для начала одна общеизвестная информация - разработчики и сопровождение исходя из своих задач обречены на противостояние.
Задача разработчиков - менять приложение, задача сопровождения - обеспечивать его работоспособность. А как известно: работает - не трогай) Любое изменение потенциальный источник проблем.
Отсюда часто следует одна крайность - сопровождение максимально критично относится к любым изменениям, разработка "виновна" по умолчанию, требуется строгое соблюдение регламентов, любая нестандартная просьба разработки встречается "в штыки".
Почему это плохо?
Сейчас основная методология разработки - это 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
На собеседовании я иногда задаю вопрос: приведите пример нарушения принципа Single responsibility. Или альтернативный вариант - а вот если в методе, к примеру, activateCard мы заодно отбросим метрики или залогируем результат - это нарушение принципа или нет.
На первый взгляд ответ - нет. Метрики и логи - это технический код, не бизнес функционал. Он может понадобиться в любом месте кода. Часто такой функционал реализуют с помощью аспектов, т.к. во-первых - это можно реализовать с помощью аспектов, а во-вторых - это красиво))), т.е. некий синтаксический сахар, улучшающий читаемость кода.
Но можно рассмотреть немного другую ситуацию. Предположим, есть код с математическими вычислениям. Или любой алгоритм. Или логика обработки данных. То, что хорошо реализуется в функциональном стиле - входные данные метода, результат, никаких внешних зависимостей. В нём нет внешних взаимодействий, сохранения в хранилище. Чистая логика. В этом случае логирование и метрики - это уже некая обработка полученного результата. Мы же не просто так выводим что-то в лог - это либо данные для разбора ошибки, либо отслеживание пользовательского пути, сбор статистики, отслеживание времени выполнения кода... Т.е. есть отдельная логика по месту и составу того, что мы логируем. Опять же контекст логирования часто требует инициализации, что добавляет ненужные зависимости в нашу логику. Поэтому такой код лучше поместить на уровень выше.
Итого: бизнес функционал и логирование/метрики - да, "чистая" логика - нет.
#logging #metrics #interview_question #code_design #solid #dev_compromises
Всем привет!
Может ли API с широким функционалом стать проблемой? Имея несколько реализаций от разных производителей ПО, являясь эталонной реализацией паттернов интеграции https://www.enterpriseintegrationpatterns.com/ при этом быть хуже конкретного продукта с ограниченым набором функционала?
Как можно догадаться из вопроса - ответ: да. Я про JMS vs Kafka.
JMS - это API из состава Java EE (сейчас Jakarta EE). Есть несколько реализаций: практически у каждого сервера приложений есть свой JMS - IBM, Oracle, JBoss, SAP, есть и Open source решения - ActiveMQ, Artemis MQ и другие.
Что есть в JMS? Стандарт широкий: есть очереди (точка-точка, ака P2P) и топики (подписка, ака PubSub), опциональная персистентность и транзационность, возможность настраиваемой маршрутизации и конвертации сообщений. И с security все хорошо.
У Kafka же только топики, нет продвинутой маршрутизации, трансформации, персистентность постоянная, транзакционности нету, гарантии однократной вычитки должен обеспечивать клиент. Да и вендор один. Но справедливости ради vendor lock нет, т.к продукт open source, за деньги только поддержка.
При этом Kafka успешно отвоевывает долю рынка. В чем же дело?
Секрет Kafka в том, что она выпустила достаточно простой, очень быстрый open source продукт, достаточный для большого числа клиентов. Этим нивелируется преимущество JMS в возможности выбора реализации. Причем Kafka быстрая с включённой по умолчанию персистентностью, а значит и высокой надёжностью. По скорости однозначно бьет все реализации JMS. Про то, как удалось добиться такого результата, я писал тут https://t.me/javaKotlinDevOps/91
В чем проблемы JMS:
попытка объять необъятное в API = переусложнение,
много вендорской специфики, которая может помешать смену реализации,
слишком большая роль брокера - все возможности по маршрутизации и трансформации не бесплатны по производительности, и кроме того ведут к тому, что обычная очередь превращается в Enterprise Service Bus, а у этой концепции есть свои минусы.
Я не хочу сказать, что JMS можно выкидывать, а лишь пишу почему Kafka удалось ее так сильно потеснить. Если вам нужно взаимодействие точка-точка и нет больших объёмов данных - JMS вполне подойдёт.
#kafka #jms #comparison
Может ли API с широким функционалом стать проблемой? Имея несколько реализаций от разных производителей ПО, являясь эталонной реализацией паттернов интеграции https://www.enterpriseintegrationpatterns.com/ при этом быть хуже конкретного продукта с ограниченым набором функционала?
Как можно догадаться из вопроса - ответ: да. Я про JMS vs Kafka.
JMS - это API из состава Java EE (сейчас Jakarta EE). Есть несколько реализаций: практически у каждого сервера приложений есть свой JMS - IBM, Oracle, JBoss, SAP, есть и Open source решения - ActiveMQ, Artemis MQ и другие.
Что есть в JMS? Стандарт широкий: есть очереди (точка-точка, ака P2P) и топики (подписка, ака PubSub), опциональная персистентность и транзационность, возможность настраиваемой маршрутизации и конвертации сообщений. И с security все хорошо.
У Kafka же только топики, нет продвинутой маршрутизации, трансформации, персистентность постоянная, транзакционности нету, гарантии однократной вычитки должен обеспечивать клиент. Да и вендор один. Но справедливости ради vendor lock нет, т.к продукт open source, за деньги только поддержка.
При этом Kafka успешно отвоевывает долю рынка. В чем же дело?
Секрет Kafka в том, что она выпустила достаточно простой, очень быстрый open source продукт, достаточный для большого числа клиентов. Этим нивелируется преимущество JMS в возможности выбора реализации. Причем Kafka быстрая с включённой по умолчанию персистентностью, а значит и высокой надёжностью. По скорости однозначно бьет все реализации JMS. Про то, как удалось добиться такого результата, я писал тут https://t.me/javaKotlinDevOps/91
В чем проблемы JMS:
попытка объять необъятное в API = переусложнение,
много вендорской специфики, которая может помешать смену реализации,
слишком большая роль брокера - все возможности по маршрутизации и трансформации не бесплатны по производительности, и кроме того ведут к тому, что обычная очередь превращается в Enterprise Service Bus, а у этой концепции есть свои минусы.
Я не хочу сказать, что JMS можно выкидывать, а лишь пишу почему Kafka удалось ее так сильно потеснить. Если вам нужно взаимодействие точка-точка и нет больших объёмов данных - JMS вполне подойдёт.
#kafka #jms #comparison
Enterprise Integration Patterns
A comprehensive pattern language for the robust design of asynchronous messaging solutions. The patterns stay product neutral and emphasize design trade-offs over specific technology choices.
Всем привет!
Я уже писал о важности рефакторинга.
Но как оценить, что рефакторинг достиг своих целей?
Можно экспертно. Но не всегда этот вариант годится для обоснования времени на рефакторинг у бизнеса.
Можно ли как-то подтвердить эффект цифрами?
Самый простой вариант - если проблема в производительности. Тогда изменяем TPS - transaction per second - до и после и демонстрируем эффект.
А если проблема в сложности кода как это чаще всего бывает?
Тогда на помощь могут прийти следующие метрики:
- цикломатическая сложность кода и другие похожие метрики https://www.tiobe.com/knowledge/article/demystifying-code-complexity/ После рефакторинга сложность должна снизиться, при этом в процессе может временно повышаться. А стандартные метрики хороши тем, что уже есть утилиты для их подсчёта. Например, Checkstyle https://checkstyle.org/checks/metrics/cyclomaticcomplexity.html или Sonarqube https://habr.com/ru/articles/565652/ Также благодаря тому, что эти метрики широко известны в узких кругах разработчиков )) их проще объяснить бизнесу. Или пусть изучают формулу, или верят на слово разработчикам)
- если сложность кода по каким-то причинам не подходит - можно рассмотреть среднее число строк в классе или методе. После рефакторинга это число также должно снижаться,
- в некоторых случаях может даже подойти абсолютное число строк кода. Если приложение «раздуто», то результатом рефакторинга станет уменьшение размера кодовой базы.
- число TODO в коде. Каждая TODO - потенциальный техдолг.
- число длинных комментариев в коде. Если код самодокументирующийся - с говорящими названиями методов и классов - то длинный комментарий можно рассматривать как аналог TODO
- скорость вникания в код сервиса для новых разработчиков. Изменить достаточно трудоёмко, но метрика хорошая
- число ошибок ПРОМ, связанных с изменяемым кодом. Если их много - значит с кодом что-то не так
- постоянно увеличивающееся или стабильно большое время разработки новых фичей. Например, по сравнению с временем в начале жизненного цикла сервиса. Сравнивать с другими командами и сервисам часто не корректно.
Отдельно я бы рассмотрел случай, когда новая фича не вписывается в существующую архитектуру. Может получится, что просто сделать ее будет быстрее, чем отрефакторить и сделать без техдолга. Эффект может проявится на следующих фичах, но априори это оценить сложно
#java refactoring #metrics
Я уже писал о важности рефакторинга.
Но как оценить, что рефакторинг достиг своих целей?
Можно экспертно. Но не всегда этот вариант годится для обоснования времени на рефакторинг у бизнеса.
Можно ли как-то подтвердить эффект цифрами?
Самый простой вариант - если проблема в производительности. Тогда изменяем TPS - transaction per second - до и после и демонстрируем эффект.
А если проблема в сложности кода как это чаще всего бывает?
Тогда на помощь могут прийти следующие метрики:
- цикломатическая сложность кода и другие похожие метрики https://www.tiobe.com/knowledge/article/demystifying-code-complexity/ После рефакторинга сложность должна снизиться, при этом в процессе может временно повышаться. А стандартные метрики хороши тем, что уже есть утилиты для их подсчёта. Например, Checkstyle https://checkstyle.org/checks/metrics/cyclomaticcomplexity.html или Sonarqube https://habr.com/ru/articles/565652/ Также благодаря тому, что эти метрики широко известны в узких кругах разработчиков )) их проще объяснить бизнесу. Или пусть изучают формулу, или верят на слово разработчикам)
- если сложность кода по каким-то причинам не подходит - можно рассмотреть среднее число строк в классе или методе. После рефакторинга это число также должно снижаться,
- в некоторых случаях может даже подойти абсолютное число строк кода. Если приложение «раздуто», то результатом рефакторинга станет уменьшение размера кодовой базы.
- число TODO в коде. Каждая TODO - потенциальный техдолг.
- число длинных комментариев в коде. Если код самодокументирующийся - с говорящими названиями методов и классов - то длинный комментарий можно рассматривать как аналог TODO
- скорость вникания в код сервиса для новых разработчиков. Изменить достаточно трудоёмко, но метрика хорошая
- число ошибок ПРОМ, связанных с изменяемым кодом. Если их много - значит с кодом что-то не так
- постоянно увеличивающееся или стабильно большое время разработки новых фичей. Например, по сравнению с временем в начале жизненного цикла сервиса. Сравнивать с другими командами и сервисам часто не корректно.
Отдельно я бы рассмотрел случай, когда новая фича не вписывается в существующую архитектуру. Может получится, что просто сделать ее будет быстрее, чем отрефакторить и сделать без техдолга. Эффект может проявится на следующих фичах, но априори это оценить сложно
#java refactoring #metrics
Всем привет!
Продолжая тему рефакторинга. Основное предусловие для начала рефакторинга - это наличие хорошего тестового покрытия. Т.к. мы не меняем бизнес функционал, а улучшаем код либо для повышения производительности, либо для его упрощения. Но при этом нужно гарантировать, что бизнес функционал не сломался, т.е. не появились регрессионные баги. Ведь рефакторинг, в отличие от новой фичи, может затронуть все приложение. Соответственно, баги могут появиться в любом месте, и без тестового покрытия - это большие риски.
Можно рассмотреть похожий кейс. У нас есть монолит, мы хотим распилить его на микросервисы. Это тоже своего рода рефакторинг, только на уровне архитектурном уровне. И тоже аналогичное условие для его начала - наличие достаточного набора тестов. В данном случае повышается важность интеграционных тестов.
Важный момент: в процессе подготовки к разбиению монолита или серьёзному рефакторингу может возникнуть вопрос-предложение - а давайте все выкинем и напишем заново. Так вот - одним из базовых критериев для ответа на этот вопрос также является покрытие тестами. Очевидно, не единственным, но важным. Другие критерии - объем техдолга, соответствие текущей архитектуры и целевой.
Еще кейс - разработчик «боится» рефакторить код, т.к. он слишком сложный или затрагивает слишком много зависимостей. С тестами решиться на рефакторинг намного проще.
Вывод простой - пишите тесты, это страховка при рефакторинге)
#refactoring #unittests #microservices
Продолжая тему рефакторинга. Основное предусловие для начала рефакторинга - это наличие хорошего тестового покрытия. Т.к. мы не меняем бизнес функционал, а улучшаем код либо для повышения производительности, либо для его упрощения. Но при этом нужно гарантировать, что бизнес функционал не сломался, т.е. не появились регрессионные баги. Ведь рефакторинг, в отличие от новой фичи, может затронуть все приложение. Соответственно, баги могут появиться в любом месте, и без тестового покрытия - это большие риски.
Можно рассмотреть похожий кейс. У нас есть монолит, мы хотим распилить его на микросервисы. Это тоже своего рода рефакторинг, только на уровне архитектурном уровне. И тоже аналогичное условие для его начала - наличие достаточного набора тестов. В данном случае повышается важность интеграционных тестов.
Важный момент: в процессе подготовки к разбиению монолита или серьёзному рефакторингу может возникнуть вопрос-предложение - а давайте все выкинем и напишем заново. Так вот - одним из базовых критериев для ответа на этот вопрос также является покрытие тестами. Очевидно, не единственным, но важным. Другие критерии - объем техдолга, соответствие текущей архитектуры и целевой.
Еще кейс - разработчик «боится» рефакторить код, т.к. он слишком сложный или затрагивает слишком много зависимостей. С тестами решиться на рефакторинг намного проще.
Вывод простой - пишите тесты, это страховка при рефакторинге)
#refactoring #unittests #microservices
Всем привет!
Я упоминал в посте про JMS vs Kafka про ESB - Enterprise Service Bus. Она же Корпоративная Сервисная Шина.
Какие плюсы и минусы данного решения?
Плюсов я вижу три:
1) унификация API в пределах компании
2) единая точка мониторинга и контроля всех интеграций
3) больше возможностей для переиспользования уже существующих API
Минусы такие:
1) т.к. ESB - это отдельная система, то ее разрабатывает как правило отдельная команда, которая быстро становится узким местом. Особенно при внедрении микросервисной архитектуры и резком увеличении числа интеграций
2) ESB как правило вносит дополнительные задержки, что особенно критично при синхронном взаимодействии
3) как правило ESB предлагают коммерческие компании, что приводит к vendor lock. Слезть с такого решения будет сложно
4) неочевидная штука - с одной стороны команда ESB унифицирует все API. Но с другой - API становятся перегруженными. Там будут какие-то стандартизированные для всех поля, общие базовые типы, которые во многих случаях будут избыточными
5) также ESB из-за стандартизации затрудняет развитие API. Т.к. унификация, а кроме того больше команд участвует в согласовании - три вместо двух
Вывод: на данном этапе развития ПО идея ESB выглядит избыточной.
Но что же с плюсами, как добиться того же результата с API точка-точка и микросервисной архитектурой?
1) унификация вещь полезная, главное чтобы она не была избыточной. Архитектор может выставить требования по API, DevOps - встроить их проверку в pipeline.
2) единая точка контроля - в случае облачной среды такой точкой может быть Istio или k8s. Либо ставить proxy на границах сред.
3) для переиспользования можно использовать каталог API. Да и не всегда переиспользование полезно, см. выше про избыточное API. Также отдельное API для каждого потребителя позволяет лучше контролировать доступ к данным
#api #esb
Я упоминал в посте про JMS vs Kafka про ESB - Enterprise Service Bus. Она же Корпоративная Сервисная Шина.
Какие плюсы и минусы данного решения?
Плюсов я вижу три:
1) унификация API в пределах компании
2) единая точка мониторинга и контроля всех интеграций
3) больше возможностей для переиспользования уже существующих API
Минусы такие:
1) т.к. ESB - это отдельная система, то ее разрабатывает как правило отдельная команда, которая быстро становится узким местом. Особенно при внедрении микросервисной архитектуры и резком увеличении числа интеграций
2) ESB как правило вносит дополнительные задержки, что особенно критично при синхронном взаимодействии
3) как правило ESB предлагают коммерческие компании, что приводит к vendor lock. Слезть с такого решения будет сложно
4) неочевидная штука - с одной стороны команда ESB унифицирует все API. Но с другой - API становятся перегруженными. Там будут какие-то стандартизированные для всех поля, общие базовые типы, которые во многих случаях будут избыточными
5) также ESB из-за стандартизации затрудняет развитие API. Т.к. унификация, а кроме того больше команд участвует в согласовании - три вместо двух
Вывод: на данном этапе развития ПО идея ESB выглядит избыточной.
Но что же с плюсами, как добиться того же результата с API точка-точка и микросервисной архитектурой?
1) унификация вещь полезная, главное чтобы она не была избыточной. Архитектор может выставить требования по API, DevOps - встроить их проверку в pipeline.
2) единая точка контроля - в случае облачной среды такой точкой может быть Istio или k8s. Либо ставить proxy на границах сред.
3) для переиспользования можно использовать каталог API. Да и не всегда переиспользование полезно, см. выше про избыточное API. Также отдельное API для каждого потребителя позволяет лучше контролировать доступ к данным
#api #esb
Всем привет!
Рекомендую далеко не новую, но интересную статья от всем известного Тагира Валеева про редкие фичи Java https://habr.com/ru/articles/253787/
#java
Рекомендую далеко не новую, но интересную статья от всем известного Тагира Валеева про редкие фичи Java https://habr.com/ru/articles/253787/
#java
Хабр
10 вещей, которых вы не знали о Java
Итак, вы работаете на Java с самого её появления? Вы помните те дни, когда она называлась «Oak», когда про ООП говорили на каждом углу, когда сиплюсплюсники думали, что у Java нет шансов, а апплеты...
Всем привет!
Ситуация: новый разработчик приходит в команды, начинает разбираться с кодом, pipeline, архитектурой, у него возникают вопросы. Когда стоит подойти к более опытному товарищу - тимлиду, техлиду, senior-у - а в каких случаях разобраться самому?
Однозначного ответа нет. Но есть ряд условий.
Когда стоит разобраться самому?
1) проблема не относится к специфике компании. Т.е. это что-то, что скорее всего можно нагуглить на stackoverflow или хабре. По своему опыту скажу, что такие вопросы очень раздражают, уметь гуглить должны все) Далее я рассматриваю случаи, когда вопрос касается специфики компании. Люди, которые спрашивают у коллег: объясни на пальцах как устроены сервлеты - могут стать героями локальных мемов (из собственного опыта)
2) примерно понятно где искать ответ. Если искать лень - для этого существуют чаты разработчиков. Там отвечает либо те, у кого в данный момент есть свободное время, либо те, кто настроен на помощь коллегам. А возможно даже "специально обученные" для ответов на вопросы по разработке люди
3) известно, что тимлид занимается более важной задачей, чем ваша текущая. Тогда стоит поискать другого специалиста в проблемной области, спросить об этом у коллег.
4) на похожий вопрос уже был получен ответ. Открою небольшой лайфхак на примере код-ревью. Есть простой способ проверить внимательность и понимание кода у автора Pull request: если в коде несколько однотипных багов - указываешь только первый и смотришь, поправил ли он остальные. Тут работает тот же принцип.
Когда можно и нужно спрашивать?
1) вообще не понятна причина проблемы. Опять же по моему опыту на самостоятельное решение такого рода проблем могут уходить дни, и даже недели. Либо делаются правки наугад и проверяются на тестовых стендах. А каждый такой цикл может занимать несколько часов. Или заводятся ошибочные тикеты, их отклоняют или перенаправляют и т.д При этом велика вероятность, что эксперт в данной области решит проблему за пару часов: или подскажет "секретный ингридиент", или скажет, что все надо делать по-другому))), или хотя бы направит к человеку, который сможет проблему решить. Еще важный момент - такое "хождение в потемках" может сильно демотивировать новичка и команду.
2) частный случай предыдущего - куда копать понятно, но есть подозрение, что вам на это потребуется условно день, а тимлид может разъяснить все на пальцах за 10 минут. В этом случае стоит в вопросе упомянуть про свою оценку.
3) тимлид сам предложил подходить к нему по любому вопросу. Не совсем по любому - см. пункты выше про гугление и повторы. Если сомневаетесь в сложности вопроса - об этом тоже можно спросить у лида.
4) сжатые сроки по текущей задаче, задача важная
5) все коллеги указывают на данного человека как на эксперта по вашему вопросу
6) вопрос важный: архитектурный или по структуре БД - и хочется посоветоваться с более опытным коллегой
Вывод: не нужно боятся спрашивать. Но не нужно спрашивать то, что вы как разработчик или должны знать, или можете быстро выяснить сами
#people_interactions
Ситуация: новый разработчик приходит в команды, начинает разбираться с кодом, pipeline, архитектурой, у него возникают вопросы. Когда стоит подойти к более опытному товарищу - тимлиду, техлиду, senior-у - а в каких случаях разобраться самому?
Однозначного ответа нет. Но есть ряд условий.
Когда стоит разобраться самому?
1) проблема не относится к специфике компании. Т.е. это что-то, что скорее всего можно нагуглить на stackoverflow или хабре. По своему опыту скажу, что такие вопросы очень раздражают, уметь гуглить должны все) Далее я рассматриваю случаи, когда вопрос касается специфики компании. Люди, которые спрашивают у коллег: объясни на пальцах как устроены сервлеты - могут стать героями локальных мемов (из собственного опыта)
2) примерно понятно где искать ответ. Если искать лень - для этого существуют чаты разработчиков. Там отвечает либо те, у кого в данный момент есть свободное время, либо те, кто настроен на помощь коллегам. А возможно даже "специально обученные" для ответов на вопросы по разработке люди
3) известно, что тимлид занимается более важной задачей, чем ваша текущая. Тогда стоит поискать другого специалиста в проблемной области, спросить об этом у коллег.
4) на похожий вопрос уже был получен ответ. Открою небольшой лайфхак на примере код-ревью. Есть простой способ проверить внимательность и понимание кода у автора Pull request: если в коде несколько однотипных багов - указываешь только первый и смотришь, поправил ли он остальные. Тут работает тот же принцип.
Когда можно и нужно спрашивать?
1) вообще не понятна причина проблемы. Опять же по моему опыту на самостоятельное решение такого рода проблем могут уходить дни, и даже недели. Либо делаются правки наугад и проверяются на тестовых стендах. А каждый такой цикл может занимать несколько часов. Или заводятся ошибочные тикеты, их отклоняют или перенаправляют и т.д При этом велика вероятность, что эксперт в данной области решит проблему за пару часов: или подскажет "секретный ингридиент", или скажет, что все надо делать по-другому))), или хотя бы направит к человеку, который сможет проблему решить. Еще важный момент - такое "хождение в потемках" может сильно демотивировать новичка и команду.
2) частный случай предыдущего - куда копать понятно, но есть подозрение, что вам на это потребуется условно день, а тимлид может разъяснить все на пальцах за 10 минут. В этом случае стоит в вопросе упомянуть про свою оценку.
3) тимлид сам предложил подходить к нему по любому вопросу. Не совсем по любому - см. пункты выше про гугление и повторы. Если сомневаетесь в сложности вопроса - об этом тоже можно спросить у лида.
4) сжатые сроки по текущей задаче, задача важная
5) все коллеги указывают на данного человека как на эксперта по вашему вопросу
6) вопрос важный: архитектурный или по структуре БД - и хочется посоветоваться с более опытным коллегой
Вывод: не нужно боятся спрашивать. Но не нужно спрашивать то, что вы как разработчик или должны знать, или можете быстро выяснить сами
#people_interactions