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

LSP (Liskov Substitution Principle) - Принцип подстановки Барбары Лисков.
Меня лично всегда “конфьюзило” это неочевидное название принципа. Но - “так исторически сложилось”. Итак, о чем же он?
Он про наследование - в основном он призывает нас к тому, чтоб мы организовывали наш код таким образом, что в каком-либо месте подстановка одного объекта вместо объекта другого класса, но имплементирующего тот же интерфейс, - не приводило бы к изменению поведения методов где эти эти объекты используются. Иными словами - вызывающие методы не должны ничего знать о конкретных реализациях используемых ими интерфейсов и уж тем более как-то изменять свое поведение в зависимости от того, объект какого типа они получили на входе.

Пример:
interface Animal {
String getSound();
}
class Dog implements Animal {
public String getSound() {
return "woof";
}
}
class Cat implements Animal {
public String getSound() {
return "meow";
}
}
void doSomething(Animal animal) {
String sound = animal.getSound();
if (sound.equals("woof")) {
System.out.print("Dog");
} else {
System.out.print("Cat");
}
System.out.println(" says " + sound);
}


В примере выше можно было бы заменить и проверку по звуку на, например, проверку по instanceof. В любом случае в методе doSomething после этого появляется знание о деталях реализации интерфейса. Более того так мы нарушили и принцип #OCP - теперь для добавления новой имплементации Animal нам придется и обновлять метод doSomething.



Правильно так:

interface Animal {
String getSound();
String getName();
}
class Dog implements Animal {
public String getSound() {
return "woof";
}
public String getName() {
return "Dog";
}
}
class Cat implements Animal {
public String getSound() {
return "meow";
}
public String getName() {
return "Cat";
}
}
void doSomething(Animal animal) {
System.out.println(animal.getName() + " says " + animal.getSound());
}
solId
ISP
interface segregation principle
принцип разделения интерфейсов заключается в том, что лучше иметь более узкоспециализированные интерфейсы и уже через их имплементацию реализовывать определенное поведение у конкретных классов. Разумеется смысл тут в генерации низкосвязанного кода - когда методы и модули зависимы не от больших объектов и интерфейсов, а именно от узких интерфейсов. Наверное один из самых ярких примеров - это Iterable или Comparable в базовой поставке Java. Только представьте, насколько менее удобно было бы использовать циклы или методы сравнения, будь они основаны на более толстых интерфейсах или даже отдельных реализациях. Например, насколько сложнее стала бы наша жизнь при необходимости использовать в циклах только наследников класса Collection.
Любопытная деталь в том, что мы достаточно редко анализируем альтернативно возможные реализации нашего кода/модуля потому что в целом мы всегда можем справиться с архитектурными недостатками. Но стоит один раз переписать модуль сделав акцент на ISP как становятся понятны плюсы. Особенно если мы хотим вынести кусок модуля в виде библиотеки. Именно в процессах, критичных к низкой связности, и чувствуется огромный потенциал подходов основанных на правильном использовании интерфейсов.

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

Сейчас я веду несколько лекций на одном из курсов повышения квалификации программистов. Так вот, готовясь к лекции по паттернам проектирования у меня появилась одна интересная мысль.

Представьте что вы идете к стоматологу и вам предлагают выбрать - идти к стоматологу, который недавно выпустился из университета и имеет стаж работы 2 года, или идти к стоматологу без образования, но со стажем работы в 5 лет. И давайте притворимся, что кто-то его пустил работать без образования и это норма. Так вот, к какому вы пойдете? Уверен что многие предпочтут пойти к первому - потому что какое бы образование ни было, оно все равно позволяет хоть на каком-то уровне понимать причины болезней и применять те инструменты, которые позволят достичь результата. Конечно у стоматолога с 5-летним стажем скорее всего лучше получается управляться с инструментами, но в нужных ли ситуациях он их применяет? Понимает ли он почему и зачем определенный инструмент нужно брать в той или иной ситуации.

И с паттернами в разработке та же история, - это очень мощные инструменты в руках разработчика, но непонимание зачем они нужны и какие проблемы решают ведут к тому что инструменты используются неверно. В последние пару месяцев я собеседовал пару десятков человек уровня Junior/middle, и только половина из них хорошо отвечает на вопрос "какую боль или проблему решает этот инструмент?".

Поэтому моя лекция, изначально называвшая "паттерны", превратилась в лекцию "паттерны и чистый код", причем про Clean Code там 80% материала, а про паттерны - оставшиеся крохи. Потому что именно в этом цель применения паттернов - сделать код читабельным, тестируемым, менее подверженным багам и т.д.

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

Интересный эффект попыток применить принципы Clean Code заключаются в том, что инструменты (т.е. паттерны) вы можете переизобрести заново самостоятельно, но уже с очень глубоким пониманием концепций их работы и проблем, которые они могут решить.
https://habr.com/ru/post/521062/

Вчера написали пост на хабр про программу Ноль Смертей (Vision Zero), которой в России занимаются пока что активисты. Я в этой истории немного учавствую в качеству разработчика телеграм бота для репортов аварий. Если кому-то вдруг хочется поучавствовать в чем-то опенсорс, социальном и на котлине одновременно - то welcome https://github.com/asundukov/crash_map - там пока не добавлены issues, но идейки как доработать конечно уже есть.
year++ ;)
У меня новая фраза-фаворит с последнего рабочего митинга (да-да, некоторые уже работают):

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

}
}


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

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


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

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

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

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

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

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

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

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

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