Всем привет!
Сегодня пост с картинками, поэтому ловите https://telegra.ph/Urovni-izolyacii-sovremennogo-Java-prilozheniya-07-29
#java #jvm #docker #performance
Сегодня пост с картинками, поэтому ловите https://telegra.ph/Urovni-izolyacii-sovremennogo-Java-prilozheniya-07-29
#java #jvm #docker #performance
Telegraph
Уровни изоляции современного Java приложения
Всем привет! Уже писал про плюсы Docker - https://t.me/javaKotlinDevOps/165 Но все ли так безоблачно?) Обычно, когда сравнивают Docker с виртуальной машиной (VM) приводят такую схему Из нее видно, что Docker - более легковесное решение по сравнению с VM.…
Всем привет!
Нашёл отличное сравнение скорости конкатенации строк разными методами, от + до StringBuffer, StringBuilder и StringJoiner. И даже есть такая экзотика как String.format со стримами.
Для затравки три интересных факта.
1) StringBuffer даже с синхронизацией существенно быстрее обычной конкатенации.
2) String.format очень(!) медленный.
3) скорость обычной конкатенации с увеличением числа строк растёт экспоненциально.
Подробнее тут https://www.baeldung.com/java-string-concatenation-methods
#java #performance #string
Нашёл отличное сравнение скорости конкатенации строк разными методами, от + до StringBuffer, StringBuilder и StringJoiner. И даже есть такая экзотика как String.format со стримами.
Для затравки три интересных факта.
1) StringBuffer даже с синхронизацией существенно быстрее обычной конкатенации.
2) String.format очень(!) медленный.
3) скорость обычной конкатенации с увеличением числа строк растёт экспоненциально.
Подробнее тут https://www.baeldung.com/java-string-concatenation-methods
#java #performance #string
Baeldung
Performance Comparison Between Different Java String Concatenation Methods | Baeldung
Explore different string concatenation methods in Java and evaluate their performance using JMH.
Всем привет!
Про DI и DI.
Аббревиатура DI может расшифровываться на Dependency Inversion, а может как Dependency Injection.
Dependency Inversion - это буква D из SOLID - базовых принципов разработки.
Означает, что высокоуровневые классы не должны зависеть от конкретных реализации, и в Java API любых классов лучше использовать интерфейсы везде, где это возможно. Почему такая ремарка: интерфейс с единственной реализацией - очень странная штука) Но я отвлекся) Следование принципу облегчает тестирование и расширение функциональности системы, т.к. позволяет легко заменить любую реализацию.
Dependency Injection - это механизм внедрения зависимостей, важнейшая особенность которого - собственно внедрение зависимостей отдается на откуп внешнему модулю. Самые известный пример - Spring c его IoC контейнером, но есть и другие заточенные конкретно на эту задачу и поэтому более шустрые альтернативы.
Если подходить формально - это два разных понятия, кроме аббревиатуры никак не связанные. Но с другой стороны Dependency Injection по сути - это инструмент, сильно облегчающий реализацию принципа Dependency Inversion. А хороший инструмент помогает писать правильный код. Важное замечание - Spring IoC не обеспечит за вас реализацию инверсии зависимостей. Если метод API завязывается на конкретную реализацию или уровни приложения связаны циклически - Spring тут не поможет. Поможет предварительное проектирование на уровне кода и TDD.
#code_architecture #interview_question #arch #patterns #solid
Про DI и DI.
Аббревиатура DI может расшифровываться на Dependency Inversion, а может как Dependency Injection.
Dependency Inversion - это буква D из SOLID - базовых принципов разработки.
Означает, что высокоуровневые классы не должны зависеть от конкретных реализации, и в Java API любых классов лучше использовать интерфейсы везде, где это возможно. Почему такая ремарка: интерфейс с единственной реализацией - очень странная штука) Но я отвлекся) Следование принципу облегчает тестирование и расширение функциональности системы, т.к. позволяет легко заменить любую реализацию.
Dependency Injection - это механизм внедрения зависимостей, важнейшая особенность которого - собственно внедрение зависимостей отдается на откуп внешнему модулю. Самые известный пример - Spring c его IoC контейнером, но есть и другие заточенные конкретно на эту задачу и поэтому более шустрые альтернативы.
Если подходить формально - это два разных понятия, кроме аббревиатуры никак не связанные. Но с другой стороны Dependency Injection по сути - это инструмент, сильно облегчающий реализацию принципа Dependency Inversion. А хороший инструмент помогает писать правильный код. Важное замечание - Spring IoC не обеспечит за вас реализацию инверсии зависимостей. Если метод API завязывается на конкретную реализацию или уровни приложения связаны циклически - Spring тут не поможет. Поможет предварительное проектирование на уровне кода и TDD.
#code_architecture #interview_question #arch #patterns #solid
Всем привет!
Есть такой принцип - "безобразно, но единообразно". Если верить интернету, он появился в армии. Но может ли он быть применим к исходному коду и архитектуре?
Ответ - да.
Начну издалека - любой код и архитектура устаревает.
Но это не так страшно если выполняются три условия:
1) хорошо спроектированная гибкая архитектура. Я здесь про архитектуру уровня сервиса\АС. Маленькая ремарка - по отношению к коду термин архитектура плох тем, что в здании архитектуру изменить очень сложно. А в коде - можно.
2) высокое покрытие тестами. Тут все очевидно: много тестов - можно спокойно рефакторить
3) наличие времени для рефакторинга, т.е. другими словами - отсутствие вечного "это надо было сделать еще вчера"
В этом случае сервис можно отрефакторить для адаптации архитектуры к новым требованиям.
И делать это лучше сразу для всего приложения. Чтобы было "единообразно".
Почему так - я уже много об этом писал: изучать код - сложная задача. Один из способов облегчить ее - единообразие архитектуры, в т.ч. наименований, практики применения паттернов, разделения на уровни и модули.
Особенно сильно тяжесть изучения кода бьет по новичкам, но и для "старичков" спустя полгода-год код можно забыть.
В этом плане переделывать выглядящий "безобразно" код в отдельном методе или классе в рамках реализации текущей фичи - плохая идея, т.к. это ухудшит читаемость кода приложения в целом.
Лучше поговорить с бизнесом о рисках, зафиксировать техдолг, выделить на него отдельный технический релиз, согласовать время и все отрефакторить за раз.
Если вам кажется этот вариант фантастическим - понимаю, да, увы, такое бывает.
В этом случае предлагаю рефакторить максимально крупные куски кода вместе с бизнес-фичами. Ну например - модуль в терминах Maven или Gradle. Или набор модулей, относящийся к одному бизнес-процессу, если вы построили маленький монолитик. Или большой)
С монолитами, кстати, хуже всего - именно из-за устаревшей архитектуры, поменять которую разом невозможно, они зачастую и умирают.
При этом неплохо бы где-то рядом с кодом зафиксировать все архитектурные проблемы и план рефакторинга. В файлике типа todo.md в корне проекта. Точно не в wiki или в тикете, т.к. большинство разработчиков в wiki не пойдут.
Также подойдет JavaDoc. Часто он бывает тривиален и не несет ценности. Здесь же ситуация обратная.
Ну и конечно при ползучем рефакторинге поможет расстановка @Deprecated. На них ругается IDEA, SonarQube, они будут мозолить глаза. Это не гарантия того, что код будет поправлен, но уже что-то.
А лучший вариант - технический релиз. И не поосторожнее с монолитами)
P.S. Кстати, сложность рефакторинга является одним из возможных сигналов для разделения монолита на части
#arch #техдолг #refactoring #unittests
Есть такой принцип - "безобразно, но единообразно". Если верить интернету, он появился в армии. Но может ли он быть применим к исходному коду и архитектуре?
Ответ - да.
Начну издалека - любой код и архитектура устаревает.
Но это не так страшно если выполняются три условия:
1) хорошо спроектированная гибкая архитектура. Я здесь про архитектуру уровня сервиса\АС. Маленькая ремарка - по отношению к коду термин архитектура плох тем, что в здании архитектуру изменить очень сложно. А в коде - можно.
2) высокое покрытие тестами. Тут все очевидно: много тестов - можно спокойно рефакторить
3) наличие времени для рефакторинга, т.е. другими словами - отсутствие вечного "это надо было сделать еще вчера"
В этом случае сервис можно отрефакторить для адаптации архитектуры к новым требованиям.
И делать это лучше сразу для всего приложения. Чтобы было "единообразно".
Почему так - я уже много об этом писал: изучать код - сложная задача. Один из способов облегчить ее - единообразие архитектуры, в т.ч. наименований, практики применения паттернов, разделения на уровни и модули.
Особенно сильно тяжесть изучения кода бьет по новичкам, но и для "старичков" спустя полгода-год код можно забыть.
В этом плане переделывать выглядящий "безобразно" код в отдельном методе или классе в рамках реализации текущей фичи - плохая идея, т.к. это ухудшит читаемость кода приложения в целом.
Лучше поговорить с бизнесом о рисках, зафиксировать техдолг, выделить на него отдельный технический релиз, согласовать время и все отрефакторить за раз.
Если вам кажется этот вариант фантастическим - понимаю, да, увы, такое бывает.
В этом случае предлагаю рефакторить максимально крупные куски кода вместе с бизнес-фичами. Ну например - модуль в терминах Maven или Gradle. Или набор модулей, относящийся к одному бизнес-процессу, если вы построили маленький монолитик. Или большой)
С монолитами, кстати, хуже всего - именно из-за устаревшей архитектуры, поменять которую разом невозможно, они зачастую и умирают.
При этом неплохо бы где-то рядом с кодом зафиксировать все архитектурные проблемы и план рефакторинга. В файлике типа todo.md в корне проекта. Точно не в wiki или в тикете, т.к. большинство разработчиков в wiki не пойдут.
Также подойдет JavaDoc. Часто он бывает тривиален и не несет ценности. Здесь же ситуация обратная.
Ну и конечно при ползучем рефакторинге поможет расстановка @Deprecated. На них ругается IDEA, SonarQube, они будут мозолить глаза. Это не гарантия того, что код будет поправлен, но уже что-то.
А лучший вариант - технический релиз. И не поосторожнее с монолитами)
P.S. Кстати, сложность рефакторинга является одним из возможных сигналов для разделения монолита на части
#arch #техдолг #refactoring #unittests
Всем привет!
Раз уж я начал говорить про 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